]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib: istream-base64 - Add support for base64url encoding.
authorStephan Bosch <stephan.bosch@dovecot.fi>
Wed, 13 Feb 2019 18:36:52 +0000 (19:36 +0100)
committerStephan Bosch <stephan.bosch@open-xchange.com>
Mon, 26 Aug 2019 19:15:29 +0000 (21:15 +0200)
src/lib/istream-base64-decoder.c
src/lib/istream-base64-encoder.c
src/lib/istream-base64.h
src/lib/test-base64.c
src/lib/test-istream-base64-decoder.c
src/lib/test-istream-base64-encoder.c

index 43c08b631e6f4a604d41ceff8f12259cc165fb02..aa94be58818f57d5e16330d0c246861fb235ad3f 100644 (file)
@@ -9,6 +9,8 @@
 
 struct base64_decoder_istream {
        struct istream_private istream;
+
+       const struct base64_scheme *b64;
 };
 
 static int i_stream_read_parent(struct istream_private *stream)
@@ -58,7 +60,7 @@ i_stream_base64_try_decode_block(struct base64_decoder_istream *bstream)
 
        buffer_create_from_data(&buf, stream->w_buffer + stream->pos,
                                buffer_avail);
-       if (base64_decode(data, size, &pos, &buf) < 0) {
+       if (base64_scheme_decode(bstream->b64, data, size, &pos, &buf) < 0) {
                io_stream_set_error(&stream->iostream,
                        "Invalid base64 data: 0x%s",
                        binary_to_hex(data+pos, I_MIN(size-pos, 8)));
@@ -71,15 +73,17 @@ i_stream_base64_try_decode_block(struct base64_decoder_istream *bstream)
        return pos > 0 ? 1 : 0;
 }
 
-static void i_stream_base64_last_partial_block(struct istream_private *stream)
+static void
+i_stream_base64_last_partial_block(struct base64_decoder_istream *bstream)
 {
+       struct istream_private *stream = &bstream->istream;
        const unsigned char *data;
        size_t i, size;
 
        /* base64 input with a partial block */
        data = i_stream_get_data(stream->parent, &size);
        for (i = 0; i < size; i++) {
-               if (!base64_is_valid_char(data[i]))
+               if (!base64_scheme_is_valid_char(bstream->b64, data[i]))
                        break;
        }
        if (i == size) {
@@ -107,7 +111,7 @@ static ssize_t i_stream_base64_decoder_read(struct istream_private *stream)
                if (ret <= 0) {
                        if (ret < 0 && stream->istream.stream_errno == 0 &&
                            i_stream_get_data_size(stream->parent) > 0)
-                               i_stream_base64_last_partial_block(stream);
+                               i_stream_base64_last_partial_block(bstream);
                        return ret;
                }
 
@@ -139,12 +143,14 @@ i_stream_base64_decoder_seek(struct istream_private *stream,
        i_stream_default_seek_nonseekable(stream, v_offset, mark);
 }
 
-struct istream *
-i_stream_create_base64_decoder(struct istream *input)
+static struct istream *
+i_stream_create_base64_decoder_common(const struct base64_scheme *b64,
+                                     struct istream *input)
 {
        struct base64_decoder_istream *bstream;
 
        bstream = i_new(struct base64_decoder_istream, 1);
+       bstream->b64 = b64;
        bstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
 
        bstream->istream.read = i_stream_base64_decoder_read;
@@ -156,3 +162,15 @@ i_stream_create_base64_decoder(struct istream *input)
        return i_stream_create(&bstream->istream, input,
                               i_stream_get_fd(input), 0);
 }
+
+struct istream *
+i_stream_create_base64_decoder(struct istream *input)
+{
+       return i_stream_create_base64_decoder_common(&base64_scheme, input);
+}
+
+struct istream *
+i_stream_create_base64url_decoder(struct istream *input)
+{
+       return i_stream_create_base64_decoder_common(&base64url_scheme, input);
+}
index 8d8f4766bc3a496372dc3f00dd984c8922d751e3..7cc1a26290c5a947b2fe1adad764435b919b1c7b 100644 (file)
@@ -9,6 +9,8 @@
 struct base64_encoder_istream {
        struct istream_private istream;
 
+       const struct base64_scheme *b64;
+
        /* current encoded line length. */
        size_t cur_line_len;
 
@@ -84,7 +86,7 @@ i_stream_base64_try_encode_line(struct base64_encoder_istream *bstream)
 
        buffer_create_from_data(&buf, stream->w_buffer + stream->pos,
                                buffer_avail);
-       base64_encode(data, size, &buf);
+       base64_scheme_encode(bstream->b64, data, size, &buf);
        i_assert(buf.used > 0);
 
        bstream->cur_line_len += buf.used;
@@ -174,15 +176,17 @@ i_stream_base64_encoder_stat(struct istream_private *stream,
        return 0;
 }
 
-struct istream *
-i_stream_create_base64_encoder(struct istream *input,
-                              unsigned int chars_per_line, bool crlf)
+static struct istream *
+i_stream_create_base64_encoder_common(const struct base64_scheme *b64,
+                                     struct istream *input,
+                                     unsigned int chars_per_line, bool crlf)
 {
        struct base64_encoder_istream *bstream;
 
        i_assert(chars_per_line % 4 == 0);
 
        bstream = i_new(struct base64_encoder_istream, 1);
+       bstream->b64 = b64;
        bstream->chars_per_line = chars_per_line;
        bstream->crlf = crlf;
        bstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
@@ -197,3 +201,19 @@ i_stream_create_base64_encoder(struct istream *input,
        return i_stream_create(&bstream->istream, input,
                               i_stream_get_fd(input), 0);
 }
+
+struct istream *
+i_stream_create_base64_encoder(struct istream *input,
+                              unsigned int chars_per_line, bool crlf)
+{
+       return i_stream_create_base64_encoder_common(&base64_scheme, input,
+                                                    chars_per_line, crlf);
+}
+
+struct istream *
+i_stream_create_base64url_encoder(struct istream *input,
+                                 unsigned int chars_per_line, bool crlf)
+{
+       return i_stream_create_base64_encoder_common(&base64url_scheme, input,
+                                                    chars_per_line, crlf);
+}
index 8639cf78bc98df5473b0d834986d6c057e329c4f..4f422f7bd66d289e9ebbb1107192fe5ed686a285 100644 (file)
@@ -5,6 +5,12 @@ struct istream *
 i_stream_create_base64_encoder(struct istream *input,
                               unsigned int chars_per_line, bool crlf);
 struct istream *
+i_stream_create_base64url_encoder(struct istream *input,
+                                 unsigned int chars_per_line, bool crlf);
+
+struct istream *
 i_stream_create_base64_decoder(struct istream *input);
+struct istream *
+i_stream_create_base64url_decoder(struct istream *input);
 
 #endif
index 69cab35a2a3c12e597836fc75ecbc8f763fbc1b5..e1392047b9d71281eabbf2d9b561e619225c3320 100644 (file)
@@ -191,21 +191,31 @@ static void test_base64url_decode(void)
                  "\x81\xd1\x82\x2e", 0, UINT_MAX },
        };
        string_t *str;
+       buffer_t buf;
        unsigned int i;
        size_t src_pos;
        int ret;
 
        test_begin("base64url_decode()");
-       str = t_str_new(256);
        for (i = 0; i < N_ELEMENTS(tests); i++) {
-               str_truncate(str, 0);
+               /* Some of the base64_decode() callers use fixed size buffers.
+                  Use a fixed size buffer here as well to test that
+                  base64_decode() can't allocate any extra space even
+                  temporarily. */
+               size_t max_decoded_size =
+                       MAX_BASE64_DECODED_SIZE(strlen(tests[i].input));
 
+               buffer_create_from_data(&buf, t_malloc0(max_decoded_size),
+                                       max_decoded_size);
+               str = &buf;
                src_pos = 0;
                ret = base64url_decode(tests[i].input, strlen(tests[i].input),
                                       &src_pos, str);
 
                test_assert_idx(tests[i].ret == ret, i);
-               test_assert_idx(strcmp(tests[i].output, str_c(str)) == 0, i);
+               test_assert_idx(strlen(tests[i].output) == str_len(str) &&
+                               memcmp(tests[i].output, str_data(str),
+                                      str_len(str)) == 0, i);
                test_assert_idx(src_pos == tests[i].src_pos ||
                                (tests[i].src_pos == UINT_MAX &&
                                 src_pos == strlen(tests[i].input)), i);
index 1dd89db63488594f9932fc15ce5ca54b1b52da07..cc642352a6b0f724ee90b50f6cff49d3eb52e1a6 100644 (file)
@@ -5,14 +5,46 @@
 #include "istream-private.h"
 #include "istream-base64.h"
 
-static const struct {
+struct base64_istream_test {
        const char *input;
        const char *output;
        int stream_errno;
-} tests[] = {
+};
+
+static const struct base64_istream_test base64_tests[] = {
        { "aGVsbG8gd29ybGQ=", "hello world", 0 },
        { "\naGVs\nbG8g\nd29y\nbGQ=\n", "hello world", 0 },
-       { "  aGVs    \r\n bG8g  \r\n   d29y  \t \r\n    bGQ= \r\n\r\n", "hello world", 0 },
+       { "  aGVs    \r\n bG8g  \r\n   d29y  \t \r\n    bGQ= \r\n\r\n",
+         "hello world", 0 },
+       { "0JPQvtCy0L7RgNGPzIHRgiwg0YfRgtC+INC60YPRgCDQtNC+0Y/MgdGCLg==",
+         "\xd0\x93\xd0\xbe\xd0\xb2\xd0\xbe\xd1\x80\xd1\x8f\xcc"
+         "\x81\xd1\x82\x2c\x20\xd1\x87\xd1\x82\xd0\xbe\x20\xd0"
+         "\xba\xd1\x83\xd1\x80\x20\xd0\xb4\xd0\xbe\xd1\x8f\xcc"
+         "\x81\xd1\x82\x2e", 0 },
+       { "\r", "", 0 },
+       { "\n", "", 0 },
+       { "\r\n", "", 0 },
+       { "  ", "", 0 },
+       { "foo", "", EPIPE },
+       { "foo ", "", EINVAL },
+       { "Zm9vC", "foo", EPIPE },
+       { "Zm9v!", "foo", EINVAL },
+       { "Zm9!v", "", EINVAL },
+       { "Zm9 v", "", EINVAL },
+       { "Zm 9v", "", EINVAL },
+       { "Z m9v", "", EINVAL },
+};
+
+static const struct base64_istream_test base64url_tests[] = {
+       { "aGVsbG8gd29ybGQ=", "hello world", 0 },
+       { "\naGVs\nbG8g\nd29y\nbGQ=\n", "hello world", 0 },
+       { "  aGVs    \r\n bG8g  \r\n   d29y  \t \r\n    bGQ= \r\n\r\n",
+         "hello world", 0 },
+       { "0JPQvtCy0L7RgNGPzIHRgiwg0YfRgtC-INC60YPRgCDQtNC-0Y_MgdGCLg==",
+         "\xd0\x93\xd0\xbe\xd0\xb2\xd0\xbe\xd1\x80\xd1\x8f\xcc"
+         "\x81\xd1\x82\x2c\x20\xd1\x87\xd1\x82\xd0\xbe\x20\xd0"
+         "\xba\xd1\x83\xd1\x80\x20\xd0\xb4\xd0\xbe\xd1\x8f\xcc"
+         "\x81\xd1\x82\x2e", 0 },
        { "\r", "", 0 },
        { "\n", "", 0 },
        { "\r\n", "", 0 },
@@ -28,18 +60,14 @@ static const struct {
 };
 
 static void
-decode_test(const char *base64_input, const char *output, int stream_errno)
+decode_test(unsigned int base64_input_len,
+           struct istream *input_data, struct istream *input,
+           const char *output, int stream_errno)
 {
-       unsigned int base64_input_len = strlen(base64_input);
-       struct istream *input_data, *input;
        const unsigned char *data;
        size_t i, size;
        int ret = 0;
 
-       input_data = test_istream_create_data(base64_input, base64_input_len);
-       test_istream_set_allow_eof(input_data, FALSE);
-       input = i_stream_create_base64_decoder(input_data);
-
        for (i = 1; i <= base64_input_len; i++) {
                test_istream_set_size(input_data, i);
                while ((ret = i_stream_read(input)) > 0) ;
@@ -58,6 +86,38 @@ decode_test(const char *base64_input, const char *output, int stream_errno)
        test_assert(size == strlen(output));
        if (size > 0)
                test_assert(memcmp(data, output, size) == 0);
+}
+
+static void
+decode_base64_test(const char *base64_input, const char *output,
+                  int stream_errno)
+{
+       unsigned int base64_input_len = strlen(base64_input);
+       struct istream *input_data, *input;
+
+       input_data = test_istream_create_data(base64_input, base64_input_len);
+       test_istream_set_allow_eof(input_data, FALSE);
+       input = i_stream_create_base64_decoder(input_data);
+
+       decode_test(base64_input_len, input_data, input, output, stream_errno);
+
+       i_stream_unref(&input);
+       i_stream_unref(&input_data);
+}
+
+static void
+decode_base64url_test(const char *base64_input, const char *output,
+                     int stream_errno)
+{
+       unsigned int base64_input_len = strlen(base64_input);
+       struct istream *input_data, *input;
+
+       input_data = test_istream_create_data(base64_input, base64_input_len);
+       test_istream_set_allow_eof(input_data, FALSE);
+       input = i_stream_create_base64url_decoder(input_data);
+
+       decode_test(base64_input_len, input_data, input, output, stream_errno);
+
        i_stream_unref(&input);
        i_stream_unref(&input_data);
 }
@@ -66,9 +126,22 @@ void test_istream_base64_decoder(void)
 {
        unsigned int i;
 
-       for (i = 0; i < N_ELEMENTS(tests); i++) {
+       for (i = 0; i < N_ELEMENTS(base64_tests); i++) {
+               const struct base64_istream_test *test = &base64_tests[i];
+
                test_begin(t_strdup_printf("istream base64 decoder %u", i+1));
-               decode_test(tests[i].input, tests[i].output, tests[i].stream_errno);
+               decode_base64_test(test->input, test->output,
+                                  test->stream_errno);
+               test_end();
+       }
+
+       for (i = 0; i < N_ELEMENTS(base64url_tests); i++) {
+               const struct base64_istream_test *test = &base64url_tests[i];
+
+               test_begin(t_strdup_printf("istream base64url decoder %u",
+                                          i+1));
+               decode_base64url_test(test->input, test->output,
+                                     test->stream_errno);
                test_end();
        }
 }
index cf5c7ed60bef303df90ede3833be0a44a69efe0a..a0b68fed1e4e04e26a7a915d054ba19b3d8e2158 100644 (file)
@@ -5,12 +5,14 @@
 #include "istream-private.h"
 #include "istream-base64.h"
 
-static const struct test {
+struct base64_istream_test {
        const char *input;
        unsigned int chars_per_line;
        bool crlf;
        const char *output;
-} tests[] = {
+};
+
+static const struct base64_istream_test base64_tests[] = {
        { "", 80, FALSE, "" },
        { "1", 80, FALSE, "MQ==" },
        { "12", 80, FALSE, "MTI=" },
@@ -32,25 +34,54 @@ static const struct test {
                "aGVsbG8g\ndG8gdGhl\nIHdvcmxk\nISE=" },
        { "hello to the world!!", 8, TRUE,
                "aGVsbG8g\r\ndG8gdGhl\r\nIHdvcmxk\r\nISE=" },
+       { "\xd0\x93\xd0\xbe\xd0\xb2\xd0\xbe\xd1\x80\xd1\x8f\xcc"
+         "\x81\xd1\x82\x2c\x20\xd1\x87\xd1\x82\xd0\xbe\x20\xd0"
+         "\xba\xd1\x83\xd1\x80\x20\xd0\xb4\xd0\xbe\xd1\x8f\xcc"
+         "\x81\xd1\x82\x2e", 80, FALSE,
+         "0JPQvtCy0L7RgNGPzIHRgiwg0YfRgtC+INC60YPRgCDQtNC+0Y/MgdGCLg==" },
 };
 
+static const struct base64_istream_test base64url_tests[] = {
+       { "", 80, FALSE, "" },
+       { "1", 80, FALSE, "MQ==" },
+       { "12", 80, FALSE, "MTI=" },
+       { "123", 80, FALSE, "MTIz" },
+       { "1234", 80, FALSE, "MTIzNA==" },
+       { "12345", 80, FALSE, "MTIzNDU=" },
+       { "hello world", 80, FALSE, "aGVsbG8gd29ybGQ=" },
+       { "hello world", 4, FALSE, "aGVs\nbG8g\nd29y\nbGQ=" },
+       { "hello world", 4, TRUE, "aGVs\r\nbG8g\r\nd29y\r\nbGQ=" },
+       { "hello worlds", 80, FALSE, "aGVsbG8gd29ybGRz" },
+       { "hello worlds", 4, FALSE, "aGVs\nbG8g\nd29y\nbGRz" },
+       { "hello worlds", 4, TRUE, "aGVs\r\nbG8g\r\nd29y\r\nbGRz" },
+       { "hell world", 80, FALSE, "aGVsbCB3b3JsZA==" },
+       { "hell world", 4, FALSE, "aGVs\nbCB3\nb3Js\nZA==" },
+       { "hell world", 4, TRUE, "aGVs\r\nbCB3\r\nb3Js\r\nZA==" },
+       { "hello to the world!!", 80, FALSE,
+               "aGVsbG8gdG8gdGhlIHdvcmxkISE=" },
+       { "hello to the world!!", 8, FALSE,
+               "aGVsbG8g\ndG8gdGhl\nIHdvcmxk\nISE=" },
+       { "hello to the world!!", 8, TRUE,
+               "aGVsbG8g\r\ndG8gdGhl\r\nIHdvcmxk\r\nISE=" },
+       { "\xd0\x93\xd0\xbe\xd0\xb2\xd0\xbe\xd1\x80\xd1\x8f\xcc"
+         "\x81\xd1\x82\x2c\x20\xd1\x87\xd1\x82\xd0\xbe\x20\xd0"
+         "\xba\xd1\x83\xd1\x80\x20\xd0\xb4\xd0\xbe\xd1\x8f\xcc"
+         "\x81\xd1\x82\x2e", 80, FALSE,
+         "0JPQvtCy0L7RgNGPzIHRgiwg0YfRgtC-INC60YPRgCDQtNC-0Y_MgdGCLg==" },
+};
 
 static const char *hello = "hello world";
 
-static void encode_test(const char *text, unsigned int chars_per_line,
-                       bool crlf, const char *output)
+static void encode_test(unsigned int text_len,
+                       struct istream *input, struct istream *input_data,
+                       const char *output)
 {
-       unsigned int i, text_len = strlen(text);
-       struct istream *input, *input_data;
+       unsigned int i;
        const unsigned char *data;
        uoff_t stream_size;
        size_t size;
        ssize_t ret;
 
-       input_data = test_istream_create_data(text, text_len);
-       test_istream_set_allow_eof(input_data, FALSE);
-       input = i_stream_create_base64_encoder(input_data, chars_per_line, crlf);
-
        for (i = 1; i <= text_len; i++) {
                test_istream_set_size(input_data, i);
                while ((ret = i_stream_read(input)) > 0) ;
@@ -66,23 +97,52 @@ static void encode_test(const char *text, unsigned int chars_per_line,
        ret = i_stream_get_size(input, TRUE, &stream_size);
        test_assert(ret > 0);
        test_assert(size == stream_size);
+}
+
+static void
+encode_base64_test(const char *text, unsigned int chars_per_line,
+                  bool crlf, const char *output)
+{
+       unsigned int text_len = strlen(text);
+       struct istream *input, *input_data;
+
+       input_data = test_istream_create_data(text, text_len);
+       test_istream_set_allow_eof(input_data, FALSE);
+       input = i_stream_create_base64_encoder(input_data, chars_per_line,
+                                              crlf);
+
+       encode_test(text_len, input, input_data, output);
 
        i_stream_unref(&input);
        i_stream_unref(&input_data);
 }
 
 static void
-test_istream_base64_encoder_seek(const char *textin, const char *textout)
+encode_base64url_test(const char *text, unsigned int chars_per_line,
+                     bool crlf, const char *output)
 {
-       unsigned int offset, len = strlen(textout);
+       unsigned int text_len = strlen(text);
        struct istream *input, *input_data;
+
+       input_data = test_istream_create_data(text, text_len);
+       test_istream_set_allow_eof(input_data, FALSE);
+       input = i_stream_create_base64url_encoder(input_data, chars_per_line,
+                                                 crlf);
+
+       encode_test(text_len, input, input_data, output);
+
+       i_stream_unref(&input);
+       i_stream_unref(&input_data);
+}
+
+static void
+test_encoder_seek(struct istream *input, const char *textout)
+{
+       unsigned int offset, len = strlen(textout);
        const unsigned char *data;
        size_t size;
        ssize_t ret;
 
-       input_data = i_stream_create_from_data(textin, strlen(textin));
-       input = i_stream_create_base64_encoder(input_data, 4, TRUE);
-
        while (i_stream_read(input) > 0) ;
        i_stream_skip(input, i_stream_get_data_size(input));
 
@@ -96,6 +156,31 @@ test_istream_base64_encoder_seek(const char *textin, const char *textout)
                test_assert(memcmp(data, textout+offset, size) == 0);
                i_stream_skip(input, size);
        }
+}
+
+static void
+test_istream_base64_encoder_seek(const char *textin, const char *textout)
+{
+       struct istream *input, *input_data;
+
+       input_data = i_stream_create_from_data(textin, strlen(textin));
+       input = i_stream_create_base64_encoder(input_data, 4, TRUE);
+
+       test_encoder_seek(input, textout);
+
+       i_stream_unref(&input);
+       i_stream_unref(&input_data);
+}
+
+static void
+test_istream_base64url_encoder_seek(const char *textin, const char *textout)
+{
+       struct istream *input, *input_data;
+
+       input_data = i_stream_create_from_data(textin, strlen(textin));
+       input = i_stream_create_base64url_encoder(input_data, 4, TRUE);
+
+       test_encoder_seek(input, textout);
 
        i_stream_unref(&input);
        i_stream_unref(&input_data);
@@ -105,13 +190,33 @@ void test_istream_base64_encoder(void)
 {
        unsigned int i;
 
-       for (i = 0; i < N_ELEMENTS(tests); i++) {
-               test_begin(t_strdup_printf("istream base64 decoder %u", i+1));
-               encode_test(tests[i].input, tests[i].chars_per_line,
-                           tests[i].crlf, tests[i].output);
+       for (i = 0; i < N_ELEMENTS(base64_tests); i++) {
+               const struct base64_istream_test *test = &base64_tests[i];
+
+               test_begin(t_strdup_printf(
+                       "istream base64 decoder %u", i+1));
+               encode_base64_test(test->input, test->chars_per_line,
+                                  test->crlf, test->output);
+               test_end();
+       }
+
+       for (i = 0; i < N_ELEMENTS(base64url_tests); i++) {
+               const struct base64_istream_test *test = &base64url_tests[i];
+
+               test_begin(t_strdup_printf(
+                       "istream base64url decoder %u", i+1));
+               encode_base64url_test(test->input, test->chars_per_line,
+                                     test->crlf, test->output);
                test_end();
        }
+
        test_begin("istream base64 encoder seek");
-       test_istream_base64_encoder_seek(hello, "aGVs\r\nbG8g\r\nd29y\r\nbGQ=");
+       test_istream_base64_encoder_seek(
+               hello, "aGVs\r\nbG8g\r\nd29y\r\nbGQ=");
+       test_end();
+
+       test_begin("istream base64url encoder seek");
+       test_istream_base64url_encoder_seek(
+               hello, "aGVs\r\nbG8g\r\nd29y\r\nbGQ=");
        test_end();
 }