#include #include "wvtest.h" #include "wvbuf.h" #include "wvbase64.h" #include "wvstream.h" #define THREE_LETTERS "ken" #define THREE_LETTERS_ENC "a2Vu" #define FOUR_LETTERS "kenk" #define FOUR_LETTERS_ENC "a2Vuaw==" #define FIVE_LETTERS "kenke" #define FIVE_LETTERS_ENC "a2Vua2U=" #define SIX_LETTERS "kenken" #define SIX_LETTERS_ENC "a2Vua2Vu" #define FOURTEEN_LETTERS "aaaaaa:bbbbbbb" #define FOURTEEN_LETTERS_ENC "YWFhYWFhOmJiYmJiYmI=" #define BASE64_ALPHABET_DEC {0, 16, 131, 16, 81, 135, 32, 146, 139, 48, 211, 143, 65, 20, 147, 81, 85, 151, 97, 150, 155, 113, 215, 159, 130, 24, 163, 146, 89, 167, 162, 154, 171, 178, 219, 175, 195, 28, 179, 211, 93, 183, 227, 158, 187, 243, 223, 191}; #define BASE64_ALPHABET "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "abcdefghijklmnopqrstuvwxyz" \ "0123456789+/" #define BASE64_ALPHABET_ENC "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLw==" WVTEST_MAIN("basic encoding") { // always use a brand new encoder since we don't want to have to // deal with 'finish' issues in this WVTEST_MAIN block { // 3 characters... no padding on end WvBase64Encoder enc; WvString result = enc.strflushstr(THREE_LETTERS, true); WVPASS(result == THREE_LETTERS_ENC); } { // 4 characters... should have two pad characters WvBase64Encoder enc; WvString result = enc.strflushstr(FOUR_LETTERS, true); WVPASS(result == FOUR_LETTERS_ENC); WVFAIL(result == "a2Vuaw="); // forgot pad WVFAIL(result == "a2Vuaw"); // forgot pad WVFAIL(result == "a2Vua"); // output if flushing didn't occur WVFAIL(result == "a2Vu"); // not reading enough input } { // 5 characters... should have one pad character WvBase64Encoder enc; WvString result = enc.strflushstr(FIVE_LETTERS, true); WVPASS(result == FIVE_LETTERS_ENC); WVFAIL(result == "a2Vua2U"); // forgot pad WVFAIL(result == "a2Vua"); // output if flushing didn't occur WVFAIL(result == "a2Vu"); // not reading enough input } { // 6 characters WvBase64Encoder enc; WvString result = enc.strflushstr(SIX_LETTERS, true); WVPASS(result == SIX_LETTERS_ENC); } { // 14 characters WvBase64Encoder enc; WvString result = enc.strflushstr(FOURTEEN_LETTERS, true); WVPASS(result == FOURTEEN_LETTERS_ENC); } { // many characters long... no newlines should be put in after 76 // characters (see RFC2045) since this encoder is not MIME-specific WvBase64Encoder enc; WvString result = enc.strflushstr(BASE64_ALPHABET, true); WVPASS(result == BASE64_ALPHABET_ENC); } { // very special test that makes sure encoded output uses all possible // characters WvBase64Encoder enc; const unsigned char a[] = BASE64_ALPHABET_DEC; WvString result = enc.strflushmem(a, sizeof(a), true); WVPASS(result == BASE64_ALPHABET); } } WVTEST_MAIN("basic decoding") { // same tests as above, but now doing the reverse operation { // 3 characters... no padding on end WvBase64Decoder dec; WvString result = dec.strflushstr(THREE_LETTERS_ENC, true); WVPASS(result == THREE_LETTERS); } { // 4 characters... should have two pad characters WvBase64Decoder dec; WvString result = dec.strflushstr(FOUR_LETTERS_ENC, true); WVPASS(result == FOUR_LETTERS); } { // 5 characters... should have one pad character WvBase64Decoder dec; WvString result = dec.strflushstr(FIVE_LETTERS_ENC, true); WVPASS(result == FIVE_LETTERS); } { // 6 characters WvBase64Decoder dec; WvString result = dec.strflushstr(SIX_LETTERS_ENC, true); WVPASS(result == SIX_LETTERS); } { // many characters long... WvBase64Decoder dec; WvString result = dec.strflushstr(BASE64_ALPHABET_ENC, true); WVPASS(result == BASE64_ALPHABET); } { // special case... WvBase64Decoder dec; WvDynBuf dest; dec.flushstrbuf( BASE64_ALPHABET, dest, true ); const unsigned char a[] = BASE64_ALPHABET_DEC; WVPASS(dest.used() == sizeof( a ) && !memcmp( a, dest.get(dest.used()), sizeof(a)) ); } } WVTEST_MAIN("encode/decode feedback loop") { // Base64 is totally reversible... this test encodes a string // in a feedback loop n times, then decodes it n times. // number of times to repeatedly encode for (int n = 2; n < 9; n++ ) { WvString orig("Milk and Cereal"), src, dest; src = orig; for (int i = 0; i < n; i++) { WvBase64Encoder enc; enc.flushstrstr(src, dest, true); src = dest; dest = ""; } for (int i = 0; i < n; i++) { WvBase64Decoder dec; dec.flushstrstr(src, dest, true); src = dest; dest = ""; } WVPASS( src == orig ); } } WVTEST_MAIN("nothing to encode/decode") { for (int i = 1; i <= 2; i++ ) { { // "null" WvString WvEncoder *enc = (i == 1) ? (WvEncoder *) new WvBase64Encoder : new WvBase64Decoder; WvString nul, result("stuff there"); enc->flushstrstr(nul, result, true); WVPASS(result == "stuff there"); delete(enc); } { // empty WvString WvEncoder *enc = (i == 1) ? (WvEncoder *) new WvBase64Encoder : new WvBase64Decoder; WvString empty(""), result("stuff there"); enc->flushstrstr(empty, result, true); WVPASS(result == "stuff there"); delete(enc); } { // empty WvBuf WvEncoder *enc = (i == 1) ? (WvEncoder *) new WvBase64Encoder : new WvBase64Decoder; WvDynBuf empty, dest; empty.zap(); // ... just to be sure dest.put( "stuff there", 11 ); enc->encode(empty, dest, true, true); WVPASS(dest.used() == 11 && !memcmp(dest.get(dest.used()), "stuff there", 11)); delete(enc); } } } WVTEST_MAIN("decoding whitespace") { { // white space at beginning WvBase64Decoder dec; WvString a_enc(" " FOUR_LETTERS_ENC), result; dec.flushstrstr(a_enc,result,true); WVPASS(result == FOUR_LETTERS); } { // white space at end WvBase64Decoder dec; WvString a_enc(FOUR_LETTERS_ENC " \n "), result; dec.flushstrstr(a_enc,result,true); WVPASS(result == FOUR_LETTERS); } { // white space in middle WvBase64Decoder dec; WvString a_enc("a 2 V\tu \t\n a w = ="), result; dec.flushstrstr(a_enc,result,true); WVPASS(result == FOUR_LETTERS); } { // all kinds of crazy whitespace everywhere WvBase64Decoder dec; WvString a_enc("a 2\v\n V u \t a\n\n\nw =\r ="), result; dec.flushstrstr(a_enc,result,true); WVPASS(result == FOUR_LETTERS); } } WVTEST_MAIN("decoding invalid data") { { // out of range data at beginning WvBase64Decoder dec; WvDynBuf src,dest; unsigned char invalid[] = {199,200,201}; src.put(invalid, 3); src.put(FOUR_LETTERS_ENC, sizeof(FOUR_LETTERS_ENC)-1); size_t original_size = src.used(); bool ret = dec.encode(src,dest,true,false); WVFAIL(dec.isok()); // read bad data WVPASS(ret == false && dest.used() == 0); // should have removed the character from the buffer WVPASS(src.used() == original_size - 1); } { // out of range data in middle WvBase64Decoder dec; WvDynBuf src,dest; src.put("a2Vu", 4); src.put(200); // the invalid character src.put("2Vu", 3); bool ret = dec.encode(src,dest,true,false); WVFAIL(dec.isok()); // read bad data WVPASS(ret == false); // this line was causing valgrind errors, but it appears to be debugging. // wvcon->print( "%s\n", (const char*) dest.peek( 0, dest.used() )); // decoded as much as possible though... a2Vu -> ken WVPASS(dest.used() == 3 && !memcmp(dest.get(dest.used()), "ken", 3)); WVPASS(src.used() == 3); // should have removed the bad character } { // not enough equal signs.. should be two, but only one WvBase64Decoder dec; WvString src("a2Vuaw="), dest; bool ret = dec.flushstrstr(src, dest, true); WVPASS(dec.isok()); WVPASS(ret == false); WVPASS(dest == "kenk"); // should have been able to decode } { // not enough equal signs.. should be one, but none WvBase64Decoder dec; WvString src("a2Vua2U"), dest; bool ret = dec.flushstrstr(src, dest, true); WVPASS(dec.isok()); WVPASS(ret == false); WVPASS(dest == "kenke"); // should have been able to decode } } #define SHORT_MESSAGE "The quick brown fox jumps over the lazy dog." #define SHORT_MESSAGE_ENC "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4=" WVTEST_MAIN("encoding data that is streamed in") { // Tests that the encoder works when not all of the data is available at // once. { // Encoder only has access to one character at a time WvBase64Encoder enc; WvDynBuf src; WvString a(SHORT_MESSAGE),result; for (unsigned int i = 0; i < a.len(); i++) { src.put(a.edit()[i]); enc.flushbufstr(src, result, false); } enc.flushbufstr(src, result, true); WVPASS(result == SHORT_MESSAGE_ENC); } { // encoder has access to three letters at a time WvBase64Encoder enc; WvDynBuf src; WvString result; src.put(THREE_LETTERS, sizeof(THREE_LETTERS) - 1); enc.flushbufstr(src, result, false); src.put(THREE_LETTERS, sizeof(THREE_LETTERS) - 1); enc.flushbufstr(src, result, true); // end of stream now WVPASS(result == SIX_LETTERS_ENC); WVFAIL(result == THREE_LETTERS_ENC); } } WVTEST_MAIN("decoding data that is streamed in") { // same tests as above, but with decoding... { // one char at a time WvBase64Decoder dec; WvDynBuf src; WvString a(SHORT_MESSAGE_ENC),result; for (unsigned int i = 0; i < a.len(); i++) { src.put(a.edit()[i]); dec.flushbufstr(src, result, false); } dec.flushbufstr(src, result, true); WVPASS(result == SHORT_MESSAGE); } { // four chars at a time WvBase64Decoder dec; WvDynBuf src; WvString result; src.put(THREE_LETTERS_ENC, sizeof(THREE_LETTERS_ENC) - 1); dec.flushbufstr(src, result, false); src.put(THREE_LETTERS_ENC, sizeof(THREE_LETTERS_ENC) - 1); dec.flushbufstr(src, result, true); // end of stream now WVPASS(result == SIX_LETTERS); WVFAIL(result == THREE_LETTERS); } } WVTEST_MAIN("flushing") { // flush is true { // Putting in 4 characters will leave 1 byte of leftover data // (base64 works in chunks of 3 bytes). So result will first be // false. Adding 2 more characters will give 6 characters total // in the input buffer (4+2). So there's no leftover data and // result will be true the second time. WvBase64Encoder enc; WvDynBuf src, dest; bool result; src.put( "kenk", 4 ); result = enc.encode(src, dest, true, false); // flush it WVFAIL(result); // should be one byte left over... WVPASS(dest.used() == 5 && !memcmp( dest.peek( 0, dest.used() ), "a2Vua", 5)); src.put( "en", 2 ); result = enc.encode(src, dest, true, false); // flush it WVPASS(result); // now had 4+2 = 6 bytes... no leftover data WVPASS(dest.used() == 8 && !memcmp( dest.peek( 0, dest.used() ), "a2Vua2Vu", 8)); } // same test, but with flush == false // flush is true { WvBase64Encoder enc; WvDynBuf src, dest; bool result; src.put( "kenk", 4 ); result = enc.encode(src, dest, false, false); // flush it WVPASS(result); // should be one byte left over... WVPASS(dest.used() == 5 && !memcmp( dest.peek( 0, dest.used() ), "a2Vua", 5)); src.put( "en", 2 ); result = enc.encode(src, dest, false, false); // flush it WVPASS(result); // now had 4+2 = 6 bytes... no leftover data WVPASS(dest.used() == 8 && !memcmp( dest.peek( 0, dest.used() ), "a2Vua2Vu", 8)); } // same test as first, but calling flush() instead // flush is true { WvBase64Encoder enc; WvDynBuf src, dest; bool result; src.put( "kenk", 4 ); result = enc.flush(src, dest, false); // flush it WVFAIL(result); // should be one byte left over... WVPASS(dest.used() == 5 && !memcmp( dest.peek( 0, dest.used() ), "a2Vua", 5)); src.put( "en", 2 ); result = enc.flush(src, dest, false); // flush it WVPASS(result); // now had 4+2 = 6 bytes... no leftover data WVPASS(dest.used() == 8 && !memcmp( dest.peek( 0, dest.used() ), "a2Vua2Vu", 8)); } } WVTEST_MAIN("finishing") { // Tests that calling finish() works correctly { WvBase64Encoder enc; WvString a(FOUR_LETTERS), a_enc(FOUR_LETTERS_ENC), result; enc.flushstrstr(a, result, true); WVPASS(result == a_enc); WVPASS(enc.isfinished()); WVPASS(enc.isfinished() == enc.isok()); // should be able to call twice WvString should_be_empty(""); bool b = enc.flushstrstr( a, should_be_empty, true ); // can't encode again WVPASS(b == false); WVPASS(should_be_empty == ""); WVFAIL(should_be_empty == a_enc); } // Same test as above, but explicitly call finish() { WvBase64Encoder enc; WvDynBuf src, dest; src.put(FOUR_LETTERS, sizeof(FOUR_LETTERS) - 1); enc.encode(src, dest, true, false); // don't finish yet. enc.finish(dest); // ok, NOW finish. WVPASS(dest.used() == 8 && !memcmp( dest.peek( 0, dest.used() ), FOUR_LETTERS_ENC, 8)); WVPASS(enc.isfinished()); WVPASS(enc.isfinished() == enc.isok()); // should be able to call twice // shouldn't be able to encode again dest.zap(); bool b = enc.encode( src, dest, true, true ); WVPASS(b == false); WVPASS(dest.used() == 0); } // Now we do the same two tests with Decoders { WvBase64Decoder dec; WvString a(FOUR_LETTERS_ENC), a_dec(FOUR_LETTERS), result; dec.flushstrstr(a, result, true); WVPASS(result == a_dec); WVPASS(dec.isfinished()); WVPASS(dec.isfinished() == dec.isok()); // should be able to call twice WvString should_be_empty(""); bool b = dec.flushstrstr( a, should_be_empty, true ); // can't encode again WVPASS(b == false); WVPASS(should_be_empty == ""); WVFAIL(should_be_empty == a_dec); } { WvBase64Decoder dec; WvDynBuf src, dest; src.put(FOUR_LETTERS_ENC, sizeof(FOUR_LETTERS_ENC) - 1); dec.encode(src, dest, true, false); // don't finish yet. dec.finish(dest); // ok, NOW finish. WVPASS(dest.used() == 4 && !memcmp( dest.peek( 0, dest.used() ), FOUR_LETTERS, 4)); WVPASS(dec.isfinished()); WVPASS(dec.isfinished() == dec.isok()); // should be able to call twice // shouldn't be able to encode again dest.zap(); bool b = dec.encode( src, dest, true, true ); WVPASS(b == false); WVPASS(dest.used() == 0); } } WVTEST_MAIN("resetting") { { // call finish, make sure it was finished, then reset it and // use it again WvBase64Encoder enc; WvDynBuf temp; WvString b(SIX_LETTERS), b_enc(SIX_LETTERS_ENC); WvString should_be_empty(""); enc.finish(temp); WVPASS(enc.isfinished()); // ensure finished bool ret = enc.flushstrstr( b, should_be_empty, true ); // can't encode again WVPASS(ret == false); WVPASS(should_be_empty == ""); WVFAIL(should_be_empty == b_enc); enc.reset(); WVFAIL(enc.isfinished()); WvString result(""); enc.flushstrstr(b, result, true); WVPASS(result == b_enc); WVPASS(enc.isfinished()); } // same test as above with Decoder { // call finish, make sure it was finished, then reset it and // use it again WvBase64Decoder dec; WvDynBuf temp; WvString b(SIX_LETTERS), b_enc(SIX_LETTERS_ENC); WvString should_be_empty(""); dec.finish(temp); WVPASS(dec.isfinished()); // ensure finished bool ret = dec.flushstrstr( b_enc, should_be_empty, true ); // can't encode again WVPASS(ret == false); WVPASS(should_be_empty == ""); WVFAIL(should_be_empty == b); dec.reset(); WVFAIL(dec.isfinished()); WvString result(""); dec.flushstrstr(b_enc, result, true); wvcon->print( "%s", result ); WVPASS(result == b); WVPASS(dec.isfinished()); } }