static const char *test_parse_http_message() { struct mg_connection ri; char req1[] = "GET / HTTP/1.1\r\n\r\n"; char req2[] = "BLAH / HTTP/1.1\r\n\r\n"; char req3[] = "GET / HTTP/1.1\r\nBah\r\n"; char req4[] = "GET / HTTP/1.1\r\nA: foo bar\r\nB: bar\r\nbaz\r\n\r\n"; char req5[] = "GET / HTTP/1.1\r\n\r\n"; char req6[] = "G"; char req7[] = " blah "; char req8[] = " HTTP/1.1 200 OK \n\n"; char req9[] = "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n"; ASSERT(get_request_len("\r\n", 3) == -1); ASSERT(get_request_len("\r\n", 2) == 0); ASSERT(get_request_len("GET", 3) == 0); ASSERT(get_request_len("\n\n", 2) == 2); ASSERT(get_request_len("\n\r\n", 3) == 3); ASSERT(get_request_len("\xdd\xdd", 2) == 0); ASSERT(get_request_len("\xdd\x03", 2) == -1); ASSERT(get_request_len(req3, sizeof(req3) - 1) == 0); ASSERT(get_request_len(req6, sizeof(req6) - 1) == 0); ASSERT(get_request_len(req7, sizeof(req7) - 1) == 0); ASSERT(parse_http_message(req9, sizeof(req9) - 1, &ri) == sizeof(req9) - 1); ASSERT(ri.num_headers == 1); ASSERT(parse_http_message(req1, sizeof(req1) - 1, &ri) == sizeof(req1) - 1); ASSERT(strcmp(ri.http_version, "1.1") == 0); ASSERT(ri.num_headers == 0); ASSERT(parse_http_message(req2, sizeof(req2) - 1, &ri) == -1); ASSERT(parse_http_message(req6, 0, &ri) == -1); ASSERT(parse_http_message(req8, sizeof(req8) - 1, &ri) == sizeof(req8) - 1); // TODO(lsm): Fix this. Header value may span multiple lines. ASSERT(parse_http_message(req4, sizeof(req4) - 1, &ri) == sizeof(req4) - 1); ASSERT(strcmp(ri.http_version, "1.1") == 0); ASSERT(ri.num_headers == 3); ASSERT(strcmp(ri.http_headers[0].name, "A") == 0); ASSERT(strcmp(ri.http_headers[0].value, "foo bar") == 0); ASSERT(strcmp(ri.http_headers[1].name, "B") == 0); ASSERT(strcmp(ri.http_headers[1].value, "bar") == 0); ASSERT(strcmp(ri.http_headers[2].name, "baz\r\n\r") == 0); ASSERT(strcmp(ri.http_headers[2].value, "") == 0); ASSERT(parse_http_message(req5, sizeof(req5) - 1, &ri) == sizeof(req5) - 1); ASSERT(strcmp(ri.request_method, "GET") == 0); ASSERT(strcmp(ri.http_version, "1.1") == 0); return NULL; }
// Keep reading the input from socket sock // into buffer buf, until \r\n\r\n appears in the buffer (which marks the end // of HTTP request). Buffer buf may already have some data. The length of the // data is stored in nread. Upon every read operation, increase nread by the // number of bytes read. static int read_request(SOCKET sock, char *buf, int bufsiz, int *nread) { int n, request_len; request_len = 0; while (*nread < bufsiz && request_len == 0) { n = pull(sock, buf + *nread, bufsiz - *nread); if (n <= 0) { break; } else { *nread += n; request_len = get_request_len(buf, *nread); } } return request_len; }
// Parse HTTP request, fill in mg_request_info structure. // This function modifies the buffer by NUL-terminating // HTTP request components, header names and header values. static int parse_http_message(char *buf, int len, struct mg_request_info *ri) { int request_length = get_request_len(buf, len); if (request_length > 0) { // Reset attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port ri->remote_user = ri->request_method = ri->uri = ri->http_version = NULL; ri->num_headers = 0; buf[request_length - 1] = '\0'; // RFC says that all initial whitespaces should be ingored while (*buf != '\0' && isspace(* (unsigned char *) buf)) { buf++; } ri->request_method = skip(&buf, " "); ri->uri = skip(&buf, " "); ri->http_version = skip(&buf, "\r\n"); parse_http_headers(&buf, ri); } return request_length; }
static void process_new_connection(struct mg_connection *conn) { struct mg_request_info *ri = &conn->request_info; const char *cl; reset_per_request_attributes(conn); // If next request is not pipelined, read it in if ((conn->request_len = get_request_len(conn->buf, conn->data_len)) == 0) { conn->request_len = read_request(conn->client.sock, conn->buf, conn->buf_size, &conn->data_len); } assert(conn->data_len >= conn->request_len); if (conn->request_len == 0 && conn->data_len == conn->buf_size) { mg_send_http_error(conn, 413, "Request Too Large", ""); return; } if (conn->request_len <= 0) { return; // Remote end closed the connection } // Nul-terminate the request cause parse_http_request() uses sscanf conn->buf[conn->request_len - 1] = '\0'; if (!parse_http_request(conn->buf, ri)) { // Do not put garbage in the access log, just send it back to the client mg_send_http_error(conn, 400, "Bad Request", "Cannot parse HTTP request: [%.*s]", conn->data_len, conn->buf); } else if (strcmp(ri->http_version, "1.0") && strcmp(ri->http_version, "1.1")) { // Request seems valid, but HTTP version is strange mg_send_http_error(conn, 505, "HTTP version not supported", ""); } else { // Request is valid, handle it cl = get_header(ri, "Content-Length"); conn->content_len = cl == NULL ? -1 : strtoll(cl, NULL, 10); conn->birth_time = time(NULL); handle_request(conn); discard_current_request_from_buffer(conn); } }
FILE *mg_upload(struct mg_connection *conn, const char *destination_dir, char *path, int path_len) { const char *content_type_header, *boundary_start; char *buf, fname[1024], boundary[100], *s, *p; int bl, n, i, j, headers_len, boundary_len, eof, buf_len, to_read, len = 0; FILE *fp; // Request looks like this: // // POST /upload HTTP/1.1 // Host: 127.0.0.1:8080 // Content-Length: 244894 // Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRVr // // ------WebKitFormBoundaryRVr // Content-Disposition: form-data; name="file"; filename="accum.png" // Content-Type: image/png // // <89>PNG // <PNG DATA> // ------WebKitFormBoundaryRVr // Extract boundary string from the Content-Type header if ((content_type_header = mg_get_header(conn, "Content-Type")) == NULL || (boundary_start = mg_strcasestr(content_type_header, "boundary=")) == NULL || (sscanf(boundary_start, "boundary=\"%99[^\"]\"", boundary) == 0 && sscanf(boundary_start, "boundary=%99s", boundary) == 0) || boundary[0] == '\0') { return NULL; } boundary_len = strlen(boundary); bl = boundary_len + 4; // \r\n--<boundary> // buf // conn->buf |<--------- buf_len ------>| // |=================|==========|===============| // |<--request_len-->|<--len--->| | // |<-----------data_len------->| conn->buf + conn->buf_size buf = conn->buf + conn->request_len; buf_len = conn->buf_size - conn->request_len; len = conn->data_len - conn->request_len; for (;;) { // Pull in headers assert(len >= 0 && len <= buf_len); to_read = buf_len - len; if (to_read > left_to_read(conn)) { to_read = (int) left_to_read(conn); } while (len < buf_len && (n = pull(NULL, conn, buf + len, to_read)) > 0) { len += n; } if ((headers_len = get_request_len(buf, len)) <= 0) { break; } // Fetch file name. fname[0] = '\0'; for (i = j = 0; i < headers_len; i++) { if (buf[i] == '\r' && buf[i + 1] == '\n') { buf[i] = buf[i + 1] = '\0'; // TODO(lsm): don't expect filename to be the 3rd field, // parse the header properly instead. sscanf(&buf[j], "Content-Disposition: %*s %*s filename=\"%1023[^\"]", fname); j = i + 2; } } // Give up if the headers are not what we expect if (fname[0] == '\0') { break; } // Move data to the beginning of the buffer assert(len >= headers_len); memmove(buf, &buf[headers_len], len - headers_len); len -= headers_len; conn->data_len = conn->request_len + len; // We open the file with exclusive lock held. This guarantee us // there is no other thread can save into the same file simultaneously. fp = NULL; // Construct destination file name. Do not allow paths to have slashes. s = fname; if ((p = strrchr(fname, '/')) != NULL && p > s) s = p; if ((p = strrchr(fname, '\\')) != NULL && p > s) s = p; // Open file in binary mode. TODO: set an exclusive lock. snprintf(path, path_len, "%s/%s", destination_dir, s); if ((fp = fopen(path, "wb")) == NULL) { break; } // Read POST data, write into file until boundary is found. eof = n = 0; do { len += n; for (i = 0; i < len - bl; i++) { if (!memcmp(&buf[i], "\r\n--", 4) && !memcmp(&buf[i + 4], boundary, boundary_len)) { // Found boundary, that's the end of file data. fwrite(buf, 1, i, fp); eof = 1; memmove(buf, &buf[i + bl], len - (i + bl)); len -= i + bl; break; } } if (!eof && len > bl) { fwrite(buf, 1, len - bl, fp); memmove(buf, &buf[len - bl], bl); len = bl; } to_read = buf_len - len; if (to_read > left_to_read(conn)) { to_read = (int) left_to_read(conn); } } while (!eof && (n = pull(NULL, conn, buf + len, to_read)) > 0); conn->data_len = conn->request_len + len; if (eof) { rewind(fp); return fp; } else { fclose(fp); } } return NULL; }