|  | @@ -1960,15 +1960,19 @@ static int mg_stat(const struct mg_connection *conn,
 | 
	
		
			
				|  |  |                     struct mg_file_stat *filep);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +#define MG_FOPEN_MODE_READ (1)
 | 
	
		
			
				|  |  | +#define MG_FOPEN_MODE_WRITE (2)
 | 
	
		
			
				|  |  | +#define MG_FOPEN_MODE_APPEND (4)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  /* mg_fopen will open a file either in memory or on the disk.
 | 
	
		
			
				|  |  |   * The input parameter path is a string in UTF-8 encoding.
 | 
	
		
			
				|  |  | - * The input parameter mode is the same as for fopen.
 | 
	
		
			
				|  |  | + * The input parameter mode is MG_FOPEN_MODE_*
 | 
	
		
			
				|  |  |   * Either fp or membuf will be set in the output struct file.
 | 
	
		
			
				|  |  |   * The function returns 1 on success, 0 on error. */
 | 
	
		
			
				|  |  |  static int
 | 
	
		
			
				|  |  |  mg_fopen(const struct mg_connection *conn,
 | 
	
		
			
				|  |  |           const char *path,
 | 
	
		
			
				|  |  | -         const char *mode,
 | 
	
		
			
				|  |  | +         int mode,
 | 
	
		
			
				|  |  |           struct mg_file *filep)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	int found;
 | 
	
	
		
			
				|  | @@ -1976,6 +1980,8 @@ mg_fopen(const struct mg_connection *conn,
 | 
	
		
			
				|  |  |  	if (!filep) {
 | 
	
		
			
				|  |  |  		return 0;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | +	filep->access.fp = NULL;
 | 
	
		
			
				|  |  | +	filep->access.membuf = NULL;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	if (!is_file_in_memory(conn, path)) {
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -1983,19 +1989,42 @@ mg_fopen(const struct mg_connection *conn,
 | 
	
		
			
				|  |  |  		* some fields like size and modification date with values */
 | 
	
		
			
				|  |  |  		found = mg_stat(conn, path, &(filep->stat));
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -/* TODO: if found=false, only call fopen if the file should
 | 
	
		
			
				|  |  | - * be created. If it should only be read, fail early. */
 | 
	
		
			
				|  |  | +		if ((mode == MG_FOPEN_MODE_READ) && (!found)) {
 | 
	
		
			
				|  |  | +			/* file does not exist and will not be created */
 | 
	
		
			
				|  |  | +			return 0;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #ifdef _WIN32
 | 
	
		
			
				|  |  |  		{
 | 
	
		
			
				|  |  | -			wchar_t wbuf[PATH_MAX], wmode[20];
 | 
	
		
			
				|  |  | +			wchar_t wbuf[PATH_MAX];
 | 
	
		
			
				|  |  |  			path_to_unicode(conn, path, wbuf, ARRAY_SIZE(wbuf));
 | 
	
		
			
				|  |  | -			MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, ARRAY_SIZE(wmode));
 | 
	
		
			
				|  |  | -			filep->access.fp = _wfopen(wbuf, wmode);
 | 
	
		
			
				|  |  | +			switch (mode) {
 | 
	
		
			
				|  |  | +			case MG_FOPEN_MODE_READ:
 | 
	
		
			
				|  |  | +				filep->access.fp = _wfopen(wbuf, L"rb");
 | 
	
		
			
				|  |  | +				break;
 | 
	
		
			
				|  |  | +			case MG_FOPEN_MODE_WRITE:
 | 
	
		
			
				|  |  | +				filep->access.fp = _wfopen(wbuf, L"wb");
 | 
	
		
			
				|  |  | +				break;
 | 
	
		
			
				|  |  | +			case MG_FOPEN_MODE_APPEND:
 | 
	
		
			
				|  |  | +				filep->access.fp = _wfopen(wbuf, L"ab");
 | 
	
		
			
				|  |  | +				break;
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  #else
 | 
	
		
			
				|  |  |  		/* Linux et al already use unicode. No need to convert. */
 | 
	
		
			
				|  |  | -		filep->access.fp = fopen(path, mode);
 | 
	
		
			
				|  |  | +		switch (mode) {
 | 
	
		
			
				|  |  | +		case MG_FOPEN_MODE_READ:
 | 
	
		
			
				|  |  | +			filep->access.fp = fopen(path, "r");
 | 
	
		
			
				|  |  | +			break;
 | 
	
		
			
				|  |  | +		case MG_FOPEN_MODE_WRITE:
 | 
	
		
			
				|  |  | +			filep->access.fp = fopen(path, "w");
 | 
	
		
			
				|  |  | +			break;
 | 
	
		
			
				|  |  | +		case MG_FOPEN_MODE_APPEND:
 | 
	
		
			
				|  |  | +			filep->access.fp = fopen(path, "a");
 | 
	
		
			
				|  |  | +			break;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |  		if (!found) {
 | 
	
		
			
				|  |  |  			/* File did not exist before fopen was called.
 | 
	
	
		
			
				|  | @@ -2395,8 +2424,10 @@ mg_cry(const struct mg_connection *conn, const char *fmt, ...)
 | 
	
		
			
				|  |  |  	    || (conn->ctx->callbacks.log_message(conn, buf) == 0)) {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  		if (conn->ctx->config[ERROR_LOG_FILE] != NULL) {
 | 
	
		
			
				|  |  | -			if (mg_fopen(conn, conn->ctx->config[ERROR_LOG_FILE], "a+", &fi)
 | 
	
		
			
				|  |  | -			    == 0) {
 | 
	
		
			
				|  |  | +			if (mg_fopen(conn,
 | 
	
		
			
				|  |  | +			             conn->ctx->config[ERROR_LOG_FILE],
 | 
	
		
			
				|  |  | +			             MG_FOPEN_MODE_APPEND,
 | 
	
		
			
				|  |  | +			             &fi) == 0) {
 | 
	
		
			
				|  |  |  				fi.access.fp = NULL;
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  |  		} else {
 | 
	
	
		
			
				|  | @@ -3910,7 +3941,7 @@ spawn_process(struct mg_connection *conn,
 | 
	
		
			
				|  |  |  			goto spawn_cleanup;
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		if (mg_fopen(conn, cmdline, "r", &file)) {
 | 
	
		
			
				|  |  | +		if (mg_fopen(conn, cmdline, MG_FOPEN_MODE_READ, &file)) {
 | 
	
		
			
				|  |  |  			p = (char *)file.access.membuf;
 | 
	
		
			
				|  |  |  			mg_fgets(buf, sizeof(buf), &file, &p);
 | 
	
		
			
				|  |  |  			(void)mg_fclose(&file.access); /* ignore error on read only file */
 | 
	
	
		
			
				|  | @@ -5856,7 +5887,7 @@ open_auth_file(struct mg_connection *conn,
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  		if (gpass != NULL) {
 | 
	
		
			
				|  |  |  			/* Use global passwords file */
 | 
	
		
			
				|  |  | -			if (!mg_fopen(conn, gpass, "r", filep)) {
 | 
	
		
			
				|  |  | +			if (!mg_fopen(conn, gpass, MG_FOPEN_MODE_READ, filep)) {
 | 
	
		
			
				|  |  |  #ifdef DEBUG
 | 
	
		
			
				|  |  |  				mg_cry(conn, "fopen(%s): %s", gpass, strerror(ERRNO));
 | 
	
		
			
				|  |  |  #endif
 | 
	
	
		
			
				|  | @@ -5876,7 +5907,7 @@ open_auth_file(struct mg_connection *conn,
 | 
	
		
			
				|  |  |  			            path,
 | 
	
		
			
				|  |  |  			            PASSWORDS_FILE_NAME);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -			if (truncated || !mg_fopen(conn, name, "r", filep)) {
 | 
	
		
			
				|  |  | +			if (truncated || !mg_fopen(conn, name, MG_FOPEN_MODE_READ, filep)) {
 | 
	
		
			
				|  |  |  #ifdef DEBUG
 | 
	
		
			
				|  |  |  				mg_cry(conn, "fopen(%s): %s", name, strerror(ERRNO));
 | 
	
		
			
				|  |  |  #endif
 | 
	
	
		
			
				|  | @@ -5897,7 +5928,7 @@ open_auth_file(struct mg_connection *conn,
 | 
	
		
			
				|  |  |  			            p,
 | 
	
		
			
				|  |  |  			            PASSWORDS_FILE_NAME);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -			if (truncated || !mg_fopen(conn, name, "r", filep)) {
 | 
	
		
			
				|  |  | +			if (truncated || !mg_fopen(conn, name, MG_FOPEN_MODE_READ, filep)) {
 | 
	
		
			
				|  |  |  #ifdef DEBUG
 | 
	
		
			
				|  |  |  				mg_cry(conn, "fopen(%s): %s", name, strerror(ERRNO));
 | 
	
		
			
				|  |  |  #endif
 | 
	
	
		
			
				|  | @@ -6105,7 +6136,10 @@ read_auth_file(struct mg_file *filep, struct read_auth_file_struct *workdata)
 | 
	
		
			
				|  |  |  				/* :# is a comment */
 | 
	
		
			
				|  |  |  				continue;
 | 
	
		
			
				|  |  |  			} else if (!strncmp(workdata->f_user + 1, "include=", 8)) {
 | 
	
		
			
				|  |  | -				if (mg_fopen(workdata->conn, workdata->f_user + 9, "r", &fp)) {
 | 
	
		
			
				|  |  | +				if (mg_fopen(workdata->conn,
 | 
	
		
			
				|  |  | +				             workdata->f_user + 9,
 | 
	
		
			
				|  |  | +				             MG_FOPEN_MODE_READ,
 | 
	
		
			
				|  |  | +				             &fp)) {
 | 
	
		
			
				|  |  |  					is_authorized = read_auth_file(&fp, workdata);
 | 
	
		
			
				|  |  |  					(void)mg_fclose(
 | 
	
		
			
				|  |  |  					    &fp.access); /* ignore error on read only file */
 | 
	
	
		
			
				|  | @@ -6213,7 +6247,8 @@ check_authorization(struct mg_connection *conn, const char *path)
 | 
	
		
			
				|  |  |  			            (int)filename_vec.len,
 | 
	
		
			
				|  |  |  			            filename_vec.ptr);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -			if (truncated || !mg_fopen(conn, fname, "r", &file)) {
 | 
	
		
			
				|  |  | +			if (truncated
 | 
	
		
			
				|  |  | +			    || !mg_fopen(conn, fname, MG_FOPEN_MODE_READ, &file)) {
 | 
	
		
			
				|  |  |  				mg_cry(conn,
 | 
	
		
			
				|  |  |  				       "%s: cannot open %s: %s",
 | 
	
		
			
				|  |  |  				       __func__,
 | 
	
	
		
			
				|  | @@ -6282,7 +6317,8 @@ is_authorized_for_put(struct mg_connection *conn)
 | 
	
		
			
				|  |  |  		const char *passfile = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE];
 | 
	
		
			
				|  |  |  		int ret = 0;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -		if (passfile != NULL && mg_fopen(conn, passfile, "r", &file)) {
 | 
	
		
			
				|  |  | +		if (passfile != NULL
 | 
	
		
			
				|  |  | +		    && mg_fopen(conn, passfile, MG_FOPEN_MODE_READ, &file)) {
 | 
	
		
			
				|  |  |  			ret = authorize(conn, &file);
 | 
	
		
			
				|  |  |  			(void)mg_fclose(&file.access); /* ignore error on read only file */
 | 
	
		
			
				|  |  |  		}
 | 
	
	
		
			
				|  | @@ -7197,7 +7233,7 @@ handle_static_file_request(struct mg_connection *conn,
 | 
	
		
			
				|  |  |  		encoding = "Content-Encoding: gzip\r\n";
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	if (!mg_fopen(conn, path, "rb", filep)) {
 | 
	
		
			
				|  |  | +	if (!mg_fopen(conn, path, MG_FOPEN_MODE_READ, filep)) {
 | 
	
		
			
				|  |  |  		send_http_error(conn,
 | 
	
		
			
				|  |  |  		                500,
 | 
	
		
			
				|  |  |  		                "Error: Cannot open file\nfopen(%s): %s",
 | 
	
	
		
			
				|  | @@ -7464,7 +7500,7 @@ mg_store_body(struct mg_connection *conn, const char *path)
 | 
	
		
			
				|  |  |  		return 0;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	if (mg_fopen(conn, path, "w", &fi) == 0) {
 | 
	
		
			
				|  |  | +	if (mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &fi) == 0) {
 | 
	
		
			
				|  |  |  		return -12;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -8593,7 +8629,9 @@ put_file(struct mg_connection *conn, const char *path)
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	/* A file should be created or overwritten. */
 | 
	
		
			
				|  |  | -	if (!mg_fopen(conn, path, "wb+", &file) || file.access.fp == NULL) {
 | 
	
		
			
				|  |  | +	/* TODO: Test if write or write+read is required. */
 | 
	
		
			
				|  |  | +	if (!mg_fopen(conn, path, MG_FOPEN_MODE_WRITE, &file)
 | 
	
		
			
				|  |  | +	    || file.access.fp == NULL) {
 | 
	
		
			
				|  |  |  		(void)mg_fclose(&file.access);
 | 
	
		
			
				|  |  |  		send_http_error(conn,
 | 
	
		
			
				|  |  |  		                500,
 | 
	
	
		
			
				|  | @@ -8774,7 +8812,7 @@ do_ssi_include(struct mg_connection *conn,
 | 
	
		
			
				|  |  |  		return;
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	if (!mg_fopen(conn, path, "rb", &file)) {
 | 
	
		
			
				|  |  | +	if (!mg_fopen(conn, path, MG_FOPEN_MODE_READ, &file)) {
 | 
	
		
			
				|  |  |  		mg_cry(conn,
 | 
	
		
			
				|  |  |  		       "Cannot open SSI #include: [%s]: fopen(%s): %s",
 | 
	
		
			
				|  |  |  		       tag,
 | 
	
	
		
			
				|  | @@ -8930,7 +8968,7 @@ handle_ssi_file_request(struct mg_connection *conn,
 | 
	
		
			
				|  |  |  		cors1 = cors2 = cors3 = "";
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	if (!mg_fopen(conn, path, "rb", filep)) {
 | 
	
		
			
				|  |  | +	if (!mg_fopen(conn, path, MG_FOPEN_MODE_READ, filep)) {
 | 
	
		
			
				|  |  |  		/* File exists (precondition for calling this function),
 | 
	
		
			
				|  |  |  		 * but can not be opened by the server. */
 | 
	
		
			
				|  |  |  		send_http_error(conn,
 | 
	
	
		
			
				|  | @@ -11426,8 +11464,10 @@ log_access(const struct mg_connection *conn)
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	if (conn->ctx->config[ACCESS_LOG_FILE] != NULL) {
 | 
	
		
			
				|  |  | -		if (mg_fopen(conn, conn->ctx->config[ACCESS_LOG_FILE], "a+", &fi)
 | 
	
		
			
				|  |  | -		    == 0) {
 | 
	
		
			
				|  |  | +		if (mg_fopen(conn,
 | 
	
		
			
				|  |  | +		             conn->ctx->config[ACCESS_LOG_FILE],
 | 
	
		
			
				|  |  | +		             MG_FOPEN_MODE_APPEND,
 | 
	
		
			
				|  |  | +		             &fi) == 0) {
 | 
	
		
			
				|  |  |  			fi.access.fp = NULL;
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  	} else {
 |