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.
This commit is contained in:
Andrew Bettison 2015-10-01 06:55:00 +09:30
parent 367d54f5f8
commit 404cc1476d
4 changed files with 93 additions and 34 deletions

View File

@ -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) 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) if (_run_out(r) || isspace(*r->cursor) || !isprint(*r->cursor) || *r->cursor == until)
return 0; return 0;
const char *start = r->cursor;
for (++r->cursor; !_run_out(r) && !isspace(*r->cursor) && isprint(*r->cursor) && *r->cursor != until; ++r->cursor) for (++r->cursor; !_run_out(r) && !isspace(*r->cursor) && isprint(*r->cursor) && *r->cursor != until; ++r->cursor)
; ;
if (_run_out(r)) if (_run_out(r))
return 0; return 0;
assert(r->cursor > start); assert(r->cursor > start);
assert(isspace(*r->cursor) || *r->cursor == until); assert(isspace(*r->cursor) || *r->cursor == until);
if (str) { if (str)
str->start = start;
str->end = r->cursor; str->end = r->cursor;
}
return r->cursor - start; 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); 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) static unsigned _parse_ranges(struct http_request *r, struct http_range *range, unsigned nrange)
{ {
unsigned i = 0; unsigned i = 0;
@ -748,6 +753,43 @@ static int _parse_authorization(struct http_request *r, struct http_client_autho
return 0; 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) static int _parse_quoted_rfc822_time(struct http_request *r, time_t *timep)
{ {
char datestr[40]; char datestr[40];
@ -1069,27 +1111,28 @@ static int http_request_parse_header(struct http_request *r)
return 0; return 0;
} }
if (r->response.result_code) if (r->response.result_code)
return r->response.result_code; return r->response.result_code;
goto malformed; goto malformed;
} }
_rewind(r); _rewind(r);
if (_skip_literal_nocase(r, "Origin:")) { 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)); IDEBUGF(r->debug, "Skipping duplicate HTTP header Origin: %s", alloca_toprint(50, sol, r->end - sol));
r->cursor = nextline; r->cursor = nextline;
_commit(r); _commit(r);
return 0; return 0;
} }
_skip_optional_space(r); _skip_optional_space(r);
struct substring origin; if ( _parse_origin(r, &r->request_header.origin, eol - r->cursor)
if (_skip_word_printable(r, &origin, ' ')
&& _skip_optional_space(r) && _skip_optional_space(r)
&& r->cursor == eol) { && r->cursor == eol
) {
r->cursor = nextline; r->cursor = nextline;
_commit(r); _commit(r);
_reserve_substring(r, &r->request_header.origin, origin);
return 0; return 0;
} }
if (r->response.result_code)
return r->response.result_code;
goto malformed; goto malformed;
} }
_rewind(r); _rewind(r);

View File

@ -87,11 +87,17 @@ struct http_www_authenticate {
const char *realm; const char *realm;
}; };
struct http_origin {
const char *scheme;
const char *hostname;
uint16_t port;
};
struct http_request_headers { struct http_request_headers {
http_size_t content_length; http_size_t content_length;
struct mime_content_type content_type; struct mime_content_type content_type;
unsigned short content_range_count; 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_range content_ranges[5];
struct http_client_authorization authorization; struct http_client_authorization authorization;
}; };
@ -102,7 +108,7 @@ struct http_response_headers {
http_size_t resource_length; // size of entire resource http_size_t resource_length; // size of entire resource
const char *content_type; // "type/subtype" const char *content_type; // "type/subtype"
const char *boundary; 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_methods;
const char *allow_headers; const char *allow_headers;
struct http_www_authenticate www_authenticate; struct http_www_authenticate www_authenticate;

45
httpd.c
View File

@ -357,20 +357,39 @@ int authorize_restful(struct http_request *r)
{ {
if (!is_from_loopback(r)) if (!is_from_loopback(r))
return 403; return 403;
if (r->request_header.origin){ if (r->request_header.origin.hostname) {
const char *remainder; assert(r->request_header.origin.scheme);
if (strcasecmp(r->request_header.origin, "null")==0 if ( ( ( strcmp(r->request_header.origin.scheme, "http") == 0
|| (strcase_startswith(r->request_header.origin, "http://localhost", &remainder) || strcmp(r->request_header.origin.scheme, "https") == 0
&& (*remainder==':' || *remainder=='\0')) )
|| (strcase_startswith(r->request_header.origin, "http://127.0.0.1", &remainder) && ( strcmp(r->request_header.origin.hostname, "localhost") == 0
&& (*remainder==':' || *remainder=='\0')) || strcmp(r->request_header.origin.hostname, "127.0.0.1") == 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); || ( strcmp(r->request_header.origin.scheme, "file") == 0
r->response.header.allow_methods="GET, POST, OPTIONS"; && ( strcmp(r->request_header.origin.hostname, "localhost") == 0
r->response.header.allow_headers="Authorization"; || strcmp(r->request_header.origin.hostname, "127.0.0.1") == 0
}else || 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; return 403;
}
} }
if (r->verb == HTTP_VERB_OPTIONS){ if (r->verb == HTTP_VERB_OPTIONS){
http_request_simple_response(r, 200, NULL); http_request_simple_response(r, 200, NULL);

View File

@ -136,15 +136,6 @@ test_CORS_Request(){
"http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json" "http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json"
assertStdoutIs '200' assertStdoutIs '200'
assertGrep http.headers "^Access-Control-Allow-Origin: http://localhost:1234$CR\$" 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 \ executeOk curl \
--silent --show-error --write-out '%{http_code}' \ --silent --show-error --write-out '%{http_code}' \
--output http.output \ --output http.output \