Skip to content

Commit e3b45a1

Browse files
vmo69sirainen
authored andcommitted
lib-mail: Extend quoted-printable decoding tests
1 parent 78b86e7 commit e3b45a1

File tree

3 files changed

+233
-18
lines changed

3 files changed

+233
-18
lines changed

src/lib-mail/test-istream-qp-decoder.c

Lines changed: 123 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,92 @@ static const struct {
99
const char *input;
1010
const char *output;
1111
int stream_errno;
12+
int eof;
1213
} tests[] = {
13-
{ "p=C3=A4=C3=A4t=C3=B6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0 },
14-
{ "p=c3=a4=c3=a4t=c3=b6s= \n", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0 },
15-
{ "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0 },
16-
17-
{ "p=c3=a4\rasdf", "p\xC3\xA4", EINVAL },
18-
{ "p=c", "p", EPIPE },
19-
{ "p=A", "p", EPIPE },
20-
{ "p=Ax", "p", EINVAL },
21-
{ "p=c3=a4=c3=a4t=c3=b6s= ", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", EPIPE }
14+
{ "p=C3=A4=C3=A4t=C3=B6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0 , 0 },
15+
{ "p=c3=a4=c3=a4t=c3=b6s= \n", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 0 },
16+
{ "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 1 },
17+
{ "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 2 },
18+
{ "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 3 },
19+
{ "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 4 },
20+
{ "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 5 },
21+
{ "p=c3=a4= \t \n=c3=\r\n=a4t= \r\n=c3=b6s", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", 0, 7 },
22+
{ "p=c3", "p\xC3", 0, 2 },
23+
{ "=0A=0D ", "\n\r", 0, 7 },
24+
{ "foo_bar", "foo_bar", 0, 0 },
25+
{ "\n\n", "\r\n\r\n", 0, 0 },
26+
{ "\r\n\n\n\r\n", "\r\n\r\n\r\n\r\n", 0, 0 },
27+
/* Unnecessarily encoded */
28+
{ "=66=6f=6f=42=61=72", "fooBar", 0, 4 },
29+
/* Expected to be encoded but not */
30+
{ "\xc3\x9c""berm=c3=a4\xc3\x9figer Gebrauch", "\xc3\x9c""berm\xc3\xa4\xc3\x9figer Gebrauch", 0, 9 },
31+
/* Decode control characters */
32+
{ "=0C=07", "\x0C\x07", 0, 0 },
33+
/* Data */
34+
{ "=DE=AD=BE=EF", "\xDE\xAD\xBE\xEF", 0, 0 },
35+
/* Non hex data */
36+
{ "=FJ=X1", "", EINVAL, 0 },
37+
/* No content allowed after Soft Line Break */
38+
{ "=C3=9C = ","\xc3\x9c ", EPIPE, 0 },
39+
/* Boundary delimiter */
40+
{ "=C3=9C=\r\n-------","\xc3\x9c-------", 0, 0 },
41+
{ "=----------- =C3=9C","", EINVAL, 0 },
42+
{ "=___________ =C3=9C","", EINVAL, 0 },
43+
{ "___________ =C3=9C","___________ \xc3\x9c", 0, 0 },
44+
{ "=2D=2D=2D=2D=2D=2D =C3=9C","------ \xc3\x9c", 0, 0 },
45+
{ "=FC=83=BF=BF=BF=BF", "\xFC\x83\xBF\xBF\xBF\xBF", 0, 0 },
46+
{ "=FE=FE=FF=FF", "\xFE\xFE\xFF\xFF", 0, 0 },
47+
{ "\xFF=C3=9C\xFE\xFF""foobar", "\xFF\xc3\x9c\xFE\xFF""foobar", 0, 0 },
48+
49+
{ "p=c3=a4\rasdf", "p\xC3\xA4", EINVAL, 0 },
50+
{ "=___________ \xc3\x9c","", EINVAL, 0 },
51+
{ "p=c", "p", EPIPE, 0 },
52+
{ "p=A", "p", EPIPE, 0 },
53+
{ "p=Ax", "p", EINVAL, 0 },
54+
{ "___________ \xc3\x9c=C3=9","___________ \xc3\x9c\xC3", EPIPE, 0},
55+
{ "p=c3=a4=c3=a4t=c3=b6s= ", "p\xC3\xA4\xC3\xA4t\xC3\xB6s", EPIPE, 0 },
56+
/* Soft Line Break example from the RFC */
57+
{
58+
"Now's the time =\r\nfor all folk to come=\r\n to the aid of "
59+
"their country.", "Now's the time for all folk to come to the"
60+
" aid of their country.", 0, 41
61+
},
2262
};
2363

64+
static bool is_hex(char c) {
65+
return ((c >= 48 && c <= 57) || (c >= 65 && c <= 70)
66+
|| (c >= 97 && c <= 102));
67+
68+
}
69+
70+
static unsigned int
71+
get_encoding_size_diff(const char *qp_input, unsigned int limit)
72+
{
73+
unsigned int encoded_chars = 0;
74+
unsigned int soft_line_breaks = 0;
75+
for (unsigned int i = 0; i < limit; i++) {
76+
char c = qp_input[i];
77+
if (c == '=' && i+2 < limit) {
78+
if (qp_input[i+1] == '\r' && qp_input[i+2] == '\n') {
79+
soft_line_breaks++;
80+
i += 2;
81+
limit += 3;
82+
} else if (is_hex(qp_input[i+1]) && is_hex(qp_input[i+2])) {
83+
encoded_chars++;
84+
i += 2;
85+
limit += 2;
86+
}
87+
}
88+
}
89+
return encoded_chars*2 + soft_line_breaks*3;
90+
}
91+
2492
static void
2593
decode_test(const char *qp_input, const char *output, int stream_errno,
26-
unsigned int buffer_size)
94+
unsigned int buffer_size, unsigned int eof)
2795
{
2896
size_t qp_input_len = strlen(qp_input);
29-
struct istream *input_data, *input;
97+
struct istream *input_data, *input_data_limited, *input;
3098
const unsigned char *data;
3199
size_t i, size;
32100
string_t *str = t_str_new(32);
@@ -57,7 +125,49 @@ decode_test(const char *qp_input, const char *output, int stream_errno,
57125
test_assert(ret == -1);
58126
test_assert(input->stream_errno == stream_errno);
59127

60-
test_assert(strcmp(str_c(str), output) == 0);
128+
if (stream_errno == 0) {
129+
/* Test seeking on streams where the testcases do not
130+
* expect a specific errno already */
131+
uoff_t v_off = input->v_offset;
132+
/* Seeking backwards */
133+
i_stream_seek(input, 0);
134+
test_assert(input->v_offset == 0);
135+
136+
/* Seeking forward */
137+
i_stream_seek(input, v_off+1);
138+
test_assert(input->stream_errno == ESPIPE);
139+
}
140+
/* Compare outputs */
141+
test_assert_strcmp(str_c(str), output);
142+
143+
if (eof > 0) {
144+
/* Insert early EOF into input_data */
145+
i_stream_seek(input_data, 0);
146+
str_truncate(str, 0);
147+
input_data_limited = i_stream_create_limit(input_data, eof);
148+
test_istream_set_allow_eof(input_data_limited, TRUE);
149+
i_stream_unref(&input);
150+
input = i_stream_create_qp_decoder(input_data_limited);
151+
while ((ret = i_stream_read_more(input, &data, &size)) > 0) {
152+
str_append_data(str, data, size);
153+
i_stream_skip(input, size);
154+
}
155+
test_assert(ret == -1);
156+
/* If there is no error still assume that the result is valid
157+
* till artifical eof. */
158+
if (input->stream_errno == 0) {
159+
unsigned int encoding_margin =
160+
get_encoding_size_diff(qp_input, eof);
161+
162+
/* Cut the expected output at eof of input*/
163+
const char *expected_output =
164+
t_strdup_printf("%.*s", eof-encoding_margin,
165+
output);
166+
test_assert_strcmp(str_c(str), expected_output);
167+
}
168+
test_assert(input->eof);
169+
}
170+
61171
i_stream_unref(&input);
62172
i_stream_unref(&input_data);
63173
}
@@ -70,7 +180,7 @@ static void test_istream_qp_decoder(void)
70180
test_begin(t_strdup_printf("istream qp decoder %u", i+1));
71181
for (j = 1; j < 10; j++) T_BEGIN {
72182
decode_test(tests[i].input, tests[i].output,
73-
tests[i].stream_errno, j);
183+
tests[i].stream_errno, j, tests[i].eof);
74184
} T_END;
75185
test_end();
76186
}

src/lib-mail/test-qp-decoder.c

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ static void test_qp_decoder(void)
2929
{ "\r\n\n\n\r\n", "\r\n\r\n\r\n\r\n", 0, 0 },
3030

3131
{ "foo=", "foo=", 4, -1 },
32+
{ "foo= =66", "foo= f", 5, -1 },
3233
{ "foo= \t", "foo= \t", 6, -1 },
3334
{ "foo= \r", "foo= \r", 6, -1 },
3435
{ "foo= \r bar", "foo= \r bar", 6, -1 },
@@ -41,7 +42,99 @@ static void test_qp_decoder(void)
4142
{ WHITESPACE70" 7\n", WHITESPACE70" 7\r\n", 0, 0 },
4243
{ WHITESPACE70" 8\n", WHITESPACE70" 8\r\n", 77, -1 },
4344
{ WHITESPACE70" 9\n", WHITESPACE70" 9\r\n", 78, -1 },
44-
{ WHITESPACE70" 0\n", WHITESPACE70" 0\r\n", 79, -1 }
45+
{ WHITESPACE70" 0\n", WHITESPACE70" 0\r\n", 79, -1 },
46+
/* Expect extra whitespace to be truncated */
47+
{ WHITESPACE70" 7\n"WHITESPACE10"", WHITESPACE70" 7\r\n", 0, 0 },
48+
{ WHITESPACE70" 7=\r\n"WHITESPACE10, WHITESPACE70" 7", 0, 0 },
49+
/* Unnecessarily encoded */
50+
{ "=66=6f=6f=42=61=72", "fooBar", 0, 0 },
51+
/* Expected to be encoded but not */
52+
{ "\xc3\x9c""berm=c3=a4\xc3\x9figer Gebrauch", "\xc3\x9c""berm\xc3\xa4\xc3\x9figer Gebrauch", 0, 0 },
53+
/* Decode control characters */
54+
{ "=0C=07", "\x0C\x07", 0, 0 },
55+
/* Data */
56+
{ "=DE=AD=BE=EF", "\xDE\xAD\xBE\xEF", 0, 0 },
57+
/* Non hex data */
58+
{ "=FJ=X1", "=FJ=X1", 2, -1 },
59+
/* No content allowed after Soft Line Break */
60+
{ "=C3=9C = ","\xc3\x9c"" = ", 9, -1 },
61+
/* Boundary delimiter */
62+
{ "=C3=9C=\r\n-------","\xc3\x9c""-------", 0, 0 },
63+
{ "=----------- =C3=9C","=----------- \xc3\x9c""", 1, -1 },
64+
{ "=___________ =C3=9C","=___________ \xc3\x9c""", 1, -1 },
65+
{ "___________ =C3=9C","___________ \xc3\x9c""", 0, 0 },
66+
{ "=2D=2D=2D=2D=2D=2D =C3=9C","------ \xc3\x9c""", 0, 0 },
67+
{ "=FC=83=BF=BF=BF=BF", "\xFC\x83\xBF\xBF\xBF\xBF", 0, 0 },
68+
{ "=FE=FE=FF=FF", "\xFE\xFE\xFF\xFF", 0, 0 },
69+
{ "\xFF=C3=9C\xFE\xFF""foobar", "\xFF\xc3\x9c""\xFE\xFF""foobar", 0, 0 },
70+
/* Unnecessarily encoded and trailing whitespace */
71+
{
72+
"=66=6f=6f=42=61=72 ",
73+
"fooBar", 0, 0
74+
},
75+
/* Indicate error if encoded line is longer then 76 */
76+
{
77+
WHITESPACE70" =C3=9C\n",
78+
WHITESPACE70" \xc3\x9c""\r\n", 77, -1
79+
},
80+
/* Soft Line Break example from the RFC */
81+
{
82+
"Now's the time =\r\nfor all folk to come=\r\n to the"
83+
" aid of their country.",
84+
"Now's the time for all folk to come to the aid of "
85+
"their country.", 0, 0
86+
},
87+
{
88+
"=C3=9Cberm=C3=A4=C3=9Figer Gebrauch",
89+
"\xc3\x9c""berm\xc3\xa4\xc3\x9figer Gebrauch", 0, 0
90+
},
91+
/* Softlinebreak without following content */
92+
{
93+
"=C3=9Cberm=C3=A4=C3=9Figer Gebrauch=",
94+
"\xc3\x9c""berm\xc3\xa4\xc3\x9figer Gebrauch=", 36, -1
95+
},
96+
/* Lowercase formally illegal but allowed for robustness */
97+
{
98+
"=c3=9cberm=c3=a4=c3=9figer Gebrauch",
99+
"\xc3\x9c""berm\xc3\xa4\xc3\x9figer Gebrauch", 0, 0
100+
},
101+
/* Control characters in input */
102+
{
103+
"=c3=9c=10berm=c3=a4=c3=9figer Geb=0Frauch",
104+
"\xc3\x9c\x10""berm\xc3\xa4\xc3\x9figer Geb\x0Frauch", 0, 0
105+
},
106+
/* Trailing whitespace */
107+
{
108+
"Trailing Whitesp=C3=A4ce =\r\n ",
109+
"Trailing Whitesp\xc3\xa4""ce ", 0 ,0
110+
},
111+
{
112+
"Trailing Whitesp=C3=A4ce ",
113+
"Trailing Whitesp\xc3\xa4""ce", 0 ,0
114+
},
115+
{
116+
"=54=65=73=74=20=6D=65=73=73=61=67=65",
117+
"Test message", 0 , 0
118+
},
119+
{
120+
"=E3=81=93=E3=82=8C=E3=81=AF=E5=A2\r\n=83=E7=95=8C=E3"
121+
"=81=AE=E3=81=82=E3=82=8B=E3=83=A1=E3=83=83=E3=82=BB="
122+
"E3=83=BC=E3=82=B8=E3=81=A7=E3=81=99",
123+
"\xE3\x81\x93\xE3\x82\x8C\xE3\x81\xAF\xE5\xA2\r\n\x83"
124+
"\xE7\x95\x8C\xE3\x81\xAE\xE3\x81\x82\xE3\x82\x8B\xE3"
125+
"\x83\xA1\xE3\x83\x83\xE3\x82\xBB\xE3\x83\xBC\xE3\x82"
126+
"\xB8\xE3\x81\xA7\xE3\x81\x99", 0, 0
127+
},
128+
{
129+
"=E3=81\xc3\xf1=93=E3=82=8\xff""C=E3=81=AF=E5=A2",
130+
"\xE3\x81\xc3\xf1\x93\xE3\x82=8\xff""C\xE3\x81\xAF\xE5\xA2",
131+
19, -1
132+
},
133+
{
134+
"\x77Hello\x76=20 \x20 =E3=81\xc3\xf1=93=E3=82",
135+
"wHellov \xE3\x81\xc3\xf1\x93\xE3\x82",
136+
0, 0
137+
},
45138
};
46139
string_t *str;
47140
unsigned int i, j;
@@ -64,7 +157,7 @@ static void test_qp_decoder(void)
64157
}
65158
test_assert_idx(ret == tests[i].ret, i);
66159
test_assert_idx(ret == 0 || error_pos == tests[i].error_pos, i);
67-
test_assert_idx(strcmp(str_c(str), tests[i].output) == 0, i);
160+
test_assert_strcmp_idx(str_c(str), tests[i].output, i);
68161

69162
/* try in small pieces */
70163
str_truncate(str, 0);
@@ -77,7 +170,7 @@ static void test_qp_decoder(void)
77170
if (qp_decoder_finish(qp, &error) < 0)
78171
ret = -1;
79172
test_assert_idx(ret == tests[i].ret, i);
80-
test_assert_idx(strcmp(str_c(str), tests[i].output) == 0, i);
173+
test_assert_strcmp_idx(str_c(str), tests[i].output, i);
81174

82175
qp_decoder_deinit(&qp);
83176
str_truncate(str, 0);

src/lib-mail/test-quoted-printable.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,19 @@ static void test_quoted_printable_q_decode(void)
1414
"foo=", "foo=",
1515
"foo=A", "foo=A",
1616
"foo=Ax", "foo=Ax",
17-
"foo=Ax=xy", "foo=Ax=xy"
17+
"foo=Ax=xy", "foo=Ax=xy",
18+
"=C3=9Cberm=C3=A4=C3=9Figer Gebrauch", "\xc3\x9c""berm\xc3\xa4\xc3\x9figer Gebrauch",
19+
/* Lowercase formally illegal but allowed for robustness */
20+
"=c3=9cberm=c3=a4=c3=9figer Gebrauch", "\xc3\x9c""berm\xc3\xa4\xc3\x9figer Gebrauch",
21+
/* Unnecessarily encoded */
22+
"=66=6f=6f=42=61=72", "fooBar",
23+
/* Expected to be encoded but not */
24+
"\xc3\x9c""berm=c3=a4\xc3\x9figer Gebrauch", "\xc3\x9c""berm\xc3\xa4\xc3\x9figer Gebrauch",
25+
/* Decode control characters */
26+
"=0C=07", "\x0C\x07",
27+
"=DE=AD=BE=EF", "\xDE\xAD\xBE\xEF",
28+
/* Non-Hex data */
29+
"=FJ=X1", "=FJ=X1",
1830
};
1931
buffer_t *buf;
2032
unsigned int i;
@@ -24,7 +36,7 @@ static void test_quoted_printable_q_decode(void)
2436
for (i = 0; i < N_ELEMENTS(data); i += 2) {
2537
quoted_printable_q_decode((const void *)data[i], strlen(data[i]),
2638
buf);
27-
test_assert(strcmp(data[i+1], str_c(buf)) == 0);
39+
test_assert_strcmp_idx(data[i+1], str_c(buf), i/2);
2840
buffer_set_used_size(buf, 0);
2941
}
3042
test_end();

0 commit comments

Comments
 (0)