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