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 | |
24 | #include <config.h> | |
25 | #include "types.h" | |
26 | ||
27 | #include <string.h> | |
28 | #include <ctype.h> | |
29 | ||
22896b25 RK |
30 | #include <stdio.h> |
31 | ||
460b9539 | 32 | #include "mem.h" |
33 | #include "mime.h" | |
34 | #include "vector.h" | |
35 | #include "hex.h" | |
39d4aa6b | 36 | #include "log.h" |
fce810c2 | 37 | #include "base64.h" |
9bce81d1 | 38 | #include "kvp.h" |
460b9539 | 39 | |
39d4aa6b | 40 | /** @brief Match whitespace characters */ |
460b9539 | 41 | static int whitespace(int c) { |
42 | switch(c) { | |
43 | case ' ': | |
44 | case '\t': | |
45 | case '\r': | |
46 | case '\n': | |
47 | return 1; | |
48 | default: | |
49 | return 0; | |
50 | } | |
51 | } | |
52 | ||
39d4aa6b | 53 | /** @brief Match RFC2045 tspecial characters */ |
a1bedb6d | 54 | int mime_tspecial(int c) { |
460b9539 | 55 | switch(c) { |
56 | case '(': | |
57 | case ')': | |
58 | case '<': | |
59 | case '>': | |
60 | case '@': | |
61 | case ',': | |
62 | case ';': | |
63 | case ':': | |
64 | case '\\': | |
65 | case '"': | |
66 | case '/': | |
67 | case '[': | |
68 | case ']': | |
69 | case '?': | |
70 | case '=': | |
71 | return 1; | |
72 | default: | |
73 | return 0; | |
74 | } | |
75 | } | |
76 | ||
82c01b31 | 77 | /** @brief Match RFC2616 separator characters */ |
a1bedb6d | 78 | int mime_http_separator(int c) { |
39d4aa6b RK |
79 | switch(c) { |
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 '=': | |
95 | case '{': | |
96 | case '}': | |
97 | case ' ': | |
98 | case '\t': | |
99 | return 1; | |
100 | default: | |
101 | return 0; | |
102 | } | |
103 | } | |
104 | ||
105 | /** @brief Match CRLF */ | |
106 | static int iscrlf(const char *ptr) { | |
107 | return ptr[0] == '\r' && ptr[1] == '\n'; | |
108 | } | |
109 | ||
110 | /** @brief Skip whitespace | |
158d0961 | 111 | * @param s Pointer into string |
39d4aa6b | 112 | * @param rfc822_comments If true, skip RFC822 nested comments |
158d0961 | 113 | * @return Pointer into string after whitespace |
39d4aa6b RK |
114 | */ |
115 | static const char *skipwhite(const char *s, int rfc822_comments) { | |
460b9539 | 116 | int c, depth; |
117 | ||
118 | for(;;) { | |
119 | switch(c = *s) { | |
120 | case ' ': | |
121 | case '\t': | |
122 | case '\r': | |
123 | case '\n': | |
124 | ++s; | |
125 | break; | |
126 | case '(': | |
39d4aa6b RK |
127 | if(!rfc822_comments) |
128 | return s; | |
460b9539 | 129 | ++s; |
130 | depth = 1; | |
131 | while(*s && depth) { | |
132 | c = *s++; | |
133 | switch(c) { | |
134 | case '(': ++depth; break; | |
135 | case ')': --depth; break; | |
136 | case '\\': | |
121f51ac RK |
137 | if(!*s) |
138 | return 0; | |
460b9539 | 139 | ++s; |
140 | break; | |
141 | } | |
142 | } | |
121f51ac RK |
143 | if(depth) |
144 | return 0; | |
460b9539 | 145 | break; |
146 | default: | |
147 | return s; | |
148 | } | |
149 | } | |
150 | } | |
151 | ||
39d4aa6b RK |
152 | /** @brief Test for a word character |
153 | * @param c Character to test | |
a1bedb6d | 154 | * @param special mime_tspecial() (MIME/RFC2405) or mime_http_separator() (HTTP/RFC2616) |
39d4aa6b RK |
155 | * @return 1 if @p c is a word character, else 0 |
156 | */ | |
157 | static int iswordchar(int c, int (*special)(int)) { | |
158 | return !(c <= ' ' || c > '~' || special(c)); | |
159 | } | |
160 | ||
161 | /** @brief Parse an RFC1521/RFC2616 word | |
162 | * @param s Pointer to start of word | |
163 | * @param valuep Where to store value | |
a1bedb6d | 164 | * @param special mime_tspecial() (MIME/RFC2405) or mime_http_separator() (HTTP/RFC2616) |
39d4aa6b RK |
165 | * @return Pointer just after end of word or NULL if there's no word |
166 | * | |
167 | * A word is a token or a quoted-string. | |
168 | */ | |
a1bedb6d RK |
169 | const char *mime_parse_word(const char *s, char **valuep, |
170 | int (*special)(int)) { | |
39d4aa6b | 171 | struct dynstr value[1]; |
460b9539 | 172 | int c; |
173 | ||
39d4aa6b RK |
174 | dynstr_init(value); |
175 | if(*s == '"') { | |
176 | ++s; | |
177 | while((c = *s++) != '"') { | |
178 | switch(c) { | |
179 | case '\\': | |
121f51ac RK |
180 | if(!(c = *s++)) |
181 | return 0; | |
39d4aa6b RK |
182 | default: |
183 | dynstr_append(value, c); | |
184 | break; | |
185 | } | |
460b9539 | 186 | } |
121f51ac RK |
187 | if(!c) |
188 | return 0; | |
39d4aa6b RK |
189 | } else { |
190 | if(!iswordchar((unsigned char)*s, special)) | |
191 | return NULL; | |
192 | dynstr_init(value); | |
193 | while(iswordchar((unsigned char)*s, special)) | |
194 | dynstr_append(value, *s++); | |
460b9539 | 195 | } |
39d4aa6b RK |
196 | dynstr_terminate(value); |
197 | *valuep = value->vec; | |
460b9539 | 198 | return s; |
199 | } | |
200 | ||
39d4aa6b RK |
201 | /** @brief Parse an RFC1521/RFC2616 token |
202 | * @param s Pointer to start of token | |
203 | * @param valuep Where to store value | |
a1bedb6d | 204 | * @param special mime_tspecial() (MIME/RFC2405) or mime_http_separator() (HTTP/RFC2616) |
39d4aa6b RK |
205 | * @return Pointer just after end of token or NULL if there's no token |
206 | */ | |
207 | static const char *parsetoken(const char *s, char **valuep, | |
208 | int (*special)(int)) { | |
121f51ac RK |
209 | if(*s == '"') |
210 | return 0; | |
a1bedb6d | 211 | return mime_parse_word(s, valuep, special); |
39d4aa6b RK |
212 | } |
213 | ||
214 | /** @brief Parse a MIME content-type field | |
215 | * @param s Start of field | |
216 | * @param typep Where to store type | |
9bce81d1 | 217 | * @param parametersp Where to store parameter list |
39d4aa6b | 218 | * @return 0 on success, non-0 on error |
78d8e29d RK |
219 | * |
220 | * See <a href="http://tools.ietf.org/html/rfc2045#section-5">RFC 2045 s5</a>. | |
39d4aa6b | 221 | */ |
460b9539 | 222 | int mime_content_type(const char *s, |
223 | char **typep, | |
9bce81d1 | 224 | struct kvp **parametersp) { |
39d4aa6b | 225 | struct dynstr type, parametername; |
9bce81d1 | 226 | struct kvp *parameters = 0; |
227 | char *parametervalue; | |
460b9539 | 228 | |
229 | dynstr_init(&type); | |
121f51ac RK |
230 | if(!(s = skipwhite(s, 1))) |
231 | return -1; | |
232 | if(!*s) | |
233 | return -1; | |
a1bedb6d | 234 | while(*s && !mime_tspecial(*s) && !whitespace(*s)) |
460b9539 | 235 | dynstr_append(&type, tolower((unsigned char)*s++)); |
121f51ac RK |
236 | if(!(s = skipwhite(s, 1))) |
237 | return -1; | |
238 | if(*s++ != '/') | |
239 | return -1; | |
460b9539 | 240 | dynstr_append(&type, '/'); |
121f51ac RK |
241 | if(!(s = skipwhite(s, 1))) |
242 | return -1; | |
a1bedb6d | 243 | while(*s && !mime_tspecial(*s) && !whitespace(*s)) |
460b9539 | 244 | dynstr_append(&type, tolower((unsigned char)*s++)); |
121f51ac RK |
245 | if(!(s = skipwhite(s, 1))) |
246 | return -1; | |
460b9539 | 247 | |
9bce81d1 | 248 | while(*s == ';') { |
460b9539 | 249 | dynstr_init(¶metername); |
250 | ++s; | |
121f51ac RK |
251 | if(!(s = skipwhite(s, 1))) |
252 | return -1; | |
253 | if(!*s) | |
254 | return -1; | |
a1bedb6d | 255 | while(*s && !mime_tspecial(*s) && !whitespace(*s)) |
460b9539 | 256 | dynstr_append(¶metername, tolower((unsigned char)*s++)); |
121f51ac RK |
257 | if(!(s = skipwhite(s, 1))) |
258 | return -1; | |
259 | if(*s++ != '=') | |
260 | return -1; | |
261 | if(!(s = skipwhite(s, 1))) | |
262 | return -1; | |
a1bedb6d | 263 | if(!(s = mime_parse_word(s, ¶metervalue, mime_tspecial))) |
121f51ac RK |
264 | return -1; |
265 | if(!(s = skipwhite(s, 1))) | |
266 | return -1; | |
460b9539 | 267 | dynstr_terminate(¶metername); |
9bce81d1 | 268 | kvp_set(¶meters, parametername.vec, parametervalue); |
269 | } | |
460b9539 | 270 | dynstr_terminate(&type); |
271 | *typep = type.vec; | |
9bce81d1 | 272 | *parametersp = parameters; |
460b9539 | 273 | return 0; |
274 | } | |
275 | ||
39d4aa6b RK |
276 | /** @brief Parse a MIME message |
277 | * @param s Start of message | |
278 | * @param callback Called for each header field | |
279 | * @param u Passed to callback | |
78d8e29d RK |
280 | * @return Pointer to decoded body (might be in original string), or NULL on error |
281 | * | |
282 | * This does an RFC 822-style parse and honors Content-Transfer-Encoding as | |
283 | * described in <a href="http://tools.ietf.org/html/rfc2045#section-6">RFC 2045 | |
284 | * s6</a>. @p callback is called for each header field encountered, in order, | |
285 | * with ASCII characters in the header name forced to lower case. | |
39d4aa6b | 286 | */ |
460b9539 | 287 | const char *mime_parse(const char *s, |
288 | int (*callback)(const char *name, const char *value, | |
289 | void *u), | |
290 | void *u) { | |
291 | struct dynstr name, value; | |
292 | char *cte = 0, *p; | |
293 | ||
294 | while(*s && !iscrlf(s)) { | |
295 | dynstr_init(&name); | |
296 | dynstr_init(&value); | |
a1bedb6d | 297 | while(*s && !mime_tspecial(*s) && !whitespace(*s)) |
460b9539 | 298 | dynstr_append(&name, tolower((unsigned char)*s++)); |
121f51ac RK |
299 | if(!(s = skipwhite(s, 1))) |
300 | return 0; | |
301 | if(*s != ':') | |
302 | return 0; | |
460b9539 | 303 | ++s; |
121f51ac RK |
304 | while(*s && !(*s == '\n' && !(s[1] == ' ' || s[1] == '\t'))) { |
305 | const int c = *s++; | |
306 | /* Strip leading whitespace */ | |
307 | if(value.nvec || !(c == ' ' || c == '\t' || c == '\n' || c == '\r')) | |
308 | dynstr_append(&value, c); | |
309 | } | |
310 | /* Strip trailing whitespace */ | |
311 | while(value.nvec > 0 && (value.vec[value.nvec - 1] == ' ' | |
312 | || value.vec[value.nvec - 1] == '\t' | |
313 | || value.vec[value.nvec - 1] == '\n' | |
314 | || value.vec[value.nvec - 1] == '\r')) | |
315 | --value.nvec; | |
316 | if(*s) | |
317 | ++s; | |
460b9539 | 318 | dynstr_terminate(&name); |
319 | dynstr_terminate(&value); | |
320 | if(!strcmp(name.vec, "content-transfer-encoding")) { | |
321 | cte = xstrdup(value.vec); | |
322 | for(p = cte; *p; p++) | |
323 | *p = tolower((unsigned char)*p); | |
324 | } | |
121f51ac RK |
325 | if(callback(name.vec, value.vec, u)) |
326 | return 0; | |
460b9539 | 327 | } |
121f51ac RK |
328 | if(*s) |
329 | s += 2; | |
460b9539 | 330 | if(cte) { |
121f51ac RK |
331 | if(!strcmp(cte, "base64")) |
332 | return mime_base64(s, 0); | |
333 | if(!strcmp(cte, "quoted-printable")) | |
334 | return mime_qp(s); | |
335 | if(!strcmp(cte, "7bit") || !strcmp(cte, "8bit")) | |
336 | return s; | |
337 | error(0, "unknown content-transfer-encoding '%s'", cte); | |
338 | return 0; | |
460b9539 | 339 | } |
340 | return s; | |
341 | } | |
342 | ||
78d8e29d | 343 | /** @brief Match the boundary string */ |
460b9539 | 344 | static int isboundary(const char *ptr, const char *boundary, size_t bl) { |
345 | return (ptr[0] == '-' | |
346 | && ptr[1] == '-' | |
347 | && !strncmp(ptr + 2, boundary, bl) | |
348 | && (iscrlf(ptr + bl + 2) | |
349 | || (ptr[bl + 2] == '-' | |
350 | && ptr[bl + 3] == '-' | |
22896b25 | 351 | && (iscrlf(ptr + bl + 4) || *(ptr + bl + 4) == 0)))); |
460b9539 | 352 | } |
353 | ||
78d8e29d | 354 | /** @brief Match the final boundary string */ |
460b9539 | 355 | static int isfinal(const char *ptr, const char *boundary, size_t bl) { |
356 | return (ptr[0] == '-' | |
357 | && ptr[1] == '-' | |
358 | && !strncmp(ptr + 2, boundary, bl) | |
359 | && ptr[bl + 2] == '-' | |
360 | && ptr[bl + 3] == '-' | |
22896b25 | 361 | && (iscrlf(ptr + bl + 4) || *(ptr + bl + 4) == 0)); |
460b9539 | 362 | } |
363 | ||
39d4aa6b RK |
364 | /** @brief Parse a multipart MIME body |
365 | * @param s Start of message | |
78d8e29d | 366 | * @param callback Callback for each part |
39d4aa6b RK |
367 | * @param boundary Boundary string |
368 | * @param u Passed to callback | |
369 | * @return 0 on success, non-0 on error | |
78d8e29d RK |
370 | * |
371 | * See <a href="http://tools.ietf.org/html/rfc2046#section-5.1">RFC 2046 | |
372 | * s5.1</a>. @p callback is called for each part (not yet decoded in any way) | |
373 | * in succession; you should probably call mime_parse() for each part. | |
39d4aa6b | 374 | */ |
460b9539 | 375 | int mime_multipart(const char *s, |
376 | int (*callback)(const char *s, void *u), | |
377 | const char *boundary, | |
378 | void *u) { | |
379 | size_t bl = strlen(boundary); | |
380 | const char *start, *e; | |
381 | int ret; | |
382 | ||
22896b25 RK |
383 | /* We must start with a boundary string */ |
384 | if(!isboundary(s, boundary, bl)) | |
385 | return -1; | |
386 | /* Keep going until we hit a final boundary */ | |
460b9539 | 387 | while(!isfinal(s, boundary, bl)) { |
388 | s = strstr(s, "\r\n") + 2; | |
389 | start = s; | |
390 | while(!isboundary(s, boundary, bl)) { | |
22896b25 RK |
391 | if(!(e = strstr(s, "\r\n"))) |
392 | return -1; | |
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 | ||
39d4aa6b RK |
530 | /** @brief Parse a RFC2109 Cookie: header |
531 | * @param s Header field value | |
532 | * @param cd Where to store result | |
533 | * @return 0 on success, non-0 on error | |
36bde473 | 534 | * |
535 | * See <a href="http://tools.ietf.org/html/rfc2109">RFC 2109</a>. | |
39d4aa6b RK |
536 | */ |
537 | int parse_cookie(const char *s, | |
538 | struct cookiedata *cd) { | |
539 | char *n = 0, *v = 0; | |
540 | ||
541 | memset(cd, 0, sizeof *cd); | |
542 | s = skipwhite(s, 0); | |
543 | while(*s) { | |
544 | /* Skip separators */ | |
545 | if(*s == ';' || *s == ',') { | |
546 | ++s; | |
547 | s = skipwhite(s, 0); | |
548 | continue; | |
549 | } | |
06819653 | 550 | if(!(s = parsetoken(s, &n, cookie_separator))) { |
551 | error(0, "parse_cookie: cannot parse attribute name"); | |
121f51ac | 552 | return -1; |
06819653 | 553 | } |
39d4aa6b | 554 | s = skipwhite(s, 0); |
06819653 | 555 | if(*s++ != '=') { |
556 | error(0, "parse_cookie: did not find expected '='"); | |
121f51ac | 557 | return -1; |
06819653 | 558 | } |
39d4aa6b | 559 | s = skipwhite(s, 0); |
06819653 | 560 | if(!(s = mime_parse_word(s, &v, cookie_separator))) { |
561 | error(0, "parse_cookie: cannot parse value for '%s'", n); | |
121f51ac | 562 | return -1; |
06819653 | 563 | } |
39d4aa6b RK |
564 | if(n[0] == '$') { |
565 | /* Some bit of meta-information */ | |
566 | if(!strcmp(n, "$Version")) | |
567 | cd->version = v; | |
568 | else if(!strcmp(n, "$Path")) { | |
569 | if(cd->ncookies > 0 && cd->cookies[cd->ncookies-1].path == 0) | |
570 | cd->cookies[cd->ncookies-1].path = v; | |
571 | else { | |
572 | error(0, "redundant $Path in Cookie: header"); | |
573 | return -1; | |
574 | } | |
575 | } else if(!strcmp(n, "$Domain")) { | |
576 | if(cd->ncookies > 0 && cd->cookies[cd->ncookies-1].domain == 0) | |
577 | cd->cookies[cd->ncookies-1].domain = v; | |
578 | else { | |
579 | error(0, "redundant $Domain in Cookie: header"); | |
580 | return -1; | |
581 | } | |
582 | } | |
583 | } else { | |
584 | /* It's a new cookie */ | |
585 | cd->cookies = xrealloc(cd->cookies, | |
586 | (cd->ncookies + 1) * sizeof (struct cookie)); | |
587 | cd->cookies[cd->ncookies].name = n; | |
588 | cd->cookies[cd->ncookies].value = v; | |
589 | cd->cookies[cd->ncookies].path = 0; | |
590 | cd->cookies[cd->ncookies].domain = 0; | |
591 | ++cd->ncookies; | |
592 | } | |
593 | s = skipwhite(s, 0); | |
594 | if(*s && (*s != ',' && *s != ';')) { | |
595 | error(0, "missing separator in Cookie: header"); | |
596 | return -1; | |
597 | } | |
598 | } | |
599 | return 0; | |
600 | } | |
601 | ||
602 | /** @brief Find a named cookie | |
603 | * @param cd Parse cookie data | |
604 | * @param name Name of cookie | |
605 | * @return Cookie structure or NULL if not found | |
606 | */ | |
607 | const struct cookie *find_cookie(const struct cookiedata *cd, | |
608 | const char *name) { | |
609 | int n; | |
610 | ||
611 | for(n = 0; n < cd->ncookies; ++n) | |
612 | if(!strcmp(cd->cookies[n].name, name)) | |
613 | return &cd->cookies[n]; | |
614 | return 0; | |
615 | } | |
616 | ||
36bde473 | 617 | /** @brief RFC822 quoting |
618 | * @param s String to quote | |
619 | * @param force If non-0, always quote | |
620 | * @return Possibly quoted string | |
621 | */ | |
622 | char *quote822(const char *s, int force) { | |
623 | const char *t; | |
624 | struct dynstr d[1]; | |
625 | int c; | |
626 | ||
627 | if(!force) { | |
628 | /* See if we need to quote */ | |
629 | for(t = s; (c = (unsigned char)*t); ++t) { | |
a1bedb6d | 630 | if(mime_tspecial(c) || mime_http_separator(c) || whitespace(c)) |
36bde473 | 631 | break; |
632 | } | |
633 | if(*t) | |
634 | force = 1; | |
635 | } | |
636 | ||
637 | if(!force) | |
638 | return xstrdup(s); | |
639 | ||
640 | dynstr_init(d); | |
641 | dynstr_append(d, '"'); | |
642 | for(t = s; (c = (unsigned char)*t); ++t) { | |
643 | if(c == '"' || c == '\\') | |
644 | dynstr_append(d, '\\'); | |
645 | dynstr_append(d, c); | |
646 | } | |
647 | dynstr_append(d, '"'); | |
648 | dynstr_terminate(d); | |
649 | return d->vec; | |
650 | } | |
651 | ||
bb6ae3fb | 652 | /** @brief Return true if @p ptr points at trailing space */ |
653 | static int is_trailing_space(const char *ptr) { | |
654 | if(*ptr == ' ' || *ptr == '\t') { | |
655 | while(*ptr == ' ' || *ptr == '\t') | |
656 | ++ptr; | |
657 | return *ptr == '\n' || *ptr == 0; | |
658 | } else | |
659 | return 0; | |
660 | } | |
661 | ||
662 | /** @brief Encoding text as quoted-printable | |
663 | * @param text String to encode | |
664 | * @return Encoded string | |
665 | * | |
666 | * See <a href="http://tools.ietf.org/html/rfc2045#section-6.7">RFC2045 | |
667 | * s6.7</a>. | |
668 | */ | |
669 | char *mime_to_qp(const char *text) { | |
670 | struct dynstr d[1]; | |
671 | int linelength = 0; /* length of current line */ | |
672 | char buffer[10]; | |
673 | ||
674 | dynstr_init(d); | |
675 | /* The rules are: | |
676 | * 1. Anything except newline can be replaced with =%02X | |
677 | * 2. Newline, 33-60 and 62-126 stand for themselves (i.e. not '=') | |
678 | * 3. Non-trailing space/tab stand for themselves. | |
679 | * 4. Output lines are limited to 76 chars, with =<newline> being used | |
680 | * as a soft line break | |
681 | * 5. Newlines aren't counted towards the 76 char limit. | |
682 | */ | |
683 | while(*text) { | |
684 | const int c = (unsigned char)*text; | |
685 | if(c == '\n') { | |
686 | /* Newline stands as itself */ | |
687 | dynstr_append(d, '\n'); | |
688 | linelength = 0; | |
689 | } else if((c >= 33 && c <= 126 && c != '=') | |
690 | || ((c == ' ' || c == '\t') | |
691 | && !is_trailing_space(text))) { | |
692 | /* Things that can stand for themselves */ | |
693 | dynstr_append(d, c); | |
694 | ++linelength; | |
695 | } else { | |
696 | /* Anything else that needs encoding */ | |
697 | snprintf(buffer, sizeof buffer, "=%02X", c); | |
698 | dynstr_append_string(d, buffer); | |
699 | linelength += 3; | |
700 | } | |
701 | ++text; | |
702 | if(linelength > 73 && *text && *text != '\n') { | |
703 | /* Next character might overflow 76 character limit if encoded, so we | |
704 | * insert a soft break */ | |
705 | dynstr_append_string(d, "=\n"); | |
706 | linelength = 0; | |
707 | } | |
708 | } | |
709 | /* Ensure there is a final newline */ | |
710 | if(linelength) | |
711 | dynstr_append(d, '\n'); | |
712 | /* That's all */ | |
713 | dynstr_terminate(d); | |
714 | return d->vec; | |
715 | } | |
716 | ||
717 | /** @brief Encode text | |
718 | * @param text Underlying UTF-8 text | |
719 | * @param charsetp Where to store charset string | |
720 | * @param encodingp Where to store encoding string | |
0590cedc | 721 | * @return Encoded text (might be @p text) |
bb6ae3fb | 722 | */ |
723 | const char *mime_encode_text(const char *text, | |
724 | const char **charsetp, | |
725 | const char **encodingp) { | |
726 | const char *ptr; | |
727 | ||
728 | /* See if there are in fact any non-ASCII characters */ | |
729 | for(ptr = text; *ptr; ++ptr) | |
730 | if((unsigned char)*ptr >= 128) | |
731 | break; | |
732 | if(!*ptr) { | |
733 | /* Plain old ASCII, no encoding required */ | |
734 | *charsetp = "us-ascii"; | |
735 | *encodingp = "7bit"; | |
736 | return text; | |
737 | } | |
738 | *charsetp = "utf-8"; | |
739 | *encodingp = "quoted-printable"; | |
740 | return mime_to_qp(text); | |
741 | } | |
742 | ||
460b9539 | 743 | /* |
744 | Local Variables: | |
745 | c-basic-offset:2 | |
746 | comment-column:40 | |
747 | fill-column:79 | |
748 | End: | |
749 | */ |