chiark / gitweb /
Remove some obsolete code
[disorder] / lib / t-mime.c
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
22 static 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
29 static 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
37 static void test_mime(void) {
38   char *t, *n, *v;
39   struct vector parts[1];
40   struct kvp *k;
41   const char *s, *cs, *enc;
42   hash *h;
43
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   /* Bogus inputs to mime_multipart() */
194   fprintf(stderr, "expect two mime_multipart errors:\n");
195   insist(mime_multipart("--inner\r\n"
196                         "Content-Type: text/plain\r\n"
197                         "Content-Disposition: inline\r\n"
198                         "Content-Description: text-part-2\r\n"
199                         "\r\n"
200                         "Some more text here.\r\n"
201                         "\r\n"
202                         "--inner\r\n"
203                         "Content-Type: image/jpeg\r\n"
204                         "Content-Disposition: attachment\r\n"
205                         "Content-Description: jpeg-1\r\n"
206                         "\r\n"
207                         "<jpeg data>\r\n",
208                         test_multipart_callback,
209                         "inner",
210                         parts) == -1);
211   insist(mime_multipart("--wrong\r\n"
212                         "Content-Type: text/plain\r\n"
213                         "Content-Disposition: inline\r\n"
214                         "Content-Description: text-part-2\r\n"
215                         "\r\n"
216                         "Some more text here.\r\n"
217                         "\r\n"
218                         "--inner--\r\n",
219                         test_multipart_callback,
220                         "inner",
221                         parts) == -1);
222     
223   /* XXX mime_parse */
224
225   check_string(mime_qp(""), "");
226   check_string(mime_qp("foobar"), "foobar");
227   check_string(mime_qp("foo=20bar"), "foo bar");
228   check_string(mime_qp("x \r\ny"), "x\r\ny");
229   check_string(mime_qp("x=\r\ny"), "xy");
230   check_string(mime_qp("x= \r\ny"), "xy");
231   check_string(mime_qp("x =\r\ny"), "x y");
232   check_string(mime_qp("x = \r\ny"), "x y");
233
234   check_string(mime_to_qp(""), "");
235   check_string(mime_to_qp("foobar\n"), "foobar\n");
236   check_string(mime_to_qp("foobar \n"), "foobar=20\n");
237   check_string(mime_to_qp("foobar\t\n"), "foobar=09\n"); 
238   check_string(mime_to_qp("foobar \t \n"), "foobar=20=09=20\n");
239   check_string(mime_to_qp(" foo=bar"), " foo=3Dbar\n");
240   check_string(mime_to_qp("copyright \xC2\xA9"), "copyright =C2=A9\n");
241   check_string(mime_to_qp("foo\nbar\nbaz\n"), "foo\nbar\nbaz\n");
242   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");
243  
244   /* from RFC2045 */
245   check_string(mime_qp("Now's the time =\r\n"
246 "for all folk to come=\r\n"
247 " to the aid of their country."),
248                "Now's the time for all folk to come to the aid of their country.");
249
250 #define check_base64(encoded, decoded) do {                     \
251     size_t ns;                                                  \
252     check_string(mime_base64(encoded, &ns), decoded);           \
253     insist(ns == (sizeof decoded) - 1);                         \
254     check_string(mime_to_base64((const uint8_t *)decoded,       \
255                                          (sizeof decoded) - 1), \
256                  encoded);                                      \
257   } while(0)
258     
259   
260   check_base64("",  "");
261   check_base64("BBBB", "\x04\x10\x41");
262   check_base64("////", "\xFF\xFF\xFF");
263   check_base64("//BB", "\xFF\xF0\x41");
264   check_base64("BBBB//BB////",
265              "\x04\x10\x41" "\xFF\xF0\x41" "\xFF\xFF\xFF");
266   check_base64("BBBBBA==",
267                "\x04\x10\x41" "\x04");
268   check_base64("BBBBBBA=",
269                "\x04\x10\x41" "\x04\x10");
270
271   /* Check that decoding handles various kinds of rubbish OK */
272   check_string(mime_base64("B B B B  / / B B / / / /", 0),
273              "\x04\x10\x41" "\xFF\xF0\x41" "\xFF\xFF\xFF");
274   check_string(mime_base64("B\r\nBBB.// B-B//~//", 0),
275                "\x04\x10\x41" "\xFF\xF0\x41" "\xFF\xFF\xFF");
276   check_string(mime_base64("BBBB BB==", 0),
277                "\x04\x10\x41" "\x04");
278   check_string(mime_base64("BBBB BB = =", 0),
279                "\x04\x10\x41" "\x04");
280   check_string(mime_base64("BBBB BBB=", 0),
281                "\x04\x10\x41" "\x04\x10");
282   check_string(mime_base64("BBBB BBB = ", 0),
283                "\x04\x10\x41" "\x04\x10");
284   check_string(mime_base64("BBBB=", 0),
285                "\x04\x10\x41");
286   check_string(mime_base64("BBBBBB==", 0),
287                "\x04\x10\x41" "\x04");
288   check_string(mime_base64("BBBBBBB=", 0),
289                "\x04\x10\x41" "\x04\x10");
290   /* Not actually valid base64 */
291   check_string(mime_base64("BBBBx=", 0),
292                "\x04\x10\x41");
293
294   h = hash_new(sizeof (char *));
295   s = mime_parse("From: sender@example.com\r\n"
296                  "To: rcpt@example.com\r\n"
297                  "Subject: test #1\r\n"
298                  "\r\n"
299                  "body\r\n",
300                  header_callback, h);
301   insist(s != 0);
302   check_string(*(char **)hash_find(h, "from"), "sender@example.com");
303   check_string(*(char **)hash_find(h, "to"), "rcpt@example.com");
304   check_string(*(char **)hash_find(h, "subject"), "test #1");
305   check_string(s, "body\r\n");
306
307   h = hash_new(sizeof (char *));
308   s = mime_parse("FROM: sender@example.com\r\n"
309                  "TO: rcpt@example.com\r\n"
310                  "SUBJECT: test #1\r\n"
311                  "CONTENT-TRANSFER-ENCODING: 7bit\r\n"
312                  "\r\n"
313                  "body\r\n",
314                  header_callback, h);
315   insist(s != 0);
316   check_string(*(char **)hash_find(h, "from"), "sender@example.com");
317   check_string(*(char **)hash_find(h, "to"), "rcpt@example.com");
318   check_string(*(char **)hash_find(h, "subject"), "test #1");
319   check_string(*(char **)hash_find(h, "content-transfer-encoding"), "7bit");
320   check_string(s, "body\r\n");
321
322   h = hash_new(sizeof (char *));
323   s = mime_parse("From: sender@example.com\r\n"
324                  "To:    \r\n"
325                  "     rcpt@example.com\r\n"
326                  "Subject: test #1\r\n"
327                  "MIME-Version: 1.0\r\n"
328                  "Content-Type: text/plain\r\n"
329                  "Content-Transfer-Encoding: BASE64\r\n"
330                  "\r\n"
331                  "d2liYmxlDQo=\r\n",
332                  header_callback, h);
333   insist(s != 0); 
334   check_string(*(char **)hash_find(h, "from"), "sender@example.com");
335   check_string(*(char **)hash_find(h, "to"), "rcpt@example.com");
336   check_string(*(char **)hash_find(h, "subject"), "test #1");
337   check_string(*(char **)hash_find(h, "mime-version"), "1.0");
338   check_string(*(char **)hash_find(h, "content-type"), "text/plain");
339   check_string(*(char **)hash_find(h, "content-transfer-encoding"), "BASE64");
340   check_string(s, "wibble\r\n");
341
342 #define CHECK_QUOTE(INPUT, EXPECT) do {                 \
343   s = quote822(INPUT, 0);                               \
344   insist(s != 0);                                       \
345   check_string(s, EXPECT);                              \
346   s = mime_parse_word(s, &t, mime_http_separator);      \
347   check_string(t, INPUT);                               \
348 } while(0)
349   CHECK_QUOTE("wibble", "wibble");
350   CHECK_QUOTE("wibble spong", "\"wibble spong\"");
351   CHECK_QUOTE("wibble\\spong", "\"wibble\\\\spong\"");
352   CHECK_QUOTE("wibble\"spong", "\"wibble\\\"spong\"");
353   CHECK_QUOTE("(wibble)", "\"(wibble)\"");
354
355   s = mime_encode_text("wibble\n", &cs, &enc);
356   insist(s != 0);
357   check_string(s, "wibble\n");
358   check_string(cs, "us-ascii");
359   check_string(enc, "7bit");
360
361   s = mime_encode_text("wibble\xC3\xB7\n", &cs, &enc);
362   insist(s != 0);
363   check_string(s, "wibble=C3=B7\n");
364   check_string(cs, "utf-8");
365   check_string(enc, "quoted-printable");
366 }
367
368 TEST(mime);
369
370 /*
371 Local Variables:
372 c-basic-offset:2
373 comment-column:40
374 fill-column:79
375 indent-tabs-mode:nil
376 End:
377 */