--- libsoup/soup-headers.c 2007-01-16 23:35:13.965088000 +0100 +++ libsoup/soup-headers.c.new 2007-01-16 23:37:11.845091000 +0100 @@ -18,80 +18,80 @@ int len, GHashTable *dest) { - char *key = NULL, *val = NULL, *end = NULL; - int offset = 0, lws = 0; - - key = strstr (str, "\r\n"); - key += 2; - - /* join continuation headers, using a comma */ - while ((key = strstr (key, "\r\n"))) { - key += 2; - offset = key - str; - - if (!*key) - break; - - /* check if first character on the line is whitespace */ - if (*key == ' ' || *key == '\t') { - key -= 2; - - /* eat any trailing space from the previous line*/ - while (key [-1] == ' ' || key [-1] == '\t') key--; - - /* count how many characters are whitespace */ - lws = strspn (key, " \t\r\n"); - - /* if continuation line, replace whitespace with ", " */ - if (key [-1] != ':') { - lws -= 2; - key [0] = ','; - key [1] = ' '; - } - - g_memmove (key, &key [lws], len - offset - lws); - } - } - - key = str; - - /* set eos for header key and value and add to hashtable */ - while ((key = strstr (key, "\r\n"))) { - GSList *exist_hdrs; - - /* set end of last val, or end of http reason phrase */ - key [0] = '\0'; - key += 2; - - if (!*key) - break; - - val = strchr (key, ':'); /* find start of val */ - - if (!val || val > strchr (key, '\r')) + const char *end = str + len; + const char *name_start, *name_end, *value_start, *value_end; + char *name, *value, *eol, *sol; + GSList *hdrs; + + /* As per RFC 2616 section 19.3, we treat '\n' as the + * line terminator, and '\r', if it appears, merely as + * ignorable trailing whitespace. + */ + + /* Skip over the Request-Line / Status-Line */ + value_end = memchr (str, '\n', len); + if (!value_end) + return FALSE; + + while (value_end < end - 1) { + name_start = value_end + 1; + name_end = memchr (name_start, ':', end - name_start); + if (!name_end) return FALSE; - /* set end of key */ - val [0] = '\0'; - - val++; - val += strspn (val, " \t"); /* skip whitespace */ - - /* find the end of the value */ - end = strstr (val, "\r\n"); - if (!end) + /* Find the end of the value; ie, an end-of-line that + * isn't followed by a continuation line. + */ + value_end = memchr (name_start, '\n', end - name_start); + if (!value_end || value_end < name_end) return FALSE; + while (value_end != end - 1 && + (*(value_end + 1) == ' ' || *(value_end + 1) == '\t')) { + value_end = memchr (value_end + 1, '\n', end - value_end); + if (!value_end) + return FALSE; + } - exist_hdrs = g_hash_table_lookup (dest, key); - exist_hdrs = g_slist_append (exist_hdrs, - g_strndup (val, end - val)); + name = g_strndup (name_start, name_end - name_start); - if (!exist_hdrs->next) - g_hash_table_insert (dest, g_strdup (key), exist_hdrs); + value_start = name_end + 1; + while (value_start < value_end && + (*value_start == ' ' || *value_start == '\t' || + *value_start == '\r' || *value_start == '\n')) + value_start++; + value = g_strndup (value_start, value_end - value_start); + + /* Collapse continuation lines inside value */ + while ((eol = strchr (value, '\n'))) { + /* find start of next line */ + sol = eol + 1; + while (*sol == ' ' || *sol == '\t') + sol++; + + /* back up over trailing whitespace on current line */ + while (eol[-1] == ' ' || eol[-1] == '\t' || eol[-1] == '\r') + eol--; + + /* Delete all but one SP */ + *eol = ' '; + g_memmove (eol + 1, sol, strlen (sol) + 1); + } - key = end; + /* clip trailing whitespace */ + eol = strchr (value, '\0'); + while (eol > value && + (eol[-1] == ' ' || eol[-1] == '\t' || eol[-1] == '\r')) + eol--; + *eol = '\0'; + + hdrs = g_hash_table_lookup (dest, name); + hdrs = g_slist_append (hdrs, value); + if (!hdrs->next) + g_hash_table_insert (dest, name, hdrs); + else + g_free (name); } - + return TRUE; } @@ -103,44 +103,72 @@ char **req_path, SoupHttpVersion *ver) { - gulong http_major, http_minor; - char *s1, *s2, *cr, *p; + const char *method, *method_end, *path, *path_end, *version, *headers; + int minor_version; if (!str || !*str) return FALSE; - cr = memchr (str, '\r', len); - if (!cr) + /* RFC 2616 4.1 "servers SHOULD ignore any empty line(s) + * received where a Request-Line is expected." + */ + while (*str == '\r' || *str == '\n') { + str++; + len--; + } + + /* RFC 2616 19.3 "[servers] SHOULD accept any amount of SP or + * HT characters between [Request-Line] fields" + */ + + method = method_end = str; + while (method_end < str + len && *method_end != ' ' && *method_end != '\t') + method_end++; + if (method_end >= str + len) return FALSE; - s1 = memchr (str, ' ', cr - str); - if (!s1) + path = method_end; + while (path < str + len && (*path == ' ' || *path == '\t')) + path++; + if (path >= str + len) return FALSE; - s2 = memchr (s1 + 1, ' ', cr - (s1 + 1)); - if (!s2) + + path_end = path; + while (path_end < str + len && *path_end != ' ' && *path_end != '\t') + path_end++; + if (path_end >= str + len) return FALSE; - if (strncmp (s2, " HTTP/", 6) != 0) + version = path_end; + while (version < str + len && (*version == ' ' || *version == '\t')) + version++; + if (version + 8 >= str + len) return FALSE; - http_major = strtoul (s2 + 6, &p, 10); - if (*p != '.') + + /* FIXME: we want SoupServer to return + * SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED here + */ + if (strncmp (version, "HTTP/1.", 7) != 0) return FALSE; - http_minor = strtoul (p + 1, &p, 10); - if (p != cr) + minor_version = version[7] - '0'; + if (minor_version < 0 || minor_version > 1) return FALSE; - if (!soup_headers_parse (str, len, dest)) + headers = version + 8; + if (headers < str + len && *headers == '\r') + headers++; + if (headers >= str + len || *headers != '\n') return FALSE; - *req_method = g_strndup (str, s1 - str); - *req_path = g_strndup (s1 + 1, s2 - (s1 + 1)); + if (!soup_headers_parse (str, len, dest)) + return FALSE; - if (ver) { - if (http_major == 1 && http_minor == 1) - *ver = SOUP_HTTP_1_1; - else - *ver = SOUP_HTTP_1_0; - } + if (req_method) + *req_method = g_strndup (method, method_end - method); + if (req_path) + *req_path = g_strndup (path, path_end - path); + if (ver) + *ver = (minor_version == 0) ? SOUP_HTTP_1_0 : SOUP_HTTP_1_1; return TRUE; }