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 | 383 | /* We must start with a boundary string */ |
d16be832 RK |
384 | if(!isboundary(s, boundary, bl)) { |
385 | error(0, "mime_multipart: first line is not the boundary string"); | |
22896b25 | 386 | return -1; |
d16be832 | 387 | } |
22896b25 | 388 | /* Keep going until we hit a final boundary */ |
460b9539 | 389 | while(!isfinal(s, boundary, bl)) { |
390 | s = strstr(s, "\r\n") + 2; | |
391 | start = s; | |
392 | while(!isboundary(s, boundary, bl)) { | |
d16be832 RK |
393 | if(!(e = strstr(s, "\r\n"))) { |
394 | error(0, "mime_multipart: line does not end CRLF"); | |
22896b25 | 395 | return -1; |
d16be832 | 396 | } |
460b9539 | 397 | s = e + 2; |
398 | } | |
399 | if((ret = callback(xstrndup(start, | |
400 | s == start ? 0 : s - start - 2), | |
401 | u))) | |
402 | return ret; | |
403 | } | |
404 | return 0; | |
405 | } | |
406 | ||
39d4aa6b RK |
407 | /** @brief Parse an RFC2388-style content-disposition field |
408 | * @param s Start of field | |
158d0961 | 409 | * @param dispositionp Where to store disposition |
39d4aa6b | 410 | * @param parameternamep Where to store parameter name |
158d0961 | 411 | * @param parametervaluep Wher to store parameter value |
39d4aa6b | 412 | * @return 0 on success, non-0 on error |
78d8e29d RK |
413 | * |
414 | * See <a href="http://tools.ietf.org/html/rfc2388#section-3">RFC 2388 s3</a> | |
415 | * and <a href="http://tools.ietf.org/html/rfc2183">RFC 2183</a>. | |
39d4aa6b | 416 | */ |
460b9539 | 417 | int mime_rfc2388_content_disposition(const char *s, |
418 | char **dispositionp, | |
419 | char **parameternamep, | |
420 | char **parametervaluep) { | |
39d4aa6b | 421 | struct dynstr disposition, parametername; |
460b9539 | 422 | |
423 | dynstr_init(&disposition); | |
121f51ac RK |
424 | if(!(s = skipwhite(s, 1))) |
425 | return -1; | |
426 | if(!*s) | |
427 | return -1; | |
a1bedb6d | 428 | while(*s && !mime_tspecial(*s) && !whitespace(*s)) |
460b9539 | 429 | dynstr_append(&disposition, tolower((unsigned char)*s++)); |
121f51ac RK |
430 | if(!(s = skipwhite(s, 1))) |
431 | return -1; | |
460b9539 | 432 | |
433 | if(*s == ';') { | |
434 | dynstr_init(¶metername); | |
435 | ++s; | |
121f51ac RK |
436 | if(!(s = skipwhite(s, 1))) |
437 | return -1; | |
438 | if(!*s) | |
439 | return -1; | |
a1bedb6d | 440 | while(*s && !mime_tspecial(*s) && !whitespace(*s)) |
460b9539 | 441 | dynstr_append(¶metername, tolower((unsigned char)*s++)); |
121f51ac RK |
442 | if(!(s = skipwhite(s, 1))) |
443 | return -1; | |
444 | if(*s++ != '=') | |
445 | return -1; | |
446 | if(!(s = skipwhite(s, 1))) | |
447 | return -1; | |
a1bedb6d | 448 | if(!(s = mime_parse_word(s, parametervaluep, mime_tspecial))) |
121f51ac RK |
449 | return -1; |
450 | if(!(s = skipwhite(s, 1))) | |
451 | return -1; | |
460b9539 | 452 | dynstr_terminate(¶metername); |
453 | *parameternamep = parametername.vec; | |
454 | } else | |
455 | *parametervaluep = *parameternamep = 0; | |
456 | dynstr_terminate(&disposition); | |
457 | *dispositionp = disposition.vec; | |
458 | return 0; | |
459 | } | |
460 | ||
39d4aa6b RK |
461 | /** @brief Convert MIME quoted-printable |
462 | * @param s Quoted-printable data | |
463 | * @return Decoded data | |
78d8e29d RK |
464 | * |
465 | * See <a href="http://tools.ietf.org/html/rfc2045#section-6.7">RFC 2045 | |
466 | * s6.7</a>. | |
39d4aa6b | 467 | */ |
460b9539 | 468 | char *mime_qp(const char *s) { |
469 | struct dynstr d; | |
470 | int c, a, b; | |
471 | const char *t; | |
472 | ||
473 | dynstr_init(&d); | |
474 | while((c = *s++)) { | |
475 | switch(c) { | |
476 | case '=': | |
477 | if((a = unhexdigitq(s[0])) != -1 | |
478 | && (b = unhexdigitq(s[1])) != -1) { | |
479 | dynstr_append(&d, a * 16 + b); | |
480 | s += 2; | |
481 | } else { | |
482 | t = s; | |
483 | while(*t == ' ' || *t == '\t') ++t; | |
484 | if(iscrlf(t)) { | |
485 | /* soft line break */ | |
486 | s = t + 2; | |
487 | } else | |
488 | return 0; | |
489 | } | |
490 | break; | |
491 | case ' ': | |
492 | case '\t': | |
493 | t = s; | |
494 | while(*t == ' ' || *t == '\t') ++t; | |
495 | if(iscrlf(t)) | |
496 | /* trailing space is always eliminated */ | |
497 | s = t; | |
498 | else | |
499 | dynstr_append(&d, c); | |
500 | break; | |
501 | default: | |
502 | dynstr_append(&d, c); | |
503 | break; | |
504 | } | |
505 | } | |
506 | dynstr_terminate(&d); | |
507 | return d.vec; | |
508 | } | |
509 | ||
06819653 | 510 | /** @brief Match cookie separator characters |
511 | * | |
512 | * This is a subset of the RFC2616 specials, and technically is in breach of | |
513 | * the specification. However rejecting (in particular) slashes is | |
514 | * unreasonably strict and has broken at least one (admittedly somewhat | |
515 | * obscure) browser, so we're more forgiving. | |
516 | */ | |
517 | static int cookie_separator(int c) { | |
518 | switch(c) { | |
519 | case '(': | |
520 | case ')': | |
521 | case ',': | |
522 | case ';': | |
523 | case '=': | |
524 | case ' ': | |
525 | case '"': | |
526 | case '\t': | |
527 | return 1; | |
528 | ||
529 | default: | |
530 | return 0; | |
531 | } | |
532 | } | |
533 | ||
e9eb8f7b RK |
534 | /** @brief Match cookie value separator characters |
535 | * | |
536 | * Same as cookie_separator() but allows for @c = in cookie values. | |
537 | */ | |
538 | static int cookie_value_separator(int c) { | |
539 | switch(c) { | |
540 | case '(': | |
541 | case ')': | |
542 | case ',': | |
543 | case ';': | |
544 | case ' ': | |
545 | case '"': | |
546 | case '\t': | |
547 | return 1; | |
548 | ||
549 | default: | |
550 | return 0; | |
551 | } | |
552 | } | |
553 | ||
39d4aa6b RK |
554 | /** @brief Parse a RFC2109 Cookie: header |
555 | * @param s Header field value | |
556 | * @param cd Where to store result | |
557 | * @return 0 on success, non-0 on error | |
36bde473 | 558 | * |
559 | * See <a href="http://tools.ietf.org/html/rfc2109">RFC 2109</a>. | |
39d4aa6b RK |
560 | */ |
561 | int parse_cookie(const char *s, | |
562 | struct cookiedata *cd) { | |
563 | char *n = 0, *v = 0; | |
564 | ||
565 | memset(cd, 0, sizeof *cd); | |
566 | s = skipwhite(s, 0); | |
567 | while(*s) { | |
568 | /* Skip separators */ | |
569 | if(*s == ';' || *s == ',') { | |
570 | ++s; | |
571 | s = skipwhite(s, 0); | |
572 | continue; | |
573 | } | |
06819653 | 574 | if(!(s = parsetoken(s, &n, cookie_separator))) { |
575 | error(0, "parse_cookie: cannot parse attribute name"); | |
121f51ac | 576 | return -1; |
06819653 | 577 | } |
39d4aa6b | 578 | s = skipwhite(s, 0); |
06819653 | 579 | if(*s++ != '=') { |
580 | error(0, "parse_cookie: did not find expected '='"); | |
121f51ac | 581 | return -1; |
06819653 | 582 | } |
39d4aa6b | 583 | s = skipwhite(s, 0); |
e9eb8f7b | 584 | if(!(s = mime_parse_word(s, &v, cookie_value_separator))) { |
06819653 | 585 | error(0, "parse_cookie: cannot parse value for '%s'", n); |
121f51ac | 586 | return -1; |
06819653 | 587 | } |
39d4aa6b RK |
588 | if(n[0] == '$') { |
589 | /* Some bit of meta-information */ | |
590 | if(!strcmp(n, "$Version")) | |
591 | cd->version = v; | |
592 | else if(!strcmp(n, "$Path")) { | |
593 | if(cd->ncookies > 0 && cd->cookies[cd->ncookies-1].path == 0) | |
594 | cd->cookies[cd->ncookies-1].path = v; | |
595 | else { | |
596 | error(0, "redundant $Path in Cookie: header"); | |
597 | return -1; | |
598 | } | |
599 | } else if(!strcmp(n, "$Domain")) { | |
600 | if(cd->ncookies > 0 && cd->cookies[cd->ncookies-1].domain == 0) | |
601 | cd->cookies[cd->ncookies-1].domain = v; | |
602 | else { | |
603 | error(0, "redundant $Domain in Cookie: header"); | |
604 | return -1; | |
605 | } | |
606 | } | |
607 | } else { | |
608 | /* It's a new cookie */ | |
609 | cd->cookies = xrealloc(cd->cookies, | |
610 | (cd->ncookies + 1) * sizeof (struct cookie)); | |
611 | cd->cookies[cd->ncookies].name = n; | |
612 | cd->cookies[cd->ncookies].value = v; | |
613 | cd->cookies[cd->ncookies].path = 0; | |
614 | cd->cookies[cd->ncookies].domain = 0; | |
615 | ++cd->ncookies; | |
616 | } | |
617 | s = skipwhite(s, 0); | |
618 | if(*s && (*s != ',' && *s != ';')) { | |
619 | error(0, "missing separator in Cookie: header"); | |
620 | return -1; | |
621 | } | |
622 | } | |
623 | return 0; | |
624 | } | |
625 | ||
626 | /** @brief Find a named cookie | |
627 | * @param cd Parse cookie data | |
628 | * @param name Name of cookie | |
629 | * @return Cookie structure or NULL if not found | |
630 | */ | |
631 | const struct cookie *find_cookie(const struct cookiedata *cd, | |
632 | const char *name) { | |
633 | int n; | |
634 | ||
635 | for(n = 0; n < cd->ncookies; ++n) | |
636 | if(!strcmp(cd->cookies[n].name, name)) | |
637 | return &cd->cookies[n]; | |
638 | return 0; | |
639 | } | |
640 | ||
36bde473 | 641 | /** @brief RFC822 quoting |
642 | * @param s String to quote | |
643 | * @param force If non-0, always quote | |
644 | * @return Possibly quoted string | |
645 | */ | |
646 | char *quote822(const char *s, int force) { | |
647 | const char *t; | |
648 | struct dynstr d[1]; | |
649 | int c; | |
650 | ||
651 | if(!force) { | |
652 | /* See if we need to quote */ | |
653 | for(t = s; (c = (unsigned char)*t); ++t) { | |
a1bedb6d | 654 | if(mime_tspecial(c) || mime_http_separator(c) || whitespace(c)) |
36bde473 | 655 | break; |
656 | } | |
657 | if(*t) | |
658 | force = 1; | |
659 | } | |
660 | ||
661 | if(!force) | |
662 | return xstrdup(s); | |
663 | ||
664 | dynstr_init(d); | |
665 | dynstr_append(d, '"'); | |
666 | for(t = s; (c = (unsigned char)*t); ++t) { | |
667 | if(c == '"' || c == '\\') | |
668 | dynstr_append(d, '\\'); | |
669 | dynstr_append(d, c); | |
670 | } | |
671 | dynstr_append(d, '"'); | |
672 | dynstr_terminate(d); | |
673 | return d->vec; | |
674 | } | |
675 | ||
bb6ae3fb | 676 | /** @brief Return true if @p ptr points at trailing space */ |
677 | static int is_trailing_space(const char *ptr) { | |
678 | if(*ptr == ' ' || *ptr == '\t') { | |
679 | while(*ptr == ' ' || *ptr == '\t') | |
680 | ++ptr; | |
681 | return *ptr == '\n' || *ptr == 0; | |
682 | } else | |
683 | return 0; | |
684 | } | |
685 | ||
686 | /** @brief Encoding text as quoted-printable | |
687 | * @param text String to encode | |
688 | * @return Encoded string | |
689 | * | |
690 | * See <a href="http://tools.ietf.org/html/rfc2045#section-6.7">RFC2045 | |
691 | * s6.7</a>. | |
692 | */ | |
693 | char *mime_to_qp(const char *text) { | |
694 | struct dynstr d[1]; | |
695 | int linelength = 0; /* length of current line */ | |
696 | char buffer[10]; | |
697 | ||
698 | dynstr_init(d); | |
699 | /* The rules are: | |
700 | * 1. Anything except newline can be replaced with =%02X | |
701 | * 2. Newline, 33-60 and 62-126 stand for themselves (i.e. not '=') | |
702 | * 3. Non-trailing space/tab stand for themselves. | |
703 | * 4. Output lines are limited to 76 chars, with =<newline> being used | |
704 | * as a soft line break | |
705 | * 5. Newlines aren't counted towards the 76 char limit. | |
706 | */ | |
707 | while(*text) { | |
708 | const int c = (unsigned char)*text; | |
709 | if(c == '\n') { | |
710 | /* Newline stands as itself */ | |
711 | dynstr_append(d, '\n'); | |
712 | linelength = 0; | |
713 | } else if((c >= 33 && c <= 126 && c != '=') | |
714 | || ((c == ' ' || c == '\t') | |
715 | && !is_trailing_space(text))) { | |
716 | /* Things that can stand for themselves */ | |
717 | dynstr_append(d, c); | |
718 | ++linelength; | |
719 | } else { | |
720 | /* Anything else that needs encoding */ | |
721 | snprintf(buffer, sizeof buffer, "=%02X", c); | |
722 | dynstr_append_string(d, buffer); | |
723 | linelength += 3; | |
724 | } | |
725 | ++text; | |
726 | if(linelength > 73 && *text && *text != '\n') { | |
727 | /* Next character might overflow 76 character limit if encoded, so we | |
728 | * insert a soft break */ | |
729 | dynstr_append_string(d, "=\n"); | |
730 | linelength = 0; | |
731 | } | |
732 | } | |
733 | /* Ensure there is a final newline */ | |
734 | if(linelength) | |
735 | dynstr_append(d, '\n'); | |
736 | /* That's all */ | |
737 | dynstr_terminate(d); | |
738 | return d->vec; | |
739 | } | |
740 | ||
741 | /** @brief Encode text | |
742 | * @param text Underlying UTF-8 text | |
743 | * @param charsetp Where to store charset string | |
744 | * @param encodingp Where to store encoding string | |
0590cedc | 745 | * @return Encoded text (might be @p text) |
bb6ae3fb | 746 | */ |
747 | const char *mime_encode_text(const char *text, | |
748 | const char **charsetp, | |
749 | const char **encodingp) { | |
750 | const char *ptr; | |
751 | ||
752 | /* See if there are in fact any non-ASCII characters */ | |
753 | for(ptr = text; *ptr; ++ptr) | |
754 | if((unsigned char)*ptr >= 128) | |
755 | break; | |
756 | if(!*ptr) { | |
757 | /* Plain old ASCII, no encoding required */ | |
758 | *charsetp = "us-ascii"; | |
759 | *encodingp = "7bit"; | |
760 | return text; | |
761 | } | |
762 | *charsetp = "utf-8"; | |
763 | *encodingp = "quoted-printable"; | |
764 | return mime_to_qp(text); | |
765 | } | |
766 | ||
460b9539 | 767 | /* |
768 | Local Variables: | |
769 | c-basic-offset:2 | |
770 | comment-column:40 | |
771 | fill-column:79 | |
772 | End: | |
773 | */ |