From: Stephan Bosch Date: Wed, 13 Feb 2019 18:36:52 +0000 (+0100) Subject: lib: istream-base64 - Add support for base64url encoding. X-Git-Tag: 2.3.9~281 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=4f7358460f4dbdc3857456c9c6b869b021eddf24;p=thirdparty%2Fdovecot%2Fcore.git lib: istream-base64 - Add support for base64url encoding. --- diff --git a/src/lib/istream-base64-decoder.c b/src/lib/istream-base64-decoder.c index 43c08b631e..aa94be5881 100644 --- a/src/lib/istream-base64-decoder.c +++ b/src/lib/istream-base64-decoder.c @@ -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); +} diff --git a/src/lib/istream-base64-encoder.c b/src/lib/istream-base64-encoder.c index 8d8f4766bc..7cc1a26290 100644 --- a/src/lib/istream-base64-encoder.c +++ b/src/lib/istream-base64-encoder.c @@ -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); +} diff --git a/src/lib/istream-base64.h b/src/lib/istream-base64.h index 8639cf78bc..4f422f7bd6 100644 --- a/src/lib/istream-base64.h +++ b/src/lib/istream-base64.h @@ -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 diff --git a/src/lib/test-base64.c b/src/lib/test-base64.c index 69cab35a2a..e1392047b9 100644 --- a/src/lib/test-base64.c +++ b/src/lib/test-base64.c @@ -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); diff --git a/src/lib/test-istream-base64-decoder.c b/src/lib/test-istream-base64-decoder.c index 1dd89db634..cc642352a6 100644 --- a/src/lib/test-istream-base64-decoder.c +++ b/src/lib/test-istream-base64-decoder.c @@ -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(); } } diff --git a/src/lib/test-istream-base64-encoder.c b/src/lib/test-istream-base64-encoder.c index cf5c7ed60b..a0b68fed1e 100644 --- a/src/lib/test-istream-base64-encoder.c +++ b/src/lib/test-istream-base64-encoder.c @@ -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(); }