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
];