From 404cc1476db32349bf6580cc7cc2f426a2be17d4 Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Thu, 1 Oct 2015 06:55:00 +0930 Subject: [PATCH] Improve HTTP Origin header parsing Parses the separate parts of the URI: scheme, hostname, port. No longer supports "Origin: null", because that is not in the spec. --- http_server.c | 63 +++++++++++++++++++++++++++++++++++++------- http_server.h | 10 +++++-- httpd.c | 45 ++++++++++++++++++++++--------- tests/rhizomerestful | 9 ------- 4 files changed, 93 insertions(+), 34 deletions(-) diff --git a/http_server.c b/http_server.c index 8a97646c..c97580ec 100644 --- a/http_server.c +++ b/http_server.c @@ -503,19 +503,19 @@ static inline int _skip_space(struct http_request *r) static size_t _skip_word_printable(struct http_request *r, struct substring *str, char until) { + const char *start = r->cursor; + if (str) + str->start = str->end = start; if (_run_out(r) || isspace(*r->cursor) || !isprint(*r->cursor) || *r->cursor == until) return 0; - const char *start = r->cursor; for (++r->cursor; !_run_out(r) && !isspace(*r->cursor) && isprint(*r->cursor) && *r->cursor != until; ++r->cursor) ; if (_run_out(r)) return 0; assert(r->cursor > start); assert(isspace(*r->cursor) || *r->cursor == until); - if (str) { - str->start = start; + if (str) str->end = r->cursor; - } return r->cursor - start; } @@ -600,6 +600,11 @@ static inline int _parse_uint32(struct http_request *r, uint32_t *uint32p) return !_run_out(r) && isdigit(*r->cursor) && str_to_uint32(r->cursor, 10, uint32p, (const char **)&r->cursor); } +static inline int _parse_uint16(struct http_request *r, uint16_t *uint16p) +{ + return !_run_out(r) && isdigit(*r->cursor) && str_to_uint16(r->cursor, 10, uint16p, (const char **)&r->cursor); +} + static unsigned _parse_ranges(struct http_request *r, struct http_range *range, unsigned nrange) { unsigned i = 0; @@ -748,6 +753,43 @@ static int _parse_authorization(struct http_request *r, struct http_client_autho return 0; } +static int _parse_origin(struct http_request *r, struct http_origin *origin, size_t header_bytes) +{ + char *start = r->cursor; + char *end = start + header_bytes; + if (_skip_literal(r, "http://")) { + origin->scheme = "http"; + } else if (_skip_literal(r, "https://")) { + origin->scheme = "https"; + } else if (_skip_literal(r, "file://")) { + origin->scheme = "file"; + } else { + IDEBUGF(r->debug, "Ignoring HTTP Origin with unsupported URI scheme: %s", alloca_toprint(50, start, header_bytes)); + r->cursor = end; + return 1; + } + origin->hostname = ""; + origin->port = 0; + struct substring hostname; + if (_skip_word_printable(r, &hostname, '/') > 0) { + const char *port = hostname.end - 1; + while (port > hostname.start && isdigit(*port)) + --port; + if (port >= hostname.start && *port == ':' && port < hostname.end - 1) { + const char *e = NULL; + if (port && port + 1 < r->cursor && str_to_uint16(port + 1, 10, &origin->port, &e)) { + assert(e == r->cursor); + hostname.end = port; + } + } + assert(hostname.end > hostname.start); + if (!_reserve_substring(r, &origin->hostname, hostname)) + return 0; // error + } + _skip_literal(r, "/"); + return 1; +} + static int _parse_quoted_rfc822_time(struct http_request *r, time_t *timep) { char datestr[40]; @@ -1069,27 +1111,28 @@ static int http_request_parse_header(struct http_request *r) return 0; } if (r->response.result_code) - return r->response.result_code; + return r->response.result_code; goto malformed; } _rewind(r); if (_skip_literal_nocase(r, "Origin:")) { - if (r->request_header.origin) { + if (r->request_header.origin.scheme) { IDEBUGF(r->debug, "Skipping duplicate HTTP header Origin: %s", alloca_toprint(50, sol, r->end - sol)); r->cursor = nextline; _commit(r); return 0; } _skip_optional_space(r); - struct substring origin; - if (_skip_word_printable(r, &origin, ' ') + if ( _parse_origin(r, &r->request_header.origin, eol - r->cursor) && _skip_optional_space(r) - && r->cursor == eol) { + && r->cursor == eol + ) { r->cursor = nextline; _commit(r); - _reserve_substring(r, &r->request_header.origin, origin); return 0; } + if (r->response.result_code) + return r->response.result_code; goto malformed; } _rewind(r); diff --git a/http_server.h b/http_server.h index 618e1036..101fbaec 100644 --- a/http_server.h +++ b/http_server.h @@ -87,11 +87,17 @@ struct http_www_authenticate { const char *realm; }; +struct http_origin { + const char *scheme; + const char *hostname; + uint16_t port; +}; + struct http_request_headers { http_size_t content_length; struct mime_content_type content_type; unsigned short content_range_count; - const char *origin; // points into buffer; nul terminated + struct http_origin origin; struct http_range content_ranges[5]; struct http_client_authorization authorization; }; @@ -102,7 +108,7 @@ struct http_response_headers { http_size_t resource_length; // size of entire resource const char *content_type; // "type/subtype" const char *boundary; - char allow_origin[23]; // max supported str (for now) "http://localhost:65537" + char allow_origin[24]; // max supported str (for now) "https://localhost:65537" const char *allow_methods; const char *allow_headers; struct http_www_authenticate www_authenticate; diff --git a/httpd.c b/httpd.c index 96f65ef5..84523c17 100644 --- a/httpd.c +++ b/httpd.c @@ -357,20 +357,39 @@ int authorize_restful(struct http_request *r) { if (!is_from_loopback(r)) return 403; - if (r->request_header.origin){ - const char *remainder; - if (strcasecmp(r->request_header.origin, "null")==0 - || (strcase_startswith(r->request_header.origin, "http://localhost", &remainder) - && (*remainder==':' || *remainder=='\0')) - || (strcase_startswith(r->request_header.origin, "http://127.0.0.1", &remainder) - && (*remainder==':' || *remainder=='\0')) - || (strcase_startswith(r->request_header.origin, "file://", &remainder)) - ){ - strncpy(r->response.header.allow_origin,r->request_header.origin, sizeof r->response.header.allow_origin); - r->response.header.allow_methods="GET, POST, OPTIONS"; - r->response.header.allow_headers="Authorization"; - }else + if (r->request_header.origin.hostname) { + assert(r->request_header.origin.scheme); + if ( ( ( strcmp(r->request_header.origin.scheme, "http") == 0 + || strcmp(r->request_header.origin.scheme, "https") == 0 + ) + && ( strcmp(r->request_header.origin.hostname, "localhost") == 0 + || strcmp(r->request_header.origin.hostname, "127.0.0.1") == 0 + ) + ) + || ( strcmp(r->request_header.origin.scheme, "file") == 0 + && ( strcmp(r->request_header.origin.hostname, "localhost") == 0 + || strcmp(r->request_header.origin.hostname, "127.0.0.1") == 0 + || strcmp(r->request_header.origin.hostname, "") == 0 + ) + ) + ) { + strbuf sb = strbuf_local(r->response.header.allow_origin, sizeof r->response.header.allow_origin); + strbuf_puts(sb, r->request_header.origin.scheme); + strbuf_puts(sb, "://"); + strbuf_puts(sb, r->request_header.origin.hostname); + if (r->request_header.origin.port) { + strbuf_sprintf(sb, ":%u", r->request_header.origin.port); + } + if (!strbuf_overrun(sb)) { + r->response.header.allow_methods = "GET, POST, OPTIONS"; + r->response.header.allow_headers = "Authorization"; + } else { + r->response.header.allow_origin[0] = '\0'; + return 403; + } + } else { return 403; + } } if (r->verb == HTTP_VERB_OPTIONS){ http_request_simple_response(r, 200, NULL); diff --git a/tests/rhizomerestful b/tests/rhizomerestful index f175e866..066acad7 100755 --- a/tests/rhizomerestful +++ b/tests/rhizomerestful @@ -136,15 +136,6 @@ test_CORS_Request(){ "http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json" assertStdoutIs '200' assertGrep http.headers "^Access-Control-Allow-Origin: http://localhost:1234$CR\$" - executeOk curl \ - --silent --fail --show-error --write-out '%{http_code}' \ - --output http.output \ - --dump-header http.headers \ - --header "Origin: null" \ - --request "OPTIONS" \ - "http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json" - assertStdoutIs '200' - assertGrep http.headers "^Access-Control-Allow-Origin: null$CR\$" executeOk curl \ --silent --show-error --write-out '%{http_code}' \ --output http.output \