in source/src/modules/internet/Rhttpd.c [763:1080]
static void worker_input_handler(void *data) {
httpd_conn_t *c = (httpd_conn_t*) data;
DBG(printf("worker_input_handler, data=%p\n", data));
if (!c) return;
if (in_process) return; /* we don't allow recursive entrance */
DBG(printf("input handler for worker %p (sock=%d, part=%d, method=%d, line_pos=%d)\n", (void*) c, (int)c->sock, (int)c->part, (int)c->method, (int)c->line_pos));
/* FIXME: there is one edge case that is not caught on unix: if
* recv reads two or more full requests into the line buffer then
* this function exits after the first one, but input handlers may
* not trigger, because there may be no further data. It is not
* trivial to fix, because just checking for a full line at the
* beginning and not calling recv won't trigger a new input
* handler. However, under normal circumstance this should not
* happen, because clients should wait for the response and even
* if they don't it's unlikely that both requests get combined
* into one packet. */
if (c->part < PART_BODY) {
char *s = c->line_buf;
ssize_t n = recv(c->sock, c->line_buf + c->line_pos,
LINE_BUF_SIZE - c->line_pos - 1, 0);
DBG(printf("[recv n=%d, line_pos=%d, part=%d]\n", n, c->line_pos, (int)c->part));
if (n < 0) { /* error, scrape this worker */
remove_worker(c);
return;
}
if (n == 0) { /* connection closed -> try to process and then remove */
process_request(c);
remove_worker(c);
return;
}
c->line_pos += n;
c->line_buf[c->line_pos] = 0;
DBG(printf("in buffer: {%s}\n", c->line_buf));
while (*s) {
/* ok, we have genuine data in the line buffer */
if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n')) { /* single, empty line - end of headers */
/* --- check request validity --- */
DBG(printf(" end of request, moving to body\n"));
if (!(c->attr & HTTP_1_0) && !(c->attr & HOST_HEADER)) { /* HTTP/1.1 mandates Host: header */
send_http_response(c, " 400 Bad Request (Host: missing)\r\nConnection: close\r\n\r\n");
remove_worker(c);
return;
}
if (c->attr & CONTENT_LENGTH && c->content_length) {
if (c->content_length < 0 || /* we are parsing signed so negative numbers are bad */
c->content_length > 2147483640 || /* R will currently have issues with body around 2Gb or more, so better to not go there */
!(c->body = (char*) malloc(c->content_length + 1 /* allocate an extra termination byte */ ))) {
send_http_response(c, " 413 Request Entity Too Large (request body too big)\r\nConnection: close\r\n\r\n");
remove_worker(c);
return;
}
}
c->body_pos = 0;
c->part = PART_BODY;
if (s[0] == '\r') s++;
s++;
/* move the body part to the beginning of the buffer */
c->line_pos -= s - c->line_buf;
memmove(c->line_buf, s, c->line_pos);
/* GET/HEAD or no content length mean no body */
if (c->method == METHOD_GET || c->method == METHOD_HEAD ||
!(c->attr & CONTENT_LENGTH) || c->content_length == 0) {
if ((c->attr & CONTENT_LENGTH) && c->content_length > 0) {
send_http_response(c, " 400 Bad Request (GET/HEAD with body)\r\n\r\n");
remove_worker(c);
return;
}
process_request(c);
if (c->attr & CONNECTION_CLOSE) {
remove_worker(c);
return;
}
/* keep-alive - reset the worker so it can process a new request */
if (c->url) { free(c->url); c->url = NULL; }
if (c->body) { free(c->body); c->body = NULL; }
if (c->content_type) { free(c->content_type); c->content_type = NULL; }
if (c->headers) { free_buffer(c->headers); c->headers = NULL; }
c->body_pos = 0;
c->method = 0;
c->part = PART_REQUEST;
c->attr = 0;
c->content_length = 0;
return;
}
/* copy body content (as far as available) */
c->body_pos = (c->content_length < c->line_pos) ? c->content_length : c->line_pos;
if (c->body_pos) {
memcpy(c->body, c->line_buf, c->body_pos);
c->line_pos -= c->body_pos; /* NOTE: we are NOT moving the buffer since non-zero left-over causes connection close */
}
/* POST will continue into the BODY part */
break;
}
{
char *bol = s;
while (*s && *s != '\r' && *s != '\n') s++;
if (!*s) { /* incomplete line */
if (bol == c->line_buf) {
if (c->line_pos < LINE_BUF_SIZE) /* one, incomplete line, but the buffer is not full yet, just return */
return;
/* the buffer is full yet the line is incomplete - we're in trouble */
send_http_response(c, " 413 Request entity too large\r\nConnection: close\r\n\r\n");
remove_worker(c);
return;
}
/* move the line to the begining of the buffer for later requests */
c->line_pos -= bol - c->line_buf;
memmove(c->line_buf, bol, c->line_pos);
return;
} else { /* complete line, great! */
if (*s == '\r') *(s++) = 0;
if (*s == '\n') *(s++) = 0;
DBG(printf("complete line: {%s}\n", bol));
if (c->part == PART_REQUEST) {
/* --- process request line --- */
size_t rll = strlen(bol); /* request line length */
char *url = strchr(bol, ' ');
if (!url || rll < 14 || strncmp(bol + rll - 9, " HTTP/1.", 8)) { /* each request must have at least 14 characters [GET / HTTP/1.0] and have HTTP/1.x */
send_response(c->sock, "HTTP/1.0 400 Bad Request\r\n\r\n", 28);
remove_worker(c);
return;
}
url++;
if (!strncmp(bol + rll - 3, "1.0", 3)) c->attr |= HTTP_1_0;
if (!strncmp(bol, "GET ", 4)) c->method = METHOD_GET;
if (!strncmp(bol, "POST ", 5)) c->method = METHOD_POST;
if (!strncmp(bol, "HEAD ", 5)) c->method = METHOD_HEAD;
/* only custom handlers can use other methods */
if (!strncmp(url, "/custom/", 8)) {
char *mend = url - 1;
/* we generate a header with the method so it can be passed to the handler */
if (!c->headers)
c->headers = alloc_buffer(1024, NULL);
/* make sure it fits */
if (c->headers->size - c->headers->length >= 18 + (mend - bol)) {
if (!c->method) c->method = METHOD_OTHER;
/* add "Request-Method: xxx" */
memcpy(c->headers->data + c->headers->length, "Request-Method: ", 16);
c->headers->length += 16;
memcpy(c->headers->data + c->headers->length, bol, mend - bol);
c->headers->length += mend - bol;
c->headers->data[c->headers->length++] = '\n';
}
}
if (!c->method) {
send_http_response(c, " 501 Invalid or unimplemented method\r\n\r\n");
remove_worker(c);
return;
}
bol[strlen(bol) - 9] = 0;
c->url = strdup(url);
c->part = PART_HEADER;
DBG(printf("parsed request, method=%d, URL='%s'\n", (int)c->method, c->url));
} else if (c->part == PART_HEADER) {
/* --- process headers --- */
char *k = bol;
if (!c->headers)
c->headers = alloc_buffer(1024, NULL);
if (c->headers) { /* record the header line in the buffer */
size_t l = strlen(bol);
if (l) { /* this should be really always true */
if (c->headers->length + l + 1 > c->headers->size) { /* not enough space? */
size_t fits = c->headers->size - c->headers->length;
if (fits) memcpy(c->headers->data + c->headers->length, bol, fits);
if (alloc_buffer(2048, c->headers)) {
c->headers = c->headers->next;
memcpy(c->headers->data, bol + fits, l - fits);
c->headers->length = l - fits;
c->headers->data[c->headers->length++] = '\n';
}
} else {
memcpy(c->headers->data + c->headers->length, bol, l);
c->headers->length += l;
c->headers->data[c->headers->length++] = '\n';
}
}
}
while (*k && *k != ':') {
if (*k >= 'A' && *k <= 'Z')
*k |= 0x20;
k++;
}
if (*k == ':') {
*(k++) = 0;
while (*k == ' ' || *k == '\t') k++;
DBG(printf("header '%s' => '%s'\n", bol, k));
if (!strcmp(bol, "content-length")) {
c->attr |= CONTENT_LENGTH;
c->content_length = atol(k);
}
if (!strcmp(bol, "content-type")) {
char *l = k;
/* convert content-type to lowercase to facilitate comparison
since MIME types are case-insensitive.
However, we have to stop at ; since parameters
may be case-sensitive (see PR 16541) */
while (*l && *l != ';') { if (*l >= 'A' && *l <= 'Z') *l |= 0x20; l++; }
c->attr |= CONTENT_TYPE;
if (c->content_type) free(c->content_type);
c->content_type = strdup(k);
if (!strncmp(k, "application/x-www-form-urlencoded", 33))
c->attr |= CONTENT_FORM_UENC;
}
if (!strcmp(bol, "host"))
c->attr |= HOST_HEADER;
if (!strcmp(bol, "connection")) {
char *l = k;
while (*l) { if (*l >= 'A' && *l <= 'Z') *l |= 0x20; l++; }
if (!strncmp(k, "close", 5))
c->attr |= CONNECTION_CLOSE;
}
}
}
}
}
}
if (c->part < PART_BODY) {
/* we end here if we processed a buffer of exactly one line */
c->line_pos = 0;
return;
}
}
if (c->part == PART_BODY && c->body) { /* BODY - this branch always returns */
if (c->body_pos < c->content_length) { /* need to receive more ? */
DBG(printf("BODY: body_pos=%d, content_length=%ld\n", c->body_pos, c->content_length));
ssize_t n = recv(c->sock, c->body + c->body_pos,
c->content_length - c->body_pos, 0);
DBG(printf(" [recv n=%d - had %u of %lu]\n", n, c->body_pos, c->content_length));
c->line_pos = 0;
if (n < 0) { /* error, scrap this worker */
remove_worker(c);
return;
}
if (n == 0) { /* connection closed -> try to process and then remove */
process_request(c);
remove_worker(c);
return;
}
c->body_pos += n;
}
if (c->body_pos == c->content_length) { /* yay! we got the whole body */
process_request(c);
if (c->attr & CONNECTION_CLOSE || c->line_pos) { /* we have to close the connection if there was a double-hit */
remove_worker(c);
return;
}
/* keep-alive - reset the worker so it can process a new request */
if (c->url) { free(c->url); c->url = NULL; }
if (c->body) { free(c->body); c->body = NULL; }
if (c->content_type) { free(c->content_type); c->content_type = NULL; }
if (c->headers) { free_buffer(c->headers); c->headers = NULL; }
c->line_pos = 0; c->body_pos = 0;
c->method = 0;
c->part = PART_REQUEST;
c->attr = 0;
c->content_length = 0;
return;
}
}
/* we enter here only if recv was used to leave the headers with no body */
if (c->part == PART_BODY && !c->body) {
char *s = c->line_buf;
if (c->line_pos > 0) {
if ((s[0] != '\r' || s[1] != '\n') && (s[0] != '\n')) {
send_http_response(c, " 411 length is required for non-empty body\r\nConnection: close\r\n\r\n");
remove_worker(c);
return;
}
/* empty body, good */
process_request(c);
if (c->attr & CONNECTION_CLOSE) {
remove_worker(c);
return;
} else { /* keep-alive */
int sh = 1;
if (s[0] == '\r') sh++;
if (c->line_pos <= sh)
c->line_pos = 0;
else { /* shift the remaining buffer */
memmove(c->line_buf, c->line_buf + sh, c->line_pos - sh);
c->line_pos -= sh;
}
/* keep-alive - reset the worker so it can process a new request */
if (c->url) { free(c->url); c->url = NULL; }
if (c->body) { free(c->body); c->body = NULL; }
if (c->content_type) { free(c->content_type); c->content_type = NULL; }
if (c->headers) { free_buffer(c->headers); c->headers = NULL; }
c->body_pos = 0;
c->method = 0;
c->part = PART_REQUEST;
c->attr = 0;
c->content_length = 0;
return;
}
}
ssize_t n = recv(c->sock, c->line_buf + c->line_pos,
LINE_BUF_SIZE - c->line_pos - 1, 0);
if (n < 0) { /* error, scrap this worker */
remove_worker(c);
return;
}
if (n == 0) { /* connection closed -> try to process and then remove */
process_request(c);
remove_worker(c);
return;
}
if ((s[0] != '\r' || s[1] != '\n') && (s[0] != '\n')) {
send_http_response(c, " 411 length is required for non-empty body\r\nConnection: close\r\n\r\n");
remove_worker(c);
return;
}
}
}