mirror of
https://github.com/servalproject/serval-dna.git
synced 2025-01-29 15:43:56 +00:00
Refactor HTTP response parsing
Remove need to nul-terminate the received buffers in HTTP fetch reply handling and HTTP server request parsing. Remove redundant copying of data. More rigorous parsing code, probably less vulnerable to overrun exploits. Better debug logging of requests and responses.
This commit is contained in:
parent
0c260a966e
commit
c791ba94d0
@ -163,6 +163,10 @@ int rhizome_str_is_file_hash(const char *text);
|
||||
|
||||
#define alloca_tohex_bid(bid) alloca_tohex((bid), RHIZOME_MANIFEST_ID_BYTES)
|
||||
|
||||
int http_header_complete(const char *buf, size_t len, size_t tail);
|
||||
int str_startswith(char *str, const char *substring, char **afterp);
|
||||
int strcase_startswith(char *str, const char *substring, char **afterp);
|
||||
|
||||
int rhizome_write_manifest_file(rhizome_manifest *m, const char *filename);
|
||||
int rhizome_manifest_selfsign(rhizome_manifest *m);
|
||||
int rhizome_drop_stored_file(const char *id,int maximum_priority);
|
||||
@ -248,4 +252,3 @@ int rhizome_ignore_manifest_check(rhizome_manifest *m,
|
||||
|
||||
int rhizome_suggest_queue_manifest_import(rhizome_manifest *m,
|
||||
struct sockaddr_in *peerip);
|
||||
|
||||
|
231
rhizome_fetch.c
231
rhizome_fetch.c
@ -817,127 +817,130 @@ void rhizome_fetch_poll(struct sched_ent *alarm)
|
||||
return;
|
||||
}
|
||||
|
||||
switch(q->state)
|
||||
{
|
||||
switch(q->state) {
|
||||
case RHIZOME_FETCH_CONNECTING:
|
||||
case RHIZOME_FETCH_SENDINGHTTPREQUEST:
|
||||
rhizome_fetch_write(q);
|
||||
break;
|
||||
case RHIZOME_FETCH_RXFILE:
|
||||
/* Keep reading until we have the promised amount of data */
|
||||
|
||||
sigPipeFlag=0;
|
||||
|
||||
errno=0;
|
||||
char buffer[8192];
|
||||
|
||||
int bytes=read(q->alarm.poll.fd,buffer,8192);
|
||||
|
||||
/* If we got some data, see if we have found the end of the HTTP request */
|
||||
if (bytes>0)
|
||||
rhizome_write_content(q, buffer, bytes);
|
||||
|
||||
break;
|
||||
case RHIZOME_FETCH_RXHTTPHEADERS:
|
||||
/* Keep reading until we have two CR/LFs in a row */
|
||||
sigPipeFlag=0;
|
||||
|
||||
errno=0;
|
||||
bytes=read(q->alarm.poll.fd,&q->request[q->request_len],
|
||||
1024-q->request_len-1);
|
||||
|
||||
if (sigPipeFlag||((bytes==0)&&(errno==0))) {
|
||||
/* broken pipe, so close connection */
|
||||
if (debug&DEBUG_RHIZOME)
|
||||
DEBUG("Closing rhizome fetch connection due to sigpipe");
|
||||
rhizome_fetch_close(q);
|
||||
return;
|
||||
}
|
||||
|
||||
/* If we got some data, see if we have found the end of the HTTP request */
|
||||
if (bytes>0) {
|
||||
int lfcount=0;
|
||||
int i=q->request_len-160;
|
||||
|
||||
// reset timeout
|
||||
unschedule(&q->alarm);
|
||||
q->alarm.alarm=overlay_gettime_ms() + RHIZOME_IDLE_TIMEOUT;
|
||||
schedule(&q->alarm);
|
||||
|
||||
if (i<0) i=0;
|
||||
q->request_len+=bytes;
|
||||
if (q->request_len<1024)
|
||||
q->request[q->request_len]=0;
|
||||
|
||||
for(;i<(q->request_len+bytes);i++)
|
||||
{
|
||||
switch(q->request[i]) {
|
||||
case '\n': lfcount++; break;
|
||||
case '\r': /* ignore CR */ break;
|
||||
case 0: /* ignore NUL (telnet inserts them) */ break;
|
||||
default: lfcount=0; break;
|
||||
}
|
||||
if (lfcount==2) break;
|
||||
}
|
||||
|
||||
if (debug&DEBUG_RHIZOME)
|
||||
dump("http reply headers",(unsigned char *)q->request,lfcount==2?i:q->request_len);
|
||||
|
||||
if (lfcount==2) {
|
||||
/* We have the response headers, so parse.
|
||||
(we may also have some bytes of content, so we need to be a little
|
||||
careful) */
|
||||
|
||||
/* Terminate string at end of headers */
|
||||
q->request[i]=0;
|
||||
|
||||
/* Get HTTP result code */
|
||||
char *s=strstr(q->request,"HTTP/1.0 ");
|
||||
if (!s) {
|
||||
if (debug&DEBUG_RHIZOME) DEBUGF("HTTP response lacked HTTP/1.0 response code.");
|
||||
rhizome_fetch_close(q);
|
||||
return;
|
||||
}
|
||||
int http_response_code=strtoll(&s[9],NULL,10);
|
||||
if (http_response_code!=200) {
|
||||
if (debug&DEBUG_RHIZOME) DEBUGF("Rhizome web server returned %d != 200 OK",http_response_code);
|
||||
rhizome_fetch_close(q);
|
||||
return;
|
||||
}
|
||||
/* Get content length */
|
||||
s=strstr(q->request,"Content-length: ");
|
||||
if (!s) {
|
||||
if (debug&DEBUG_RHIZOME)
|
||||
DEBUGF("Missing Content-Length: header.");
|
||||
rhizome_fetch_close(q);
|
||||
return;
|
||||
}
|
||||
q->file_len=strtoll(&s[16],NULL,10);
|
||||
|
||||
if (q->file_len<0) {
|
||||
if (debug&DEBUG_RHIZOME)
|
||||
DEBUGF("Illegal file size (%d).",q->file_len);
|
||||
rhizome_fetch_close(q);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Okay, we have both, and are all set.
|
||||
File is already open, so just write out any initial bytes of the
|
||||
file we read, and update state flag.
|
||||
*/
|
||||
|
||||
q->state=RHIZOME_FETCH_RXFILE;
|
||||
int fileRxBytes=q->request_len-(i+1);
|
||||
|
||||
if (fileRxBytes>0)
|
||||
rhizome_write_content(q, &q->request[i+1], fileRxBytes);
|
||||
|
||||
case RHIZOME_FETCH_RXFILE: {
|
||||
/* Keep reading until we have the promised amount of data */
|
||||
char buffer[8192];
|
||||
sigPipeFlag = 0;
|
||||
int bytes = read_nonblock(q->alarm.poll.fd, buffer, sizeof buffer);
|
||||
/* If we got some data, see if we have found the end of the HTTP request */
|
||||
if (bytes > 0) {
|
||||
rhizome_write_content(q, buffer, bytes);
|
||||
} else {
|
||||
if (debug & DEBUG_RHIZOME_RX)
|
||||
DEBUG("Empty read, closing connection");
|
||||
rhizome_fetch_close(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (sigPipeFlag) {
|
||||
if (debug & DEBUG_RHIZOME_RX)
|
||||
DEBUG("Received SIGPIPE, closing connection");
|
||||
rhizome_fetch_close(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RHIZOME_FETCH_RXHTTPHEADERS: {
|
||||
/* Keep reading until we have two CR/LFs in a row */
|
||||
sigPipeFlag = 0;
|
||||
int bytes = read_nonblock(q->alarm.poll.fd, &q->request[q->request_len], 1024 - q->request_len - 1);
|
||||
/* If we got some data, see if we have found the end of the HTTP reply */
|
||||
if (bytes > 0) {
|
||||
// reset timeout
|
||||
unschedule(&q->alarm);
|
||||
q->alarm.alarm = overlay_gettime_ms() + RHIZOME_IDLE_TIMEOUT;
|
||||
schedule(&q->alarm);
|
||||
q->request_len += bytes;
|
||||
if (http_header_complete(q->request, q->request_len, bytes + 4)) {
|
||||
if (debug & DEBUG_RHIZOME_RX)
|
||||
DEBUGF("Got HTTP reply: %s", alloca_toprint(160, (unsigned char *)q->request, q->request_len));
|
||||
/* We have all the reply headers, so parse them, taking care of any following bytes of
|
||||
content. */
|
||||
char *p = NULL;
|
||||
if (!str_startswith(q->request, "HTTP/1.0 ", &p)) {
|
||||
if (debug&DEBUG_RHIZOME_RX)
|
||||
DEBUGF("Malformed HTTP reply: missing HTTP/1.0 preamble");
|
||||
rhizome_fetch_close(q);
|
||||
return;
|
||||
}
|
||||
int http_response_code = 0;
|
||||
char *nump;
|
||||
for (nump = p; isdigit(*p); ++p)
|
||||
http_response_code = http_response_code * 10 + *p - '0';
|
||||
if (p == nump || *p != ' ') {
|
||||
if (debug&DEBUG_RHIZOME_RX)
|
||||
DEBUGF("Malformed HTTP reply: missing decimal status code");
|
||||
rhizome_fetch_close(q);
|
||||
return;
|
||||
}
|
||||
if (http_response_code != 200) {
|
||||
if (debug & DEBUG_RHIZOME_RX)
|
||||
DEBUGF("Failed HTTP request: rhizome server returned %d != 200 OK", http_response_code);
|
||||
rhizome_fetch_close(q);
|
||||
return;
|
||||
}
|
||||
// This loop will terminate, because http_header_complete() above found at least
|
||||
// "\n\n" at the end of the header, and probably "\r\n\r\n".
|
||||
while (*p++ != '\n')
|
||||
;
|
||||
// Iterate over header lines until the last blank line.
|
||||
long long content_length = -1;
|
||||
while (*p != '\r' && *p != '\n') {
|
||||
if (strcase_startswith(p, "Content-Length:", &p)) {
|
||||
while (*p == ' ')
|
||||
++p;
|
||||
content_length = 0;
|
||||
for (nump = p; isdigit(*p); ++p)
|
||||
content_length = content_length * 10 + *p - '0';
|
||||
if (p == nump || (*p != '\r' && *p != '\n')) {
|
||||
if (debug & DEBUG_RHIZOME_RX) {
|
||||
DEBUGF("Invalid HTTP reply: malformed Content-Length header");
|
||||
rhizome_fetch_close(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (*p++ != '\n')
|
||||
;
|
||||
}
|
||||
if (*p == '\r')
|
||||
++p;
|
||||
++p; // skip '\n' at end of blank line
|
||||
if (content_length == -1) {
|
||||
if (debug & DEBUG_RHIZOME_RX)
|
||||
DEBUGF("Invalid HTTP reply: missing Content-Length header");
|
||||
rhizome_fetch_close(q);
|
||||
return;
|
||||
}
|
||||
q->file_len = content_length;
|
||||
/* We have all we need. The file is already open, so just write out any initial bytes of
|
||||
the body we read.
|
||||
*/
|
||||
q->state = RHIZOME_FETCH_RXFILE;
|
||||
int content_bytes = q->request + q->request_len - p;
|
||||
if (content_bytes > 0)
|
||||
rhizome_write_content(q, p, content_bytes);
|
||||
}
|
||||
} else {
|
||||
if (debug & DEBUG_RHIZOME_RX)
|
||||
DEBUG("Empty read, closing connection");
|
||||
rhizome_fetch_close(q);
|
||||
return;
|
||||
}
|
||||
if (sigPipeFlag) {
|
||||
if (debug & DEBUG_RHIZOME_RX)
|
||||
DEBUG("Received SIGPIPE, closing connection");
|
||||
rhizome_fetch_close(q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (debug&DEBUG_RHIZOME)
|
||||
if (debug & DEBUG_RHIZOME_RX)
|
||||
DEBUG("Closing rhizome fetch connection due to illegal/unimplemented state.");
|
||||
rhizome_fetch_close(q);
|
||||
return;
|
||||
|
117
rhizome_http.c
117
rhizome_http.c
@ -225,30 +225,15 @@ void rhizome_client_poll(struct sched_ent *alarm)
|
||||
/* Keep reading until we have two CR/LFs in a row */
|
||||
r->request[r->request_length] = '\0';
|
||||
sigPipeFlag=0;
|
||||
int bytes = read_nonblock(r->alarm.poll.fd, &r->request[r->request_length], RHIZOME_HTTP_REQUEST_MAXLEN - r->request_length - 1);
|
||||
int bytes = read_nonblock(r->alarm.poll.fd, &r->request[r->request_length], RHIZOME_HTTP_REQUEST_MAXLEN - r->request_length);
|
||||
/* If we got some data, see if we have found the end of the HTTP request */
|
||||
if (bytes > 0) {
|
||||
// reset inactivity timer
|
||||
r->alarm.alarm = overlay_gettime_ms() + RHIZOME_IDLE_TIMEOUT;
|
||||
unschedule(&r->alarm);
|
||||
schedule(&r->alarm);
|
||||
int i = r->request_length - 160;
|
||||
if (i<0) i=0;
|
||||
r->request_length+=bytes;
|
||||
if (r->request_length<RHIZOME_HTTP_REQUEST_MAXLEN)
|
||||
r->request[r->request_length]=0;
|
||||
if (0)
|
||||
dump("request", (unsigned char *)r->request,r->request_length);
|
||||
int lfcount;
|
||||
for(lfcount = 0; lfcount < 2 && i < r->request_length + bytes; ++i) {
|
||||
switch (r->request[i]) {
|
||||
case '\n': ++lfcount; break;
|
||||
case '\r': break;
|
||||
case '\0': break; // ignore NUL (telnet inserts them)
|
||||
default: lfcount = 0; break;
|
||||
}
|
||||
}
|
||||
if (lfcount == 2) {
|
||||
r->request_length += bytes;
|
||||
if (http_header_complete(r->request, r->request_length, bytes + 4)) {
|
||||
/* We have the request. Now parse it to see if we can respond to it */
|
||||
rhizome_server_parse_http_request(r);
|
||||
}
|
||||
@ -273,7 +258,6 @@ void rhizome_client_poll(struct sched_ent *alarm)
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void rhizome_server_poll(struct sched_ent *alarm)
|
||||
{
|
||||
struct sockaddr addr;
|
||||
@ -498,15 +482,56 @@ static int rhizome_server_sql_query_fill_buffer(rhizome_http_request *r, char *t
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int strcmp_prefix(char *str, const char *prefix, char **afterp)
|
||||
int http_header_complete(const char *buf, size_t len, size_t tail)
|
||||
{
|
||||
while (*prefix && *str && *prefix == *str)
|
||||
++prefix, ++str;
|
||||
if (*prefix)
|
||||
return (unsigned char)*str - (unsigned char)*prefix;
|
||||
const char *bufend = buf + len;
|
||||
if (tail < len)
|
||||
buf = bufend - tail;
|
||||
int count = 0;
|
||||
for (; count < 2 && buf != bufend; ++buf) {
|
||||
switch (*buf) {
|
||||
case '\n': ++count; break;
|
||||
case '\r': break;
|
||||
case '\0': break; // ignore NUL (telnet inserts them)
|
||||
default: count = 0; break;
|
||||
}
|
||||
}
|
||||
return count == 2;
|
||||
}
|
||||
|
||||
/* Check if a given string starts with a given sub-string. If so, return 1 and, if afterp is not
|
||||
NULL, set *afterp to point to the character immediately following the substring. Otherwise
|
||||
return 0.
|
||||
This function is used to parse HTTP headers and responses, which are typically not
|
||||
nul-terminated, but are held in a buffer which has an associated length. To avoid this function
|
||||
running past the end of the buffer, the caller must ensure that the buffer contains a sub-string
|
||||
that is not part of the sub-string being sought, eg, "\r\n\r\n" as detected by
|
||||
http_header_complete(). This guarantees that this function will return nonzero before running
|
||||
past the end of the buffer.
|
||||
@author Andrew Bettison <andrew@servalproject.com>
|
||||
*/
|
||||
int str_startswith(char *str, const char *substring, char **afterp)
|
||||
{
|
||||
while (*substring && *substring == *str)
|
||||
++substring, ++str;
|
||||
if (*substring)
|
||||
return 0;
|
||||
if (afterp)
|
||||
*afterp = str;
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Case-insensitive form of str_startswith().
|
||||
*/
|
||||
int strcase_startswith(char *str, const char *substring, char **afterp)
|
||||
{
|
||||
while (*substring && *str && toupper(*substring) == toupper(*str))
|
||||
++substring, ++str;
|
||||
if (*substring)
|
||||
return 0;
|
||||
if (afterp)
|
||||
*afterp = str;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int rhizome_server_parse_http_request(rhizome_http_request *r)
|
||||
@ -514,18 +539,30 @@ static int rhizome_server_parse_http_request(rhizome_http_request *r)
|
||||
/* Switching to writing, so update the call-back */
|
||||
r->alarm.poll.events=POLLOUT;
|
||||
watch(&r->alarm);
|
||||
|
||||
/* Clear request type flags */
|
||||
r->request_type=0;
|
||||
char path[1024];
|
||||
if (sscanf(r->request, "GET %1024s HTTP/1.%*1[01]%*[\r\n]", path) != 1) {
|
||||
if (debug & DEBUG_RHIZOME_TX)
|
||||
DEBUGF("Received malformed HTTP request: %s", alloca_toprint(120, (unsigned char *)r->request, r->request_length));
|
||||
rhizome_server_simple_http_response(r, 400, "<html><h1>Malformed request</h1></html>\r\n");
|
||||
} else {
|
||||
// Start building up a response.
|
||||
r->request_type = 0;
|
||||
// Parse the HTTP "GET" line.
|
||||
char *path = NULL;
|
||||
size_t pathlen = 0;
|
||||
if (str_startswith(r->request, "GET ", &path)) {
|
||||
char *p;
|
||||
// This loop is guaranteed to terminate before the end of the buffer, because we know that the
|
||||
// buffer contains at least "\n\n" and maybe "\r\n\r\n" at the end of the header block.
|
||||
for (p = path; !isspace(*p); ++p)
|
||||
;
|
||||
pathlen = p - path;
|
||||
if ( str_startswith(p, " HTTP/1.", &p)
|
||||
&& (str_startswith(p, "0", &p) || str_startswith(p, "1", &p))
|
||||
&& (str_startswith(p, "\r\n", &p) || str_startswith(p, "\n", &p))
|
||||
)
|
||||
path[pathlen] = '\0';
|
||||
else
|
||||
path = NULL;
|
||||
}
|
||||
if (path) {
|
||||
char *id = NULL;
|
||||
if (debug & DEBUG_RHIZOME_TX)
|
||||
DEBUGF("GET %s", path);
|
||||
DEBUGF("GET %s", alloca_toprint(1024, (unsigned char *)path, pathlen));
|
||||
if (strcmp(path, "/favicon.ico") == 0) {
|
||||
r->request_type = RHIZOME_HTTP_REQUEST_FAVICON;
|
||||
rhizome_server_http_response_header(r, 200, "image/vnd.microsoft.icon", favicon_len);
|
||||
@ -538,7 +575,7 @@ static int rhizome_server_parse_http_request(rhizome_http_request *r)
|
||||
} else if (strcmp(path, "/rhizome/bars") == 0) {
|
||||
/* Return the list of known BARs */
|
||||
rhizome_server_sql_query_http_response(r, "bar", "manifests", "from manifests", 32, 0);
|
||||
} else if (strcmp_prefix(path, "/rhizome/file/", &id) == 0) {
|
||||
} else if (str_startswith(path, "/rhizome/file/", &id)) {
|
||||
/* Stream the specified payload */
|
||||
if (!rhizome_str_is_file_hash(id)) {
|
||||
rhizome_server_simple_http_response(r, 400, "<html><h1>Invalid payload ID</h1></html>\r\n");
|
||||
@ -558,12 +595,16 @@ static int rhizome_server_parse_http_request(rhizome_http_request *r)
|
||||
r->request_type |= RHIZOME_HTTP_REQUEST_BLOB;
|
||||
}
|
||||
}
|
||||
} else if (strcmp_prefix(path, "/rhizome/manifest/", &id) == 0) {
|
||||
/* TODO: Stream the specified manifest */
|
||||
} else if (str_startswith(path, "/rhizome/manifest/", &id)) {
|
||||
// TODO: Stream the specified manifest
|
||||
rhizome_server_simple_http_response(r, 500, "<html><h1>Not implemented</h1></html>\r\n");
|
||||
} else {
|
||||
rhizome_server_simple_http_response(r, 404, "<html><h1>Not found</h1></html>\r\n");
|
||||
}
|
||||
} else {
|
||||
if (debug & DEBUG_RHIZOME_TX)
|
||||
DEBUGF("Received malformed HTTP request: %s", alloca_toprint(120, (unsigned char *)r->request, r->request_length));
|
||||
rhizome_server_simple_http_response(r, 400, "<html><h1>Malformed request</h1></html>\r\n");
|
||||
}
|
||||
|
||||
/* Try sending data immediately. */
|
||||
|
Loading…
x
Reference in New Issue
Block a user