libbb: split decode_base64 off read_base64
authorLeonid Lisovskiy <lly.dev@gmail.com>
Fri, 28 Oct 2011 11:59:04 +0000 (13:59 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Fri, 28 Oct 2011 11:59:55 +0000 (13:59 +0200)
function                                             old     new   delta
decode_base64                                          -     182    +182
read_base64                                          378     255    -123
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 0/1 up/down: 182/-123)           Total: 59 bytes

Signed-off-by: Leonid Lisovskiy <lly.dev@gmail.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
include/libbb.h
libbb/uuencode.c
testsuite/uuencode.tests

index d248781..791cdd9 100644 (file)
@@ -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 */
index 03e708f..23e2123 100644 (file)
@@ -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);
+       }
 }
index cd6191b..6ce70f7 100755 (executable)
@@ -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