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