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