const char *input;
const char *output;
int stream_errno;
+ int eof;
} tests[] = {
- { "p=C3=A4=C3=A4t=C3=B6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0 },
- { "p=c3=a4=c3=a4t=c3=b6s= \n", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0 },
- { "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0 },
-
- { "p=c3=a4\rasdf", "p\xC3\xA4", EINVAL },
- { "p=c", "p", EPIPE },
- { "p=A", "p", EPIPE },
- { "p=Ax", "p", EINVAL },
- { "p=c3=a4=c3=a4t=c3=b6s= ", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", EPIPE }
+ { "p=C3=A4=C3=A4t=C3=B6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0 , 0 },
+ { "p=c3=a4=c3=a4t=c3=b6s= \n", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 0 },
+ { "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 1 },
+ { "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 2 },
+ { "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 3 },
+ { "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 4 },
+ { "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 5 },
+ { "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 7 },
+ { "p=c3", "p\xC3", 0, 2 },
+ { "=0A=0D ", "\n\r", 0, 7 },
+ { "foo_bar", "foo_bar", 0, 0 },
+ { "\n\n", "\r\n\r\n", 0, 0 },
+ { "\r\n\n\n\r\n", "\r\n\r\n\r\n\r\n", 0, 0 },
+ /* Unnecessarily encoded */
+ { "=66=6f=6f=42=61=72", "fooBar", 0, 4 },
+ /* Expected to be encoded but not */
+ { "\xc3\x9c""berm=c3=a4\xc3\x9figer Gebrauch", "\xc3\x9c""berm\xc3\xa4\xc3\x9figer Gebrauch", 0, 9 },
+ /* Decode control characters */
+ { "=0C=07", "\x0C\x07", 0, 0 },
+ /* Data */
+ { "=DE=AD=BE=EF", "\xDE\xAD\xBE\xEF", 0, 0 },
+ /* Non hex data */
+ { "=FJ=X1", "", EINVAL, 0 },
+ /* No content allowed after Soft Line Break */
+ { "=C3=9C = ","\xc3\x9c ", EPIPE, 0 },
+ /* Boundary delimiter */
+ { "=C3=9C=\r\n-------","\xc3\x9c-------", 0, 0 },
+ { "=----------- =C3=9C","", EINVAL, 0 },
+ { "=___________ =C3=9C","", EINVAL, 0 },
+ { "___________ =C3=9C","___________ \xc3\x9c", 0, 0 },
+ { "=2D=2D=2D=2D=2D=2D =C3=9C","------ \xc3\x9c", 0, 0 },
+ { "=FC=83=BF=BF=BF=BF", "\xFC\x83\xBF\xBF\xBF\xBF", 0, 0 },
+ { "=FE=FE=FF=FF", "\xFE\xFE\xFF\xFF", 0, 0 },
+ { "\xFF=C3=9C\xFE\xFF""foobar", "\xFF\xc3\x9c\xFE\xFF""foobar", 0, 0 },
+
+ { "p=c3=a4\rasdf", "p\xC3\xA4", EINVAL, 0 },
+ { "=___________ \xc3\x9c","", EINVAL, 0 },
+ { "p=c", "p", EPIPE, 0 },
+ { "p=A", "p", EPIPE, 0 },
+ { "p=Ax", "p", EINVAL, 0 },
+ { "___________ \xc3\x9c=C3=9","___________ \xc3\x9c\xC3", EPIPE, 0},
+ { "p=c3=a4=c3=a4t=c3=b6s= ", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", EPIPE, 0 },
+ /* Soft Line Break example from the RFC */
+ {
+ "Now's the time =\r\nfor all folk to come=\r\n to the aid of "
+ "their country.", "Now's the time for all folk to come to the"
+ " aid of their country.", 0, 41
+ },
};
+static bool is_hex(char c) {
+ return ((c >= 48 && c <= 57) || (c >= 65 && c <= 70)
+ || (c >= 97 && c <= 102));
+
+}
+
+static unsigned int
+get_encoding_size_diff(const char *qp_input, unsigned int limit)
+{
+ unsigned int encoded_chars = 0;
+ unsigned int soft_line_breaks = 0;
+ for (unsigned int i = 0; i < limit; i++) {
+ char c = qp_input[i];
+ if (c == '=' && i+2 < limit) {
+ if (qp_input[i+1] == '\r' && qp_input[i+2] == '\n') {
+ soft_line_breaks++;
+ i += 2;
+ limit += 3;
+ } else if (is_hex(qp_input[i+1]) && is_hex(qp_input[i+2])) {
+ encoded_chars++;
+ i += 2;
+ limit += 2;
+ }
+ }
+ }
+ return encoded_chars*2 + soft_line_breaks*3;
+}
+
static void
decode_test(const char *qp_input, const char *output, int stream_errno,
- unsigned int buffer_size)
+ unsigned int buffer_size, unsigned int eof)
{
size_t qp_input_len = strlen(qp_input);
- struct istream *input_data, *input;
+ struct istream *input_data, *input_data_limited, *input;
const unsigned char *data;
size_t i, size;
string_t *str = t_str_new(32);
test_assert(ret == -1);
test_assert(input->stream_errno == stream_errno);
- test_assert(strcmp(str_c(str), output) == 0);
+ if (stream_errno == 0) {
+ /* Test seeking on streams where the testcases do not
+ * expect a specific errno already */
+ uoff_t v_off = input->v_offset;
+ /* Seeking backwards */
+ i_stream_seek(input, 0);
+ test_assert(input->v_offset == 0);
+
+ /* Seeking forward */
+ i_stream_seek(input, v_off+1);
+ test_assert(input->stream_errno == ESPIPE);
+ }
+ /* Compare outputs */
+ test_assert_strcmp(str_c(str), output);
+
+ if (eof > 0) {
+ /* Insert early EOF into input_data */
+ i_stream_seek(input_data, 0);
+ str_truncate(str, 0);
+ input_data_limited = i_stream_create_limit(input_data, eof);
+ test_istream_set_allow_eof(input_data_limited, TRUE);
+ i_stream_unref(&input);
+ input = i_stream_create_qp_decoder(input_data_limited);
+ while ((ret = i_stream_read_more(input, &data, &size)) > 0) {
+ str_append_data(str, data, size);
+ i_stream_skip(input, size);
+ }
+ test_assert(ret == -1);
+ /* If there is no error still assume that the result is valid
+ * till artifical eof. */
+ if (input->stream_errno == 0) {
+ unsigned int encoding_margin =
+ get_encoding_size_diff(qp_input, eof);
+
+ /* Cut the expected output at eof of input*/
+ const char *expected_output =
+ t_strdup_printf("%.*s", eof-encoding_margin,
+ output);
+ test_assert_strcmp(str_c(str), expected_output);
+ }
+ test_assert(input->eof);
+ }
+
i_stream_unref(&input);
i_stream_unref(&input_data);
}
test_begin(t_strdup_printf("istream qp decoder %u", i+1));
for (j = 1; j < 10; j++) T_BEGIN {
decode_test(tests[i].input, tests[i].output,
- tests[i].stream_errno, j);
+ tests[i].stream_errno, j, tests[i].eof);
} T_END;
test_end();
}
{ "\r\n\n\n\r\n", "\r\n\r\n\r\n\r\n", 0, 0 },
{ "foo=", "foo=", 4, -1 },
+ { "foo= =66", "foo= f", 5, -1 },
{ "foo= \t", "foo= \t", 6, -1 },
{ "foo= \r", "foo= \r", 6, -1 },
{ "foo= \r bar", "foo= \r bar", 6, -1 },
{ WHITESPACE70" 7\n", WHITESPACE70" 7\r\n", 0, 0 },
{ WHITESPACE70" 8\n", WHITESPACE70" 8\r\n", 77, -1 },
{ WHITESPACE70" 9\n", WHITESPACE70" 9\r\n", 78, -1 },
- { WHITESPACE70" 0\n", WHITESPACE70" 0\r\n", 79, -1 }
+ { WHITESPACE70" 0\n", WHITESPACE70" 0\r\n", 79, -1 },
+ /* Expect extra whitespace to be truncated */
+ { WHITESPACE70" 7\n"WHITESPACE10"", WHITESPACE70" 7\r\n", 0, 0 },
+ { WHITESPACE70" 7=\r\n"WHITESPACE10, WHITESPACE70" 7", 0, 0 },
+ /* Unnecessarily encoded */
+ { "=66=6f=6f=42=61=72", "fooBar", 0, 0 },
+ /* Expected to be encoded but not */
+ { "\xc3\x9c""berm=c3=a4\xc3\x9figer Gebrauch", "\xc3\x9c""berm\xc3\xa4\xc3\x9figer Gebrauch", 0, 0 },
+ /* Decode control characters */
+ { "=0C=07", "\x0C\x07", 0, 0 },
+ /* Data */
+ { "=DE=AD=BE=EF", "\xDE\xAD\xBE\xEF", 0, 0 },
+ /* Non hex data */
+ { "=FJ=X1", "=FJ=X1", 2, -1 },
+ /* No content allowed after Soft Line Break */
+ { "=C3=9C = ","\xc3\x9c"" = ", 9, -1 },
+ /* Boundary delimiter */
+ { "=C3=9C=\r\n-------","\xc3\x9c""-------", 0, 0 },
+ { "=----------- =C3=9C","=----------- \xc3\x9c""", 1, -1 },
+ { "=___________ =C3=9C","=___________ \xc3\x9c""", 1, -1 },
+ { "___________ =C3=9C","___________ \xc3\x9c""", 0, 0 },
+ { "=2D=2D=2D=2D=2D=2D =C3=9C","------ \xc3\x9c""", 0, 0 },
+ { "=FC=83=BF=BF=BF=BF", "\xFC\x83\xBF\xBF\xBF\xBF", 0, 0 },
+ { "=FE=FE=FF=FF", "\xFE\xFE\xFF\xFF", 0, 0 },
+ { "\xFF=C3=9C\xFE\xFF""foobar", "\xFF\xc3\x9c""\xFE\xFF""foobar", 0, 0 },
+ /* Unnecessarily encoded and trailing whitespace */
+ {
+ "=66=6f=6f=42=61=72 ",
+ "fooBar", 0, 0
+ },
+ /* Indicate error if encoded line is longer then 76 */
+ {
+ WHITESPACE70" =C3=9C\n",
+ WHITESPACE70" \xc3\x9c""\r\n", 77, -1
+ },
+ /* Soft Line Break example from the RFC */
+ {
+ "Now's the time =\r\nfor all folk to come=\r\n to the"
+ " aid of their country.",
+ "Now's the time for all folk to come to the aid of "
+ "their country.", 0, 0
+ },
+ {
+ "=C3=9Cberm=C3=A4=C3=9Figer Gebrauch",
+ "\xc3\x9c""berm\xc3\xa4\xc3\x9figer Gebrauch", 0, 0
+ },
+ /* Softlinebreak without following content */
+ {
+ "=C3=9Cberm=C3=A4=C3=9Figer Gebrauch=",
+ "\xc3\x9c""berm\xc3\xa4\xc3\x9figer Gebrauch=", 36, -1
+ },
+ /* Lowercase formally illegal but allowed for robustness */
+ {
+ "=c3=9cberm=c3=a4=c3=9figer Gebrauch",
+ "\xc3\x9c""berm\xc3\xa4\xc3\x9figer Gebrauch", 0, 0
+ },
+ /* Control characters in input */
+ {
+ "=c3=9c=10berm=c3=a4=c3=9figer Geb=0Frauch",
+ "\xc3\x9c\x10""berm\xc3\xa4\xc3\x9figer Geb\x0Frauch", 0, 0
+ },
+ /* Trailing whitespace */
+ {
+ "Trailing Whitesp=C3=A4ce =\r\n ",
+ "Trailing Whitesp\xc3\xa4""ce ", 0 ,0
+ },
+ {
+ "Trailing Whitesp=C3=A4ce ",
+ "Trailing Whitesp\xc3\xa4""ce", 0 ,0
+ },
+ {
+ "=54=65=73=74=20=6D=65=73=73=61=67=65",
+ "Test message", 0 , 0
+ },
+ {
+ "=E3=81=93=E3=82=8C=E3=81=AF=E5=A2\r\n=83=E7=95=8C=E3"
+ "=81=AE=E3=81=82=E3=82=8B=E3=83=A1=E3=83=83=E3=82=BB="
+ "E3=83=BC=E3=82=B8=E3=81=A7=E3=81=99",
+ "\xE3\x81\x93\xE3\x82\x8C\xE3\x81\xAF\xE5\xA2\r\n\x83"
+ "\xE7\x95\x8C\xE3\x81\xAE\xE3\x81\x82\xE3\x82\x8B\xE3"
+ "\x83\xA1\xE3\x83\x83\xE3\x82\xBB\xE3\x83\xBC\xE3\x82"
+ "\xB8\xE3\x81\xA7\xE3\x81\x99", 0, 0
+ },
+ {
+ "=E3=81\xc3\xf1=93=E3=82=8\xff""C=E3=81=AF=E5=A2",
+ "\xE3\x81\xc3\xf1\x93\xE3\x82=8\xff""C\xE3\x81\xAF\xE5\xA2",
+ 19, -1
+ },
+ {
+ "\x77Hello\x76=20 \x20 =E3=81\xc3\xf1=93=E3=82",
+ "wHellov \xE3\x81\xc3\xf1\x93\xE3\x82",
+ 0, 0
+ },
};
string_t *str;
unsigned int i, j;
}
test_assert_idx(ret == tests[i].ret, i);
test_assert_idx(ret == 0 || error_pos == tests[i].error_pos, i);
- test_assert_idx(strcmp(str_c(str), tests[i].output) == 0, i);
+ test_assert_strcmp_idx(str_c(str), tests[i].output, i);
/* try in small pieces */
str_truncate(str, 0);
if (qp_decoder_finish(qp, &error) < 0)
ret = -1;
test_assert_idx(ret == tests[i].ret, i);
- test_assert_idx(strcmp(str_c(str), tests[i].output) == 0, i);
+ test_assert_strcmp_idx(str_c(str), tests[i].output, i);
qp_decoder_deinit(&qp);
str_truncate(str, 0);