|  | @@ -2368,8 +2368,12 @@ struct mg_connection {
 | 
	
		
			
				|  |  |  	int64_t num_bytes_sent;   /* Total bytes sent to client */
 | 
	
		
			
				|  |  |  	int64_t content_len;      /* Content-Length header value */
 | 
	
		
			
				|  |  |  	int64_t consumed_content; /* How many bytes of content have been read */
 | 
	
		
			
				|  |  | -	int is_chunked;           /* Transfer-Encoding is chunked: 0=no, 1=yes:
 | 
	
		
			
				|  |  | -	                           * data available, 2: all data read */
 | 
	
		
			
				|  |  | +	int is_chunked;           /* Transfer-Encoding is chunked:
 | 
	
		
			
				|  |  | +	                           * 0 = not chunked,
 | 
	
		
			
				|  |  | +	                           * 1 = chunked, do data read yet,
 | 
	
		
			
				|  |  | +	                           * 2 = chunked, some data read,
 | 
	
		
			
				|  |  | +	                           * 3 = chunked, all data read
 | 
	
		
			
				|  |  | +	                           */
 | 
	
		
			
				|  |  |  	size_t chunk_remainder;   /* Unread data from the last chunk */
 | 
	
		
			
				|  |  |  	char *buf;                /* Buffer for received data */
 | 
	
		
			
				|  |  |  	char *path_info;          /* PATH_INFO part of the URL */
 | 
	
	
		
			
				|  | @@ -5858,9 +5862,9 @@ discard_unread_request_data(struct mg_connection *conn)
 | 
	
		
			
				|  |  |  	to_read = sizeof(buf);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	if (conn->is_chunked) {
 | 
	
		
			
				|  |  | -		/* Chunked encoding: 1=chunk not read completely, 2=chunk read
 | 
	
		
			
				|  |  | +		/* Chunked encoding: 3=chunk read completely
 | 
	
		
			
				|  |  |  		 * completely */
 | 
	
		
			
				|  |  | -		while (conn->is_chunked == 1) {
 | 
	
		
			
				|  |  | +		while (conn->is_chunked != 3) {
 | 
	
		
			
				|  |  |  			nread = mg_read(conn, buf, to_read);
 | 
	
		
			
				|  |  |  			if (nread <= 0) {
 | 
	
		
			
				|  |  |  				break;
 | 
	
	
		
			
				|  | @@ -5898,11 +5902,19 @@ mg_read_inner(struct mg_connection *conn, void *buf, size_t len)
 | 
	
		
			
				|  |  |  		return 0;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	/* If Content-Length is not set for a PUT or POST request, read until
 | 
	
		
			
				|  |  | -	 * socket is closed */
 | 
	
		
			
				|  |  | -	if ((conn->consumed_content) == 0 && (conn->content_len == -1)) {
 | 
	
		
			
				|  |  | -		conn->content_len = INT64_MAX;
 | 
	
		
			
				|  |  | -		conn->must_close = 1;
 | 
	
		
			
				|  |  | +	/* If Content-Length is not set for a request with body data
 | 
	
		
			
				|  |  | +	 * (e.g., a PUT or POST request), we do not know in advance
 | 
	
		
			
				|  |  | +	 * how much data should be read. */
 | 
	
		
			
				|  |  | +	if ((conn->consumed_content == 0) && (conn->content_len == -1)) {
 | 
	
		
			
				|  |  | +		if (conn->is_chunked) {
 | 
	
		
			
				|  |  | +			conn->content_len = 0;
 | 
	
		
			
				|  |  | +			conn->is_chunked = 2;
 | 
	
		
			
				|  |  | +		} else {
 | 
	
		
			
				|  |  | +			/* The body data is completed when the connection
 | 
	
		
			
				|  |  | +			 * is closed. */
 | 
	
		
			
				|  |  | +			conn->content_len = INT64_MAX;
 | 
	
		
			
				|  |  | +			conn->must_close = 1;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	nread = 0;
 | 
	
	
		
			
				|  | @@ -5951,7 +5963,6 @@ mg_getc(struct mg_connection *conn)
 | 
	
		
			
				|  |  |  	if (conn == NULL) {
 | 
	
		
			
				|  |  |  		return 0;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | -	conn->content_len++;
 | 
	
		
			
				|  |  |  	if (mg_read_inner(conn, &c, 1) <= 0) {
 | 
	
		
			
				|  |  |  		return (char)0;
 | 
	
		
			
				|  |  |  	}
 | 
	
	
		
			
				|  | @@ -5975,7 +5986,7 @@ mg_read(struct mg_connection *conn, void *buf, size_t len)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  		while (len > 0) {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -			if (conn->is_chunked == 2) {
 | 
	
		
			
				|  |  | +			if (conn->is_chunked == 3) {
 | 
	
		
			
				|  |  |  				/* No more data left to read */
 | 
	
		
			
				|  |  |  				return 0;
 | 
	
		
			
				|  |  |  			}
 | 
	
	
		
			
				|  | @@ -6003,8 +6014,10 @@ mg_read(struct mg_connection *conn, void *buf, size_t len)
 | 
	
		
			
				|  |  |  				if (conn->chunk_remainder == 0) {
 | 
	
		
			
				|  |  |  					/* Add data bytes in the current chunk have been read,
 | 
	
		
			
				|  |  |  					 * so we are expecting \r\n now. */
 | 
	
		
			
				|  |  | -					char x1 = mg_getc(conn);
 | 
	
		
			
				|  |  | -					char x2 = mg_getc(conn);
 | 
	
		
			
				|  |  | +					char x1, x2;
 | 
	
		
			
				|  |  | +					conn->content_len += 2;
 | 
	
		
			
				|  |  | +					x1 = mg_getc(conn);
 | 
	
		
			
				|  |  | +					x2 = mg_getc(conn);
 | 
	
		
			
				|  |  |  					if ((x1 != '\r') || (x2 != '\n')) {
 | 
	
		
			
				|  |  |  						/* Protocol violation */
 | 
	
		
			
				|  |  |  						return -1;
 | 
	
	
		
			
				|  | @@ -6019,6 +6032,7 @@ mg_read(struct mg_connection *conn, void *buf, size_t len)
 | 
	
		
			
				|  |  |  				unsigned long chunkSize = 0;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  				for (i = 0; i < ((int)sizeof(lenbuf) - 1); i++) {
 | 
	
		
			
				|  |  | +					conn->content_len++;
 | 
	
		
			
				|  |  |  					lenbuf[i] = mg_getc(conn);
 | 
	
		
			
				|  |  |  					if ((i > 0) && (lenbuf[i] == '\r')
 | 
	
		
			
				|  |  |  					    && (lenbuf[i - 1] != '\r')) {
 | 
	
	
		
			
				|  | @@ -6030,7 +6044,7 @@ mg_read(struct mg_connection *conn, void *buf, size_t len)
 | 
	
		
			
				|  |  |  						chunkSize = strtoul(lenbuf, &end, 16);
 | 
	
		
			
				|  |  |  						if (chunkSize == 0) {
 | 
	
		
			
				|  |  |  							/* regular end of content */
 | 
	
		
			
				|  |  | -							conn->is_chunked = 2;
 | 
	
		
			
				|  |  | +							conn->is_chunked = 3;
 | 
	
		
			
				|  |  |  						}
 | 
	
		
			
				|  |  |  						break;
 | 
	
		
			
				|  |  |  					}
 | 
	
	
		
			
				|  | @@ -9176,47 +9190,96 @@ parse_http_headers(char **buf, struct mg_header hdr[MG_MAX_HEADERS])
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -static int
 | 
	
		
			
				|  |  | -is_valid_http_method(const char *method)
 | 
	
		
			
				|  |  | +struct mg_http_method_info {
 | 
	
		
			
				|  |  | +	const char *name;
 | 
	
		
			
				|  |  | +	int request_has_body;
 | 
	
		
			
				|  |  | +	int response_has_body;
 | 
	
		
			
				|  |  | +	int is_safe;
 | 
	
		
			
				|  |  | +	int is_idempotent;
 | 
	
		
			
				|  |  | +	int is_cacheable;
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/* https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods */
 | 
	
		
			
				|  |  | +static struct mg_http_method_info http_methods[] = {
 | 
	
		
			
				|  |  | +    /* HTTP (RFC 2616) */
 | 
	
		
			
				|  |  | +    {"GET", 0, 1, 1, 1, 1},
 | 
	
		
			
				|  |  | +    {"POST", 1, 1, 0, 0, 0},
 | 
	
		
			
				|  |  | +    {"PUT", 1, 0, 0, 1, 0},
 | 
	
		
			
				|  |  | +    {"DELETE", 0, 0, 0, 1, 0},
 | 
	
		
			
				|  |  | +    {"HEAD", 0, 0, 1, 1, 1},
 | 
	
		
			
				|  |  | +    {"OPTIONS", 0, 0, 1, 1, 0},
 | 
	
		
			
				|  |  | +    {"CONNECT", 1, 1, 0, 0, 0},
 | 
	
		
			
				|  |  | +    /* TRACE method (RFC 2616) is not supported for security reasons */
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /* PATCH method (RFC 5789) */
 | 
	
		
			
				|  |  | +    {"PATCH", 1, 0, 0, 0, 0},
 | 
	
		
			
				|  |  | +    /* PATCH method only allowed for CGI/Lua/LSP and callbacks. */
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /* WEBDAV (RFC 2518) */
 | 
	
		
			
				|  |  | +    {"PROPFIND", 0, 1, 1, 1, 0},
 | 
	
		
			
				|  |  | +    /* http://www.webdav.org/specs/rfc4918.html, 9.1:
 | 
	
		
			
				|  |  | +     * Some PROPFIND results MAY be cached, with care,
 | 
	
		
			
				|  |  | +     * as there is no cache validation mechanism for
 | 
	
		
			
				|  |  | +     * most properties. This method is both safe and
 | 
	
		
			
				|  |  | +     * idempotent (see Section 9.1 of [RFC2616]). */
 | 
	
		
			
				|  |  | +    {"MKCOL", 0, 0, 0, 1, 0},
 | 
	
		
			
				|  |  | +    /* http://www.webdav.org/specs/rfc4918.html, 9.1:
 | 
	
		
			
				|  |  | +     * When MKCOL is invoked without a request body,
 | 
	
		
			
				|  |  | +     * the newly created collection SHOULD have no
 | 
	
		
			
				|  |  | +     * members. A MKCOL request message may contain
 | 
	
		
			
				|  |  | +     * a message body. The precise behavior of a MKCOL
 | 
	
		
			
				|  |  | +     * request when the body is present is undefined,
 | 
	
		
			
				|  |  | +     * ... ==> We do not support MKCOL with body data.
 | 
	
		
			
				|  |  | +     * This method is idempotent, but not safe (see
 | 
	
		
			
				|  |  | +     * Section 9.1 of [RFC2616]). Responses to this
 | 
	
		
			
				|  |  | +     * method MUST NOT be cached. */
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /* Unsupported WEBDAV Methods: */
 | 
	
		
			
				|  |  | +    /* PROPPATCH, COPY, MOVE, LOCK, UNLOCK (RFC 2518) */
 | 
	
		
			
				|  |  | +    /* + 11 methods from RFC 3253 */
 | 
	
		
			
				|  |  | +    /* ORDERPATCH (RFC 3648) */
 | 
	
		
			
				|  |  | +    /* ACL (RFC 3744) */
 | 
	
		
			
				|  |  | +    /* SEARCH (RFC 5323) */
 | 
	
		
			
				|  |  | +    /* + MicroSoft extensions
 | 
	
		
			
				|  |  | +     * https://msdn.microsoft.com/en-us/library/aa142917.aspx */
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /* REPORT method (RFC 3253) */
 | 
	
		
			
				|  |  | +    {"REPORT", 1, 1, 1, 1, 1},
 | 
	
		
			
				|  |  | +    /* REPORT method only allowed for CGI/Lua/LSP and callbacks. */
 | 
	
		
			
				|  |  | +    /* It was defined for WEBDAV in RFC 3253, Sec. 3.6
 | 
	
		
			
				|  |  | +     * (https://tools.ietf.org/html/rfc3253#section-3.6), but seems
 | 
	
		
			
				|  |  | +     * to be useful for REST in case a "GET request with body" is
 | 
	
		
			
				|  |  | +     * required. */
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    {NULL, 0, 0, 0, 0, 0}
 | 
	
		
			
				|  |  | +    /* end of list */
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +static const struct mg_http_method_info *
 | 
	
		
			
				|  |  | +get_http_method_info(const char *method)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	/* Check if the method is known to the server. The list of all known
 | 
	
		
			
				|  |  |  	 * HTTP methods can be found here at
 | 
	
		
			
				|  |  |  	 * http://www.iana.org/assignments/http-methods/http-methods.xhtml
 | 
	
		
			
				|  |  |  	 */
 | 
	
		
			
				|  |  | +	const struct mg_http_method_info *m = http_methods;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	while (m->name) {
 | 
	
		
			
				|  |  | +		if (!strcmp(m->name, method)) {
 | 
	
		
			
				|  |  | +			return m;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		m++;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +	return NULL;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	return !strcmp(method, "GET")        /* HTTP (RFC 2616) */
 | 
	
		
			
				|  |  | -	       || !strcmp(method, "POST")    /* HTTP (RFC 2616) */
 | 
	
		
			
				|  |  | -	       || !strcmp(method, "HEAD")    /* HTTP (RFC 2616) */
 | 
	
		
			
				|  |  | -	       || !strcmp(method, "PUT")     /* HTTP (RFC 2616) */
 | 
	
		
			
				|  |  | -	       || !strcmp(method, "DELETE")  /* HTTP (RFC 2616) */
 | 
	
		
			
				|  |  | -	       || !strcmp(method, "OPTIONS") /* HTTP (RFC 2616) */
 | 
	
		
			
				|  |  | -	       /* TRACE method (RFC 2616) is not supported for security reasons
 | 
	
		
			
				|  |  | -	          */
 | 
	
		
			
				|  |  | -	       || !strcmp(method, "CONNECT") /* HTTP (RFC 2616) */
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	       || !strcmp(method, "PROPFIND") /* WEBDAV (RFC 2518) */
 | 
	
		
			
				|  |  | -	       || !strcmp(method, "MKCOL")    /* WEBDAV (RFC 2518) */
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	       /* Unsupported WEBDAV Methods: */
 | 
	
		
			
				|  |  | -	       /* PROPPATCH, COPY, MOVE, LOCK, UNLOCK (RFC 2518) */
 | 
	
		
			
				|  |  | -	       /* + 11 methods from RFC 3253 */
 | 
	
		
			
				|  |  | -	       /* ORDERPATCH (RFC 3648) */
 | 
	
		
			
				|  |  | -	       /* ACL (RFC 3744) */
 | 
	
		
			
				|  |  | -	       /* SEARCH (RFC 5323) */
 | 
	
		
			
				|  |  | -	       /* + MicroSoft extensions
 | 
	
		
			
				|  |  | -	        * https://msdn.microsoft.com/en-us/library/aa142917.aspx */
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	       /* PATCH method only allowed for CGI/Lua/LSP and callbacks. */
 | 
	
		
			
				|  |  | -	       || !strcmp(method, "PATCH") /* PATCH method (RFC 5789) */
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	       /* REPORT method only allowed for CGI/Lua/LSP and callbacks. */
 | 
	
		
			
				|  |  | -	       /* It was defined for WEBDAV in RFC 3253, Sec. 3.6
 | 
	
		
			
				|  |  | -	        * (https://tools.ietf.org/html/rfc3253#section-3.6), but seems
 | 
	
		
			
				|  |  | -	        * to be useful for REST in case a "GET request with body" is
 | 
	
		
			
				|  |  | -	        * required. */
 | 
	
		
			
				|  |  | -	       || !strcmp(method, "REPORT") /* REPORT method (RFC 3253) */
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	    ;
 | 
	
		
			
				|  |  | +static int
 | 
	
		
			
				|  |  | +is_valid_http_method(const char *method)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +	return (get_http_method_info(method) != NULL);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -9549,7 +9612,7 @@ forward_body_data(struct mg_connection *conn, FILE *fp, SOCKET sock, SSL *ssl)
 | 
	
		
			
				|  |  |  		return 0;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	if ((conn->content_len == -1) && !conn->is_chunked) {
 | 
	
		
			
				|  |  | +	if ((conn->content_len == -1) && (!conn->is_chunked)) {
 | 
	
		
			
				|  |  |  		/* Content length is not specified by the client. */
 | 
	
		
			
				|  |  |  		mg_send_http_error(conn,
 | 
	
		
			
				|  |  |  		                   411,
 | 
	
	
		
			
				|  | @@ -10059,7 +10122,7 @@ handle_cgi_request(struct mg_connection *conn, const char *prog)
 | 
	
		
			
				|  |  |  	setbuf(err, NULL);
 | 
	
		
			
				|  |  |  	fout.access.fp = out;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	if ((conn->request_info.content_length > 0) || conn->is_chunked) {
 | 
	
		
			
				|  |  | +	if ((conn->request_info.content_length > 0) || (conn->is_chunked)) {
 | 
	
		
			
				|  |  |  		/* This is a POST/PUT request, or another request with body data. */
 | 
	
		
			
				|  |  |  		if (!forward_body_data(conn, in, INVALID_SOCKET, NULL)) {
 | 
	
		
			
				|  |  |  			/* Error sending the body data */
 | 
	
	
		
			
				|  | @@ -15200,16 +15263,14 @@ get_request(struct mg_connection *conn, char *ebuf, size_t ebuf_len, int *err)
 | 
	
		
			
				|  |  |  	                            "Transfer-Encoding")) != NULL
 | 
	
		
			
				|  |  |  	           && !mg_strcasecmp(cl, "chunked")) {
 | 
	
		
			
				|  |  |  		conn->is_chunked = 1;
 | 
	
		
			
				|  |  | -	} else if (!mg_strcasecmp(conn->request_info.request_method, "POST")
 | 
	
		
			
				|  |  | -	           || !mg_strcasecmp(conn->request_info.request_method, "PUT")) {
 | 
	
		
			
				|  |  | +		conn->content_len = -1; /* unknown content length */
 | 
	
		
			
				|  |  | +	} else if (get_http_method_info(conn->request_info.request_method)
 | 
	
		
			
				|  |  | +	               ->request_has_body) {
 | 
	
		
			
				|  |  |  		/* POST or PUT request without content length set */
 | 
	
		
			
				|  |  | -		conn->content_len = -1;
 | 
	
		
			
				|  |  | -	} else if (!mg_strncasecmp(conn->request_info.request_method, "HTTP/", 5)) {
 | 
	
		
			
				|  |  | -		/* Response without content length set */
 | 
	
		
			
				|  |  | -		conn->content_len = -1;
 | 
	
		
			
				|  |  | +		conn->content_len = -1; /* unknown content length */
 | 
	
		
			
				|  |  |  	} else {
 | 
	
		
			
				|  |  |  		/* Other request */
 | 
	
		
			
				|  |  | -		conn->content_len = 0;
 | 
	
		
			
				|  |  | +		conn->content_len = 0; /* No content */
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	conn->connection_type = 1; /* Valid request */
 | 
	
	
		
			
				|  | @@ -15266,8 +15327,9 @@ get_response(struct mg_connection *conn, char *ebuf, size_t ebuf_len, int *err)
 | 
	
		
			
				|  |  |  	                            "Transfer-Encoding")) != NULL
 | 
	
		
			
				|  |  |  	           && !mg_strcasecmp(cl, "chunked")) {
 | 
	
		
			
				|  |  |  		conn->is_chunked = 1;
 | 
	
		
			
				|  |  | +		conn->content_len = -1; /* unknown content length */
 | 
	
		
			
				|  |  |  	} else {
 | 
	
		
			
				|  |  | -		conn->content_len = -1;
 | 
	
		
			
				|  |  | +		conn->content_len = -1; /* unknown content length */
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 |