X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/82c01b317cd1892f4376c68be88a74f971493428..8ab2aa9fd51a89e06d92a4f7c3792aaa4a08cc71:/lib/mime.c
diff --git a/lib/mime.c b/lib/mime.c
index 8776647..99dac39 100644
--- a/lib/mime.c
+++ b/lib/mime.c
@@ -1,40 +1,36 @@
/*
* This file is part of DisOrder
- * Copyright (C) 2005, 2007 Richard Kettlewell
+ * Copyright (C) 2005, 2007-10, 2013 Richard Kettlewell
*
- * This program is free software; you can redistribute it and/or modify
+ * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
* You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- * USA
+ * along with this program. If not, see .
*/
/** @file lib/mime.c
* @brief Support for MIME and allied protocols
*/
-#include
-#include "types.h"
+#include "common.h"
-#include
#include
-#include
-
#include "mem.h"
#include "mime.h"
#include "vector.h"
#include "hex.h"
#include "log.h"
#include "base64.h"
+#include "kvp.h"
+#include "printf.h"
/** @brief Match whitespace characters */
static int whitespace(int c) {
@@ -50,7 +46,7 @@ static int whitespace(int c) {
}
/** @brief Match RFC2045 tspecial characters */
-static int tspecial(int c) {
+int mime_tspecial(int c) {
switch(c) {
case '(':
case ')':
@@ -74,7 +70,7 @@ static int tspecial(int c) {
}
/** @brief Match RFC2616 separator characters */
-static int http_separator(int c) {
+int mime_http_separator(int c) {
switch(c) {
case '(':
case ')':
@@ -115,7 +111,7 @@ static const char *skipwhite(const char *s, int rfc822_comments) {
int c, depth;
for(;;) {
- switch(c = *s) {
+ switch(*s) {
case ' ':
case '\t':
case '\r':
@@ -133,12 +129,14 @@ static const char *skipwhite(const char *s, int rfc822_comments) {
case '(': ++depth; break;
case ')': --depth; break;
case '\\':
- if(!*s) return 0;
+ if(!*s)
+ return 0;
++s;
break;
}
}
- if(depth) return 0;
+ if(depth)
+ return 0;
break;
default:
return s;
@@ -148,7 +146,7 @@ static const char *skipwhite(const char *s, int rfc822_comments) {
/** @brief Test for a word character
* @param c Character to test
- * @param special tspecial() (MIME/RFC2405) or http_separator() (HTTP/RFC2616)
+ * @param special mime_tspecial() (MIME/RFC2405) or mime_http_separator() (HTTP/RFC2616)
* @return 1 if @p c is a word character, else 0
*/
static int iswordchar(int c, int (*special)(int)) {
@@ -158,13 +156,13 @@ static int iswordchar(int c, int (*special)(int)) {
/** @brief Parse an RFC1521/RFC2616 word
* @param s Pointer to start of word
* @param valuep Where to store value
- * @param special tspecial() (MIME/RFC2405) or http_separator() (HTTP/RFC2616)
+ * @param special mime_tspecial() (MIME/RFC2405) or mime_http_separator() (HTTP/RFC2616)
* @return Pointer just after end of word or NULL if there's no word
*
* A word is a token or a quoted-string.
*/
-static const char *parseword(const char *s, char **valuep,
- int (*special)(int)) {
+const char *mime_parse_word(const char *s, char **valuep,
+ int (*special)(int)) {
struct dynstr value[1];
int c;
@@ -174,13 +172,16 @@ static const char *parseword(const char *s, char **valuep,
while((c = *s++) != '"') {
switch(c) {
case '\\':
- if(!(c = *s++)) return 0;
+ if(!(c = *s++))
+ return 0;
+ /* else fall through ... */
default:
dynstr_append(value, c);
break;
}
}
- if(!c) return 0;
+ if(!c)
+ return 0;
} else {
if(!iswordchar((unsigned char)*s, special))
return NULL;
@@ -196,61 +197,75 @@ static const char *parseword(const char *s, char **valuep,
/** @brief Parse an RFC1521/RFC2616 token
* @param s Pointer to start of token
* @param valuep Where to store value
- * @param special tspecial() (MIME/RFC2405) or http_separator() (HTTP/RFC2616)
+ * @param special mime_tspecial() (MIME/RFC2405) or mime_http_separator() (HTTP/RFC2616)
* @return Pointer just after end of token or NULL if there's no token
*/
static const char *parsetoken(const char *s, char **valuep,
int (*special)(int)) {
- if(*s == '"') return 0;
- return parseword(s, valuep, special);
+ if(*s == '"')
+ return 0;
+ return mime_parse_word(s, valuep, special);
}
/** @brief Parse a MIME content-type field
* @param s Start of field
* @param typep Where to store type
- * @param parameternamep Where to store parameter name
- * @param parametervaluep Wher to store parameter value
+ * @param parametersp Where to store parameter list
* @return 0 on success, non-0 on error
*
* See RFC 2045 s5.
*/
int mime_content_type(const char *s,
char **typep,
- char **parameternamep,
- char **parametervaluep) {
+ struct kvp **parametersp) {
struct dynstr type, parametername;
+ struct kvp *parameters = 0;
+ char *parametervalue;
dynstr_init(&type);
- if(!(s = skipwhite(s, 1))) return -1;
- if(!*s) return -1;
- while(*s && !tspecial(*s) && !whitespace(*s))
+ if(!(s = skipwhite(s, 1)))
+ return -1;
+ if(!*s)
+ return -1;
+ while(*s && !mime_tspecial(*s) && !whitespace(*s))
dynstr_append(&type, tolower((unsigned char)*s++));
- if(!(s = skipwhite(s, 1))) return -1;
- if(*s++ != '/') return -1;
+ if(!(s = skipwhite(s, 1)))
+ return -1;
+ if(*s++ != '/')
+ return -1;
dynstr_append(&type, '/');
- if(!(s = skipwhite(s, 1))) return -1;
- while(*s && !tspecial(*s) && !whitespace(*s))
+ if(!(s = skipwhite(s, 1)))
+ return -1;
+ while(*s && !mime_tspecial(*s) && !whitespace(*s))
dynstr_append(&type, tolower((unsigned char)*s++));
- if(!(s = skipwhite(s, 1))) return -1;
+ if(!(s = skipwhite(s, 1)))
+ return -1;
- if(*s == ';') {
+ while(*s == ';') {
dynstr_init(¶metername);
++s;
- if(!(s = skipwhite(s, 1))) return -1;
- if(!*s) return -1;
- while(*s && !tspecial(*s) && !whitespace(*s))
+ if(!(s = skipwhite(s, 1)))
+ return -1;
+ if(!*s)
+ return -1;
+ while(*s && !mime_tspecial(*s) && !whitespace(*s))
dynstr_append(¶metername, tolower((unsigned char)*s++));
- if(!(s = skipwhite(s, 1))) return -1;
- if(*s++ != '=') return -1;
- if(!(s = skipwhite(s, 1))) return -1;
- if(!(s = parseword(s, parametervaluep, tspecial))) return -1;
- if(!(s = skipwhite(s, 1))) return -1;
+ if(!(s = skipwhite(s, 1)))
+ return -1;
+ if(*s++ != '=')
+ return -1;
+ if(!(s = skipwhite(s, 1)))
+ return -1;
+ if(!(s = mime_parse_word(s, ¶metervalue, mime_tspecial)))
+ return -1;
+ if(!(s = skipwhite(s, 1)))
+ return -1;
dynstr_terminate(¶metername);
- *parameternamep = parametername.vec;
- } else
- *parametervaluep = *parameternamep = 0;
+ kvp_set(¶meters, parametername.vec, parametervalue);
+ }
dynstr_terminate(&type);
*typep = type.vec;
+ *parametersp = parameters;
return 0;
}
@@ -275,14 +290,27 @@ const char *mime_parse(const char *s,
while(*s && !iscrlf(s)) {
dynstr_init(&name);
dynstr_init(&value);
- while(*s && !tspecial(*s) && !whitespace(*s))
+ while(*s && !mime_tspecial(*s) && !whitespace(*s))
dynstr_append(&name, tolower((unsigned char)*s++));
- if(!(s = skipwhite(s, 1))) return 0;
- if(*s != ':') return 0;
+ if(!(s = skipwhite(s, 1)))
+ return 0;
+ if(*s != ':')
+ return 0;
++s;
- while(*s && !(*s == '\n' && !(s[1] == ' ' || s[1] == '\t')))
- dynstr_append(&value, *s++);
- if(*s) ++s;
+ while(*s && !(*s == '\n' && !(s[1] == ' ' || s[1] == '\t'))) {
+ const int c = *s++;
+ /* Strip leading whitespace */
+ if(value.nvec || !(c == ' ' || c == '\t' || c == '\n' || c == '\r'))
+ dynstr_append(&value, c);
+ }
+ /* Strip trailing whitespace */
+ while(value.nvec > 0 && (value.vec[value.nvec - 1] == ' '
+ || value.vec[value.nvec - 1] == '\t'
+ || value.vec[value.nvec - 1] == '\n'
+ || value.vec[value.nvec - 1] == '\r'))
+ --value.nvec;
+ if(*s)
+ ++s;
dynstr_terminate(&name);
dynstr_terminate(&value);
if(!strcmp(name.vec, "content-transfer-encoding")) {
@@ -290,12 +318,20 @@ const char *mime_parse(const char *s,
for(p = cte; *p; p++)
*p = tolower((unsigned char)*p);
}
- if(callback(name.vec, value.vec, u)) return 0;
+ if(callback(name.vec, value.vec, u))
+ return 0;
}
- if(*s) s += 2;
+ if(*s)
+ s += 2;
if(cte) {
- if(!strcmp(cte, "base64")) return mime_base64(s, 0);
- if(!strcmp(cte, "quoted-printable")) return mime_qp(s);
+ if(!strcmp(cte, "base64"))
+ return mime_base64(s, 0);
+ if(!strcmp(cte, "quoted-printable"))
+ return mime_qp(s);
+ if(!strcmp(cte, "7bit") || !strcmp(cte, "8bit"))
+ return s;
+ disorder_error(0, "unknown content-transfer-encoding '%s'", cte);
+ return 0;
}
return s;
}
@@ -341,15 +377,19 @@ int mime_multipart(const char *s,
int ret;
/* We must start with a boundary string */
- if(!isboundary(s, boundary, bl))
+ if(!isboundary(s, boundary, bl)) {
+ disorder_error(0, "mime_multipart: first line is not the boundary string");
return -1;
+ }
/* Keep going until we hit a final boundary */
while(!isfinal(s, boundary, bl)) {
s = strstr(s, "\r\n") + 2;
start = s;
while(!isboundary(s, boundary, bl)) {
- if(!(e = strstr(s, "\r\n")))
+ if(!(e = strstr(s, "\r\n"))) {
+ disorder_error(0, "mime_multipart: line does not end CRLF");
return -1;
+ }
s = e + 2;
}
if((ret = callback(xstrndup(start,
@@ -364,7 +404,7 @@ int mime_multipart(const char *s,
* @param s Start of field
* @param dispositionp Where to store disposition
* @param parameternamep Where to store parameter name
- * @param parametervaluep Wher to store parameter value
+ * @param parametervaluep Where to store parameter value
* @return 0 on success, non-0 on error
*
* See RFC 2388 s3
@@ -377,24 +417,34 @@ int mime_rfc2388_content_disposition(const char *s,
struct dynstr disposition, parametername;
dynstr_init(&disposition);
- if(!(s = skipwhite(s, 1))) return -1;
- if(!*s) return -1;
- while(*s && !tspecial(*s) && !whitespace(*s))
+ if(!(s = skipwhite(s, 1)))
+ return -1;
+ if(!*s)
+ return -1;
+ while(*s && !mime_tspecial(*s) && !whitespace(*s))
dynstr_append(&disposition, tolower((unsigned char)*s++));
- if(!(s = skipwhite(s, 1))) return -1;
+ if(!(s = skipwhite(s, 1)))
+ return -1;
if(*s == ';') {
dynstr_init(¶metername);
++s;
- if(!(s = skipwhite(s, 1))) return -1;
- if(!*s) return -1;
- while(*s && !tspecial(*s) && !whitespace(*s))
+ if(!(s = skipwhite(s, 1)))
+ return -1;
+ if(!*s)
+ return -1;
+ while(*s && !mime_tspecial(*s) && !whitespace(*s))
dynstr_append(¶metername, tolower((unsigned char)*s++));
- if(!(s = skipwhite(s, 1))) return -1;
- if(*s++ != '=') return -1;
- if(!(s = skipwhite(s, 1))) return -1;
- if(!(s = parseword(s, parametervaluep, tspecial))) return -1;
- if(!(s = skipwhite(s, 1))) return -1;
+ if(!(s = skipwhite(s, 1)))
+ return -1;
+ if(*s++ != '=')
+ return -1;
+ if(!(s = skipwhite(s, 1)))
+ return -1;
+ if(!(s = mime_parse_word(s, parametervaluep, mime_tspecial)))
+ return -1;
+ if(!(s = skipwhite(s, 1)))
+ return -1;
dynstr_terminate(¶metername);
*parameternamep = parametername.vec;
} else
@@ -453,6 +503,50 @@ char *mime_qp(const char *s) {
return d.vec;
}
+/** @brief Match cookie separator characters
+ *
+ * This is a subset of the RFC2616 specials, and technically is in breach of
+ * the specification. However rejecting (in particular) slashes is
+ * unreasonably strict and has broken at least one (admittedly somewhat
+ * obscure) browser, so we're more forgiving.
+ */
+static int cookie_separator(int c) {
+ switch(c) {
+ case '(':
+ case ')':
+ case ',':
+ case ';':
+ case '=':
+ case ' ':
+ case '"':
+ case '\t':
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+/** @brief Match cookie value separator characters
+ *
+ * Same as cookie_separator() but allows for @c = in cookie values.
+ */
+static int cookie_value_separator(int c) {
+ switch(c) {
+ case '(':
+ case ')':
+ case ',':
+ case ';':
+ case ' ':
+ case '"':
+ case '\t':
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
/** @brief Parse a RFC2109 Cookie: header
* @param s Header field value
* @param cd Where to store result
@@ -473,11 +567,20 @@ int parse_cookie(const char *s,
s = skipwhite(s, 0);
continue;
}
- if(!(s = parsetoken(s, &n, http_separator))) return -1;
+ if(!(s = parsetoken(s, &n, cookie_separator))) {
+ disorder_error(0, "parse_cookie: cannot parse attribute name");
+ return -1;
+ }
s = skipwhite(s, 0);
- if(*s++ != '=') return -1;
+ if(*s++ != '=') {
+ disorder_error(0, "parse_cookie: did not find expected '='");
+ return -1;
+ }
s = skipwhite(s, 0);
- if(!(s = parseword(s, &v, http_separator))) return -1;
+ if(!(s = mime_parse_word(s, &v, cookie_value_separator))) {
+ disorder_error(0, "parse_cookie: cannot parse value for '%s'", n);
+ return -1;
+ }
if(n[0] == '$') {
/* Some bit of meta-information */
if(!strcmp(n, "$Version"))
@@ -486,14 +589,14 @@ int parse_cookie(const char *s,
if(cd->ncookies > 0 && cd->cookies[cd->ncookies-1].path == 0)
cd->cookies[cd->ncookies-1].path = v;
else {
- error(0, "redundant $Path in Cookie: header");
+ disorder_error(0, "redundant $Path in Cookie: header");
return -1;
}
} else if(!strcmp(n, "$Domain")) {
if(cd->ncookies > 0 && cd->cookies[cd->ncookies-1].domain == 0)
cd->cookies[cd->ncookies-1].domain = v;
else {
- error(0, "redundant $Domain in Cookie: header");
+ disorder_error(0, "redundant $Domain in Cookie: header");
return -1;
}
}
@@ -509,7 +612,7 @@ int parse_cookie(const char *s,
}
s = skipwhite(s, 0);
if(*s && (*s != ',' && *s != ';')) {
- error(0, "missing separator in Cookie: header");
+ disorder_error(0, "missing separator in Cookie: header");
return -1;
}
}
@@ -544,7 +647,7 @@ char *quote822(const char *s, int force) {
if(!force) {
/* See if we need to quote */
for(t = s; (c = (unsigned char)*t); ++t) {
- if(tspecial(c) || http_separator(c) || whitespace(c))
+ if(mime_tspecial(c) || mime_http_separator(c) || whitespace(c))
break;
}
if(*t)
@@ -611,7 +714,7 @@ char *mime_to_qp(const char *text) {
++linelength;
} else {
/* Anything else that needs encoding */
- snprintf(buffer, sizeof buffer, "=%02X", c);
+ byte_snprintf(buffer, sizeof buffer, "=%02X", c);
dynstr_append_string(d, buffer);
linelength += 3;
}
@@ -635,7 +738,7 @@ char *mime_to_qp(const char *text) {
* @param text Underlying UTF-8 text
* @param charsetp Where to store charset string
* @param encodingp Where to store encoding string
- * @return Encoded text (might be @ref text)
+ * @return Encoded text (might be @p text)
*/
const char *mime_encode_text(const char *text,
const char **charsetp,