Home | History | Annotate | Download | only in io
      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 }
    445