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
15package com.google.common.io;
16
17import static com.google.common.io.BaseEncoding.base16;
18import static com.google.common.io.BaseEncoding.base32;
19import static com.google.common.io.BaseEncoding.base32Hex;
20import static com.google.common.io.BaseEncoding.base64;
21
22import com.google.common.annotations.GwtCompatible;
23import com.google.common.annotations.GwtIncompatible;
24import com.google.common.base.Ascii;
25import com.google.common.base.Joiner;
26import com.google.common.base.Splitter;
27import com.google.common.collect.ImmutableList;
28import com.google.common.io.BaseEncoding.DecodingException;
29
30import junit.framework.TestCase;
31
32import java.io.IOException;
33import java.io.InputStream;
34import java.io.OutputStream;
35import java.io.StringReader;
36import java.io.StringWriter;
37import java.io.UnsupportedEncodingException;
38
39/**
40 * Tests for {@code BaseEncoding}.
41 *
42 * @author Louis Wasserman
43 */
44@GwtCompatible(emulated = true)
45public 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}
445