|  | @@ -1032,31 +1032,14 @@ static void to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len) {
 | 
	
		
			
				|  |  |    // Point p to the end of the file name
 | 
	
		
			
				|  |  |    p = buf + strlen(buf) - 1;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  // Trim trailing backslash character
 | 
	
		
			
				|  |  | -  while (p > buf && *p == '\\' && p[-1] != ':') {
 | 
	
		
			
				|  |  | -    *p-- = '\0';
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -   // Protect from CGI code disclosure.
 | 
	
		
			
				|  |  | -   // This is very nasty hole. Windows happily opens files with
 | 
	
		
			
				|  |  | -   // some garbage in the end of file name. So fopen("a.cgi    ", "r")
 | 
	
		
			
				|  |  | -   // actually opens "a.cgi", and does not return an error!
 | 
	
		
			
				|  |  | -  if (*p == 0x20 ||               // No space at the end
 | 
	
		
			
				|  |  | -      (*p == 0x2e && p > buf) ||  // No '.' but allow '.' as full path
 | 
	
		
			
				|  |  | -      *p == 0x2b ||               // No '+'
 | 
	
		
			
				|  |  | -      (*p & ~0x7f)) {             // And generally no non-ASCII chars
 | 
	
		
			
				|  |  | -    (void) fprintf(stderr, "Rejecting suspicious path: [%s]", buf);
 | 
	
		
			
				|  |  | +  // Convert to Unicode and back. If doubly-converted string does not
 | 
	
		
			
				|  |  | +  // match the original, something is fishy, reject.
 | 
	
		
			
				|  |  | +  memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
 | 
	
		
			
				|  |  | +  MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
 | 
	
		
			
				|  |  | +  WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
 | 
	
		
			
				|  |  | +                      NULL, NULL);
 | 
	
		
			
				|  |  | +  if (strcmp(buf, buf2) != 0) {
 | 
	
		
			
				|  |  |      wbuf[0] = L'\0';
 | 
	
		
			
				|  |  | -  } else {
 | 
	
		
			
				|  |  | -    // Convert to Unicode and back. If doubly-converted string does not
 | 
	
		
			
				|  |  | -    // match the original, something is fishy, reject.
 | 
	
		
			
				|  |  | -    memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
 | 
	
		
			
				|  |  | -    MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
 | 
	
		
			
				|  |  | -    WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
 | 
	
		
			
				|  |  | -                        NULL, NULL);
 | 
	
		
			
				|  |  | -    if (strcmp(buf, buf2) != 0) {
 | 
	
		
			
				|  |  | -      wbuf[0] = L'\0';
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -1126,6 +1109,16 @@ static int mg_rename(const char* oldname, const char* newname) {
 | 
	
		
			
				|  |  |    return MoveFileW(woldbuf, wnewbuf) ? 0 : -1;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +// Windows happily opens files with some garbage at the end of file name.
 | 
	
		
			
				|  |  | +// For example, fopen("a.cgi    ", "r") on Windows successfully opens
 | 
	
		
			
				|  |  | +// "a.cgi", despite one would expect an error back.
 | 
	
		
			
				|  |  | +// This function returns non-0 if path ends with some garbage.
 | 
	
		
			
				|  |  | +static int path_cannot_disclose_cgi(const char *path) {
 | 
	
		
			
				|  |  | +  static const char *allowed_last_characters = "_-";
 | 
	
		
			
				|  |  | +  int last = path[strlen(path) - 1];
 | 
	
		
			
				|  |  | +  return isalnum(last) || strchr(allowed_last_characters, last) != NULL;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  static int mg_stat(struct mg_connection *conn, const char *path,
 | 
	
		
			
				|  |  |                     struct file *filep) {
 | 
	
		
			
				|  |  |    wchar_t wbuf[PATH_MAX];
 | 
	
	
		
			
				|  | @@ -1139,6 +1132,12 @@ static int mg_stat(struct mg_connection *conn, const char *path,
 | 
	
		
			
				|  |  |            info.ftLastWriteTime.dwLowDateTime,
 | 
	
		
			
				|  |  |            info.ftLastWriteTime.dwHighDateTime);
 | 
	
		
			
				|  |  |        filep->is_directory = info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
 | 
	
		
			
				|  |  | +      // If file name is fishy, reset the file structure and return error.
 | 
	
		
			
				|  |  | +      // Note it is important to reset, not just return the error, cause
 | 
	
		
			
				|  |  | +      // functions like is_file_opened() check the struct.
 | 
	
		
			
				|  |  | +      if (!filep->is_directory && !path_cannot_disclose_cgi(path)) {
 | 
	
		
			
				|  |  | +        memset(filep, 0, sizeof(*filep));
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 |