1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tools.build.apkzlib.zip; 18 19 import com.google.common.base.Charsets; 20 import java.io.IOException; 21 import java.nio.ByteBuffer; 22 import java.nio.charset.CharacterCodingException; 23 import java.nio.charset.Charset; 24 import java.nio.charset.CodingErrorAction; 25 import javax.annotation.Nonnull; 26 27 /** 28 * Utilities to encode and decode file names in zips. 29 */ 30 public class EncodeUtils { 31 32 /** 33 * Utility class: no constructor. 34 */ EncodeUtils()35 private EncodeUtils() { 36 /* 37 * Nothing to do. 38 */ 39 } 40 41 /** 42 * Decodes a file name. 43 * 44 * @param bytes the raw data buffer to read from 45 * @param length the number of bytes in the raw data buffer containing the string to decode 46 * @param flags the zip entry flags 47 * @return the decode file name 48 */ 49 @Nonnull decode(@onnull ByteBuffer bytes, int length, @Nonnull GPFlags flags)50 public static String decode(@Nonnull ByteBuffer bytes, int length, @Nonnull GPFlags flags) 51 throws IOException { 52 if (bytes.remaining() < length) { 53 throw new IOException("Only " + bytes.remaining() + " bytes exist in the buffer, but " 54 + "length is " + length + "."); 55 } 56 57 byte[] stringBytes = new byte[length]; 58 bytes.get(stringBytes); 59 return decode(stringBytes, flags); 60 } 61 62 /** 63 * Decodes a file name. 64 * 65 * @param data the raw data 66 * @param flags the zip entry flags 67 * @return the decode file name 68 */ 69 @Nonnull decode(@onnull byte[] data, @Nonnull GPFlags flags)70 public static String decode(@Nonnull byte[] data, @Nonnull GPFlags flags) { 71 return decode(data, flagsCharset(flags)); 72 } 73 74 /** 75 * Decodes a file name. 76 * 77 * @param data the raw data 78 * @param charset the charset to use 79 * @return the decode file name 80 */ 81 @Nonnull decode(@onnull byte[] data, @Nonnull Charset charset)82 private static String decode(@Nonnull byte[] data, @Nonnull Charset charset) { 83 try { 84 return charset.newDecoder() 85 .onMalformedInput(CodingErrorAction.REPORT) 86 .decode(ByteBuffer.wrap(data)) 87 .toString(); 88 } catch (CharacterCodingException e) { 89 // If we're trying to decode ASCII, try UTF-8. Otherwise, revert to the default 90 // behavior (usually replacing invalid characters). 91 if (charset.equals(Charsets.US_ASCII)) { 92 return decode(data, Charsets.UTF_8); 93 } else { 94 return charset.decode(ByteBuffer.wrap(data)).toString(); 95 } 96 } 97 } 98 99 /** 100 * Encodes a file name. 101 * 102 * @param name the name to encode 103 * @param flags the zip entry flags 104 * @return the encoded file name 105 */ 106 @Nonnull encode(@onnull String name, @Nonnull GPFlags flags)107 public static byte[] encode(@Nonnull String name, @Nonnull GPFlags flags) { 108 Charset charset = flagsCharset(flags); 109 ByteBuffer bytes = charset.encode(name); 110 byte[] result = new byte[bytes.remaining()]; 111 bytes.get(result); 112 return result; 113 } 114 115 /** 116 * Obtains the charset to encode and decode zip entries, given a set of flags. 117 * 118 * @param flags the flags 119 * @return the charset to use 120 */ 121 @Nonnull flagsCharset(@onnull GPFlags flags)122 private static Charset flagsCharset(@Nonnull GPFlags flags) { 123 if (flags.isUtf8FileName()) { 124 return Charsets.UTF_8; 125 } else { 126 return Charsets.US_ASCII; 127 } 128 } 129 130 /** 131 * Checks if some text may be encoded using ASCII. 132 * 133 * @param text the text to check 134 * @return can it be encoded using ASCII? 135 */ canAsciiEncode(String text)136 public static boolean canAsciiEncode(String text) { 137 return Charsets.US_ASCII.newEncoder().canEncode(text); 138 } 139 } 140