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);
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 /** @brief Convert MIME base64
435 * @param s base64 data
436 * @return Decoded data
438 char *mime_base64(const char *s) {
442 static const char table[] =
443 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
447 while((c = (unsigned char)*s++)) {
448 if((t = strchr(table, c))) {
451 dynstr_append(&d, (b[0] << 2) + (b[1] >> 4));
452 dynstr_append(&d, (b[1] << 4) + (b[2] >> 2));
453 dynstr_append(&d, (b[2] << 6) + b[3]);
456 } else if(c == '=') {
458 dynstr_append(&d, (b[0] << 2) + (b[1] >> 4));
460 dynstr_append(&d, (b[1] << 4) + (b[2] >> 2));
465 dynstr_terminate(&d);
469 /** @brief Parse a RFC2109 Cookie: header
470 * @param s Header field value
471 * @param cd Where to store result
472 * @return 0 on success, non-0 on error
474 int parse_cookie(const char *s,
475 struct cookiedata *cd) {
478 memset(cd, 0, sizeof *cd);
481 /* Skip separators */
482 if(*s == ';' || *s == ',') {
487 if(!(s = parsetoken(s, &n, http_separator))) return -1;
489 if(*s++ != '=') return -1;
491 if(!(s = parseword(s, &v, http_separator))) return -1;
493 /* Some bit of meta-information */
494 if(!strcmp(n, "$Version"))
496 else if(!strcmp(n, "$Path")) {
497 if(cd->ncookies > 0 && cd->cookies[cd->ncookies-1].path == 0)
498 cd->cookies[cd->ncookies-1].path = v;
500 error(0, "redundant $Path in Cookie: header");
503 } else if(!strcmp(n, "$Domain")) {
504 if(cd->ncookies > 0 && cd->cookies[cd->ncookies-1].domain == 0)
505 cd->cookies[cd->ncookies-1].domain = v;
507 error(0, "redundant $Domain in Cookie: header");
512 /* It's a new cookie */
513 cd->cookies = xrealloc(cd->cookies,
514 (cd->ncookies + 1) * sizeof (struct cookie));
515 cd->cookies[cd->ncookies].name = n;
516 cd->cookies[cd->ncookies].value = v;
517 cd->cookies[cd->ncookies].path = 0;
518 cd->cookies[cd->ncookies].domain = 0;
522 if(*s && (*s != ',' && *s != ';')) {
523 error(0, "missing separator in Cookie: header");
530 /** @brief Find a named cookie
531 * @param cd Parse cookie data
532 * @param name Name of cookie
533 * @return Cookie structure or NULL if not found
535 const struct cookie *find_cookie(const struct cookiedata *cd,
539 for(n = 0; n < cd->ncookies; ++n)
540 if(!strcmp(cd->cookies[n].name, name))
541 return &cd->cookies[n];