View Javadoc
1   /*
2    * Copyright (C) 2012 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5    * in compliance with the License. You may obtain a copy of the License at
6    *
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software distributed under the License
10   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11   * or implied. See the License for the specific language governing permissions and limitations under
12   * the License.
13   */
14  
15  package com.google.common.io;
16  
17  import static com.google.common.io.BaseEncoding.base16;
18  import static com.google.common.io.BaseEncoding.base32;
19  import static com.google.common.io.BaseEncoding.base32Hex;
20  import static com.google.common.io.BaseEncoding.base64;
21  
22  import com.google.common.annotations.GwtCompatible;
23  import com.google.common.annotations.GwtIncompatible;
24  import com.google.common.base.Ascii;
25  import com.google.common.base.Joiner;
26  import com.google.common.base.Splitter;
27  import com.google.common.collect.ImmutableList;
28  import com.google.common.io.BaseEncoding.DecodingException;
29  
30  import junit.framework.TestCase;
31  
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.io.OutputStream;
35  import java.io.StringReader;
36  import java.io.StringWriter;
37  import java.io.UnsupportedEncodingException;
38  
39  /**
40   * Tests for {@code BaseEncoding}.
41   *
42   * @author Louis Wasserman
43   */
44  @GwtCompatible(emulated = true)
45  public class BaseEncodingTest extends TestCase {
46    public static void assertEquals(byte[] expected, byte[] actual) {
47      assertEquals(expected.length, actual.length);
48      for (int i = 0; i < expected.length; i++) {
49        assertEquals(expected[i], actual[i]);
50      }
51    }
52  
53    public void testSeparatorsExplicitly() {
54      testEncodes(base64().withSeparator("\n", 3), "foobar", "Zm9\nvYm\nFy");
55      testEncodes(base64().withSeparator("$", 4), "foobar", "Zm9v$YmFy");
56      testEncodes(base32().withSeparator("*", 4), "foobar", "MZXW*6YTB*OI==*====");
57    }
58  
59    @SuppressWarnings("ReturnValueIgnored")
60    public void testSeparatorSameAsPadChar() {
61      try {
62        base64().withSeparator("=", 3);
63        fail("Expected IllegalArgumentException");
64      } catch (IllegalArgumentException expected) {}
65  
66      try {
67        base64().withPadChar('#').withSeparator("!#!", 3);
68        fail("Expected IllegalArgumentException");
69      } catch (IllegalArgumentException expected) {}
70    }
71  
72    @SuppressWarnings("ReturnValueIgnored")
73    public void testAtMostOneSeparator() {
74      BaseEncoding separated = base64().withSeparator("\n", 3);
75      try {
76        separated.withSeparator("$", 4);
77        fail("Expected UnsupportedOperationException");
78      } catch (UnsupportedOperationException expected) {}
79    }
80  
81    public void testBase64() {
82      // The following test vectors are specified in RFC 4648 itself
83      testEncodingWithSeparators(base64(), "", "");
84      testEncodingWithSeparators(base64(), "f", "Zg==");
85      testEncodingWithSeparators(base64(), "fo", "Zm8=");
86      testEncodingWithSeparators(base64(), "foo", "Zm9v");
87      testEncodingWithSeparators(base64(), "foob", "Zm9vYg==");
88      testEncodingWithSeparators(base64(), "fooba", "Zm9vYmE=");
89      testEncodingWithSeparators(base64(), "foobar", "Zm9vYmFy");
90    }
91  
92    @GwtIncompatible("Reader/Writer")
93    public void testBase64Streaming() throws IOException {
94      // The following test vectors are specified in RFC 4648 itself
95      testStreamingEncodingWithSeparators(base64(), "", "");
96      testStreamingEncodingWithSeparators(base64(), "f", "Zg==");
97      testStreamingEncodingWithSeparators(base64(), "fo", "Zm8=");
98      testStreamingEncodingWithSeparators(base64(), "foo", "Zm9v");
99      testStreamingEncodingWithSeparators(base64(), "foob", "Zm9vYg==");
100     testStreamingEncodingWithSeparators(base64(), "fooba", "Zm9vYmE=");
101     testStreamingEncodingWithSeparators(base64(), "foobar", "Zm9vYmFy");
102   }
103 
104   public void testBase64LenientPadding() {
105     testDecodes(base64(), "Zg", "f");
106     testDecodes(base64(), "Zg=", "f");
107     testDecodes(base64(), "Zg==", "f"); // proper padding length
108     testDecodes(base64(), "Zg===", "f");
109     testDecodes(base64(), "Zg====", "f");
110   }
111 
112   public void testBase64InvalidDecodings() {
113     // These contain bytes not in the decodabet.
114     assertFailsToDecode(base64(), "\u007f");
115     assertFailsToDecode(base64(), "Wf2!");
116     // This sentence just isn't base64() encoded.
117     assertFailsToDecode(base64(), "let's not talk of love or chains!");
118     // A 4n+1 length string is never legal base64().
119     assertFailsToDecode(base64(), "12345");
120   }
121 
122   @SuppressWarnings("ReturnValueIgnored")
123   public void testBase64CannotUpperCase() {
124     try {
125       base64().upperCase();
126       fail();
127     } catch (IllegalStateException expected) {
128       // success
129     }
130   }
131 
132   @SuppressWarnings("ReturnValueIgnored")
133   public void testBase64CannotLowerCase() {
134     try {
135       base64().lowerCase();
136       fail();
137     } catch (IllegalStateException expected) {
138       // success
139     }
140   }
141 
142   public void testBase64AlternatePadding() {
143     BaseEncoding enc = base64().withPadChar('~');
144     testEncodingWithSeparators(enc, "", "");
145     testEncodingWithSeparators(enc, "f", "Zg~~");
146     testEncodingWithSeparators(enc, "fo", "Zm8~");
147     testEncodingWithSeparators(enc, "foo", "Zm9v");
148     testEncodingWithSeparators(enc, "foob", "Zm9vYg~~");
149     testEncodingWithSeparators(enc, "fooba", "Zm9vYmE~");
150     testEncodingWithSeparators(enc, "foobar", "Zm9vYmFy");
151   }
152 
153   @GwtIncompatible("Reader/Writer")
154   public void testBase64StreamingAlternatePadding() throws IOException {
155     BaseEncoding enc = base64().withPadChar('~');
156     testStreamingEncodingWithSeparators(enc, "", "");
157     testStreamingEncodingWithSeparators(enc, "f", "Zg~~");
158     testStreamingEncodingWithSeparators(enc, "fo", "Zm8~");
159     testStreamingEncodingWithSeparators(enc, "foo", "Zm9v");
160     testStreamingEncodingWithSeparators(enc, "foob", "Zm9vYg~~");
161     testStreamingEncodingWithSeparators(enc, "fooba", "Zm9vYmE~");
162     testStreamingEncodingWithSeparators(enc, "foobar", "Zm9vYmFy");
163   }
164 
165   public void testBase64OmitPadding() {
166     BaseEncoding enc = base64().omitPadding();
167     testEncodingWithSeparators(enc, "", "");
168     testEncodingWithSeparators(enc, "f", "Zg");
169     testEncodingWithSeparators(enc, "fo", "Zm8");
170     testEncodingWithSeparators(enc, "foo", "Zm9v");
171     testEncodingWithSeparators(enc, "foob", "Zm9vYg");
172     testEncodingWithSeparators(enc, "fooba", "Zm9vYmE");
173     testEncodingWithSeparators(enc, "foobar", "Zm9vYmFy");
174   }
175 
176   @GwtIncompatible("Reader/Writer")
177   public void testBase64StreamingOmitPadding() throws IOException {
178     BaseEncoding enc = base64().omitPadding();
179     testStreamingEncodingWithSeparators(enc, "", "");
180     testStreamingEncodingWithSeparators(enc, "f", "Zg");
181     testStreamingEncodingWithSeparators(enc, "fo", "Zm8");
182     testStreamingEncodingWithSeparators(enc, "foo", "Zm9v");
183     testStreamingEncodingWithSeparators(enc, "foob", "Zm9vYg");
184     testStreamingEncodingWithSeparators(enc, "fooba", "Zm9vYmE");
185     testStreamingEncodingWithSeparators(enc, "foobar", "Zm9vYmFy");
186   }
187 
188   public void testBase32() {
189     // The following test vectors are specified in RFC 4648 itself
190     testEncodingWithCasing(base32(), "", "");
191     testEncodingWithCasing(base32(), "f", "MY======");
192     testEncodingWithCasing(base32(), "fo", "MZXQ====");
193     testEncodingWithCasing(base32(), "foo", "MZXW6===");
194     testEncodingWithCasing(base32(), "foob", "MZXW6YQ=");
195     testEncodingWithCasing(base32(), "fooba", "MZXW6YTB");
196     testEncodingWithCasing(base32(), "foobar", "MZXW6YTBOI======");
197   }
198 
199   @GwtIncompatible("Reader/Writer")
200   public void testBase32Streaming() throws IOException {
201     // The following test vectors are specified in RFC 4648 itself
202     testStreamingEncodingWithCasing(base32(), "", "");
203     testStreamingEncodingWithCasing(base32(), "f", "MY======");
204     testStreamingEncodingWithCasing(base32(), "fo", "MZXQ====");
205     testStreamingEncodingWithCasing(base32(), "foo", "MZXW6===");
206     testStreamingEncodingWithCasing(base32(), "foob", "MZXW6YQ=");
207     testStreamingEncodingWithCasing(base32(), "fooba", "MZXW6YTB");
208     testStreamingEncodingWithCasing(base32(), "foobar", "MZXW6YTBOI======");
209   }
210 
211   public void testBase32LenientPadding() {
212     testDecodes(base32(), "MZXW6", "foo");
213     testDecodes(base32(), "MZXW6=", "foo");
214     testDecodes(base32(), "MZXW6==", "foo");
215     testDecodes(base32(), "MZXW6===", "foo"); // proper padding length
216     testDecodes(base32(), "MZXW6====", "foo");
217     testDecodes(base32(), "MZXW6=====", "foo");
218   }
219 
220   public void testBase32AlternatePadding() {
221     BaseEncoding enc = base32().withPadChar('~');
222     testEncodingWithCasing(enc, "", "");
223     testEncodingWithCasing(enc, "f", "MY~~~~~~");
224     testEncodingWithCasing(enc, "fo", "MZXQ~~~~");
225     testEncodingWithCasing(enc, "foo", "MZXW6~~~");
226     testEncodingWithCasing(enc, "foob", "MZXW6YQ~");
227     testEncodingWithCasing(enc, "fooba", "MZXW6YTB");
228     testEncodingWithCasing(enc, "foobar", "MZXW6YTBOI~~~~~~");
229   }
230 
231   public void testBase32InvalidDecodings() {
232     // These contain bytes not in the decodabet.
233     assertFailsToDecode(base32(), "\u007f");
234     assertFailsToDecode(base32(), "Wf2!");
235     // This sentence just isn't base32() encoded.
236     assertFailsToDecode(base32(), "let's not talk of love or chains!");
237     // An 8n+{1,3,6} length string is never legal base32.
238     assertFailsToDecode(base32(), "A");
239     assertFailsToDecode(base32(), "ABC");
240     assertFailsToDecode(base32(), "ABCDEF");
241   }
242 
243   public void testBase32UpperCaseIsNoOp() {
244     assertSame(base32(), base32().upperCase());
245   }
246 
247   public void testBase32Hex() {
248     // The following test vectors are specified in RFC 4648 itself
249     testEncodingWithCasing(base32Hex(), "", "");
250     testEncodingWithCasing(base32Hex(), "f", "CO======");
251     testEncodingWithCasing(base32Hex(), "fo", "CPNG====");
252     testEncodingWithCasing(base32Hex(), "foo", "CPNMU===");
253     testEncodingWithCasing(base32Hex(), "foob", "CPNMUOG=");
254     testEncodingWithCasing(base32Hex(), "fooba", "CPNMUOJ1");
255     testEncodingWithCasing(base32Hex(), "foobar", "CPNMUOJ1E8======");
256   }
257 
258   @GwtIncompatible("Reader/Writer")
259   public void testBase32HexStreaming() throws IOException {
260     // The following test vectors are specified in RFC 4648 itself
261     testStreamingEncodingWithCasing(base32Hex(), "", "");
262     testStreamingEncodingWithCasing(base32Hex(), "f", "CO======");
263     testStreamingEncodingWithCasing(base32Hex(), "fo", "CPNG====");
264     testStreamingEncodingWithCasing(base32Hex(), "foo", "CPNMU===");
265     testStreamingEncodingWithCasing(base32Hex(), "foob", "CPNMUOG=");
266     testStreamingEncodingWithCasing(base32Hex(), "fooba", "CPNMUOJ1");
267     testStreamingEncodingWithCasing(base32Hex(), "foobar", "CPNMUOJ1E8======");
268   }
269 
270   public void testBase32HexLenientPadding() {
271     testDecodes(base32Hex(), "CPNMU", "foo");
272     testDecodes(base32Hex(), "CPNMU=", "foo");
273     testDecodes(base32Hex(), "CPNMU==", "foo");
274     testDecodes(base32Hex(), "CPNMU===", "foo"); // proper padding length
275     testDecodes(base32Hex(), "CPNMU====", "foo");
276     testDecodes(base32Hex(), "CPNMU=====", "foo");
277   }
278 
279   public void testBase32HexInvalidDecodings() {
280     // These contain bytes not in the decodabet.
281     assertFailsToDecode(base32Hex(), "\u007f");
282     assertFailsToDecode(base32Hex(), "Wf2!");
283     // This sentence just isn't base32 encoded.
284     assertFailsToDecode(base32Hex(), "let's not talk of love or chains!");
285     // An 8n+{1,3,6} length string is never legal base32.
286     assertFailsToDecode(base32Hex(), "A");
287     assertFailsToDecode(base32Hex(), "ABC");
288     assertFailsToDecode(base32Hex(), "ABCDEF");
289   }
290 
291   public void testBase32HexUpperCaseIsNoOp() {
292     assertSame(base32Hex(), base32Hex().upperCase());
293   }
294 
295   public void testBase16() {
296     testEncodingWithCasing(base16(), "", "");
297     testEncodingWithCasing(base16(), "f", "66");
298     testEncodingWithCasing(base16(), "fo", "666F");
299     testEncodingWithCasing(base16(), "foo", "666F6F");
300     testEncodingWithCasing(base16(), "foob", "666F6F62");
301     testEncodingWithCasing(base16(), "fooba", "666F6F6261");
302     testEncodingWithCasing(base16(), "foobar", "666F6F626172");
303   }
304 
305   public void testBase16UpperCaseIsNoOp() {
306     assertSame(base16(), base16().upperCase());
307   }
308 
309   private static void testEncodingWithCasing(
310       BaseEncoding encoding, String decoded, String encoded) {
311     testEncodingWithSeparators(encoding, decoded, encoded);
312     testEncodingWithSeparators(encoding.upperCase(), decoded, Ascii.toUpperCase(encoded));
313     testEncodingWithSeparators(encoding.lowerCase(), decoded, Ascii.toLowerCase(encoded));
314   }
315 
316   private static void testEncodingWithSeparators(
317       BaseEncoding encoding, String decoded, String encoded) {
318     testEncoding(encoding, decoded, encoded);
319 
320     // test separators work
321     for (int sepLength = 3; sepLength <= 5; sepLength++) {
322       for (String separator : ImmutableList.of(",", "\n", ";;", "")) {
323         testEncoding(encoding.withSeparator(separator, sepLength), decoded,
324             Joiner.on(separator).join(Splitter.fixedLength(sepLength).split(encoded)));
325       }
326     }
327   }
328 
329   private static void testEncoding(BaseEncoding encoding, String decoded, String encoded) {
330     testEncodes(encoding, decoded, encoded);
331     testDecodes(encoding, encoded, decoded);
332   }
333 
334   private static void testEncodes(BaseEncoding encoding, String decoded, String encoded) {
335     byte[] bytes;
336     try {
337       // GWT does not support String.getBytes(Charset)
338       bytes = decoded.getBytes("UTF-8");
339     } catch (UnsupportedEncodingException e) {
340       throw new AssertionError();
341     }
342     assertEquals(encoded, encoding.encode(bytes));
343   }
344 
345   private static void testDecodes(BaseEncoding encoding, String encoded, String decoded) {
346     byte[] bytes;
347     try {
348       // GWT does not support String.getBytes(Charset)
349       bytes = decoded.getBytes("UTF-8");
350     } catch (UnsupportedEncodingException e) {
351       throw new AssertionError();
352     }
353     assertEquals(bytes, encoding.decode(encoded));
354   }
355 
356   private static void assertFailsToDecode(BaseEncoding encoding, String cannotDecode) {
357     try {
358       encoding.decode(cannotDecode);
359       fail("Expected IllegalArgumentException");
360     } catch (IllegalArgumentException expected) {
361       // success
362     }
363     try {
364       encoding.decodeChecked(cannotDecode);
365       fail("Expected DecodingException");
366     } catch (DecodingException expected) {
367       // success
368     }
369   }
370 
371   @GwtIncompatible("Reader/Writer")
372   private static void testStreamingEncodingWithCasing(
373       BaseEncoding encoding, String decoded, String encoded) throws IOException {
374     testStreamingEncodingWithSeparators(encoding, decoded, encoded);
375     testStreamingEncodingWithSeparators(encoding.upperCase(), decoded, Ascii.toUpperCase(encoded));
376     testStreamingEncodingWithSeparators(encoding.lowerCase(), decoded, Ascii.toLowerCase(encoded));
377   }
378 
379   @GwtIncompatible("Reader/Writer")
380   private static void testStreamingEncodingWithSeparators(
381       BaseEncoding encoding, String decoded, String encoded) throws IOException {
382     testStreamingEncoding(encoding, decoded, encoded);
383 
384     // test separators work
385     for (int sepLength = 3; sepLength <= 5; sepLength++) {
386       for (String separator : ImmutableList.of(",", "\n", ";;", "")) {
387         testStreamingEncoding(encoding.withSeparator(separator, sepLength), decoded,
388             Joiner.on(separator).join(Splitter.fixedLength(sepLength).split(encoded)));
389       }
390     }
391   }
392 
393   @GwtIncompatible("Reader/Writer")
394   private static void testStreamingEncoding(BaseEncoding encoding, String decoded, String encoded)
395       throws IOException {
396     testStreamingEncodes(encoding, decoded, encoded);
397     testStreamingDecodes(encoding, encoded, decoded);
398   }
399 
400   @GwtIncompatible("Writer")
401   private static void testStreamingEncodes(BaseEncoding encoding, String decoded, String encoded)
402       throws IOException {
403     byte[] bytes;
404     try {
405       // GWT does not support String.getBytes(Charset)
406       bytes = decoded.getBytes("UTF-8");
407     } catch (UnsupportedEncodingException e) {
408       throw new AssertionError();
409     }
410     StringWriter writer = new StringWriter();
411     OutputStream encodingStream = encoding.encodingStream(writer);
412     encodingStream.write(bytes);
413     encodingStream.close();
414     assertEquals(encoded, writer.toString());
415   }
416 
417   @GwtIncompatible("Reader")
418   private static void testStreamingDecodes(BaseEncoding encoding, String encoded, String decoded)
419       throws IOException {
420     byte[] bytes;
421     try {
422       // GWT does not support String.getBytes(Charset)
423       bytes = decoded.getBytes("UTF-8");
424     } catch (UnsupportedEncodingException e) {
425       throw new AssertionError();
426     }
427     InputStream decodingStream = encoding.decodingStream(new StringReader(encoded));
428     for (int i = 0; i < bytes.length; i++) {
429       assertEquals(bytes[i] & 0xFF, decodingStream.read());
430     }
431     assertEquals(-1, decodingStream.read());
432     decodingStream.close();
433   }
434 
435   public void testToString() {
436     assertEquals("BaseEncoding.base64().withPadChar(=)", BaseEncoding.base64().toString());
437     assertEquals("BaseEncoding.base32Hex().omitPadding()",
438         BaseEncoding.base32Hex().omitPadding().toString());
439     assertEquals("BaseEncoding.base32().lowerCase().withPadChar($)",
440         BaseEncoding.base32().lowerCase().withPadChar('$').toString());
441     assertEquals("BaseEncoding.base16().withSeparator(\"\n\", 10)",
442         BaseEncoding.base16().withSeparator("\n", 10).toString());
443   }
444 }