static void worker_input_handler()

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;
	}
    }
}