Commit | Line | Data |
---|---|---|
460b9539 | 1 | /* |
2 | * This file is part of DisOrder | |
06819653 | 3 | * Copyright (C) 2005, 2007, 2008 Richard Kettlewell |
460b9539 | 4 | * |
e7eb3a27 | 5 | * This program is free software: you can redistribute it and/or modify |
460b9539 | 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 |
460b9539 | 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 | * | |
460b9539 | 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/>. |
460b9539 | 17 | */ |
39d4aa6b RK |
18 | /** @file lib/mime.c |
19 | * @brief Support for MIME and allied protocols | |
20 | */ | |
460b9539 | 21 | |
05b75f8d | 22 | #include "common.h" |
460b9539 | 23 | |
460b9539 | 24 | #include <ctype.h> |
25 | ||
26 | #include "mem.h" | |
27 | #include "mime.h" | |
28 | #include "vector.h" | |
29 | #include "hex.h" | |
39d4aa6b | 30 | #include "log.h" |
fce810c2 | 31 | #include "base64.h" |
9bce81d1 | 32 | #include "kvp.h" |
460b9539 | 33 | |
39d4aa6b | 34 | /** @brief Match whitespace characters */ |
460b9539 | 35 | static int whitespace(int c) { |
36 | switch(c) { | |
37 | case ' ': | |
38 | case '\t': | |
39 | case '\r': | |
40 | case '\n': | |
41 | return 1; | |
42 | default: | |
43 | return 0; | |
44 | } | |
45 | } | |
46 | ||
39d4aa6b | 47 | /** @brief Match RFC2045 tspecial characters */ |
a1bedb6d | 48 | int mime_tspecial(int c) { |
460b9539 | 49 | switch(c) { |
50 | case '(': | |
51 | case ')': | |
52 | case '<': | |
53 | case '>': | |
54 | case '@': | |
55 | case ',': | |
56 | case ';': | |
57 | case ':': | |
58 | case '\\': | |
59 | case '"': | |
60 | case '/': | |
61 | case '[': | |
62 | case ']': | |
63 | case '?': | |
64 | case '=': | |
65 | return 1; | |
66 | default: | |
67 | return 0; | |
68 | } | |
69 | } | |
70 | ||
82c01b31 | 71 | /** @brief Match RFC2616 separator characters */ |
a1bedb6d | 72 | int mime_http_separator(int c) { |
39d4aa6b RK |
73 | switch(c) { |
74 | case '(': | |
75 | case ')': | |
76 | case '<': | |
77 | case '>': | |
78 | case '@': | |
79 | case ',': | |
80 | case ';': | |
81 | case ':': | |
82 | case '\\': | |
83 | case '"': | |
84 | case '/': | |
85 | case '[': | |
86 | case ']': | |
87 | case '?': | |
88 | case '=': | |
89 | case '{': | |
90 | case '}': | |
91 | case ' ': | |
92 | case '\t': | |
93 | return 1; | |
94 | default: | |
95 | return 0; | |
96 | } | |
97 | } | |
98 | ||
99 | /** @brief Match CRLF */ | |
100 | static int iscrlf(const char *ptr) { | |
101 | return ptr[0] == '\r' && ptr[1] == '\n'; | |
102 | } | |
103 | ||
104 | /** @brief Skip whitespace | |
158d0961 | 105 | * @param s Pointer into string |
39d4aa6b | 106 | * @param rfc822_comments If true, skip RFC822 nested comments |
158d0961 | 107 | * @return Pointer into string after whitespace |
39d4aa6b RK |
108 | */ |
109 | static const char *skipwhite(const char *s, int rfc822_comments) { | |
460b9539 | 110 | int c, depth; |
111 | ||
112 | for(;;) { | |
113 | switch(c = *s) { | |
114 | case ' ': | |
115 | case '\t': | |
116 | case '\r': | |
117 | case '\n': | |
118 | ++s; | |
119 | break; | |
120 | case '(': | |
39d4aa6b RK |
121 | if(!rfc822_comments) |
122 | return s; | |
460b9539 | 123 | ++s; |
124 | depth = 1; | |
125 | while(*s && depth) { | |
126 | c = *s++; | |
127 | switch(c) { | |
128 | case '(': ++depth; break; | |
129 | case ')': --depth; break; | |
130 | case '\\': | |
121f51ac RK |
131 | if(!*s) |
132 | return 0; | |
460b9539 | 133 | ++s; |
134 | break; | |
135 | } | |
136 | } | |
121f51ac RK |
137 | if(depth) |
138 | return 0; | |
460b9539 | 139 | break; |
140 | default: | |
141 | return s; | |
142 | } | |
143 | } | |
144 | } | |
145 | ||
39d4aa6b RK |
146 | /** @brief Test for a word character |
147 | * @param c Character to test | |
a1bedb6d | 148 | * @param special mime_tspecial() (MIME/RFC2405) or mime_http_separator() (HTTP/RFC2616) |
39d4aa6b RK |
149 | * @return 1 if @p c is a word character, else 0 |
150 | */ | |
151 | static int iswordchar(int c, int (*special)(int)) { | |
152 | return !(c <= ' ' || c > '~' || special(c)); | |
153 | } | |
154 | ||
155 | /** @brief Parse an RFC1521/RFC2616 word | |
156 | * @param s Pointer to start of word | |
157 | * @param valuep Where to store value | |
a1bedb6d | 158 | * @param special mime_tspecial() (MIME/RFC2405) or mime_http_separator() (HTTP/RFC2616) |
39d4aa6b RK |
159 | * @return Pointer just after end of word or NULL if there's no word |
160 | * | |
161 | * A word is a token or a quoted-string. | |
162 | */ | |
a1bedb6d RK |
163 | const char *mime_parse_word(const char *s, char **valuep, |
164 | int (*special)(int)) { | |
39d4aa6b | 165 | struct dynstr value[1]; |
460b9539 | 166 | int c; |
167 | ||
39d4aa6b RK |
168 | dynstr_init(value); |
169 | if(*s == '"') { | |
170 | ++s; | |
171 | while((c = *s++) != '"') { | |
172 | switch(c) { | |
173 | case '\\': | |
121f51ac RK |
174 | if(!(c = *s++)) |
175 | return 0; | |
39d4aa6b RK |
176 | default: |
177 | dynstr_append(value, c); | |
178 | break; | |
179 | } | |
460b9539 | 180 | } |
121f51ac RK |
181 | if(!c) |
182 | return 0; | |
39d4aa6b RK |
183 | } else { |
184 | if(!iswordchar((unsigned char)*s, special)) | |
185 | return NULL; | |
186 | dynstr_init(value); | |
187 | while(iswordchar((unsigned char)*s, special)) | |
188 | dynstr_append(value, *s++); | |
460b9539 | 189 | } |
39d4aa6b RK |
190 | dynstr_terminate(value); |
191 | *valuep = value->vec; | |
460b9539 | 192 | return s; |
193 | } | |
194 | ||
39d4aa6b RK |
195 | /** @brief Parse an RFC1521/RFC2616 token |
196 | * @param s Pointer to start of token | |
197 | * @param valuep Where to store value | |
a1bedb6d | 198 | * @param special mime_tspecial() (MIME/RFC2405) or mime_http_separator() (HTTP/RFC2616) |
39d4aa6b RK |
199 | * @return Pointer just after end of token or NULL if there's no token |
200 | */ | |
201 | static const char *parsetoken(const char *s, char **valuep, | |
202 | int (*special)(int)) { | |
121f51ac RK |
203 | if(*s == '"') |
204 | return 0; | |
a1bedb6d | 205 | return mime_parse_word(s, valuep, special); |
39d4aa6b RK |
206 | } |
207 | ||
208 | /** @brief Parse a MIME content-type field | |
209 | * @param s Start of field | |
210 | * @param typep Where to store type | |
9bce81d1 | 211 | * @param parametersp Where to store parameter list |
39d4aa6b | 212 | * @return 0 on success, non-0 on error |
78d8e29d RK |
213 | * |
214 | * See <a href="http://tools.ietf.org/html/rfc2045#section-5">RFC 2045 s5</a>. | |
39d4aa6b | 215 | */ |
460b9539 | 216 | int mime_content_type(const char *s, |
217 | char **typep, | |
9bce81d1 | 218 | struct kvp **parametersp) { |
39d4aa6b | 219 | struct dynstr type, parametername; |
9bce81d1 | 220 | struct kvp *parameters = 0; |
221 | char *parametervalue; | |
460b9539 | 222 | |
223 | dynstr_init(&type); | |
121f51ac RK |
224 | if(!(s = skipwhite(s, 1))) |
225 | return -1; | |
226 | if(!*s) | |
227 | return -1; | |
a1bedb6d | 228 | while(*s && !mime_tspecial(*s) && !whitespace(*s)) |
460b9539 | 229 | dynstr_append(&type, tolower((unsigned char)*s++)); |
121f51ac RK |
230 | if(!(s = skipwhite(s, 1))) |
231 | return -1; | |
232 | if(*s++ != '/') | |
233 | return -1; | |
460b9539 | 234 | dynstr_append(&type, '/'); |
121f51ac RK |
235 | if(!(s = skipwhite(s, 1))) |
236 | return -1; | |
a1bedb6d | 237 | while(*s && !mime_tspecial(*s) && !whitespace(*s)) |
460b9539 | 238 | dynstr_append(&type, tolower((unsigned char)*s++)); |
121f51ac RK |
239 | if(!(s = skipwhite(s, 1))) |
240 | return -1; | |
460b9539 | 241 | |
9bce81d1 | 242 | while(*s == ';') { |
460b9539 | 243 | dynstr_init(¶metername); |
244 | ++s; | |
121f51ac RK |
245 | if(!(s = skipwhite(s, 1))) |
246 | return -1; | |
247 | if(!*s) | |
248 | return -1; | |
a1bedb6d | 249 | while(*s && !mime_tspecial(*s) && !whitespace(*s)) |
460b9539 | 250 | dynstr_append(¶metername, tolower((unsigned char)*s++)); |
121f51ac RK |
251 | if(!(s = skipwhite(s, 1))) |
252 | return -1; | |
253 | if(*s++ != '=') | |
254 | return -1; | |
255 | if(!(s = skipwhite(s, 1))) | |
256 | return -1; | |
a1bedb6d | 257 | if(!(s = mime_parse_word(s, ¶metervalue, mime_tspecial))) |
121f51ac RK |
258 | return -1; |
259 | if(!(s = skipwhite(s, 1))) | |
260 | return -1; | |
460b9539 | 261 | dynstr_terminate(¶metername); |
9bce81d1 | 262 | kvp_set(¶meters, parametername.vec, parametervalue); |
263 | } | |
460b9539 | 264 | dynstr_terminate(&type); |
265 | *typep = type.vec; | |
9bce81d1 | 266 | *parametersp = parameters; |
460b9539 | 267 | return 0; |
268 | } | |
269 | ||
39d4aa6b RK |
270 | /** @brief Parse a MIME message |
271 | * @param s Start of message | |
272 | * @param callback Called for each header field | |
273 | * @param u Passed to callback | |
78d8e29d RK |
274 | * @return Pointer to decoded body (might be in original string), or NULL on error |
275 | * | |
276 | * This does an RFC 822-style parse and honors Content-Transfer-Encoding as | |
277 | * described in <a href="http://tools.ietf.org/html/rfc2045#section-6">RFC 2045 | |
278 | * s6</a>. @p callback is called for each header field encountered, in order, | |
279 | * with ASCII characters in the header name forced to lower case. | |
39d4aa6b | 280 | */ |
460b9539 | 281 | const char *mime_parse(const char *s, |
282 | int (*callback)(const char *name, const char *value, | |
283 | void *u), | |
284 | void *u) { | |
285 | struct dynstr name, value; | |
286 | char *cte = 0, *p; | |
287 | ||
288 | while(*s && !iscrlf(s)) { | |
289 | dynstr_init(&name); | |
290 | dynstr_init(&value); | |
a1bedb6d | 291 | while(*s && !mime_tspecial(*s) && !whitespace(*s)) |
460b9539 | 292 | dynstr_append(&name, tolower((unsigned char)*s++)); |
121f51ac RK |
293 | if(!(s = skipwhite(s, 1))) |
294 | return 0; | |
295 | if(*s != ':') | |
296 | return 0; | |
460b9539 | 297 | ++s; |
121f51ac RK |
298 | while(*s && !(*s == '\n' && !(s[1] == ' ' || s[1] == '\t'))) { |
299 | const int c = *s++; | |
300 | /* Strip leading whitespace */ | |
301 | if(value.nvec || !(c == ' ' || c == '\t' || c == '\n' || c == '\r')) | |
302 | dynstr_append(&value, c); | |
303 | } | |
304 | /* Strip trailing whitespace */ | |
305 | while(value.nvec > 0 && (value.vec[value.nvec - 1] == ' ' | |
306 | || value.vec[value.nvec - 1] == '\t' | |
307 | || value.vec[value.nvec - 1] == '\n' | |
308 | || value.vec[value.nvec - 1] == '\r')) | |
309 | --value.nvec; | |
310 | if(*s) | |
311 | ++s; | |
460b9539 | 312 | dynstr_terminate(&name); |
313 | dynstr_terminate(&value); | |
314 | if(!strcmp(name.vec, "content-transfer-encoding")) { | |
315 | cte = xstrdup(value.vec); | |
316 | for(p = cte; *p; p++) | |
317 | *p = tolower((unsigned char)*p); | |
318 | } | |
121f51ac RK |
319 | if(callback(name.vec, value.vec, u)) |
320 | return 0; | |
460b9539 | 321 | } |
121f51ac RK |
322 | if(*s) |
323 | s += 2; | |
460b9539 | 324 | if(cte) { |
121f51ac RK |
325 | if(!strcmp(cte, "base64")) |
326 | return mime_base64(s, 0); | |
327 | if(!strcmp(cte, "quoted-printable")) | |
328 | return mime_qp(s); | |
329 | if(!strcmp(cte, "7bit") || !strcmp(cte, "8bit")) | |
330 | return s; | |
2e9ba080 | 331 | disorder_error(0, "unknown content-transfer-encoding '%s'", cte); |
121f51ac | 332 | return 0; |
460b9539 | 333 | } |
334 | return s; | |
335 | } | |
336 | ||
78d8e29d | 337 | /** @brief Match the boundary string */ |
460b9539 | 338 | static int isboundary(const char *ptr, const char *boundary, size_t bl) { |
339 | return (ptr[0] == '-' | |
340 | && ptr[1] == '-' | |
341 | && !strncmp(ptr + 2, boundary, bl) | |
342 | && (iscrlf(ptr + bl + 2) | |
343 | || (ptr[bl + 2] == '-' | |
344 | && ptr[bl + 3] == '-' | |
22896b25 | 345 | && (iscrlf(ptr + bl + 4) || *(ptr + bl + 4) == 0)))); |
460b9539 | 346 | } |
347 | ||
78d8e29d | 348 | /** @brief Match the final boundary string */ |
460b9539 | 349 | static int isfinal(const char *ptr, const char *boundary, size_t bl) { |
350 | return (ptr[0] == '-' | |
351 | && ptr[1] == '-' | |
352 | && !strncmp(ptr + 2, boundary, bl) | |
353 | && ptr[bl + 2] == '-' | |
354 | && ptr[bl + 3] == '-' | |
22896b25 | 355 | && (iscrlf(ptr + bl + 4) || *(ptr + bl + 4) == 0)); |
460b9539 | 356 | } |
357 | ||
39d4aa6b RK |
358 | /** @brief Parse a multipart MIME body |
359 | * @param s Start of message | |
78d8e29d | 360 | * @param callback Callback for each part |
39d4aa6b RK |
361 | * @param boundary Boundary string |
362 | * @param u Passed to callback | |
363 | * @return 0 on success, non-0 on error | |
78d8e29d RK |
364 | * |
365 | * See <a href="http://tools.ietf.org/html/rfc2046#section-5.1">RFC 2046 | |
366 | * s5.1</a>. @p callback is called for each part (not yet decoded in any way) | |
367 | * in succession; you should probably call mime_parse() for each part. | |
39d4aa6b | 368 | */ |
460b9539 | 369 | int mime_multipart(const char *s, |
370 | int (*callback)(const char *s, void *u), | |
371 | const char *boundary, | |
372 | void *u) { | |
373 | size_t bl = strlen(boundary); | |
374 | const char *start, *e; | |
375 | int ret; | |
376 | ||
22896b25 | 377 | /* We must start with a boundary string */ |
d16be832 | 378 | if(!isboundary(s, boundary, bl)) { |
2e9ba080 | 379 | disorder_error(0, "mime_multipart: first line is not the boundary string"); |
22896b25 | 380 | return -1; |
d16be832 | 381 | } |
22896b25 | 382 | /* Keep going until we hit a final boundary */ |
460b9539 | 383 | while(!isfinal(s, boundary, bl)) { |
384 | s = strstr(s, "\r\n") + 2; | |
385 | start = s; | |
386 | while(!isboundary(s, boundary, bl)) { | |
d16be832 | 387 | if(!(e = strstr(s, "\r\n"))) { |
2e9ba080 | 388 | disorder_error(0, "mime_multipart: line does not end CRLF"); |
22896b25 | 389 | return -1; |
d16be832 | 390 | } |
460b9539 | 391 | s = e + 2; |
392 | } | |
393 | if((ret = callback(xstrndup(start, | |
394 | s == start ? 0 : s - start - 2), | |
395 | u))) | |
396 | return ret; | |
397 | } | |
398 | return 0; | |
399 | } | |
400 | ||
39d4aa6b RK |
401 | /** @brief Parse an RFC2388-style content-disposition field |
402 | * @param s Start of field | |
158d0961 | 403 | * @param dispositionp Where to store disposition |
39d4aa6b | 404 | * @param parameternamep Where to store parameter name |
158d0961 | 405 | * @param parametervaluep Wher to store parameter value |
39d4aa6b | 406 | * @return 0 on success, non-0 on error |
78d8e29d RK |
407 | * |
408 | * See <a href="http://tools.ietf.org/html/rfc2388#section-3">RFC 2388 s3</a> | |
409 | * and <a href="http://tools.ietf.org/html/rfc2183">RFC 2183</a>. | |
39d4aa6b | 410 | */ |
460b9539 | 411 | int mime_rfc2388_content_disposition(const char *s, |
412 | char **dispositionp, | |
413 | char **parameternamep, | |
414 | char **parametervaluep) { | |
39d4aa6b | 415 | struct dynstr disposition, parametername; |
460b9539 | 416 | |
417 | dynstr_init(&disposition); | |
121f51ac RK |
418 | if(!(s = skipwhite(s, 1))) |
419 | return -1; | |
420 | if(!*s) | |
421 | return -1; | |
a1bedb6d | 422 | while(*s && !mime_tspecial(*s) && !whitespace(*s)) |
460b9539 | 423 | dynstr_append(&disposition, tolower((unsigned char)*s++)); |
121f51ac RK |
424 | if(!(s = skipwhite(s, 1))) |
425 | return -1; | |
460b9539 | 426 | |
427 | if(*s == ';') { | |
428 | dynstr_init(¶metername); | |
429 | ++s; | |
121f51ac RK |
430 | if(!(s = skipwhite(s, 1))) |
431 | return -1; | |
432 | if(!*s) | |
433 | return -1; | |
a1bedb6d | 434 | while(*s && !mime_tspecial(*s) && !whitespace(*s)) |
460b9539 | 435 | dynstr_append(¶metername, tolower((unsigned char)*s++)); |
121f51ac RK |
436 | if(!(s = skipwhite(s, 1))) |
437 | return -1; | |
438 | if(*s++ != '=') | |
439 | return -1; | |
440 | if(!(s = skipwhite(s, 1))) | |
441 | return -1; | |
a1bedb6d | 442 | if(!(s = mime_parse_word(s, parametervaluep, mime_tspecial))) |
121f51ac RK |
443 | return -1; |
444 | if(!(s = skipwhite(s, 1))) | |
445 | return -1; | |
460b9539 | 446 | dynstr_terminate(¶metername); |
447 | *parameternamep = parametername.vec; | |
448 | } else | |
449 | *parametervaluep = *parameternamep = 0; | |
450 | dynstr_terminate(&disposition); | |
451 | *dispositionp = disposition.vec; | |
452 | return 0; | |
453 | } | |
454 | ||
39d4aa6b RK |
455 | /** @brief Convert MIME quoted-printable |
456 | * @param s Quoted-printable data | |
457 | * @return Decoded data | |
78d8e29d RK |
458 | * |
459 | * See <a href="http://tools.ietf.org/html/rfc2045#section-6.7">RFC 2045 | |
460 | * s6.7</a>. | |
39d4aa6b | 461 | */ |
460b9539 | 462 | char *mime_qp(const char *s) { |
463 | struct dynstr d; | |
464 | int c, a, b; | |
465 | const char *t; | |
466 | ||
467 | dynstr_init(&d); | |
468 | while((c = *s++)) { | |
469 | switch(c) { | |
470 | case '=': | |
471 | if((a = unhexdigitq(s[0])) != -1 | |
472 | && (b = unhexdigitq(s[1])) != -1) { | |
473 | dynstr_append(&d, a * 16 + b); | |
474 | s += 2; | |
475 | } else { | |
476 | t = s; | |
477 | while(*t == ' ' || *t == '\t') ++t; | |
478 | if(iscrlf(t)) { | |
479 | /* soft line break */ | |
480 | s = t + 2; | |
481 | } else | |
482 | return 0; | |
483 | } | |
484 | break; | |
485 | case ' ': | |
486 | case '\t': | |
487 | t = s; | |
488 | while(*t == ' ' || *t == '\t') ++t; | |
489 | if(iscrlf(t)) | |
490 | /* trailing space is always eliminated */ | |
491 | s = t; | |
492 | else | |
493 | dynstr_append(&d, c); | |
494 | break; | |
495 | default: | |
496 | dynstr_append(&d, c); | |
497 | break; | |
498 | } | |
499 | } | |
500 | dynstr_terminate(&d); | |
501 | return d.vec; | |
502 | } | |
503 | ||
06819653 | 504 | /** @brief Match cookie separator characters |
505 | * | |
506 | * This is a subset of the RFC2616 specials, and technically is in breach of | |
507 | * the specification. However rejecting (in particular) slashes is | |
508 | * unreasonably strict and has broken at least one (admittedly somewhat | |
509 | * obscure) browser, so we're more forgiving. | |
510 | */ | |
511 | static int cookie_separator(int c) { | |
512 | switch(c) { | |
513 | case '(': | |
514 | case ')': | |
515 | case ',': | |
516 | case ';': | |
517 | case '=': | |
518 | case ' ': | |
519 | case '"': | |
520 | case '\t': | |
521 | return 1; | |
522 | ||
523 | default: | |
524 | return 0; | |
525 | } | |
526 | } | |
527 | ||
e9eb8f7b RK |
528 | /** @brief Match cookie value separator characters |
529 | * | |
530 | * Same as cookie_separator() but allows for @c = in cookie values. | |
531 | */ | |
532 | static int cookie_value_separator(int c) { | |
533 | switch(c) { | |
534 | case '(': | |
535 | case ')': | |
536 | case ',': | |
537 | case ';': | |
538 | case ' ': | |
539 | case '"': | |
540 | case '\t': | |
541 | return 1; | |
542 | ||
543 | default: | |
544 | return 0; | |
545 | } | |
546 | } | |
547 | ||
39d4aa6b RK |
548 | /** @brief Parse a RFC2109 Cookie: header |
549 | * @param s Header field value | |
550 | * @param cd Where to store result | |
551 | * @return 0 on success, non-0 on error | |
36bde473 | 552 | * |
553 | * See <a href="http://tools.ietf.org/html/rfc2109">RFC 2109</a>. | |
39d4aa6b RK |
554 | */ |
555 | int parse_cookie(const char *s, | |
556 | struct cookiedata *cd) { | |
557 | char *n = 0, *v = 0; | |
558 | ||
559 | memset(cd, 0, sizeof *cd); | |
560 | s = skipwhite(s, 0); | |
561 | while(*s) { | |
562 | /* Skip separators */ | |
563 | if(*s == ';' || *s == ',') { | |
564 | ++s; | |
565 | s = skipwhite(s, 0); | |
566 | continue; | |
567 | } | |
06819653 | 568 | if(!(s = parsetoken(s, &n, cookie_separator))) { |
2e9ba080 | 569 | disorder_error(0, "parse_cookie: cannot parse attribute name"); |
121f51ac | 570 | return -1; |
2e9ba080 | 571 | } |
39d4aa6b | 572 | s = skipwhite(s, 0); |
06819653 | 573 | if(*s++ != '=') { |
2e9ba080 | 574 | disorder_error(0, "parse_cookie: did not find expected '='"); |
121f51ac | 575 | return -1; |
06819653 | 576 | } |
39d4aa6b | 577 | s = skipwhite(s, 0); |
e9eb8f7b | 578 | if(!(s = mime_parse_word(s, &v, cookie_value_separator))) { |
2e9ba080 | 579 | disorder_error(0, "parse_cookie: cannot parse value for '%s'", n); |
121f51ac | 580 | return -1; |
06819653 | 581 | } |
39d4aa6b RK |
582 | if(n[0] == '$') { |
583 | /* Some bit of meta-information */ | |
584 | if(!strcmp(n, "$Version")) | |
585 | cd->version = v; | |
586 | else if(!strcmp(n, "$Path")) { | |
587 | if(cd->ncookies > 0 && cd->cookies[cd->ncookies-1].path == 0) | |
588 | cd->cookies[cd->ncookies-1].path = v; | |
589 | else { | |
2e9ba080 | 590 | disorder_error(0, "redundant $Path in Cookie: header"); |
39d4aa6b RK |
591 | return -1; |
592 | } | |
593 | } else if(!strcmp(n, "$Domain")) { | |
594 | if(cd->ncookies > 0 && cd->cookies[cd->ncookies-1].domain == 0) | |
595 | cd->cookies[cd->ncookies-1].domain = v; | |
596 | else { | |
2e9ba080 | 597 | disorder_error(0, "redundant $Domain in Cookie: header"); |
39d4aa6b RK |
598 | return -1; |
599 | } | |
600 | } | |
601 | } else { | |
602 | /* It's a new cookie */ | |
603 | cd->cookies = xrealloc(cd->cookies, | |
604 | (cd->ncookies + 1) * sizeof (struct cookie)); | |
605 | cd->cookies[cd->ncookies].name = n; | |
606 | cd->cookies[cd->ncookies].value = v; | |
607 | cd->cookies[cd->ncookies].path = 0; | |
608 | cd->cookies[cd->ncookies].domain = 0; | |
609 | ++cd->ncookies; | |
610 | } | |
611 | s = skipwhite(s, 0); | |
612 | if(*s && (*s != ',' && *s != ';')) { | |
2e9ba080 | 613 | disorder_error(0, "missing separator in Cookie: header"); |
39d4aa6b RK |
614 | return -1; |
615 | } | |
616 | } | |
617 | return 0; | |
618 | } | |
619 | ||
620 | /** @brief Find a named cookie | |
621 | * @param cd Parse cookie data | |
622 | * @param name Name of cookie | |
623 | * @return Cookie structure or NULL if not found | |
624 | */ | |
625 | const struct cookie *find_cookie(const struct cookiedata *cd, | |
626 | const char *name) { | |
627 | int n; | |
628 | ||
629 | for(n = 0; n < cd->ncookies; ++n) | |
630 | if(!strcmp(cd->cookies[n].name, name)) | |
631 | return &cd->cookies[n]; | |
632 | return 0; | |
633 | } | |
634 | ||
36bde473 | 635 | /** @brief RFC822 quoting |
636 | * @param s String to quote | |
637 | * @param force If non-0, always quote | |
638 | * @return Possibly quoted string | |
639 | */ | |
640 | char *quote822(const char *s, int force) { | |
641 | const char *t; | |
642 | struct dynstr d[1]; | |
643 | int c; | |
644 | ||
645 | if(!force) { | |
646 | /* See if we need to quote */ | |
647 | for(t = s; (c = (unsigned char)*t); ++t) { | |
a1bedb6d | 648 | if(mime_tspecial(c) || mime_http_separator(c) || whitespace(c)) |
36bde473 | 649 | break; |
650 | } | |
651 | if(*t) | |
652 | force = 1; | |
653 | } | |
654 | ||
655 | if(!force) | |
656 | return xstrdup(s); | |
657 | ||
658 | dynstr_init(d); | |
659 | dynstr_append(d, '"'); | |
660 | for(t = s; (c = (unsigned char)*t); ++t) { | |
661 | if(c == '"' || c == '\\') | |
662 | dynstr_append(d, '\\'); | |
663 | dynstr_append(d, c); | |
664 | } | |
665 | dynstr_append(d, '"'); | |
666 | dynstr_terminate(d); | |
667 | return d->vec; | |
668 | } | |
669 | ||
bb6ae3fb | 670 | /** @brief Return true if @p ptr points at trailing space */ |
671 | static int is_trailing_space(const char *ptr) { | |
672 | if(*ptr == ' ' || *ptr == '\t') { | |
673 | while(*ptr == ' ' || *ptr == '\t') | |
674 | ++ptr; | |
675 | return *ptr == '\n' || *ptr == 0; | |
676 | } else | |
677 | return 0; | |
678 | } | |
679 | ||
680 | /** @brief Encoding text as quoted-printable | |
681 | * @param text String to encode | |
682 | * @return Encoded string | |
683 | * | |
684 | * See <a href="http://tools.ietf.org/html/rfc2045#section-6.7">RFC2045 | |
685 | * s6.7</a>. | |
686 | */ | |
687 | char *mime_to_qp(const char *text) { | |
688 | struct dynstr d[1]; | |
689 | int linelength = 0; /* length of current line */ | |
690 | char buffer[10]; | |
691 | ||
692 | dynstr_init(d); | |
693 | /* The rules are: | |
694 | * 1. Anything except newline can be replaced with =%02X | |
695 | * 2. Newline, 33-60 and 62-126 stand for themselves (i.e. not '=') | |
696 | * 3. Non-trailing space/tab stand for themselves. | |
697 | * 4. Output lines are limited to 76 chars, with =<newline> being used | |
698 | * as a soft line break | |
699 | * 5. Newlines aren't counted towards the 76 char limit. | |
700 | */ | |
701 | while(*text) { | |
702 | const int c = (unsigned char)*text; | |
703 | if(c == '\n') { | |
704 | /* Newline stands as itself */ | |
705 | dynstr_append(d, '\n'); | |
706 | linelength = 0; | |
707 | } else if((c >= 33 && c <= 126 && c != '=') | |
708 | || ((c == ' ' || c == '\t') | |
709 | && !is_trailing_space(text))) { | |
710 | /* Things that can stand for themselves */ | |
711 | dynstr_append(d, c); | |
712 | ++linelength; | |
713 | } else { | |
714 | /* Anything else that needs encoding */ | |
715 | snprintf(buffer, sizeof buffer, "=%02X", c); | |
716 | dynstr_append_string(d, buffer); | |
717 | linelength += 3; | |
718 | } | |
719 | ++text; | |
720 | if(linelength > 73 && *text && *text != '\n') { | |
721 | /* Next character might overflow 76 character limit if encoded, so we | |
722 | * insert a soft break */ | |
723 | dynstr_append_string(d, "=\n"); | |
724 | linelength = 0; | |
725 | } | |
726 | } | |
727 | /* Ensure there is a final newline */ | |
728 | if(linelength) | |
729 | dynstr_append(d, '\n'); | |
730 | /* That's all */ | |
731 | dynstr_terminate(d); | |
732 | return d->vec; | |
733 | } | |
734 | ||
735 | /** @brief Encode text | |
736 | * @param text Underlying UTF-8 text | |
737 | * @param charsetp Where to store charset string | |
738 | * @param encodingp Where to store encoding string | |
0590cedc | 739 | * @return Encoded text (might be @p text) |
bb6ae3fb | 740 | */ |
741 | const char *mime_encode_text(const char *text, | |
742 | const char **charsetp, | |
743 | const char **encodingp) { | |
744 | const char *ptr; | |
745 | ||
746 | /* See if there are in fact any non-ASCII characters */ | |
747 | for(ptr = text; *ptr; ++ptr) | |
748 | if((unsigned char)*ptr >= 128) | |
749 | break; | |
750 | if(!*ptr) { | |
751 | /* Plain old ASCII, no encoding required */ | |
752 | *charsetp = "us-ascii"; | |
753 | *encodingp = "7bit"; | |
754 | return text; | |
755 | } | |
756 | *charsetp = "utf-8"; | |
757 | *encodingp = "quoted-printable"; | |
758 | return mime_to_qp(text); | |
759 | } | |
760 | ||
460b9539 | 761 | /* |
762 | Local Variables: | |
763 | c-basic-offset:2 | |
764 | comment-column:40 | |
765 | fill-column:79 | |
766 | End: | |
767 | */ |