|  | @@ -1849,19 +1849,19 @@ skip_quoted(char **buf,
 | 
	
		
			
				|  |  |  	if (end_word > begin_word) {
 | 
	
		
			
				|  |  |  		p = end_word - 1;
 | 
	
		
			
				|  |  |  		while (*p == quotechar) {
 | 
	
		
			
				|  |  | -			/* TODO (bel, low): it seems this code is never reached, so
 | 
	
		
			
				|  |  | -			 * quotechar is actually not needed - check if this code may be
 | 
	
		
			
				|  |  | -			 * droped */
 | 
	
		
			
				|  |  | +			/* While the delimiter is quoted, look for the next delimiter. */
 | 
	
		
			
				|  |  | +			/* This happens, e.g., in calls from parse_auth_header,
 | 
	
		
			
				|  |  | +			 * if the user name contains a " character. */
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -			/* If there is anything beyond end_word, copy it */
 | 
	
		
			
				|  |  | -			if (*end_word == '\0') {
 | 
	
		
			
				|  |  | -				*p = '\0';
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			} else {
 | 
	
		
			
				|  |  | +			/* If there is anything beyond end_word, copy it. */
 | 
	
		
			
				|  |  | +			if (*end_word != '\0') {
 | 
	
		
			
				|  |  |  				size_t end_off = strcspn(end_word + 1, delimiters);
 | 
	
		
			
				|  |  |  				memmove(p, end_word, end_off + 1);
 | 
	
		
			
				|  |  |  				p += end_off; /* p must correspond to end_word - 1 */
 | 
	
		
			
				|  |  |  				end_word += end_off + 1;
 | 
	
		
			
				|  |  | +			} else {
 | 
	
		
			
				|  |  | +				*p = '\0';
 | 
	
		
			
				|  |  | +				break;
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  		for (p++; p < end_word; p++) {
 | 
	
	
		
			
				|  | @@ -2692,7 +2692,7 @@ mg_stat(struct mg_connection *conn, const char *path, struct file *filep)
 | 
	
		
			
				|  |  |  	if (conn && is_file_in_memory(conn, path, filep)) {
 | 
	
		
			
				|  |  |  		/* filep->is_directory = 0; filep->gzipped = 0; .. already done by
 | 
	
		
			
				|  |  |  		 * memset */
 | 
	
		
			
				|  |  | -		last_modified = time(NULL);
 | 
	
		
			
				|  |  | +		filep->last_modified = time(NULL);
 | 
	
		
			
				|  |  |  		/* last_modified = now ... assumes the file may change during runtime,
 | 
	
		
			
				|  |  |  		 * so every mg_fopen call may return different data */
 | 
	
		
			
				|  |  |  		/* last_modified = conn->ctx.start_time;
 | 
	
	
		
			
				|  | @@ -4199,7 +4199,7 @@ is_put_or_delete_method(const struct mg_connection *conn)
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  static void
 | 
	
		
			
				|  |  | -interpret_uri(struct mg_connection *conn,   /* in: request */
 | 
	
		
			
				|  |  | +interpret_uri(struct mg_connection *conn,   /* in: request (must be valid) */
 | 
	
		
			
				|  |  |                char *filename,               /* out: filename */
 | 
	
		
			
				|  |  |                size_t filename_buf_len,      /* in: size of filename buffer */
 | 
	
		
			
				|  |  |                struct file *filep,           /* out: file structure */
 | 
	
	
		
			
				|  | @@ -4209,202 +4209,188 @@ interpret_uri(struct mg_connection *conn,   /* in: request */
 | 
	
		
			
				|  |  |                int *is_put_or_delete_request /* out: put/delete a file? */
 | 
	
		
			
				|  |  |                )
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -	/* TODO (high): Restructure this function */
 | 
	
		
			
				|  |  | -	if (conn && conn->ctx) {
 | 
	
		
			
				|  |  | +/* TODO (high): Restructure this function */
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #if !defined(NO_FILES)
 | 
	
		
			
				|  |  | -		const char *uri = conn->request_info.local_uri;
 | 
	
		
			
				|  |  | -		const char *root = conn->ctx->config[DOCUMENT_ROOT];
 | 
	
		
			
				|  |  | -		const char *rewrite;
 | 
	
		
			
				|  |  | -		struct vec a, b;
 | 
	
		
			
				|  |  | -		int match_len;
 | 
	
		
			
				|  |  | -		char gz_path[PATH_MAX];
 | 
	
		
			
				|  |  | -		char const *accept_encoding;
 | 
	
		
			
				|  |  | -		int truncated;
 | 
	
		
			
				|  |  | +	const char *uri = conn->request_info.local_uri;
 | 
	
		
			
				|  |  | +	const char *root = conn->ctx->config[DOCUMENT_ROOT];
 | 
	
		
			
				|  |  | +	const char *rewrite;
 | 
	
		
			
				|  |  | +	struct vec a, b;
 | 
	
		
			
				|  |  | +	int match_len;
 | 
	
		
			
				|  |  | +	char gz_path[PATH_MAX];
 | 
	
		
			
				|  |  | +	char const *accept_encoding;
 | 
	
		
			
				|  |  | +	int truncated;
 | 
	
		
			
				|  |  |  #if !defined(NO_CGI) || defined(USE_LUA)
 | 
	
		
			
				|  |  | -		char *p;
 | 
	
		
			
				|  |  | +	char *p;
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |  #else
 | 
	
		
			
				|  |  | -		(void)filename_buf_len; /* unused if NO_FILES is defined */
 | 
	
		
			
				|  |  | +	(void)filename_buf_len; /* unused if NO_FILES is defined */
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		memset(filep, 0, sizeof(*filep));
 | 
	
		
			
				|  |  | -		*filename = 0;
 | 
	
		
			
				|  |  | -		*is_found = 0;
 | 
	
		
			
				|  |  | -		*is_script_resource = 0;
 | 
	
		
			
				|  |  | -		*is_put_or_delete_request = is_put_or_delete_method(conn);
 | 
	
		
			
				|  |  | +	memset(filep, 0, sizeof(*filep));
 | 
	
		
			
				|  |  | +	*filename = 0;
 | 
	
		
			
				|  |  | +	*is_found = 0;
 | 
	
		
			
				|  |  | +	*is_script_resource = 0;
 | 
	
		
			
				|  |  | +	*is_put_or_delete_request = is_put_or_delete_method(conn);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #if defined(USE_WEBSOCKET)
 | 
	
		
			
				|  |  | -		*is_websocket_request = is_websocket_protocol(conn);
 | 
	
		
			
				|  |  | +	*is_websocket_request = is_websocket_protocol(conn);
 | 
	
		
			
				|  |  |  #if !defined(NO_FILES)
 | 
	
		
			
				|  |  | -		if (*is_websocket_request && conn->ctx->config[WEBSOCKET_ROOT]) {
 | 
	
		
			
				|  |  | -			root = conn->ctx->config[WEBSOCKET_ROOT];
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | +	if (*is_websocket_request && conn->ctx->config[WEBSOCKET_ROOT]) {
 | 
	
		
			
				|  |  | +		root = conn->ctx->config[WEBSOCKET_ROOT];
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |  #endif /* !NO_FILES */
 | 
	
		
			
				|  |  |  #else  /* USE_WEBSOCKET */
 | 
	
		
			
				|  |  | -		*is_websocket_request = 0;
 | 
	
		
			
				|  |  | +	*is_websocket_request = 0;
 | 
	
		
			
				|  |  |  #endif /* USE_WEBSOCKET */
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #if !defined(NO_FILES)
 | 
	
		
			
				|  |  | -		/* Note that root == NULL is a regular use case here. This occurs,
 | 
	
		
			
				|  |  | -		 * if all requests are handled by callbacks, so the WEBSOCKET_ROOT
 | 
	
		
			
				|  |  | -		 * config is not required. */
 | 
	
		
			
				|  |  | -		if (root == NULL) {
 | 
	
		
			
				|  |  | -			/* all file related outputs have already been set to 0, just return
 | 
	
		
			
				|  |  | -			 */
 | 
	
		
			
				|  |  | -			return;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | +	/* Note that root == NULL is a regular use case here. This occurs,
 | 
	
		
			
				|  |  | +	 * if all requests are handled by callbacks, so the WEBSOCKET_ROOT
 | 
	
		
			
				|  |  | +	 * config is not required. */
 | 
	
		
			
				|  |  | +	if (root == NULL) {
 | 
	
		
			
				|  |  | +		/* all file related outputs have already been set to 0, just return
 | 
	
		
			
				|  |  | +		 */
 | 
	
		
			
				|  |  | +		return;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		/* Using buf_len - 1 because memmove() for PATH_INFO may shift part
 | 
	
		
			
				|  |  | -		 * of the path one byte on the right.
 | 
	
		
			
				|  |  | -		 * If document_root is NULL, leave the file empty. */
 | 
	
		
			
				|  |  | -		mg_snprintf(conn,
 | 
	
		
			
				|  |  | -		            &truncated,
 | 
	
		
			
				|  |  | -		            filename,
 | 
	
		
			
				|  |  | -		            filename_buf_len - 1,
 | 
	
		
			
				|  |  | -		            "%s%s",
 | 
	
		
			
				|  |  | -		            root,
 | 
	
		
			
				|  |  | -		            uri);
 | 
	
		
			
				|  |  | +	/* Using buf_len - 1 because memmove() for PATH_INFO may shift part
 | 
	
		
			
				|  |  | +	 * of the path one byte on the right.
 | 
	
		
			
				|  |  | +	 * If document_root is NULL, leave the file empty. */
 | 
	
		
			
				|  |  | +	mg_snprintf(
 | 
	
		
			
				|  |  | +	    conn, &truncated, filename, filename_buf_len - 1, "%s%s", root, uri);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		if (truncated) {
 | 
	
		
			
				|  |  | -			goto interpret_cleanup;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | +	if (truncated) {
 | 
	
		
			
				|  |  | +		goto interpret_cleanup;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		rewrite = conn->ctx->config[REWRITE];
 | 
	
		
			
				|  |  | -		while ((rewrite = next_option(rewrite, &a, &b)) != NULL) {
 | 
	
		
			
				|  |  | -			if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) {
 | 
	
		
			
				|  |  | -				mg_snprintf(conn,
 | 
	
		
			
				|  |  | -				            &truncated,
 | 
	
		
			
				|  |  | -				            filename,
 | 
	
		
			
				|  |  | -				            filename_buf_len - 1,
 | 
	
		
			
				|  |  | -				            "%.*s%s",
 | 
	
		
			
				|  |  | -				            (int)b.len,
 | 
	
		
			
				|  |  | -				            b.ptr,
 | 
	
		
			
				|  |  | -				            uri + match_len);
 | 
	
		
			
				|  |  | -				break;
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | +	rewrite = conn->ctx->config[REWRITE];
 | 
	
		
			
				|  |  | +	while ((rewrite = next_option(rewrite, &a, &b)) != NULL) {
 | 
	
		
			
				|  |  | +		if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) {
 | 
	
		
			
				|  |  | +			mg_snprintf(conn,
 | 
	
		
			
				|  |  | +			            &truncated,
 | 
	
		
			
				|  |  | +			            filename,
 | 
	
		
			
				|  |  | +			            filename_buf_len - 1,
 | 
	
		
			
				|  |  | +			            "%.*s%s",
 | 
	
		
			
				|  |  | +			            (int)b.len,
 | 
	
		
			
				|  |  | +			            b.ptr,
 | 
	
		
			
				|  |  | +			            uri + match_len);
 | 
	
		
			
				|  |  | +			break;
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		if (truncated) {
 | 
	
		
			
				|  |  | -			goto interpret_cleanup;
 | 
	
		
			
				|  |  | -		}
 | 
	
		
			
				|  |  | +	if (truncated) {
 | 
	
		
			
				|  |  | +		goto interpret_cleanup;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		/* Local file path and name, corresponding to requested URI
 | 
	
		
			
				|  |  | -		 * is now stored in "filename" variable. */
 | 
	
		
			
				|  |  | -		if (mg_stat(conn, filename, filep)) {
 | 
	
		
			
				|  |  | +	/* Local file path and name, corresponding to requested URI
 | 
	
		
			
				|  |  | +	 * is now stored in "filename" variable. */
 | 
	
		
			
				|  |  | +	if (mg_stat(conn, filename, filep)) {
 | 
	
		
			
				|  |  |  #if !defined(NO_CGI) || defined(USE_LUA) || defined(USE_DUKTAPE)
 | 
	
		
			
				|  |  | -			/* File exists. Check if it is a script type. */
 | 
	
		
			
				|  |  | -			if (0
 | 
	
		
			
				|  |  | +		/* File exists. Check if it is a script type. */
 | 
	
		
			
				|  |  | +		if (0
 | 
	
		
			
				|  |  |  #if !defined(NO_CGI)
 | 
	
		
			
				|  |  | -			    || match_prefix(conn->ctx->config[CGI_EXTENSIONS],
 | 
	
		
			
				|  |  | -			                    strlen(conn->ctx->config[CGI_EXTENSIONS]),
 | 
	
		
			
				|  |  | -			                    filename) > 0
 | 
	
		
			
				|  |  | +		    || match_prefix(conn->ctx->config[CGI_EXTENSIONS],
 | 
	
		
			
				|  |  | +		                    strlen(conn->ctx->config[CGI_EXTENSIONS]),
 | 
	
		
			
				|  |  | +		                    filename) > 0
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |  #if defined(USE_LUA)
 | 
	
		
			
				|  |  | -			    || match_prefix(conn->ctx->config[LUA_SCRIPT_EXTENSIONS],
 | 
	
		
			
				|  |  | -			                    strlen(
 | 
	
		
			
				|  |  | -			                        conn->ctx->config[LUA_SCRIPT_EXTENSIONS]),
 | 
	
		
			
				|  |  | -			                    filename) > 0
 | 
	
		
			
				|  |  | +		    || match_prefix(conn->ctx->config[LUA_SCRIPT_EXTENSIONS],
 | 
	
		
			
				|  |  | +		                    strlen(conn->ctx->config[LUA_SCRIPT_EXTENSIONS]),
 | 
	
		
			
				|  |  | +		                    filename) > 0
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |  #if defined(USE_DUKTAPE)
 | 
	
		
			
				|  |  | -			    || match_prefix(
 | 
	
		
			
				|  |  | -			           conn->ctx->config[DUKTAPE_SCRIPT_EXTENSIONS],
 | 
	
		
			
				|  |  | -			           strlen(conn->ctx->config[DUKTAPE_SCRIPT_EXTENSIONS]),
 | 
	
		
			
				|  |  | -			           filename) > 0
 | 
	
		
			
				|  |  | -#endif
 | 
	
		
			
				|  |  | -			    ) {
 | 
	
		
			
				|  |  | -				/* The request addresses a CGI script or a Lua script. The URI
 | 
	
		
			
				|  |  | -				 * corresponds to the script itself (like /path/script.cgi),
 | 
	
		
			
				|  |  | -				 * and there is no additional resource path
 | 
	
		
			
				|  |  | -				 * (like /path/script.cgi/something).
 | 
	
		
			
				|  |  | -				 * Requests that modify (replace or delete) a resource, like
 | 
	
		
			
				|  |  | -				 * PUT and DELETE requests, should replace/delete the script
 | 
	
		
			
				|  |  | -				 * file.
 | 
	
		
			
				|  |  | -				 * Requests that read or write from/to a resource, like GET and
 | 
	
		
			
				|  |  | -				 * POST requests, should call the script and return the
 | 
	
		
			
				|  |  | -				 * generated response. */
 | 
	
		
			
				|  |  | -				*is_script_resource = !*is_put_or_delete_request;
 | 
	
		
			
				|  |  | -			}
 | 
	
		
			
				|  |  | -#endif /* !defined(NO_CGI) || defined(USE_LUA) || defined(USE_DUKTAPE) */
 | 
	
		
			
				|  |  | -			*is_found = 1;
 | 
	
		
			
				|  |  | -			return;
 | 
	
		
			
				|  |  | +		    || match_prefix(conn->ctx->config[DUKTAPE_SCRIPT_EXTENSIONS],
 | 
	
		
			
				|  |  | +		                    strlen(
 | 
	
		
			
				|  |  | +		                        conn->ctx->config[DUKTAPE_SCRIPT_EXTENSIONS]),
 | 
	
		
			
				|  |  | +		                    filename) > 0
 | 
	
		
			
				|  |  | +#endif
 | 
	
		
			
				|  |  | +		    ) {
 | 
	
		
			
				|  |  | +			/* The request addresses a CGI script or a Lua script. The URI
 | 
	
		
			
				|  |  | +			 * corresponds to the script itself (like /path/script.cgi),
 | 
	
		
			
				|  |  | +			 * and there is no additional resource path
 | 
	
		
			
				|  |  | +			 * (like /path/script.cgi/something).
 | 
	
		
			
				|  |  | +			 * Requests that modify (replace or delete) a resource, like
 | 
	
		
			
				|  |  | +			 * PUT and DELETE requests, should replace/delete the script
 | 
	
		
			
				|  |  | +			 * file.
 | 
	
		
			
				|  |  | +			 * Requests that read or write from/to a resource, like GET and
 | 
	
		
			
				|  |  | +			 * POST requests, should call the script and return the
 | 
	
		
			
				|  |  | +			 * generated response. */
 | 
	
		
			
				|  |  | +			*is_script_resource = !*is_put_or_delete_request;
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  | +#endif /* !defined(NO_CGI) || defined(USE_LUA) || defined(USE_DUKTAPE) */
 | 
	
		
			
				|  |  | +		*is_found = 1;
 | 
	
		
			
				|  |  | +		return;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		/* If we can't find the actual file, look for the file
 | 
	
		
			
				|  |  | -		 * with the same name but a .gz extension. If we find it,
 | 
	
		
			
				|  |  | -		 * use that and set the gzipped flag in the file struct
 | 
	
		
			
				|  |  | -		 * to indicate that the response need to have the content-
 | 
	
		
			
				|  |  | -		 * encoding: gzip header.
 | 
	
		
			
				|  |  | -		 * We can only do this if the browser declares support. */
 | 
	
		
			
				|  |  | -		if ((accept_encoding = mg_get_header(conn, "Accept-Encoding"))
 | 
	
		
			
				|  |  | -		    != NULL) {
 | 
	
		
			
				|  |  | -			if (strstr(accept_encoding, "gzip") != NULL) {
 | 
	
		
			
				|  |  | -				mg_snprintf(conn,
 | 
	
		
			
				|  |  | -				            &truncated,
 | 
	
		
			
				|  |  | -				            gz_path,
 | 
	
		
			
				|  |  | -				            sizeof(gz_path),
 | 
	
		
			
				|  |  | -				            "%s.gz",
 | 
	
		
			
				|  |  | -				            filename);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -				if (truncated) {
 | 
	
		
			
				|  |  | -					goto interpret_cleanup;
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | +	/* If we can't find the actual file, look for the file
 | 
	
		
			
				|  |  | +	 * with the same name but a .gz extension. If we find it,
 | 
	
		
			
				|  |  | +	 * use that and set the gzipped flag in the file struct
 | 
	
		
			
				|  |  | +	 * to indicate that the response need to have the content-
 | 
	
		
			
				|  |  | +	 * encoding: gzip header.
 | 
	
		
			
				|  |  | +	 * We can only do this if the browser declares support. */
 | 
	
		
			
				|  |  | +	if ((accept_encoding = mg_get_header(conn, "Accept-Encoding")) != NULL) {
 | 
	
		
			
				|  |  | +		if (strstr(accept_encoding, "gzip") != NULL) {
 | 
	
		
			
				|  |  | +			mg_snprintf(
 | 
	
		
			
				|  |  | +			    conn, &truncated, gz_path, sizeof(gz_path), "%s.gz", filename);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -				if (mg_stat(conn, gz_path, filep)) {
 | 
	
		
			
				|  |  | -					if (filep) {
 | 
	
		
			
				|  |  | -						filep->gzipped = 1;
 | 
	
		
			
				|  |  | -						*is_found = 1;
 | 
	
		
			
				|  |  | -					}
 | 
	
		
			
				|  |  | -					/* Currently gz files can not be scripts. */
 | 
	
		
			
				|  |  | -					return;
 | 
	
		
			
				|  |  | +			if (truncated) {
 | 
	
		
			
				|  |  | +				goto interpret_cleanup;
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			if (mg_stat(conn, gz_path, filep)) {
 | 
	
		
			
				|  |  | +				if (filep) {
 | 
	
		
			
				|  |  | +					filep->gzipped = 1;
 | 
	
		
			
				|  |  | +					*is_found = 1;
 | 
	
		
			
				|  |  |  				}
 | 
	
		
			
				|  |  | +				/* Currently gz files can not be scripts. */
 | 
	
		
			
				|  |  | +				return;
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #if !defined(NO_CGI) || defined(USE_LUA) || defined(USE_DUKTAPE)
 | 
	
		
			
				|  |  | -		/* Support PATH_INFO for CGI scripts. */
 | 
	
		
			
				|  |  | -		for (p = filename + strlen(filename); p > filename + 1; p--) {
 | 
	
		
			
				|  |  | -			if (*p == '/') {
 | 
	
		
			
				|  |  | -				*p = '\0';
 | 
	
		
			
				|  |  | -				if ((0
 | 
	
		
			
				|  |  | +	/* Support PATH_INFO for CGI scripts. */
 | 
	
		
			
				|  |  | +	for (p = filename + strlen(filename); p > filename + 1; p--) {
 | 
	
		
			
				|  |  | +		if (*p == '/') {
 | 
	
		
			
				|  |  | +			*p = '\0';
 | 
	
		
			
				|  |  | +			if ((0
 | 
	
		
			
				|  |  |  #if !defined(NO_CGI)
 | 
	
		
			
				|  |  | -				     || match_prefix(conn->ctx->config[CGI_EXTENSIONS],
 | 
	
		
			
				|  |  | -				                     strlen(conn->ctx->config[CGI_EXTENSIONS]),
 | 
	
		
			
				|  |  | -				                     filename) > 0
 | 
	
		
			
				|  |  | +			     || match_prefix(conn->ctx->config[CGI_EXTENSIONS],
 | 
	
		
			
				|  |  | +			                     strlen(conn->ctx->config[CGI_EXTENSIONS]),
 | 
	
		
			
				|  |  | +			                     filename) > 0
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |  #if defined(USE_LUA)
 | 
	
		
			
				|  |  | -				     || match_prefix(
 | 
	
		
			
				|  |  | -				            conn->ctx->config[LUA_SCRIPT_EXTENSIONS],
 | 
	
		
			
				|  |  | -				            strlen(conn->ctx->config[LUA_SCRIPT_EXTENSIONS]),
 | 
	
		
			
				|  |  | -				            filename) > 0
 | 
	
		
			
				|  |  | +			     || match_prefix(conn->ctx->config[LUA_SCRIPT_EXTENSIONS],
 | 
	
		
			
				|  |  | +			                     strlen(
 | 
	
		
			
				|  |  | +			                         conn->ctx->config[LUA_SCRIPT_EXTENSIONS]),
 | 
	
		
			
				|  |  | +			                     filename) > 0
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |  #if defined(USE_DUKTAPE)
 | 
	
		
			
				|  |  | -				     || match_prefix(
 | 
	
		
			
				|  |  | -				            conn->ctx->config[DUKTAPE_SCRIPT_EXTENSIONS],
 | 
	
		
			
				|  |  | -				            strlen(
 | 
	
		
			
				|  |  | -				                conn->ctx->config[DUKTAPE_SCRIPT_EXTENSIONS]),
 | 
	
		
			
				|  |  | -				            filename) > 0
 | 
	
		
			
				|  |  | -#endif
 | 
	
		
			
				|  |  | -				     ) && mg_stat(conn, filename, filep)) {
 | 
	
		
			
				|  |  | -					/* Shift PATH_INFO block one character right, e.g.
 | 
	
		
			
				|  |  | -					 * "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
 | 
	
		
			
				|  |  | -					 * conn->path_info is pointing to the local variable "path"
 | 
	
		
			
				|  |  | -					 * declared in handle_request(), so PATH_INFO is not valid
 | 
	
		
			
				|  |  | -					 * after handle_request returns. */
 | 
	
		
			
				|  |  | -					conn->path_info = p + 1;
 | 
	
		
			
				|  |  | -					memmove(p + 2, p + 1, strlen(p + 1) + 1); /* +1 is for
 | 
	
		
			
				|  |  | -					                                           * trailing \0 */
 | 
	
		
			
				|  |  | -					p[1] = '/';
 | 
	
		
			
				|  |  | -					*is_script_resource = 1;
 | 
	
		
			
				|  |  | -					break;
 | 
	
		
			
				|  |  | -				} else {
 | 
	
		
			
				|  |  | -					*p = '/';
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | +			     || match_prefix(
 | 
	
		
			
				|  |  | +			            conn->ctx->config[DUKTAPE_SCRIPT_EXTENSIONS],
 | 
	
		
			
				|  |  | +			            strlen(conn->ctx->config[DUKTAPE_SCRIPT_EXTENSIONS]),
 | 
	
		
			
				|  |  | +			            filename) > 0
 | 
	
		
			
				|  |  | +#endif
 | 
	
		
			
				|  |  | +			     ) && mg_stat(conn, filename, filep)) {
 | 
	
		
			
				|  |  | +				/* Shift PATH_INFO block one character right, e.g.
 | 
	
		
			
				|  |  | +				 * "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
 | 
	
		
			
				|  |  | +				 * conn->path_info is pointing to the local variable "path"
 | 
	
		
			
				|  |  | +				 * declared in handle_request(), so PATH_INFO is not valid
 | 
	
		
			
				|  |  | +				 * after handle_request returns. */
 | 
	
		
			
				|  |  | +				conn->path_info = p + 1;
 | 
	
		
			
				|  |  | +				memmove(p + 2, p + 1, strlen(p + 1) + 1); /* +1 is for
 | 
	
		
			
				|  |  | +				                                           * trailing \0 */
 | 
	
		
			
				|  |  | +				p[1] = '/';
 | 
	
		
			
				|  |  | +				*is_script_resource = 1;
 | 
	
		
			
				|  |  | +				break;
 | 
	
		
			
				|  |  | +			} else {
 | 
	
		
			
				|  |  | +				*p = '/';
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |  #endif /* !defined(NO_CGI) || defined(USE_LUA) || defined(USE_DUKTAPE) */
 | 
	
		
			
				|  |  |  #endif /* !defined(NO_FILES) */
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  |  	return;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #if !defined(NO_FILES)
 | 
	
	
		
			
				|  | @@ -4414,7 +4400,9 @@ interpret_cleanup:
 | 
	
		
			
				|  |  |  	*filename = 0;
 | 
	
		
			
				|  |  |  	*is_found = 0;
 | 
	
		
			
				|  |  |  	*is_script_resource = 0;
 | 
	
		
			
				|  |  | -#endif
 | 
	
		
			
				|  |  | +	*is_websocket_request = 0;
 | 
	
		
			
				|  |  | +	*is_put_or_delete_request = 0;
 | 
	
		
			
				|  |  | +#endif /* !defined(NO_FILES) */
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /* Check whether full request is buffered. Return:
 | 
	
	
		
			
				|  | @@ -4744,11 +4732,7 @@ check_password(const char *method,
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	/* NOTE(lsm): due to a bug in MSIE, we do not compare the URI */
 | 
	
		
			
				|  |  | -	/* TODO(lsm): check for authentication timeout */
 | 
	
		
			
				|  |  | -	if (/* strcmp(dig->uri, c->ouri) != 0 || */
 | 
	
		
			
				|  |  | -	    strlen(response) != 32
 | 
	
		
			
				|  |  | -	    /* || now - strtoul(dig->nonce, NULL, 10) > 3600 */
 | 
	
		
			
				|  |  | -	    ) {
 | 
	
		
			
				|  |  | +	if (strlen(response) != 32) {
 | 
	
		
			
				|  |  |  		return 0;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -4831,11 +4815,13 @@ open_auth_file(struct mg_connection *conn, const char *path, struct file *filep)
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  /* Parsed Authorization header */
 | 
	
		
			
				|  |  |  struct ah {
 | 
	
		
			
				|  |  |  	char *user, *uri, *cnonce, *response, *qop, *nc, *nonce;
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  /* Return 1 on success. Always initializes the ah structure. */
 | 
	
		
			
				|  |  |  static int
 | 
	
		
			
				|  |  |  parse_auth_header(struct mg_connection *conn,
 | 
	
	
		
			
				|  | @@ -4901,12 +4887,7 @@ parse_auth_header(struct mg_connection *conn,
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #ifndef NO_NONCE_CHECK
 | 
	
		
			
				|  |  | -	/* Convert the nonce from the client to a number and check it. */
 | 
	
		
			
				|  |  | -	/* Server side nonce check is valuable in all situations but one: if the
 | 
	
		
			
				|  |  | -	 * server restarts frequently,
 | 
	
		
			
				|  |  | -	 * but the client should not see that, so the server should accept nonces
 | 
	
		
			
				|  |  | -	 * from
 | 
	
		
			
				|  |  | -	 * previous starts. */
 | 
	
		
			
				|  |  | +	/* Read the nonce from the response. */
 | 
	
		
			
				|  |  |  	if (ah->nonce == NULL) {
 | 
	
		
			
				|  |  |  		return 0;
 | 
	
		
			
				|  |  |  	}
 | 
	
	
		
			
				|  | @@ -4915,12 +4896,25 @@ parse_auth_header(struct mg_connection *conn,
 | 
	
		
			
				|  |  |  	if ((s == NULL) || (*s != 0)) {
 | 
	
		
			
				|  |  |  		return 0;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	/* Convert the nonce from the client to a number. */
 | 
	
		
			
				|  |  |  	nonce ^= (uintptr_t)(conn->ctx);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	/* The converted number corresponds to the time the nounce has been
 | 
	
		
			
				|  |  | +	 * created. This should not be earlier than the server start. */
 | 
	
		
			
				|  |  | +	/* Server side nonce check is valuable in all situations but one:
 | 
	
		
			
				|  |  | +	 * if the server restarts frequently, but the client should not see
 | 
	
		
			
				|  |  | +	 * that, so the server should accept nonces from previous starts. */
 | 
	
		
			
				|  |  | +	/* However, the reasonable default is to not accept a nonce from a
 | 
	
		
			
				|  |  | +	 * previous start, so if anyone changed the access rights between
 | 
	
		
			
				|  |  | +	 * two restarts, a new login is required. */
 | 
	
		
			
				|  |  |  	if (nonce < conn->ctx->start_time) {
 | 
	
		
			
				|  |  |  		/* nonce is from a previous start of the server and no longer valid
 | 
	
		
			
				|  |  |  		 * (replay attack?) */
 | 
	
		
			
				|  |  |  		return 0;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | +	/* Check if the nonce is too high, so it has not (yet) been used by the
 | 
	
		
			
				|  |  | +	 * server. */
 | 
	
		
			
				|  |  |  	if (nonce >= conn->ctx->start_time + conn->ctx->nonce_count) {
 | 
	
		
			
				|  |  |  		return 0;
 | 
	
		
			
				|  |  |  	}
 | 
	
	
		
			
				|  | @@ -4936,6 +4930,7 @@ parse_auth_header(struct mg_connection *conn,
 | 
	
		
			
				|  |  |  	return 1;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  static char *
 | 
	
		
			
				|  |  |  mg_fgets(char *buf, size_t size, struct file *filep, char **p)
 | 
	
		
			
				|  |  |  {
 | 
	
	
		
			
				|  | @@ -5711,6 +5706,7 @@ remove_directory(struct mg_connection *conn, const char *dir)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  			if (truncated) {
 | 
	
		
			
				|  |  |  				/* Do not delete anything shorter */
 | 
	
		
			
				|  |  | +				ok = 0;
 | 
	
		
			
				|  |  |  				continue;
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -5720,6 +5716,7 @@ remove_directory(struct mg_connection *conn, const char *dir)
 | 
	
		
			
				|  |  |  				       __func__,
 | 
	
		
			
				|  |  |  				       path,
 | 
	
		
			
				|  |  |  				       strerror(ERRNO));
 | 
	
		
			
				|  |  | +				ok = 0;
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  |  			if (de.file.membuf == NULL) {
 | 
	
		
			
				|  |  |  				/* file is not in memory */
 | 
	
	
		
			
				|  | @@ -5732,6 +5729,9 @@ remove_directory(struct mg_connection *conn, const char *dir)
 | 
	
		
			
				|  |  |  						ok = 0;
 | 
	
		
			
				|  |  |  					}
 | 
	
		
			
				|  |  |  				}
 | 
	
		
			
				|  |  | +			} else {
 | 
	
		
			
				|  |  | +				/* file is in memory. It can not be deleted. */
 | 
	
		
			
				|  |  | +				ok = 0;
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  		(void)closedir(dirp);
 | 
	
	
		
			
				|  | @@ -6197,14 +6197,29 @@ parse_http_headers(char **buf, struct mg_request_info *ri)
 | 
	
		
			
				|  |  |  static int
 | 
	
		
			
				|  |  |  is_valid_http_method(const char *method)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -	return !strcmp(method, "GET") || !strcmp(method, "POST")
 | 
	
		
			
				|  |  | -	       || !strcmp(method, "HEAD") || !strcmp(method, "CONNECT")
 | 
	
		
			
				|  |  | -	       || !strcmp(method, "PUT") || !strcmp(method, "DELETE")
 | 
	
		
			
				|  |  | -	       || !strcmp(method, "OPTIONS") || !strcmp(method, "PROPFIND")
 | 
	
		
			
				|  |  | -	       || !strcmp(method, "MKCOL") || !strcmp(method, "PATCH");
 | 
	
		
			
				|  |  | +	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) */
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	/* TRACE method is not supported for security reasons */
 | 
	
		
			
				|  |  | -	/* PATCH method (RFC 5789) only allowed for CGI/Lua/LSP and callbacks. */
 | 
	
		
			
				|  |  | +	       || !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) */
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /* Parse HTTP request, fill in mg_request_info structure.
 | 
	
	
		
			
				|  | @@ -7112,7 +7127,7 @@ mkcol(struct mg_connection *conn, const char *path)
 | 
	
		
			
				|  |  |  	} else if (rc == -1) {
 | 
	
		
			
				|  |  |  		if (errno == EEXIST) {
 | 
	
		
			
				|  |  |  			send_http_error(
 | 
	
		
			
				|  |  | -			    conn, 405, "Error:mkcol(%s): %s", path, strerror(ERRNO));
 | 
	
		
			
				|  |  | +			    conn, 405, "Error: mkcol(%s): %s", path, strerror(ERRNO));
 | 
	
		
			
				|  |  |  		} else if (errno == EACCES) {
 | 
	
		
			
				|  |  |  			send_http_error(
 | 
	
		
			
				|  |  |  			    conn, 403, "Error: mkcol(%s): %s", path, strerror(ERRNO));
 | 
	
	
		
			
				|  | @@ -7285,10 +7300,13 @@ delete_file(struct mg_connection *conn, const char *path)
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	if (de.file.is_directory) {
 | 
	
		
			
				|  |  | -		remove_directory(conn, path);
 | 
	
		
			
				|  |  | -		/* TODO (mid): remove_dir does not return success of the operation */
 | 
	
		
			
				|  |  | -		/* Assume delete is successful: Return 204 without content. */
 | 
	
		
			
				|  |  | -		send_http_error(conn, 204, "%s", "");
 | 
	
		
			
				|  |  | +		if (remove_directory(conn, path)) {
 | 
	
		
			
				|  |  | +			/* Delete is successful: Return 204 without content. */
 | 
	
		
			
				|  |  | +			send_http_error(conn, 204, "%s", "");
 | 
	
		
			
				|  |  | +		} else {
 | 
	
		
			
				|  |  | +			/* Delete is not successful: Return 500 (Server error). */
 | 
	
		
			
				|  |  | +			send_http_error(conn, 500, "Error: Could not delete %s", path);
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  |  		return;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 |