/* * 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.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); } }