libbb: split decode_base64 off read_base64
No binary (build failed)
—
(log yok)
| @@ -1591,7 +1591,8 @@ enum { | |
| /* Sign-extends to a value which never matches fgetc result: */ | |
| BASE64_FLAG_NO_STOP_CHAR = 0x80, | |
| }; | |
| - | void FAST_FUNC read_base64(FILE *src_stream, FILE *dst_stream, int flags); |
| + | const char *decode_base64(char **pp_dst, const char *src) FAST_FUNC; |
| + | void read_base64(FILE *src_stream, FILE *dst_stream, int flags) FAST_FUNC; |
| typedef struct md5_ctx_t { | |
| uint8_t wbuffer[64]; /* always correctly aligned for uint64_t */ | |
| @@ -73,23 +73,23 @@ void FAST_FUNC bb_uuencode(char *p, const void *src, int length, const char *tbl | |
| } | |
| /* | |
| - | * Decode base64 encoded stream. |
| - | * Can stop on EOF, specified char, or on uuencode-style "====" line: |
| - | * flags argument controls it. |
| + | * Decode base64 encoded string. Stops on '\0'. |
| + | * |
| + | * Returns: pointer to the undecoded part of source. |
| + | * If points to '\0', then the source was fully decoded. |
| + | * (*dst): advanced past the last written byte. |
| */ | |
| - | void FAST_FUNC read_base64(FILE *src_stream, FILE *dst_stream, int flags) |
| + | const char* FAST_FUNC decode_base64(char **pp_dst, const char *src) |
| { | |
| - | /* Note that EOF _can_ be passed as exit_char too */ |
| - | #define exit_char ((int)(signed char)flags) |
| - | #define uu_style_end (flags & BASE64_FLAG_UU_STOP) |
| - | |
| - | int term_count = 0; |
| + | char *dst = *pp_dst; |
| + | const char *src_tail; |
| while (1) { | |
| unsigned char translated[4]; | |
| int count = 0; | |
| /* Process one group of 4 chars */ | |
| + | src_tail = src; |
| while (count < 4) { | |
| char *table_ptr; | |
| int ch; | |
| @@ -101,11 +101,20 @@ void FAST_FUNC read_base64(FILE *src_stream, FILE *dst_stream, int flags) | |
| * "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n" | |
| */ | |
| do { | |
| - | ch = fgetc(src_stream); |
| - | if (ch == exit_char && count == 0) |
| - | return; |
| - | if (ch == EOF) |
| - | bb_error_msg_and_die("truncated base64 input"); |
| + | ch = *src; |
| + | if (ch == '\0') { |
| + | if (count == 0) { |
| + | /* Example: |
| + | * If we decode "QUJD <NUL>", we want |
| + | * to return ptr to NUL, not to ' ', |
| + | * because we did fully decode |
| + | * the string (to "ABC"). |
| + | */ |
| + | src_tail = src; |
| + | } |
| + | goto ret; |
| + | } |
| + | src++; |
| table_ptr = strchr(bb_uuenc_tbl_base64, ch); | |
| //TODO: add BASE64_FLAG_foo to die on bad char? | |
| //Note that then we may need to still allow '\r' (for mail processing) | |
| @@ -114,21 +123,15 @@ void FAST_FUNC read_base64(FILE *src_stream, FILE *dst_stream, int flags) | |
| /* Convert encoded character to decimal */ | |
| ch = table_ptr - bb_uuenc_tbl_base64; | |
| - | if (ch == 65 /* '\n' */) { |
| - | /* Terminating "====" line? */ |
| - | if (uu_style_end && term_count == 4) |
| - | return; /* yes */ |
| - | term_count = 0; |
| + | if (ch == 65) { /* '\n' */ |
| continue; | |
| } | |
| /* ch is 64 if char was '=', otherwise 0..63 */ | |
| translated[count] = ch & 63; /* 64 -> 0 */ | |
| - | if (ch == 64) { |
| - | term_count++; |
| + | if (ch == 64) { /* '=' */ |
| break; | |
| } | |
| count++; | |
| - | term_count = 0; |
| } | |
| /* Merge 6 bit chars to 8 bit. | |
| @@ -136,10 +139,82 @@ void FAST_FUNC read_base64(FILE *src_stream, FILE *dst_stream, int flags) | |
| * "eQ==" -> "y", not "y NUL NUL" | |
| */ | |
| if (count > 1) | |
| - | fputc(translated[0] << 2 | translated[1] >> 4, dst_stream); |
| + | *dst++ = translated[0] << 2 | translated[1] >> 4; |
| if (count > 2) | |
| - | fputc(translated[1] << 4 | translated[2] >> 2, dst_stream); |
| + | *dst++ = translated[1] << 4 | translated[2] >> 2; |
| if (count > 3) | |
| - | fputc(translated[2] << 6 | translated[3], dst_stream); |
| + | *dst++ = translated[2] << 6 | translated[3]; |
| } /* while (1) */ | |
| + | ret: |
| + | *pp_dst = dst; |
| + | return src_tail; |
| + | } |
| + | |
| + | /* |
| + | * Decode base64 encoded stream. |
| + | * Can stop on EOF, specified char, or on uuencode-style "====" line: |
| + | * flags argument controls it. |
| + | */ |
| + | void FAST_FUNC read_base64(FILE *src_stream, FILE *dst_stream, int flags) |
| + | { |
| + | /* Note that EOF _can_ be passed as exit_char too */ |
| + | #define exit_char ((int)(signed char)flags) |
| + | #define uu_style_end (flags & BASE64_FLAG_UU_STOP) |
| + | |
| + | /* uuencoded files have 61 byte lines. Use 64 byte buffer |
| + | * to process line at a time. |
| + | */ |
| + | enum { BUFFER_SIZE = 64 }; |
| + | |
| + | char in_buf[BUFFER_SIZE + 2]; |
| + | char out_buf[BUFFER_SIZE / 4 * 3 + 2]; |
| + | char *out_tail; |
| + | const char *in_tail; |
| + | int term_seen = 0; |
| + | int in_count = 0; |
| + | |
| + | while (1) { |
| + | while (in_count < BUFFER_SIZE) { |
| + | int ch = fgetc(src_stream); |
| + | if (ch == exit_char) { |
| + | if (in_count == 0) |
| + | return; |
| + | term_seen = 1; |
| + | break; |
| + | } |
| + | if (ch == EOF) { |
| + | term_seen = 1; |
| + | break; |
| + | } |
| + | /* Prevent "====" line to be split: stop if we see '\n'. |
| + | * We can also skip other whitespace and skirt the problem |
| + | * of files with NULs by stopping on any control char or space: |
| + | */ |
| + | if (ch <= ' ') |
| + | break; |
| + | in_buf[in_count++] = ch; |
| + | } |
| + | in_buf[in_count] = '\0'; |
| + | |
| + | /* Did we encounter "====" line? */ |
| + | if (uu_style_end && strcmp(in_buf, "====") == 0) |
| + | return; |
| + | |
| + | out_tail = out_buf; |
| + | in_tail = decode_base64(&out_tail, in_buf); |
| + | |
| + | fwrite(out_buf, (out_tail - out_buf), 1, dst_stream); |
| + | |
| + | if (term_seen) { |
| + | /* Did we consume ALL characters? */ |
| + | if (*in_tail == '\0') |
| + | return; |
| + | /* No */ |
| + | bb_error_msg_and_die("truncated base64 input"); |
| + | } |
| + | |
| + | /* It was partial decode */ |
| + | in_count = strlen(in_tail); |
| + | memmove(in_buf, in_tail, in_count); |
| + | } |
| } | |
| @@ -8,9 +8,9 @@ | |
| . ./testing.sh | |
| - | # testing "test name" "options" "expected result" "file input" "stdin" |
| - | # file input will be file called "input" |
| - | # test can create a file "actual" instead of writing to stdout |
| + | # testing "test name" "command(s)" "expected result" "file input" "stdin" |
| + | # file input will be file called "input" |
| + | # test can create a file "actual" instead of writing to stdout |
| # Test setup of standard input | |
| umask 0 | |
| @@ -24,4 +24,99 @@ testing "uuencode correct encoding" "uuencode bb_uuenc_test.out" \ | |
| testing "uuencode correct base64 encoding" "uuencode -m bb_uuenc_test.out" \ | |
| "begin-base64 644 bb_uuenc_test.out\nVGhlIGZhc3QgZ3JleSBmb3gganVtcGVkIG92ZXIgdGhlIGxhenkgYnJvd24g\nZG9nLgo=\n====\n" \ | |
| "" "The fast grey fox jumped over the lazy brown dog.\n" | |
| + | |
| + | testing "uuencode empty file" 'r=`uuencode FILE`; echo "$r"; echo "$r" | uudecode -o -;' \ |
| + | 'begin 644 FILE |
| + | ` |
| + | end |
| + | ' "" "" |
| + | testing "uuencode -m empty file" 'r=`uuencode -m FILE`; echo "$r"; echo "$r" | uudecode -o -;' \ |
| + | 'begin-base64 644 FILE |
| + | ==== |
| + | ' "" "" |
| + | |
| + | testing "uuencode file 'A'" 'r=`uuencode FILE`; echo "$r"; echo "$r" | uudecode -o -;' \ |
| + | 'begin 644 FILE |
| + | !00`` |
| + | ` |
| + | end |
| + | A' "" "A" |
| + | testing "uuencode -m file 'A'" 'r=`uuencode -m FILE`; echo "$r"; echo "$r" | uudecode -o -;' \ |
| + | 'begin-base64 644 FILE |
| + | QQ== |
| + | ==== |
| + | A' "" "A" |
| + | |
| + | testing "uuencode file 'AB'" 'r=`uuencode FILE`; echo "$r"; echo "$r" | uudecode -o -;' \ |
| + | 'begin 644 FILE |
| + | "04(` |
| + | ` |
| + | end |
| + | AB' "" "AB" |
| + | testing "uuencode -m file 'AB'" 'r=`uuencode -m FILE`; echo "$r"; echo "$r" | uudecode -o -;' \ |
| + | 'begin-base64 644 FILE |
| + | QUI= |
| + | ==== |
| + | AB' "" "AB" |
| + | |
| + | testing "uuencode file 'ABC'" 'r=`uuencode FILE`; echo "$r"; echo "$r" | uudecode -o -;' \ |
| + | 'begin 644 FILE |
| + | #04)# |
| + | ` |
| + | end |
| + | ABC' "" "ABC" |
| + | testing "uuencode -m file 'ABC'" 'r=`uuencode -m FILE`; echo "$r"; echo "$r" | uudecode -o -;' \ |
| + | 'begin-base64 644 FILE |
| + | QUJD |
| + | ==== |
| + | ABC' "" "ABC" |
| + | |
| + | testing "uuencode file 'ABCD'" 'r=`uuencode FILE`; echo "$r"; echo "$r" | uudecode -o -;' \ |
| + | 'begin 644 FILE |
| + | $04)#1``` |
| + | ` |
| + | end |
| + | ABCD' "" "ABCD" |
| + | testing "uuencode -m file 'ABCD'" 'r=`uuencode -m FILE`; echo "$r"; echo "$r" | uudecode -o -;' \ |
| + | 'begin-base64 644 FILE |
| + | QUJDRA== |
| + | ==== |
| + | ABCD' "" "ABCD" |
| + | |
| + | testing "uuencode file 'ABCDE'" 'r=`uuencode FILE`; echo "$r"; echo "$r" | uudecode -o -;' \ |
| + | 'begin 644 FILE |
| + | %04)#1$4` |
| + | ` |
| + | end |
| + | ABCDE' "" "ABCDE" |
| + | testing "uuencode -m file 'ABCDE'" 'r=`uuencode -m FILE`; echo "$r"; echo "$r" | uudecode -o -;' \ |
| + | 'begin-base64 644 FILE |
| + | QUJDREU= |
| + | ==== |
| + | ABCDE' "" "ABCDE" |
| + | |
| + | testing "uuencode file 'ABCDEF'" 'r=`uuencode FILE`; echo "$r"; echo "$r" | uudecode -o -;' \ |
| + | 'begin 644 FILE |
| + | &04)#1$5& |
| + | ` |
| + | end |
| + | ABCDEF' "" "ABCDEF" |
| + | testing "uuencode -m file 'ABCDEF'" 'r=`uuencode -m FILE`; echo "$r"; echo "$r" | uudecode -o -;' \ |
| + | 'begin-base64 644 FILE |
| + | QUJDREVG |
| + | ==== |
| + | ABCDEF' "" "ABCDEF" |
| + | |
| + | testing "uuencode file 'A<NUL><0xff>Z'" 'r=`uuencode FILE`; echo "$r"; echo "$r" | uudecode -o -;' \ |
| + | 'begin 644 FILE |
| + | $00#_6@`` |
| + | ` |
| + | end |
| + | A\x0\xffZ' "" "A\x0\xffZ" |
| + | testing "uuencode -m file 'A<NUL><0xff>Z'" 'r=`uuencode -m FILE`; echo "$r"; echo "$r" | uudecode -o -;' \ |
| + | 'begin-base64 644 FILE |
| + | QQD/Wg== |
| + | ==== |
| + | A\x0\xffZ' "" "A\x0\xffZ" |
| + | |
| exit $FAILCOUNT | |