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