|  | @@ -7836,33 +7836,119 @@ parse_date_string(const char *datetime)
 | 
	
		
			
				|  |  |  #endif /* !NO_CACHING */
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -/* Protect against directory disclosure attack by removing '..',
 | 
	
		
			
				|  |  | - * excessive '/' and '\' characters */
 | 
	
		
			
				|  |  | +/* Pre-process URIs according to RFC + protect against directory disclosure attacks
 | 
	
		
			
				|  |  | + * by removing '..', excessive '/' and '\' characters */
 | 
	
		
			
				|  |  |  static void
 | 
	
		
			
				|  |  | -remove_double_dots_and_double_slashes(char *s)
 | 
	
		
			
				|  |  | -{
 | 
	
		
			
				|  |  | -	char *p = s;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	while ((s[0] == '.') && (s[1] == '.')) {
 | 
	
		
			
				|  |  | -		s++;
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | +remove_dot_segments(char *inout)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +	/* Windows backend protection (https://tools.ietf.org/html/rfc3986#section-7.3):
 | 
	
		
			
				|  |  | +	 * Replace backslash in URI by slash */
 | 
	
		
			
				|  |  | +	char *in_copy = mg_strdup(inout);
 | 
	
		
			
				|  |  | +	char *out_begin = inout;
 | 
	
		
			
				|  |  | +	char *out_end = inout;
 | 
	
		
			
				|  |  | +	char *in = in_copy;
 | 
	
		
			
				|  |  | +	
 | 
	
		
			
				|  |  | +	while (*in) {
 | 
	
		
			
				|  |  | +		if (*in == '\\') {
 | 
	
		
			
				|  |  | +			*in = '/';
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		in++;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	/* Algorithm "remove_dot_segments" from https://tools.ietf.org/html/rfc3986#section-5.2.4 */
 | 
	
		
			
				|  |  | +	/* Step 1:
 | 
	
		
			
				|  |  | +	 * The input buffer is initialized.
 | 
	
		
			
				|  |  | +	 * The output buffer is initialized to the empty string.
 | 
	
		
			
				|  |  | +	 */
 | 
	
		
			
				|  |  | +	in = in_copy;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	while (*s != '\0') {
 | 
	
		
			
				|  |  | -		*p++ = *s++;
 | 
	
		
			
				|  |  | -		if ((s[-1] == '/') || (s[-1] == '\\')) {
 | 
	
		
			
				|  |  | -			/* Skip all following slashes, backslashes and double-dots */
 | 
	
		
			
				|  |  | -			while (s[0] != '\0') {
 | 
	
		
			
				|  |  | -				if ((s[0] == '/') || (s[0] == '\\')) {
 | 
	
		
			
				|  |  | -					s++;
 | 
	
		
			
				|  |  | -				} else if ((s[0] == '.') && (s[1] == '.')) {
 | 
	
		
			
				|  |  | -					s += 2;
 | 
	
		
			
				|  |  | -				} else {
 | 
	
		
			
				|  |  | -					break;
 | 
	
		
			
				|  |  | -				}
 | 
	
		
			
				|  |  | +	/* Step 2:
 | 
	
		
			
				|  |  | +	 * While the input buffer is not empty, loop as follows:
 | 
	
		
			
				|  |  | +	 */
 | 
	
		
			
				|  |  | +	while (*in) {
 | 
	
		
			
				|  |  | +		/* Step 2a:
 | 
	
		
			
				|  |  | +		 * If the input buffer begins with a prefix of "../" or "./",
 | 
	
		
			
				|  |  | +		 * then remove that prefix from the input buffer;
 | 
	
		
			
				|  |  | +		 */
 | 
	
		
			
				|  |  | +		if (!strncmp(in, "../", 3)) {
 | 
	
		
			
				|  |  | +			in += 3;
 | 
	
		
			
				|  |  | +		} 
 | 
	
		
			
				|  |  | +		else if (!strncmp(in, "./", 2)) {
 | 
	
		
			
				|  |  | +			in += 2;
 | 
	
		
			
				|  |  | +		} 
 | 
	
		
			
				|  |  | +		/* otherwise */
 | 
	
		
			
				|  |  | +		/* Step 2b:
 | 
	
		
			
				|  |  | +		 * if the input buffer begins with a prefix of "/./" or "/.",
 | 
	
		
			
				|  |  | +		 * where "." is a complete path segment, then replace that
 | 
	
		
			
				|  |  | +		 * prefix with "/" in the input buffer;
 | 
	
		
			
				|  |  | +		 */
 | 
	
		
			
				|  |  | +		else if (!strncmp(in, "/./", 3)) {
 | 
	
		
			
				|  |  | +			in += 2;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		else if (!strcmp(in, "/.")) {
 | 
	
		
			
				|  |  | +			in[1] = 0;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		/* otherwise */
 | 
	
		
			
				|  |  | +		/* Step 2c:
 | 
	
		
			
				|  |  | +		 * if the input buffer begins with a prefix of "/../" or "/..",
 | 
	
		
			
				|  |  | +		 * where ".." is a complete path segment, then replace that
 | 
	
		
			
				|  |  | +		 * prefix with "/" in the input buffer and remove the last
 | 
	
		
			
				|  |  | +		 * segment and its preceding "/" (if any) from the output
 | 
	
		
			
				|  |  | +		 * buffer;
 | 
	
		
			
				|  |  | +		 */
 | 
	
		
			
				|  |  | +		else if (!strncmp(in, "/../", 4)) {
 | 
	
		
			
				|  |  | +			in += 3;
 | 
	
		
			
				|  |  | +			if (out_begin != out_end) {
 | 
	
		
			
				|  |  | +				/* remove last segment */
 | 
	
		
			
				|  |  | +				do {
 | 
	
		
			
				|  |  | +					out_end--;
 | 
	
		
			
				|  |  | +					*out_end = 0;
 | 
	
		
			
				|  |  | +				} while ((out_begin != out_end) && (*out_end != '/'));
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		else if (!strcmp(in, "/..")) {
 | 
	
		
			
				|  |  | +			in[1] = 0;
 | 
	
		
			
				|  |  | +			if (out_begin != out_end) {
 | 
	
		
			
				|  |  | +				/* remove last segment */
 | 
	
		
			
				|  |  | +				do {
 | 
	
		
			
				|  |  | +					out_end--;
 | 
	
		
			
				|  |  | +					*out_end = 0;
 | 
	
		
			
				|  |  | +				} while ((out_begin != out_end) && (*out_end != '/'));
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  | +		/* otherwise */
 | 
	
		
			
				|  |  | +		/* Step 2d:
 | 
	
		
			
				|  |  | +		 * if the input buffer consists only of "." or "..", then remove
 | 
	
		
			
				|  |  | +		 * that from the input buffer;
 | 
	
		
			
				|  |  | +		 */
 | 
	
		
			
				|  |  | +		else if (!strcmp(in, ".") || !strcmp(in, "..")) {
 | 
	
		
			
				|  |  | +			*in = 0;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		/* otherwise */
 | 
	
		
			
				|  |  | +		/* Step 2d:
 | 
	
		
			
				|  |  | +		 * move the first path segment in the input buffer to the end of
 | 
	
		
			
				|  |  | +		 * the output buffer, including the initial "/" character (if
 | 
	
		
			
				|  |  | +		 * any) and any subsequent characters up to, but not including,
 | 
	
		
			
				|  |  | +		 * the next "/" character or the end of the input buffer.
 | 
	
		
			
				|  |  | +		 */
 | 
	
		
			
				|  |  | +		else {
 | 
	
		
			
				|  |  | +			do {
 | 
	
		
			
				|  |  | +				*out_end = *in;
 | 
	
		
			
				|  |  | +				out_end++;
 | 
	
		
			
				|  |  | +				in++;
 | 
	
		
			
				|  |  | +			} while ((*in != 0) && (*in != '/'));
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | -	*p = '\0';
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	/* Step 3:
 | 
	
		
			
				|  |  | +	 * Finally, the output buffer is returned as the result of
 | 
	
		
			
				|  |  | +	 * remove_dot_segments.
 | 
	
		
			
				|  |  | +	 */
 | 
	
		
			
				|  |  | +	/* Terminate output */
 | 
	
		
			
				|  |  | +	*out_end = 0;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	/* Free temporary copies */
 | 
	
		
			
				|  |  | +	mg_free(in_copy);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -13814,7 +13900,7 @@ handle_request(struct mg_connection *conn)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	/* 1.4. clean URIs, so a path like allowed_dir/../forbidden_file is
 | 
	
		
			
				|  |  |  	 * not possible */
 | 
	
		
			
				|  |  | -	remove_double_dots_and_double_slashes((char *)ri->local_uri);
 | 
	
		
			
				|  |  | +	remove_dot_segments((char *)ri->local_uri);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	/* step 1. completed, the url is known now */
 | 
	
		
			
				|  |  |  	uri_len = (int)strlen(ri->local_uri);
 |