chiark / gitweb /
Merge branch 'master' of git.distorted.org.uk:~mdw/publish/public-git/disorder
[disorder] / libtests / t-mime.c
CommitLineData
b90f122b
RK
1/*
2 * This file is part of DisOrder.
3 * Copyright (C) 2005, 2007, 2008 Richard Kettlewell
4 *
e7eb3a27 5 * This program is free software: you can redistribute it and/or modify
b90f122b 6 * it under the terms of the GNU General Public License as published by
e7eb3a27 7 * the Free Software Foundation, either version 3 of the License, or
b90f122b 8 * (at your option) any later version.
e7eb3a27
RK
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
b90f122b 15 * You should have received a copy of the GNU General Public License
e7eb3a27 16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
b90f122b
RK
17 */
18#include "test.h"
19
20static int test_multipart_callback(const char *s, void *u) {
21 struct vector *parts = u;
22
23 vector_append(parts, (char *)s);
24 return 0;
25}
26
121f51ac
RK
27static int header_callback(const char *name, const char *value,
28 void *u) {
29 hash *const h = u;
30
31 hash_add(h, name, &value, HASH_INSERT);
32 return 0;
33}
34
c68d8eba 35static void test_mime(void) {
b90f122b
RK
36 char *t, *n, *v;
37 struct vector parts[1];
38 struct kvp *k;
a1bedb6d 39 const char *s, *cs, *enc;
121f51ac 40 hash *h;
b90f122b 41
b90f122b
RK
42 t = 0;
43 k = 0;
44 insist(!mime_content_type("text/plain", &t, &k));
45 check_string(t, "text/plain");
46 insist(k == 0);
47
48 insist(mime_content_type("TEXT ((broken) comment", &t, &k) < 0);
49 insist(mime_content_type("TEXT ((broken) comment\\", &t, &k) < 0);
50
51 t = 0;
52 k = 0;
53 insist(!mime_content_type("TEXT ((nested)\\ comment) /plain", &t, &k));
54 check_string(t, "text/plain");
55 insist(k == 0);
56
57 t = 0;
58 k = 0;
59 insist(!mime_content_type(" text/plain ; Charset=\"utf-\\8\"", &t, &k));
60 check_string(t, "text/plain");
61 insist(k != 0);
62 insist(k->next == 0);
63 check_string(k->name, "charset");
64 check_string(k->value, "utf-8");
65
66 t = 0;
67 k = 0;
68 insist(!mime_content_type("text/plain;charset = ISO-8859-1 ", &t, &k));
69 insist(k != 0);
70 insist(k->next == 0);
71 check_string(t, "text/plain");
72 check_string(k->name, "charset");
73 check_string(k->value, "ISO-8859-1");
74
75 t = n = v = 0;
76 insist(!mime_rfc2388_content_disposition("form-data; name=\"field1\"", &t, &n, &v));
77 check_string(t, "form-data");
78 check_string(n, "name");
79 check_string(v, "field1");
80
81 insist(!mime_rfc2388_content_disposition("inline", &t, &n, &v));
82 check_string(t, "inline");
83 insist(n == 0);
84 insist(v == 0);
85
86 /* Current versions of the code only understand a single arg to these
87 * headers. This is a bug at the level they work at but suffices for
88 * DisOrder's current purposes. */
89
90 insist(!mime_rfc2388_content_disposition(
91 "attachment; filename=genome.jpeg;\n"
92 "modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"",
93 &t, &n, &v));
94 check_string(t, "attachment");
95 check_string(n, "filename");
96 check_string(v, "genome.jpeg");
97
98 vector_init(parts);
99 insist(mime_multipart("--outer\r\n"
100 "Content-Type: text/plain\r\n"
101 "Content-Disposition: inline\r\n"
102 "Content-Description: text-part-1\r\n"
103 "\r\n"
104 "Some text goes here\r\n"
105 "\r\n"
106 "--outer\r\n"
107 "Content-Type: multipart/mixed; boundary=inner\r\n"
108 "Content-Disposition: attachment\r\n"
109 "Content-Description: multipart-2\r\n"
110 "\r\n"
111 "--inner\r\n"
112 "Content-Type: text/plain\r\n"
113 "Content-Disposition: inline\r\n"
114 "Content-Description: text-part-2\r\n"
115 "\r\n"
116 "Some more text here.\r\n"
117 "\r\n"
118 "--inner\r\n"
119 "Content-Type: image/jpeg\r\n"
120 "Content-Disposition: attachment\r\n"
121 "Content-Description: jpeg-1\r\n"
122 "\r\n"
123 "<jpeg data>\r\n"
124 "--inner--\r\n"
125 "--outer--\r\n",
126 test_multipart_callback,
127 "outer",
128 parts) == 0);
129 check_integer(parts->nvec, 2);
130 check_string(parts->vec[0],
131 "Content-Type: text/plain\r\n"
132 "Content-Disposition: inline\r\n"
133 "Content-Description: text-part-1\r\n"
134 "\r\n"
135 "Some text goes here\r\n");
136 check_string(parts->vec[1],
137 "Content-Type: multipart/mixed; boundary=inner\r\n"
138 "Content-Disposition: attachment\r\n"
139 "Content-Description: multipart-2\r\n"
140 "\r\n"
141 "--inner\r\n"
142 "Content-Type: text/plain\r\n"
143 "Content-Disposition: inline\r\n"
144 "Content-Description: text-part-2\r\n"
145 "\r\n"
146 "Some more text here.\r\n"
147 "\r\n"
148 "--inner\r\n"
149 "Content-Type: image/jpeg\r\n"
150 "Content-Disposition: attachment\r\n"
151 "Content-Description: jpeg-1\r\n"
152 "\r\n"
153 "<jpeg data>\r\n"
154 "--inner--");
155 /* No trailing CRLF is _correct_ - see RFC2046 5.1.1 note regarding CRLF
156 * preceding the boundary delimiter line. An implication of this is that we
157 * must cope with partial lines at the end of the input when recursively
158 * decomposing a multipart message. */
159 vector_init(parts);
160 insist(mime_multipart("--inner\r\n"
161 "Content-Type: text/plain\r\n"
162 "Content-Disposition: inline\r\n"
163 "Content-Description: text-part-2\r\n"
164 "\r\n"
165 "Some more text here.\r\n"
166 "\r\n"
167 "--inner\r\n"
168 "Content-Type: image/jpeg\r\n"
169 "Content-Disposition: attachment\r\n"
170 "Content-Description: jpeg-1\r\n"
171 "\r\n"
172 "<jpeg data>\r\n"
173 "--inner--",
174 test_multipart_callback,
175 "inner",
176 parts) == 0);
177 check_integer(parts->nvec, 2);
178 check_string(parts->vec[0],
179 "Content-Type: text/plain\r\n"
180 "Content-Disposition: inline\r\n"
181 "Content-Description: text-part-2\r\n"
182 "\r\n"
183 "Some more text here.\r\n");
184 check_string(parts->vec[1],
185 "Content-Type: image/jpeg\r\n"
186 "Content-Disposition: attachment\r\n"
187 "Content-Description: jpeg-1\r\n"
188 "\r\n"
189 "<jpeg data>");
75b32a4c
RK
190
191 /* Bogus inputs to mime_multipart() */
192 fprintf(stderr, "expect two mime_multipart errors:\n");
193 insist(mime_multipart("--inner\r\n"
194 "Content-Type: text/plain\r\n"
195 "Content-Disposition: inline\r\n"
196 "Content-Description: text-part-2\r\n"
197 "\r\n"
198 "Some more text here.\r\n"
199 "\r\n"
200 "--inner\r\n"
201 "Content-Type: image/jpeg\r\n"
202 "Content-Disposition: attachment\r\n"
203 "Content-Description: jpeg-1\r\n"
204 "\r\n"
205 "<jpeg data>\r\n",
206 test_multipart_callback,
207 "inner",
208 parts) == -1);
209 insist(mime_multipart("--wrong\r\n"
210 "Content-Type: text/plain\r\n"
211 "Content-Disposition: inline\r\n"
212 "Content-Description: text-part-2\r\n"
213 "\r\n"
214 "Some more text here.\r\n"
215 "\r\n"
216 "--inner--\r\n",
217 test_multipart_callback,
218 "inner",
219 parts) == -1);
220
b90f122b
RK
221 /* XXX mime_parse */
222
223 check_string(mime_qp(""), "");
224 check_string(mime_qp("foobar"), "foobar");
225 check_string(mime_qp("foo=20bar"), "foo bar");
226 check_string(mime_qp("x \r\ny"), "x\r\ny");
227 check_string(mime_qp("x=\r\ny"), "xy");
228 check_string(mime_qp("x= \r\ny"), "xy");
229 check_string(mime_qp("x =\r\ny"), "x y");
230 check_string(mime_qp("x = \r\ny"), "x y");
231
232 check_string(mime_to_qp(""), "");
233 check_string(mime_to_qp("foobar\n"), "foobar\n");
234 check_string(mime_to_qp("foobar \n"), "foobar=20\n");
235 check_string(mime_to_qp("foobar\t\n"), "foobar=09\n");
236 check_string(mime_to_qp("foobar \t \n"), "foobar=20=09=20\n");
237 check_string(mime_to_qp(" foo=bar"), " foo=3Dbar\n");
238 check_string(mime_to_qp("copyright \xC2\xA9"), "copyright =C2=A9\n");
239 check_string(mime_to_qp("foo\nbar\nbaz\n"), "foo\nbar\nbaz\n");
240 check_string(mime_to_qp("wibble wobble wibble wobble wibble wobble wibble wobble wibble wobble wibble"), "wibble wobble wibble wobble wibble wobble wibble wobble wibble wobble wibb=\nle\n");
241
242 /* from RFC2045 */
243 check_string(mime_qp("Now's the time =\r\n"
244"for all folk to come=\r\n"
245" to the aid of their country."),
246 "Now's the time for all folk to come to the aid of their country.");
247
248#define check_base64(encoded, decoded) do { \
d85d9095
RK
249 size_t ns; \
250 check_string(mime_base64(encoded, &ns), decoded); \
251 insist(ns == (sizeof decoded) - 1); \
b90f122b
RK
252 check_string(mime_to_base64((const uint8_t *)decoded, \
253 (sizeof decoded) - 1), \
254 encoded); \
255 } while(0)
256
257
258 check_base64("", "");
259 check_base64("BBBB", "\x04\x10\x41");
260 check_base64("////", "\xFF\xFF\xFF");
261 check_base64("//BB", "\xFF\xF0\x41");
262 check_base64("BBBB//BB////",
263 "\x04\x10\x41" "\xFF\xF0\x41" "\xFF\xFF\xFF");
264 check_base64("BBBBBA==",
265 "\x04\x10\x41" "\x04");
266 check_base64("BBBBBBA=",
267 "\x04\x10\x41" "\x04\x10");
268
269 /* Check that decoding handles various kinds of rubbish OK */
270 check_string(mime_base64("B B B B / / B B / / / /", 0),
271 "\x04\x10\x41" "\xFF\xF0\x41" "\xFF\xFF\xFF");
272 check_string(mime_base64("B\r\nBBB.// B-B//~//", 0),
273 "\x04\x10\x41" "\xFF\xF0\x41" "\xFF\xFF\xFF");
274 check_string(mime_base64("BBBB BB==", 0),
275 "\x04\x10\x41" "\x04");
276 check_string(mime_base64("BBBB BB = =", 0),
277 "\x04\x10\x41" "\x04");
278 check_string(mime_base64("BBBB BBB=", 0),
279 "\x04\x10\x41" "\x04\x10");
280 check_string(mime_base64("BBBB BBB = ", 0),
281 "\x04\x10\x41" "\x04\x10");
282 check_string(mime_base64("BBBB=", 0),
283 "\x04\x10\x41");
284 check_string(mime_base64("BBBBBB==", 0),
285 "\x04\x10\x41" "\x04");
286 check_string(mime_base64("BBBBBBB=", 0),
287 "\x04\x10\x41" "\x04\x10");
288 /* Not actually valid base64 */
289 check_string(mime_base64("BBBBx=", 0),
290 "\x04\x10\x41");
121f51ac
RK
291
292 h = hash_new(sizeof (char *));
293 s = mime_parse("From: sender@example.com\r\n"
294 "To: rcpt@example.com\r\n"
295 "Subject: test #1\r\n"
296 "\r\n"
297 "body\r\n",
298 header_callback, h);
299 insist(s != 0);
300 check_string(*(char **)hash_find(h, "from"), "sender@example.com");
301 check_string(*(char **)hash_find(h, "to"), "rcpt@example.com");
302 check_string(*(char **)hash_find(h, "subject"), "test #1");
303 check_string(s, "body\r\n");
304
305 h = hash_new(sizeof (char *));
306 s = mime_parse("FROM: sender@example.com\r\n"
307 "TO: rcpt@example.com\r\n"
308 "SUBJECT: test #1\r\n"
309 "CONTENT-TRANSFER-ENCODING: 7bit\r\n"
310 "\r\n"
311 "body\r\n",
312 header_callback, h);
313 insist(s != 0);
314 check_string(*(char **)hash_find(h, "from"), "sender@example.com");
315 check_string(*(char **)hash_find(h, "to"), "rcpt@example.com");
316 check_string(*(char **)hash_find(h, "subject"), "test #1");
317 check_string(*(char **)hash_find(h, "content-transfer-encoding"), "7bit");
318 check_string(s, "body\r\n");
319
320 h = hash_new(sizeof (char *));
321 s = mime_parse("From: sender@example.com\r\n"
322 "To: \r\n"
323 " rcpt@example.com\r\n"
324 "Subject: test #1\r\n"
325 "MIME-Version: 1.0\r\n"
326 "Content-Type: text/plain\r\n"
327 "Content-Transfer-Encoding: BASE64\r\n"
328 "\r\n"
329 "d2liYmxlDQo=\r\n",
330 header_callback, h);
331 insist(s != 0);
332 check_string(*(char **)hash_find(h, "from"), "sender@example.com");
333 check_string(*(char **)hash_find(h, "to"), "rcpt@example.com");
334 check_string(*(char **)hash_find(h, "subject"), "test #1");
335 check_string(*(char **)hash_find(h, "mime-version"), "1.0");
336 check_string(*(char **)hash_find(h, "content-type"), "text/plain");
337 check_string(*(char **)hash_find(h, "content-transfer-encoding"), "BASE64");
338 check_string(s, "wibble\r\n");
a1bedb6d
RK
339
340#define CHECK_QUOTE(INPUT, EXPECT) do { \
341 s = quote822(INPUT, 0); \
342 insist(s != 0); \
343 check_string(s, EXPECT); \
344 s = mime_parse_word(s, &t, mime_http_separator); \
345 check_string(t, INPUT); \
346} while(0)
347 CHECK_QUOTE("wibble", "wibble");
348 CHECK_QUOTE("wibble spong", "\"wibble spong\"");
349 CHECK_QUOTE("wibble\\spong", "\"wibble\\\\spong\"");
350 CHECK_QUOTE("wibble\"spong", "\"wibble\\\"spong\"");
351 CHECK_QUOTE("(wibble)", "\"(wibble)\"");
352
353 s = mime_encode_text("wibble\n", &cs, &enc);
354 insist(s != 0);
355 check_string(s, "wibble\n");
356 check_string(cs, "us-ascii");
357 check_string(enc, "7bit");
358
359 s = mime_encode_text("wibble\xC3\xB7\n", &cs, &enc);
360 insist(s != 0);
361 check_string(s, "wibble=C3=B7\n");
362 check_string(cs, "utf-8");
363 check_string(enc, "quoted-printable");
b90f122b
RK
364}
365
c68d8eba
RK
366TEST(mime);
367
b90f122b
RK
368/*
369Local Variables:
370c-basic-offset:2
371comment-column:40
372fill-column:79
373indent-tabs-mode:nil
374End:
375*/