2 * This file is part of DisOrder
3 * Copyright (C) 2005, 2007 Richard Kettlewell
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.
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.
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
21 * @brief Support for MIME and allied protocols
38 /** @brief Match whitespace characters */
39 static int whitespace(int c) {
51 /** @brief Match RFC2045 tspecial characters */
52 static int tspecial(int c) {
75 /** @brief Mathc RFC2616 seprator characters */
76 static int http_separator(int c) {
103 /** @brief Match CRLF */
104 static int iscrlf(const char *ptr) {
105 return ptr[0] == '\r' && ptr[1] == '\n';
108 /** @brief Skip whitespace
109 * @param rfc822_comments If true, skip RFC822 nested comments
111 static const char *skipwhite(const char *s, int rfc822_comments) {
130 case '(': ++depth; break;
131 case ')': --depth; break;
146 /** @brief Test for a word character
147 * @param c Character to test
148 * @param special tspecial() (MIME/RFC2405) or http_separator() (HTTP/RFC2616)
149 * @return 1 if @p c is a word character, else 0
151 static int iswordchar(int c, int (*special)(int)) {
152 return !(c <= ' ' || c > '~' || special(c));
155 /** @brief Parse an RFC1521/RFC2616 word
156 * @param s Pointer to start of word
157 * @param valuep Where to store value
158 * @param special tspecial() (MIME/RFC2405) or http_separator() (HTTP/RFC2616)
159 * @return Pointer just after end of word or NULL if there's no word
161 * A word is a token or a quoted-string.
163 static const char *parseword(const char *s, char **valuep,
164 int (*special)(int)) {
165 struct dynstr value[1];
171 while((c = *s++) != '"') {
174 if(!(c = *s++)) return 0;
176 dynstr_append(value, c);
182 if(!iswordchar((unsigned char)*s, special))
185 while(iswordchar((unsigned char)*s, special))
186 dynstr_append(value, *s++);
188 dynstr_terminate(value);
189 *valuep = value->vec;
193 /** @brief Parse an RFC1521/RFC2616 token
194 * @param s Pointer to start of token
195 * @param valuep Where to store value
196 * @param special tspecial() (MIME/RFC2405) or http_separator() (HTTP/RFC2616)
197 * @return Pointer just after end of token or NULL if there's no token
199 static const char *parsetoken(const char *s, char **valuep,
200 int (*special)(int)) {
201 if(*s == '"') return 0;
202 return parseword(s, valuep, special);
205 /** @brief Parse a MIME content-type field
206 * @param s Start of field
207 * @param typep Where to store type
208 * @param parameternamep Where to store parameter name
209 * @param parameternvaluep Wher to store parameter value
210 * @return 0 on success, non-0 on error
212 int mime_content_type(const char *s,
214 char **parameternamep,
215 char **parametervaluep) {
216 struct dynstr type, parametername;
219 if(!(s = skipwhite(s, 1))) return -1;
221 while(*s && !tspecial(*s) && !whitespace(*s))
222 dynstr_append(&type, tolower((unsigned char)*s++));
223 if(!(s = skipwhite(s, 1))) return -1;
224 if(*s++ != '/') return -1;
225 dynstr_append(&type, '/');
226 if(!(s = skipwhite(s, 1))) return -1;
227 while(*s && !tspecial(*s) && !whitespace(*s))
228 dynstr_append(&type, tolower((unsigned char)*s++));
229 if(!(s = skipwhite(s, 1))) return -1;
232 dynstr_init(¶metername);
234 if(!(s = skipwhite(s, 1))) return -1;
236 while(*s && !tspecial(*s) && !whitespace(*s))
237 dynstr_append(¶metername, tolower((unsigned char)*s++));
238 if(!(s = skipwhite(s, 1))) return -1;
239 if(*s++ != '=') return -1;
240 if(!(s = skipwhite(s, 1))) return -1;
241 if(!(s = parseword(s, parametervaluep, tspecial))) return -1;
242 if(!(s = skipwhite(s, 1))) return -1;
243 dynstr_terminate(¶metername);
244 *parameternamep = parametername.vec;
246 *parametervaluep = *parameternamep = 0;
247 dynstr_terminate(&type);
252 /** @brief Parse a MIME message
253 * @param s Start of message
254 * @param callback Called for each header field
255 * @param u Passed to callback
256 * @return Pointer to decoded body (might be in original string)
258 const char *mime_parse(const char *s,
259 int (*callback)(const char *name, const char *value,
262 struct dynstr name, value;
265 while(*s && !iscrlf(s)) {
268 while(*s && !tspecial(*s) && !whitespace(*s))
269 dynstr_append(&name, tolower((unsigned char)*s++));
270 if(!(s = skipwhite(s, 1))) return 0;
271 if(*s != ':') return 0;
273 while(*s && !(*s == '\n' && !(s[1] == ' ' || s[1] == '\t')))
274 dynstr_append(&value, *s++);
276 dynstr_terminate(&name);
277 dynstr_terminate(&value);
278 if(!strcmp(name.vec, "content-transfer-encoding")) {
279 cte = xstrdup(value.vec);
280 for(p = cte; *p; p++)
281 *p = tolower((unsigned char)*p);
283 if(callback(name.vec, value.vec, u)) return 0;
287 if(!strcmp(cte, "base64")) return mime_base64(s, 0);
288 if(!strcmp(cte, "quoted-printable")) return mime_qp(s);
293 static int isboundary(const char *ptr, const char *boundary, size_t bl) {
294 return (ptr[0] == '-'
296 && !strncmp(ptr + 2, boundary, bl)
297 && (iscrlf(ptr + bl + 2)
298 || (ptr[bl + 2] == '-'
299 && ptr[bl + 3] == '-'
300 && (iscrlf(ptr + bl + 4) || *(ptr + bl + 4) == 0))));
303 static int isfinal(const char *ptr, const char *boundary, size_t bl) {
304 return (ptr[0] == '-'
306 && !strncmp(ptr + 2, boundary, bl)
307 && ptr[bl + 2] == '-'
308 && ptr[bl + 3] == '-'
309 && (iscrlf(ptr + bl + 4) || *(ptr + bl + 4) == 0));
312 /** @brief Parse a multipart MIME body
313 * @param s Start of message
314 * @param callback CAllback for each part
315 * @param boundary Boundary string
316 * @param u Passed to callback
317 * @return 0 on success, non-0 on error
319 int mime_multipart(const char *s,
320 int (*callback)(const char *s, void *u),
321 const char *boundary,
323 size_t bl = strlen(boundary);
324 const char *start, *e;
327 /* We must start with a boundary string */
328 if(!isboundary(s, boundary, bl))
330 /* Keep going until we hit a final boundary */
331 while(!isfinal(s, boundary, bl)) {
332 s = strstr(s, "\r\n") + 2;
334 while(!isboundary(s, boundary, bl)) {
335 if(!(e = strstr(s, "\r\n")))
339 if((ret = callback(xstrndup(start,
340 s == start ? 0 : s - start - 2),
347 /** @brief Parse an RFC2388-style content-disposition field
348 * @param s Start of field
349 * @param typep Where to store type
350 * @param parameternamep Where to store parameter name
351 * @param parameternvaluep Wher to store parameter value
352 * @return 0 on success, non-0 on error
354 int mime_rfc2388_content_disposition(const char *s,
356 char **parameternamep,
357 char **parametervaluep) {
358 struct dynstr disposition, parametername;
360 dynstr_init(&disposition);
361 if(!(s = skipwhite(s, 1))) return -1;
363 while(*s && !tspecial(*s) && !whitespace(*s))
364 dynstr_append(&disposition, tolower((unsigned char)*s++));
365 if(!(s = skipwhite(s, 1))) return -1;
368 dynstr_init(¶metername);
370 if(!(s = skipwhite(s, 1))) return -1;
372 while(*s && !tspecial(*s) && !whitespace(*s))
373 dynstr_append(¶metername, tolower((unsigned char)*s++));
374 if(!(s = skipwhite(s, 1))) return -1;
375 if(*s++ != '=') return -1;
376 if(!(s = skipwhite(s, 1))) return -1;
377 if(!(s = parseword(s, parametervaluep, tspecial))) return -1;
378 if(!(s = skipwhite(s, 1))) return -1;
379 dynstr_terminate(¶metername);
380 *parameternamep = parametername.vec;
382 *parametervaluep = *parameternamep = 0;
383 dynstr_terminate(&disposition);
384 *dispositionp = disposition.vec;
388 /** @brief Convert MIME quoted-printable
389 * @param s Quoted-printable data
390 * @return Decoded data
392 char *mime_qp(const char *s) {
401 if((a = unhexdigitq(s[0])) != -1
402 && (b = unhexdigitq(s[1])) != -1) {
403 dynstr_append(&d, a * 16 + b);
407 while(*t == ' ' || *t == '\t') ++t;
409 /* soft line break */
418 while(*t == ' ' || *t == '\t') ++t;
420 /* trailing space is always eliminated */
423 dynstr_append(&d, c);
426 dynstr_append(&d, c);
430 dynstr_terminate(&d);
434 static const char mime_base64_table[] =
435 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
437 /** @brief Convert MIME base64
438 * @param s base64 data
439 * @return Decoded data
441 char *mime_base64(const char *s, size_t *nsp) {
448 while((c = (unsigned char)*s++)) {
449 if((t = strchr(mime_base64_table, c))) {
450 b[n++] = t - mime_base64_table;
452 dynstr_append(&d, (b[0] << 2) + (b[1] >> 4));
453 dynstr_append(&d, (b[1] << 4) + (b[2] >> 2));
454 dynstr_append(&d, (b[2] << 6) + b[3]);
457 } else if(c == '=') {
459 dynstr_append(&d, (b[0] << 2) + (b[1] >> 4));
461 dynstr_append(&d, (b[1] << 4) + (b[2] >> 2));
468 dynstr_terminate(&d);
472 /** @brief Convert a binary string to base64
473 * @param s Bytes to convert
474 * @param ns Number of bytes to convert
475 * @return Encoded data
477 * This function does not attempt to split up lines.
479 char *mime_to_base64(const uint8_t *s, size_t ns) {
484 /* Input bytes with output bits: AAAAAABB BBBBCCCC CCDDDDDD */
485 /* Output bytes with input bits: 000000 001111 111122 222222 */
486 dynstr_append(d, mime_base64_table[s[0] >> 2]);
487 dynstr_append(d, mime_base64_table[((s[0] & 3) << 4)
489 dynstr_append(d, mime_base64_table[((s[1] & 15) << 2)
491 dynstr_append(d, mime_base64_table[s[2] & 63]);
496 dynstr_append(d, mime_base64_table[s[0] >> 2]);
499 dynstr_append(d, mime_base64_table[(s[0] & 3) << 4]);
500 dynstr_append(d, '=');
501 dynstr_append(d, '=');
504 dynstr_append(d, mime_base64_table[((s[0] & 3) << 4)
506 dynstr_append(d, mime_base64_table[(s[1] & 15) << 2]);
507 dynstr_append(d, '=');
515 /** @brief Parse a RFC2109 Cookie: header
516 * @param s Header field value
517 * @param cd Where to store result
518 * @return 0 on success, non-0 on error
520 int parse_cookie(const char *s,
521 struct cookiedata *cd) {
524 memset(cd, 0, sizeof *cd);
527 /* Skip separators */
528 if(*s == ';' || *s == ',') {
533 if(!(s = parsetoken(s, &n, http_separator))) return -1;
535 if(*s++ != '=') return -1;
537 if(!(s = parseword(s, &v, http_separator))) return -1;
539 /* Some bit of meta-information */
540 if(!strcmp(n, "$Version"))
542 else if(!strcmp(n, "$Path")) {
543 if(cd->ncookies > 0 && cd->cookies[cd->ncookies-1].path == 0)
544 cd->cookies[cd->ncookies-1].path = v;
546 error(0, "redundant $Path in Cookie: header");
549 } else if(!strcmp(n, "$Domain")) {
550 if(cd->ncookies > 0 && cd->cookies[cd->ncookies-1].domain == 0)
551 cd->cookies[cd->ncookies-1].domain = v;
553 error(0, "redundant $Domain in Cookie: header");
558 /* It's a new cookie */
559 cd->cookies = xrealloc(cd->cookies,
560 (cd->ncookies + 1) * sizeof (struct cookie));
561 cd->cookies[cd->ncookies].name = n;
562 cd->cookies[cd->ncookies].value = v;
563 cd->cookies[cd->ncookies].path = 0;
564 cd->cookies[cd->ncookies].domain = 0;
568 if(*s && (*s != ',' && *s != ';')) {
569 error(0, "missing separator in Cookie: header");
576 /** @brief Find a named cookie
577 * @param cd Parse cookie data
578 * @param name Name of cookie
579 * @return Cookie structure or NULL if not found
581 const struct cookie *find_cookie(const struct cookiedata *cd,
585 for(n = 0; n < cd->ncookies; ++n)
586 if(!strcmp(cd->cookies[n].name, name))
587 return &cd->cookies[n];