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.base.Ascii;
24import com.google.common.base.Joiner;
25import com.google.common.base.Splitter;
26import com.google.common.collect.ImmutableList;
27import com.google.common.io.BaseEncoding.DecodingException;
28
29import junit.framework.TestCase;
30
31import java.io.UnsupportedEncodingException;
32
33/**
34 * Tests for {@code BaseEncoding}.
35 *
36 * @author Louis Wasserman
37 */
38@GwtCompatible(emulated = true)
39public class BaseEncodingTest extends TestCase {
40  public static void assertEquals(byte[] expected, byte[] actual) {
41    assertEquals(expected.length, actual.length);
42    for (int i = 0; i < expected.length; i++) {
43      assertEquals(expected[i], actual[i]);
44    }
45  }
46
47  public void testSeparatorsExplicitly() {
48    testEncodes(base64().withSeparator("\n", 3), "foobar", "Zm9\nvYm\nFy");
49    testEncodes(base64().withSeparator("$", 4), "foobar", "Zm9v$YmFy");
50    testEncodes(base32().withSeparator("*", 4), "foobar", "MZXW*6YTB*OI==*====");
51  }
52
53  @SuppressWarnings("ReturnValueIgnored")
54  public void testSeparatorSameAsPadChar() {
55    try {
56      base64().withSeparator("=", 3);
57      fail("Expected IllegalArgumentException");
58    } catch (IllegalArgumentException expected) {}
59
60    try {
61      base64().withPadChar('#').withSeparator("!#!", 3);
62      fail("Expected IllegalArgumentException");
63    } catch (IllegalArgumentException expected) {}
64  }
65
66  @SuppressWarnings("ReturnValueIgnored")
67  public void testAtMostOneSeparator() {
68    BaseEncoding separated = base64().withSeparator("\n", 3);
69    try {
70      separated.withSeparator("$", 4);
71      fail("Expected UnsupportedOperationException");
72    } catch (UnsupportedOperationException expected) {}
73  }
74
75  public void testBase64() {
76    // The following test vectors are specified in RFC 4648 itself
77    testEncodingWithSeparators(base64(), "", "");
78    testEncodingWithSeparators(base64(), "f", "Zg==");
79    testEncodingWithSeparators(base64(), "fo", "Zm8=");
80    testEncodingWithSeparators(base64(), "foo", "Zm9v");
81    testEncodingWithSeparators(base64(), "foob", "Zm9vYg==");
82    testEncodingWithSeparators(base64(), "fooba", "Zm9vYmE=");
83    testEncodingWithSeparators(base64(), "foobar", "Zm9vYmFy");
84  }
85
86  public void testBase64LenientPadding() {
87    testDecodes(base64(), "Zg", "f");
88    testDecodes(base64(), "Zg=", "f");
89    testDecodes(base64(), "Zg==", "f"); // proper padding length
90    testDecodes(base64(), "Zg===", "f");
91    testDecodes(base64(), "Zg====", "f");
92  }
93
94  public void testBase64InvalidDecodings() {
95    // These contain bytes not in the decodabet.
96    assertFailsToDecode(base64(), "\u007f");
97    assertFailsToDecode(base64(), "Wf2!");
98    // This sentence just isn't base64() encoded.
99    assertFailsToDecode(base64(), "let's not talk of love or chains!");
100    // A 4n+1 length string is never legal base64().
101    assertFailsToDecode(base64(), "12345");
102  }
103
104  @SuppressWarnings("ReturnValueIgnored")
105  public void testBase64CannotUpperCase() {
106    try {
107      base64().upperCase();
108      fail();
109    } catch (IllegalStateException expected) {
110      // success
111    }
112  }
113
114  @SuppressWarnings("ReturnValueIgnored")
115  public void testBase64CannotLowerCase() {
116    try {
117      base64().lowerCase();
118      fail();
119    } catch (IllegalStateException expected) {
120      // success
121    }
122  }
123
124  public void testBase64AlternatePadding() {
125    BaseEncoding enc = base64().withPadChar('~');
126    testEncodingWithSeparators(enc, "", "");
127    testEncodingWithSeparators(enc, "f", "Zg~~");
128    testEncodingWithSeparators(enc, "fo", "Zm8~");
129    testEncodingWithSeparators(enc, "foo", "Zm9v");
130    testEncodingWithSeparators(enc, "foob", "Zm9vYg~~");
131    testEncodingWithSeparators(enc, "fooba", "Zm9vYmE~");
132    testEncodingWithSeparators(enc, "foobar", "Zm9vYmFy");
133  }
134
135  public void testBase64OmitPadding() {
136    BaseEncoding enc = base64().omitPadding();
137    testEncodingWithSeparators(enc, "", "");
138    testEncodingWithSeparators(enc, "f", "Zg");
139    testEncodingWithSeparators(enc, "fo", "Zm8");
140    testEncodingWithSeparators(enc, "foo", "Zm9v");
141    testEncodingWithSeparators(enc, "foob", "Zm9vYg");
142    testEncodingWithSeparators(enc, "fooba", "Zm9vYmE");
143    testEncodingWithSeparators(enc, "foobar", "Zm9vYmFy");
144  }
145
146  public void testBase32() {
147    // The following test vectors are specified in RFC 4648 itself
148    testEncodingWithCasing(base32(), "", "");
149    testEncodingWithCasing(base32(), "f", "MY======");
150    testEncodingWithCasing(base32(), "fo", "MZXQ====");
151    testEncodingWithCasing(base32(), "foo", "MZXW6===");
152    testEncodingWithCasing(base32(), "foob", "MZXW6YQ=");
153    testEncodingWithCasing(base32(), "fooba", "MZXW6YTB");
154    testEncodingWithCasing(base32(), "foobar", "MZXW6YTBOI======");
155  }
156
157  public void testBase32LenientPadding() {
158    testDecodes(base32(), "MZXW6", "foo");
159    testDecodes(base32(), "MZXW6=", "foo");
160    testDecodes(base32(), "MZXW6==", "foo");
161    testDecodes(base32(), "MZXW6===", "foo"); // proper padding length
162    testDecodes(base32(), "MZXW6====", "foo");
163    testDecodes(base32(), "MZXW6=====", "foo");
164  }
165
166  public void testBase32AlternatePadding() {
167    BaseEncoding enc = base32().withPadChar('~');
168    testEncodingWithCasing(enc, "", "");
169    testEncodingWithCasing(enc, "f", "MY~~~~~~");
170    testEncodingWithCasing(enc, "fo", "MZXQ~~~~");
171    testEncodingWithCasing(enc, "foo", "MZXW6~~~");
172    testEncodingWithCasing(enc, "foob", "MZXW6YQ~");
173    testEncodingWithCasing(enc, "fooba", "MZXW6YTB");
174    testEncodingWithCasing(enc, "foobar", "MZXW6YTBOI~~~~~~");
175  }
176
177  public void testBase32InvalidDecodings() {
178    // These contain bytes not in the decodabet.
179    assertFailsToDecode(base32(), "\u007f");
180    assertFailsToDecode(base32(), "Wf2!");
181    // This sentence just isn't base32() encoded.
182    assertFailsToDecode(base32(), "let's not talk of love or chains!");
183    // An 8n+{1,3,6} length string is never legal base32.
184    assertFailsToDecode(base32(), "A");
185    assertFailsToDecode(base32(), "ABC");
186    assertFailsToDecode(base32(), "ABCDEF");
187  }
188
189  public void testBase32UpperCaseIsNoOp() {
190    assertSame(base32(), base32().upperCase());
191  }
192
193  public void testBase32Hex() {
194    // The following test vectors are specified in RFC 4648 itself
195    testEncodingWithCasing(base32Hex(), "", "");
196    testEncodingWithCasing(base32Hex(), "f", "CO======");
197    testEncodingWithCasing(base32Hex(), "fo", "CPNG====");
198    testEncodingWithCasing(base32Hex(), "foo", "CPNMU===");
199    testEncodingWithCasing(base32Hex(), "foob", "CPNMUOG=");
200    testEncodingWithCasing(base32Hex(), "fooba", "CPNMUOJ1");
201    testEncodingWithCasing(base32Hex(), "foobar", "CPNMUOJ1E8======");
202  }
203
204  public void testBase32HexLenientPadding() {
205    testDecodes(base32Hex(), "CPNMU", "foo");
206    testDecodes(base32Hex(), "CPNMU=", "foo");
207    testDecodes(base32Hex(), "CPNMU==", "foo");
208    testDecodes(base32Hex(), "CPNMU===", "foo"); // proper padding length
209    testDecodes(base32Hex(), "CPNMU====", "foo");
210    testDecodes(base32Hex(), "CPNMU=====", "foo");
211  }
212
213  public void testBase32HexInvalidDecodings() {
214    // These contain bytes not in the decodabet.
215    assertFailsToDecode(base32Hex(), "\u007f");
216    assertFailsToDecode(base32Hex(), "Wf2!");
217    // This sentence just isn't base32 encoded.
218    assertFailsToDecode(base32Hex(), "let's not talk of love or chains!");
219    // An 8n+{1,3,6} length string is never legal base32.
220    assertFailsToDecode(base32Hex(), "A");
221    assertFailsToDecode(base32Hex(), "ABC");
222    assertFailsToDecode(base32Hex(), "ABCDEF");
223  }
224
225  public void testBase32HexUpperCaseIsNoOp() {
226    assertSame(base32Hex(), base32Hex().upperCase());
227  }
228
229  public void testBase16() {
230    testEncodingWithCasing(base16(), "", "");
231    testEncodingWithCasing(base16(), "f", "66");
232    testEncodingWithCasing(base16(), "fo", "666F");
233    testEncodingWithCasing(base16(), "foo", "666F6F");
234    testEncodingWithCasing(base16(), "foob", "666F6F62");
235    testEncodingWithCasing(base16(), "fooba", "666F6F6261");
236    testEncodingWithCasing(base16(), "foobar", "666F6F626172");
237  }
238
239  public void testBase16UpperCaseIsNoOp() {
240    assertSame(base16(), base16().upperCase());
241  }
242
243  private static void testEncodingWithCasing(
244      BaseEncoding encoding, String decoded, String encoded) {
245    testEncodingWithSeparators(encoding, decoded, encoded);
246    testEncodingWithSeparators(encoding.upperCase(), decoded, Ascii.toUpperCase(encoded));
247    testEncodingWithSeparators(encoding.lowerCase(), decoded, Ascii.toLowerCase(encoded));
248  }
249
250  private static void testEncodingWithSeparators(
251      BaseEncoding encoding, String decoded, String encoded) {
252    testEncoding(encoding, decoded, encoded);
253
254    // test separators work
255    for (int sepLength = 3; sepLength <= 5; sepLength++) {
256      for (String separator : ImmutableList.of(",", "\n", ";;", "")) {
257        testEncoding(encoding.withSeparator(separator, sepLength), decoded,
258            Joiner.on(separator).join(Splitter.fixedLength(sepLength).split(encoded)));
259      }
260    }
261  }
262
263  private static void testEncoding(BaseEncoding encoding, String decoded, String encoded) {
264    testEncodes(encoding, decoded, encoded);
265    testDecodes(encoding, encoded, decoded);
266  }
267
268  private static void testEncodes(BaseEncoding encoding, String decoded, String encoded) {
269    byte[] bytes;
270    try {
271      // GWT does not support String.getBytes(Charset)
272      bytes = decoded.getBytes("UTF-8");
273    } catch (UnsupportedEncodingException e) {
274      throw new AssertionError();
275    }
276    assertEquals(encoded, encoding.encode(bytes));
277  }
278
279  private static void testDecodes(BaseEncoding encoding, String encoded, String decoded) {
280    byte[] bytes;
281    try {
282      // GWT does not support String.getBytes(Charset)
283      bytes = decoded.getBytes("UTF-8");
284    } catch (UnsupportedEncodingException e) {
285      throw new AssertionError();
286    }
287    assertEquals(bytes, encoding.decode(encoded));
288  }
289
290  private static void assertFailsToDecode(BaseEncoding encoding, String cannotDecode) {
291    try {
292      encoding.decode(cannotDecode);
293      fail("Expected IllegalArgumentException");
294    } catch (IllegalArgumentException expected) {
295      // success
296    }
297    try {
298      encoding.decodeChecked(cannotDecode);
299      fail("Expected DecodingException");
300    } catch (DecodingException expected) {
301      // success
302    }
303  }
304
305  public void testToString() {
306    assertEquals("BaseEncoding.base64().withPadChar(=)", BaseEncoding.base64().toString());
307    assertEquals("BaseEncoding.base32Hex().omitPadding()",
308        BaseEncoding.base32Hex().omitPadding().toString());
309    assertEquals("BaseEncoding.base32().lowerCase().withPadChar($)",
310        BaseEncoding.base32().lowerCase().withPadChar('$').toString());
311    assertEquals("BaseEncoding.base16().withSeparator(\"\n\", 10)",
312        BaseEncoding.base16().withSeparator("\n", 10).toString());
313  }
314}
315
316