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