diff options
-rw-r--r-- | include/libbb.h | 3 | ||||
-rw-r--r-- | libbb/uuencode.c | 125 | ||||
-rwxr-xr-x | testsuite/uuencode.tests | 101 |
3 files changed, 200 insertions, 29 deletions
diff --git a/include/libbb.h b/include/libbb.h index d248781c3..791cdd94e 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -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 */ diff --git a/libbb/uuencode.c b/libbb/uuencode.c index 03e708fd5..23e2123bc 100644 --- a/libbb/uuencode.c +++ b/libbb/uuencode.c @@ -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); + } } diff --git a/testsuite/uuencode.tests b/testsuite/uuencode.tests index cd6191b1b..6ce70f747 100755 --- a/testsuite/uuencode.tests +++ b/testsuite/uuencode.tests @@ -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 |