| 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 | */ |