blob: 34111e63572541f5eb0c3b788850a95c5263de7a [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.build.apkzlib.zip;
import com.google.common.base.Charsets;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CodingErrorAction;
import javax.annotation.Nonnull;
/**
* Utilities to encode and decode file names in zips.
*/
public class EncodeUtils {
/**
* Utility class: no constructor.
*/
private EncodeUtils() {
/*
* Nothing to do.
*/
}
/**
* Decodes a file name.
*
* @param bytes the raw data buffer to read from
* @param length the number of bytes in the raw data buffer containing the string to decode
* @param flags the zip entry flags
* @return the decode file name
*/
@Nonnull
public static String decode(@Nonnull ByteBuffer bytes, int length, @Nonnull GPFlags flags)
throws IOException {
if (bytes.remaining() < length) {
throw new IOException("Only " + bytes.remaining() + " bytes exist in the buffer, but "
+ "length is " + length + ".");
}
byte[] stringBytes = new byte[length];
bytes.get(stringBytes);
return decode(stringBytes, flags);
}
/**
* Decodes a file name.
*
* @param data the raw data
* @param flags the zip entry flags
* @return the decode file name
*/
@Nonnull
public static String decode(@Nonnull byte[] data, @Nonnull GPFlags flags) {
return decode(data, flagsCharset(flags));
}
/**
* Decodes a file name.
*
* @param data the raw data
* @param charset the charset to use
* @return the decode file name
*/
@Nonnull
private static String decode(@Nonnull byte[] data, @Nonnull Charset charset) {
try {
return charset.newDecoder()
.onMalformedInput(CodingErrorAction.REPORT)
.decode(ByteBuffer.wrap(data))
.toString();
} catch (CharacterCodingException e) {
// If we're trying to decode ASCII, try UTF-8. Otherwise, revert to the default
// behavior (usually replacing invalid characters).
if (charset.equals(Charsets.US_ASCII)) {
return decode(data, Charsets.UTF_8);
} else {
return charset.decode(ByteBuffer.wrap(data)).toString();
}
}
}
/**
* Encodes a file name.
*
* @param name the name to encode
* @param flags the zip entry flags
* @return the encoded file name
*/
@Nonnull
public static byte[] encode(@Nonnull String name, @Nonnull GPFlags flags) {
Charset charset = flagsCharset(flags);
ByteBuffer bytes = charset.encode(name);
byte[] result = new byte[bytes.remaining()];
bytes.get(result);
return result;
}
/**
* Obtains the charset to encode and decode zip entries, given a set of flags.
*
* @param flags the flags
* @return the charset to use
*/
@Nonnull
private static Charset flagsCharset(@Nonnull GPFlags flags) {
if (flags.isUtf8FileName()) {
return Charsets.UTF_8;
} else {
return Charsets.US_ASCII;
}
}
/**
* Checks if some text may be encoded using ASCII.
*
* @param text the text to check
* @return can it be encoded using ASCII?
*/
public static boolean canAsciiEncode(String text) {
return Charsets.US_ASCII.newEncoder().canEncode(text);
}
}