Commit 557d1f4c authored by Sergey Lyubka's avatar Sergey Lyubka

Added MG_OPEN_FILE message

parent 2539a473
...@@ -224,6 +224,12 @@ static void *mongoose_callback(enum mg_event ev, struct mg_connection *conn) { ...@@ -224,6 +224,12 @@ static void *mongoose_callback(enum mg_event ev, struct mg_connection *conn) {
printf("%s\n", (const char *) mg_get_request_info(conn)->ev_data); printf("%s\n", (const char *) mg_get_request_info(conn)->ev_data);
} }
if (ev == MG_OPEN_FILE &&
!memcmp(mg_get_request_info(conn)->ev_data, "./x", 3)) {
mg_get_request_info(conn)->ev_data = (void *) (int) 5;
return "hello";
}
// Returning NULL marks request as not handled, signalling mongoose to // Returning NULL marks request as not handled, signalling mongoose to
// proceed with handling it. // proceed with handling it.
return NULL; return NULL;
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
#if defined(_WIN32) #if defined(_WIN32)
#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005 #define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005
#else #else
#ifdef __linux__ #ifdef __linux__
#define _XOPEN_SOURCE 600 // For flockfile() on Linux #define _XOPEN_SOURCE 600 // For flockfile() on Linux
#endif #endif
...@@ -144,7 +144,6 @@ typedef DWORD pthread_t; ...@@ -144,7 +144,6 @@ typedef DWORD pthread_t;
static int pthread_mutex_lock(pthread_mutex_t *); static int pthread_mutex_lock(pthread_mutex_t *);
static int pthread_mutex_unlock(pthread_mutex_t *); static int pthread_mutex_unlock(pthread_mutex_t *);
static FILE *mg_fopen(const char *path, const char *mode);
#if defined(HAVE_STDINT) #if defined(HAVE_STDINT)
#include <stdint.h> #include <stdint.h>
...@@ -203,7 +202,6 @@ typedef struct DIR { ...@@ -203,7 +202,6 @@ typedef struct DIR {
#define O_BINARY 0 #define O_BINARY 0
#endif // O_BINARY #endif // O_BINARY
#define closesocket(a) close(a) #define closesocket(a) close(a)
#define mg_fopen(x, y) fopen(x, y)
#define mg_mkdir(x, y) mkdir(x, y) #define mg_mkdir(x, y) mkdir(x, y)
#define mg_remove(x) remove(x) #define mg_remove(x) remove(x)
#define mg_rename(x, y) rename(x, y) #define mg_rename(x, y) rename(x, y)
...@@ -412,12 +410,14 @@ struct vec { ...@@ -412,12 +410,14 @@ struct vec {
size_t len; size_t len;
}; };
// Structure used by mg_stat() function. Uses 64 bit file length. struct file {
struct mgstat { int is_directory;
int is_directory; // Directory marker time_t modification_time;
int64_t size; // File size int64_t size;
time_t mtime; // Modification time FILE *fp;
const char *membuf; // Non-NULL if file data is in memory
}; };
#define STRUCT_FILE_INITIALIZER {0, 0, 0, NULL, NULL}
// Describes listening socket, or socket which was accept()-ed by the master // Describes listening socket, or socket which was accept()-ed by the master
// thread and queued for future handling by the worker thread. // thread and queued for future handling by the worker thread.
...@@ -519,6 +519,41 @@ static void *call_user(struct mg_connection *conn, enum mg_event event) { ...@@ -519,6 +519,41 @@ static void *call_user(struct mg_connection *conn, enum mg_event event) {
NULL : conn->ctx->user_callback(event, conn); NULL : conn->ctx->user_callback(event, conn);
} }
static int is_file_in_memory(struct mg_connection *conn, const char *path,
struct file *filep) {
conn->request_info.ev_data = (void *) path;
if ((filep->membuf = call_user(conn, MG_OPEN_FILE)) != NULL) {
filep->size = (int) conn->request_info.ev_data;
}
return filep->membuf != NULL;
}
static int is_file_opened(const struct file *filep) {
return filep->membuf != NULL || filep->fp != NULL;
}
static int mg_fopen(struct mg_connection *conn, const char *path,
const char *mode, struct file *filep) {
if (!is_file_in_memory(conn, path, filep)) {
#ifdef _WIN32
wchar_t wbuf[PATH_MAX], wmode[20];
to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, ARRAY_SIZE(wmode));
filep->fp = _wfopen(wbuf, wmode);
#else
filep->fp = fopen(path, mode);
#endif
}
return is_file_opened(filep);
}
static void mg_fclose(struct file *filep) {
if (filep != NULL && filep->fp != NULL) {
fclose(filep->fp);
}
}
static int get_option_index(const char *name) { static int get_option_index(const char *name) {
int i; int i;
...@@ -577,7 +612,7 @@ static void cry(struct mg_connection *conn, const char *fmt, ...) { ...@@ -577,7 +612,7 @@ static void cry(struct mg_connection *conn, const char *fmt, ...) {
conn->request_info.ev_data = buf; conn->request_info.ev_data = buf;
if (call_user(conn, MG_EVENT_LOG) == NULL) { if (call_user(conn, MG_EVENT_LOG) == NULL) {
fp = conn->ctx == NULL || conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL : fp = conn->ctx == NULL || conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL :
mg_fopen(conn->ctx->config[ERROR_LOG_FILE], "a+"); fopen(conn->ctx->config[ERROR_LOG_FILE], "a+");
if (fp != NULL) { if (fp != NULL) {
flockfile(fp); flockfile(fp);
...@@ -592,12 +627,10 @@ static void cry(struct mg_connection *conn, const char *fmt, ...) { ...@@ -592,12 +627,10 @@ static void cry(struct mg_connection *conn, const char *fmt, ...) {
conn->request_info.uri); conn->request_info.uri);
} }
(void) fprintf(fp, "%s", buf); fprintf(fp, "%s", buf);
fputc('\n', fp); fputc('\n', fp);
funlockfile(fp); funlockfile(fp);
if (fp != stderr) { fclose(fp);
fclose(fp);
}
} }
} }
conn->request_info.ev_data = NULL; conn->request_info.ev_data = NULL;
...@@ -1077,33 +1110,23 @@ static int mg_rename(const char* oldname, const char* newname) { ...@@ -1077,33 +1110,23 @@ static int mg_rename(const char* oldname, const char* newname) {
return MoveFileW(woldbuf, wnewbuf) ? 0 : -1; return MoveFileW(woldbuf, wnewbuf) ? 0 : -1;
} }
static int mg_stat(struct mg_connection *conn, const char *path,
static FILE *mg_fopen(const char *path, const char *mode) { struct file *filep) {
wchar_t wbuf[PATH_MAX], wmode[20];
to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, ARRAY_SIZE(wmode));
return _wfopen(wbuf, wmode);
}
static int mg_stat(const char *path, struct mgstat *stp) {
int ok = -1; // Error
wchar_t wbuf[PATH_MAX]; wchar_t wbuf[PATH_MAX];
WIN32_FILE_ATTRIBUTE_DATA info; WIN32_FILE_ATTRIBUTE_DATA info;
to_unicode(path, wbuf, ARRAY_SIZE(wbuf)); if (!is_file_in_memory(conn, path, filep)) {
to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0) { if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0) {
stp->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh); filep->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh);
stp->mtime = SYS2UNIX_TIME(info.ftLastWriteTime.dwLowDateTime, filep->modification_time = SYS2UNIX_TIME(
info.ftLastWriteTime.dwHighDateTime); info.ftLastWriteTime.dwLowDateTime,
stp->is_directory = info.ftLastWriteTime.dwHighDateTime);
info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; filep->is_directory = info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
ok = 0; // Success }
} }
return ok; return filep->membuf != NULL || filep->modification_time != 0;
} }
static int mg_remove(const char *path) { static int mg_remove(const char *path) {
...@@ -1193,7 +1216,7 @@ static struct dirent *readdir(DIR *dir) { ...@@ -1193,7 +1216,7 @@ static struct dirent *readdir(DIR *dir) {
return result; return result;
} }
#define set_close_on_exec(fd) // No FD_CLOEXEC on Windows #define set_close_on_exec(x) // No FD_CLOEXEC on Windows
int mg_start_thread(mg_thread_func_t f, void *p) { int mg_start_thread(mg_thread_func_t f, void *p) {
return _beginthread((void (__cdecl *)(void *)) f, 0, p) == -1L ? -1 : 0; return _beginthread((void (__cdecl *)(void *)) f, 0, p) == -1L ? -1 : 0;
...@@ -1293,24 +1316,21 @@ static int set_non_blocking_mode(SOCKET sock) { ...@@ -1293,24 +1316,21 @@ static int set_non_blocking_mode(SOCKET sock) {
} }
#else #else
static int mg_stat(const char *path, struct mgstat *stp) { static int mg_stat(struct mg_connection *conn, const char *path,
struct file *filep) {
struct stat st; struct stat st;
int ok;
if (stat(path, &st) == 0) { if (!is_file_in_memory(conn, path, filep) && !stat(path, &st)) {
ok = 0; filep->size = st.st_size;
stp->size = st.st_size; filep->modification_time = st.st_mtime;
stp->mtime = st.st_mtime; filep->is_directory = S_ISDIR(st.st_mode);
stp->is_directory = S_ISDIR(st.st_mode);
} else {
ok = -1;
} }
return ok; return filep->membuf != NULL || filep->modification_time != (time_t) 0;
} }
static void set_close_on_exec(int fd) { static void set_close_on_exec(int fd) {
(void) fcntl(fd, F_SETFD, FD_CLOEXEC); fcntl(fd, F_SETFD, FD_CLOEXEC);
} }
int mg_start_thread(mg_thread_func_t func, void *param) { int mg_start_thread(mg_thread_func_t func, void *param) {
...@@ -1691,35 +1711,35 @@ int mg_get_cookie(const struct mg_connection *conn, const char *cookie_name, ...@@ -1691,35 +1711,35 @@ int mg_get_cookie(const struct mg_connection *conn, const char *cookie_name,
return len; return len;
} }
static int convert_uri_to_file_name(struct mg_connection *conn, char *buf, static void convert_uri_to_file_name(struct mg_connection *conn, char *buf,
size_t buf_len, struct mgstat *st) { size_t buf_len, struct file *filep) {
struct vec a, b; struct vec a, b;
const char *rewrite, *uri = conn->request_info.uri; const char *rewrite, *uri = conn->request_info.uri;
char *p; char *p;
int match_len, stat_result; int match_len;
buf_len--; // This is because memmove() for PATH_INFO may shift part // Using buf_len - 1 because memmove() for PATH_INFO may shift part
// of the path one byte on the right. // of the path one byte on the right.
mg_snprintf(conn, buf, buf_len, "%s%s", conn->ctx->config[DOCUMENT_ROOT], mg_snprintf(conn, buf, buf_len - 1, "%s%s", conn->ctx->config[DOCUMENT_ROOT],
uri); uri);
rewrite = conn->ctx->config[REWRITE]; rewrite = conn->ctx->config[REWRITE];
while ((rewrite = next_option(rewrite, &a, &b)) != NULL) { while ((rewrite = next_option(rewrite, &a, &b)) != NULL) {
if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) { if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) {
mg_snprintf(conn, buf, buf_len, "%.*s%s", (int) b.len, b.ptr, mg_snprintf(conn, buf, buf_len - 1, "%.*s%s", (int) b.len, b.ptr,
uri + match_len); uri + match_len);
break; break;
} }
} }
if ((stat_result = mg_stat(buf, st)) != 0) { if (!mg_stat(conn, buf, filep)) {
// Support PATH_INFO for CGI scripts. // Support PATH_INFO for CGI scripts.
for (p = buf + strlen(buf); p > buf + 1; p--) { for (p = buf + strlen(buf); p > buf + 1; p--) {
if (*p == '/') { if (*p == '/') {
*p = '\0'; *p = '\0';
if (match_prefix(conn->ctx->config[CGI_EXTENSIONS], if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0 && strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0 &&
(stat_result = mg_stat(buf, st)) == 0) { mg_stat(conn, buf, filep)) {
// Shift PATH_INFO block one character right, e.g. // Shift PATH_INFO block one character right, e.g.
// "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00" // "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
// conn->path_info is pointing to the local variable "path" declared // conn->path_info is pointing to the local variable "path" declared
...@@ -1731,13 +1751,10 @@ static int convert_uri_to_file_name(struct mg_connection *conn, char *buf, ...@@ -1731,13 +1751,10 @@ static int convert_uri_to_file_name(struct mg_connection *conn, char *buf,
break; break;
} else { } else {
*p = '/'; *p = '/';
stat_result = -1;
} }
} }
} }
} }
return stat_result;
} }
static int sslize(struct mg_connection *conn, SSL_CTX *s, int (*func)(SSL *)) { static int sslize(struct mg_connection *conn, SSL_CTX *s, int (*func)(SSL *)) {
...@@ -2179,34 +2196,29 @@ static int check_password(const char *method, const char *ha1, const char *uri, ...@@ -2179,34 +2196,29 @@ static int check_password(const char *method, const char *ha1, const char *uri,
// Use the global passwords file, if specified by auth_gpass option, // Use the global passwords file, if specified by auth_gpass option,
// or search for .htpasswd in the requested directory. // or search for .htpasswd in the requested directory.
static FILE *open_auth_file(struct mg_connection *conn, const char *path) { static void open_auth_file(struct mg_connection *conn, const char *path,
struct mg_context *ctx = conn->ctx; struct file *filep) {
char name[PATH_MAX]; char name[PATH_MAX];
const char *p, *e; const char *p, *e, *gpass = conn->ctx->config[GLOBAL_PASSWORDS_FILE];
struct mgstat st;
FILE *fp;
if (ctx->config[GLOBAL_PASSWORDS_FILE] != NULL) { if (gpass != NULL) {
// Use global passwords file // Use global passwords file
fp = mg_fopen(ctx->config[GLOBAL_PASSWORDS_FILE], "r"); if (!mg_fopen(conn, gpass, "r", filep)) {
if (fp == NULL) cry(conn, "fopen(%s): %s", gpass, strerror(ERRNO));
cry(fc(ctx), "fopen(%s): %s", }
ctx->config[GLOBAL_PASSWORDS_FILE], strerror(ERRNO)); } else if (mg_stat(conn, path, filep) && filep->is_directory) {
} else if (!mg_stat(path, &st) && st.is_directory) { mg_snprintf(conn, name, sizeof(name), "%s%c%s",
(void) mg_snprintf(conn, name, sizeof(name), "%s%c%s", path, '/', PASSWORDS_FILE_NAME);
path, '/', PASSWORDS_FILE_NAME); mg_fopen(conn, name, "r", filep);
fp = mg_fopen(name, "r");
} else { } else {
// Try to find .htpasswd in requested directory. // Try to find .htpasswd in requested directory.
for (p = path, e = p + strlen(p) - 1; e > p; e--) for (p = path, e = p + strlen(p) - 1; e > p; e--)
if (e[0] == '/') if (e[0] == '/')
break; break;
(void) mg_snprintf(conn, name, sizeof(name), "%.*s%c%s", mg_snprintf(conn, name, sizeof(name), "%.*s%c%s",
(int) (e - p), p, '/', PASSWORDS_FILE_NAME); (int) (e - p), p, '/', PASSWORDS_FILE_NAME);
fp = mg_fopen(name, "r"); mg_fopen(conn, name, "r", filep);
} }
return fp;
} }
// Parsed Authorization header // Parsed Authorization header
...@@ -2278,17 +2290,36 @@ static int parse_auth_header(struct mg_connection *conn, char *buf, ...@@ -2278,17 +2290,36 @@ static int parse_auth_header(struct mg_connection *conn, char *buf,
return 1; return 1;
} }
static char *mg_fgets(char *buf, size_t size, struct file *filep, char **p) {
char *eof;
size_t len;
if (filep->membuf != NULL && *p != NULL) {
eof = memchr(*p, '\n', &filep->membuf[filep->size] - *p);
len = (size_t) (eof - *p) > size - 1 ? size - 1 : (size_t) (eof - *p);
memcpy(buf, *p, len);
buf[len] = '\0';
*p = eof;
return eof;
} else if (filep->fp != NULL) {
return fgets(buf, size, filep->fp);
} else {
return NULL;
}
}
// Authorize against the opened passwords file. Return 1 if authorized. // Authorize against the opened passwords file. Return 1 if authorized.
static int authorize(struct mg_connection *conn, FILE *fp) { static int authorize(struct mg_connection *conn, struct file *filep) {
struct ah ah; struct ah ah;
char line[256], f_user[256], ha1[256], f_domain[256], buf[MG_BUF_LEN]; char line[256], f_user[256], ha1[256], f_domain[256], buf[MG_BUF_LEN], *p;
if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) { if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) {
return 0; return 0;
} }
// Loop over passwords file // Loop over passwords file
while (fgets(line, sizeof(line), fp) != NULL) { p = (char *) filep->membuf;
while (mg_fgets(line, sizeof(line), filep, &p) != NULL) {
if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) { if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) {
continue; continue;
} }
...@@ -2304,34 +2335,31 @@ static int authorize(struct mg_connection *conn, FILE *fp) { ...@@ -2304,34 +2335,31 @@ static int authorize(struct mg_connection *conn, FILE *fp) {
// Return 1 if request is authorised, 0 otherwise. // Return 1 if request is authorised, 0 otherwise.
static int check_authorization(struct mg_connection *conn, const char *path) { static int check_authorization(struct mg_connection *conn, const char *path) {
FILE *fp;
char fname[PATH_MAX]; char fname[PATH_MAX];
struct vec uri_vec, filename_vec; struct vec uri_vec, filename_vec;
const char *list; const char *list;
int authorized; struct file file = STRUCT_FILE_INITIALIZER;
int authorized = 1;
fp = NULL;
authorized = 1;
list = conn->ctx->config[PROTECT_URI]; list = conn->ctx->config[PROTECT_URI];
while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) { while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) {
if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) { if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) {
mg_snprintf(conn, fname, sizeof(fname), "%.*s", mg_snprintf(conn, fname, sizeof(fname), "%.*s",
(int) filename_vec.len, filename_vec.ptr); (int) filename_vec.len, filename_vec.ptr);
if ((fp = mg_fopen(fname, "r")) == NULL) { if (!mg_fopen(conn, fname, "r", &file)) {
cry(conn, "%s: cannot open %s: %s", __func__, fname, strerror(errno)); cry(conn, "%s: cannot open %s: %s", __func__, fname, strerror(errno));
} }
break; break;
} }
} }
if (fp == NULL) { if (!is_file_opened(&file)) {
fp = open_auth_file(conn, path); open_auth_file(conn, path, &file);
} }
if (fp != NULL) { if (is_file_opened(&file)) {
authorized = authorize(conn, fp); authorized = authorize(conn, &file);
(void) fclose(fp); mg_fclose(&file);
} }
return authorized; return authorized;
...@@ -2339,25 +2367,23 @@ static int check_authorization(struct mg_connection *conn, const char *path) { ...@@ -2339,25 +2367,23 @@ static int check_authorization(struct mg_connection *conn, const char *path) {
static void send_authorization_request(struct mg_connection *conn) { static void send_authorization_request(struct mg_connection *conn) {
conn->status_code = 401; conn->status_code = 401;
(void) mg_printf(conn, mg_printf(conn,
"HTTP/1.1 401 Unauthorized\r\n" "HTTP/1.1 401 Unauthorized\r\n"
"Content-Length: 0\r\n" "Content-Length: 0\r\n"
"WWW-Authenticate: Digest qop=\"auth\", " "WWW-Authenticate: Digest qop=\"auth\", "
"realm=\"%s\", nonce=\"%lu\"\r\n\r\n", "realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
conn->ctx->config[AUTHENTICATION_DOMAIN], conn->ctx->config[AUTHENTICATION_DOMAIN],
(unsigned long) time(NULL)); (unsigned long) time(NULL));
} }
static int is_authorized_for_put(struct mg_connection *conn) { static int is_authorized_for_put(struct mg_connection *conn) {
FILE *fp; struct file file = STRUCT_FILE_INITIALIZER;
const char *passfile = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE];
int ret = 0; int ret = 0;
fp = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE] == NULL ? NULL : if (passfile != NULL && mg_fopen(conn, passfile, "r", &file)) {
mg_fopen(conn->ctx->config[PUT_DELETE_PASSWORDS_FILE], "r"); ret = authorize(conn, &file);
mg_fclose(&file);
if (fp != NULL) {
ret = authorize(conn, fp);
(void) fclose(fp);
} }
return ret; return ret;
...@@ -2380,14 +2406,14 @@ int mg_modify_passwords_file(const char *fname, const char *domain, ...@@ -2380,14 +2406,14 @@ int mg_modify_passwords_file(const char *fname, const char *domain,
(void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname); (void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
// Create the file if does not exist // Create the file if does not exist
if ((fp = mg_fopen(fname, "a+")) != NULL) { if ((fp = fopen(fname, "a+")) != NULL) {
(void) fclose(fp); (void) fclose(fp);
} }
// Open the given file and temporary file // Open the given file and temporary file
if ((fp = mg_fopen(fname, "r")) == NULL) { if ((fp = fopen(fname, "r")) == NULL) {
return 0; return 0;
} else if ((fp2 = mg_fopen(tmp, "w+")) == NULL) { } else if ((fp2 = fopen(tmp, "w+")) == NULL) {
fclose(fp); fclose(fp);
return 0; return 0;
} }
...@@ -2405,23 +2431,23 @@ int mg_modify_passwords_file(const char *fname, const char *domain, ...@@ -2405,23 +2431,23 @@ int mg_modify_passwords_file(const char *fname, const char *domain,
fprintf(fp2, "%s:%s:%s\n", user, domain, ha1); fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
} }
} else { } else {
(void) fprintf(fp2, "%s", line); fprintf(fp2, "%s", line);
} }
} }
// If new user, just add it // If new user, just add it
if (!found && pass != NULL) { if (!found && pass != NULL) {
mg_md5(ha1, user, ":", domain, ":", pass, NULL); mg_md5(ha1, user, ":", domain, ":", pass, NULL);
(void) fprintf(fp2, "%s:%s:%s\n", user, domain, ha1); fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
} }
// Close files // Close files
(void) fclose(fp); fclose(fp);
(void) fclose(fp2); fclose(fp2);
// Put the temp file in place of real file // Put the temp file in place of real file
(void) mg_remove(fname); remove(fname);
(void) mg_rename(tmp, fname); rename(tmp, fname);
return 1; return 1;
} }
...@@ -2429,7 +2455,7 @@ int mg_modify_passwords_file(const char *fname, const char *domain, ...@@ -2429,7 +2455,7 @@ int mg_modify_passwords_file(const char *fname, const char *domain,
struct de { struct de {
struct mg_connection *conn; struct mg_connection *conn;
char *file_name; char *file_name;
struct mgstat st; struct file file;
}; };
static void url_encode(const char *src, char *dst, size_t dst_len) { static void url_encode(const char *src, char *dst, size_t dst_len) {
...@@ -2455,32 +2481,32 @@ static void url_encode(const char *src, char *dst, size_t dst_len) { ...@@ -2455,32 +2481,32 @@ static void url_encode(const char *src, char *dst, size_t dst_len) {
static void print_dir_entry(struct de *de) { static void print_dir_entry(struct de *de) {
char size[64], mod[64], href[PATH_MAX]; char size[64], mod[64], href[PATH_MAX];
if (de->st.is_directory) { if (de->file.is_directory) {
(void) mg_snprintf(de->conn, size, sizeof(size), "%s", "[DIRECTORY]"); mg_snprintf(de->conn, size, sizeof(size), "%s", "[DIRECTORY]");
} else { } else {
// We use (signed) cast below because MSVC 6 compiler cannot // We use (signed) cast below because MSVC 6 compiler cannot
// convert unsigned __int64 to double. Sigh. // convert unsigned __int64 to double. Sigh.
if (de->st.size < 1024) { if (de->file.size < 1024) {
(void) mg_snprintf(de->conn, size, sizeof(size), mg_snprintf(de->conn, size, sizeof(size), "%d", (int) de->file.size);
"%lu", (unsigned long) de->st.size); } else if (de->file.size < 0x100000) {
} else if (de->st.size < 0x100000) { mg_snprintf(de->conn, size, sizeof(size),
(void) mg_snprintf(de->conn, size, sizeof(size), "%.1fk", (double) de->file.size / 1024.0);
"%.1fk", (double) de->st.size / 1024.0); } else if (de->file.size < 0x40000000) {
} else if (de->st.size < 0x40000000) { mg_snprintf(de->conn, size, sizeof(size),
(void) mg_snprintf(de->conn, size, sizeof(size), "%.1fM", (double) de->file.size / 1048576);
"%.1fM", (double) de->st.size / 1048576);
} else { } else {
(void) mg_snprintf(de->conn, size, sizeof(size), mg_snprintf(de->conn, size, sizeof(size),
"%.1fG", (double) de->st.size / 1073741824); "%.1fG", (double) de->file.size / 1073741824);
} }
} }
(void) strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&de->st.mtime)); strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M",
localtime(&de->file.modification_time));
url_encode(de->file_name, href, sizeof(href)); url_encode(de->file_name, href, sizeof(href));
de->conn->num_bytes_sent += mg_printf(de->conn, de->conn->num_bytes_sent += mg_printf(de->conn,
"<tr><td><a href=\"%s%s%s\">%s%s</a></td>" "<tr><td><a href=\"%s%s%s\">%s%s</a></td>"
"<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n", "<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
de->conn->request_info.uri, href, de->st.is_directory ? "/" : "", de->conn->request_info.uri, href, de->file.is_directory ? "/" : "",
de->file_name, de->st.is_directory ? "/" : "", mod, size); de->file_name, de->file.is_directory ? "/" : "", mod, size);
} }
// This function is called from send_directory() and used for // This function is called from send_directory() and used for
...@@ -2496,18 +2522,18 @@ static int WINCDECL compare_dir_entries(const void *p1, const void *p2) { ...@@ -2496,18 +2522,18 @@ static int WINCDECL compare_dir_entries(const void *p1, const void *p2) {
query_string = "na"; query_string = "na";
} }
if (a->st.is_directory && !b->st.is_directory) { if (a->file.is_directory && !b->file.is_directory) {
return -1; // Always put directories on top return -1; // Always put directories on top
} else if (!a->st.is_directory && b->st.is_directory) { } else if (!a->file.is_directory && b->file.is_directory) {
return 1; // Always put directories on top return 1; // Always put directories on top
} else if (*query_string == 'n') { } else if (*query_string == 'n') {
cmp_result = strcmp(a->file_name, b->file_name); cmp_result = strcmp(a->file_name, b->file_name);
} else if (*query_string == 's') { } else if (*query_string == 's') {
cmp_result = a->st.size == b->st.size ? 0 : cmp_result = a->file.size == b->file.size ? 0 :
a->st.size > b->st.size ? 1 : -1; a->file.size > b->file.size ? 1 : -1;
} else if (*query_string == 'd') { } else if (*query_string == 'd') {
cmp_result = a->st.mtime == b->st.mtime ? 0 : cmp_result = a->file.modification_time == b->file.modification_time ? 0 :
a->st.mtime > b->st.mtime ? 1 : -1; a->file.modification_time > b->file.modification_time ? 1 : -1;
} }
return query_string[1] == 'd' ? -cmp_result : cmp_result; return query_string[1] == 'd' ? -cmp_result : cmp_result;
...@@ -2547,11 +2573,10 @@ static int scan_directory(struct mg_connection *conn, const char *dir, ...@@ -2547,11 +2573,10 @@ static int scan_directory(struct mg_connection *conn, const char *dir,
// print_dir_entry(). memset is required only if mg_stat() // print_dir_entry(). memset is required only if mg_stat()
// fails. For more details, see // fails. For more details, see
// http://code.google.com/p/mongoose/issues/detail?id=79 // http://code.google.com/p/mongoose/issues/detail?id=79
if (mg_stat(path, &de.st) != 0) { // mg_stat will memset the whole struct file with zeroes.
memset(&de.st, 0, sizeof(de.st)); mg_stat(conn, path, &de.file);
}
de.file_name = dp->d_name;
de.file_name = dp->d_name;
cb(&de, data); cb(&de, data);
} }
(void) closedir(dirp); (void) closedir(dirp);
...@@ -2578,7 +2603,7 @@ static void dir_scan_callback(struct de *de, void *data) { ...@@ -2578,7 +2603,7 @@ static void dir_scan_callback(struct de *de, void *data) {
dsd->num_entries = 0; dsd->num_entries = 0;
} else { } else {
dsd->entries[dsd->num_entries].file_name = mg_strdup(de->file_name); dsd->entries[dsd->num_entries].file_name = mg_strdup(de->file_name);
dsd->entries[dsd->num_entries].st = de->st; dsd->entries[dsd->num_entries].file = de->file;
dsd->entries[dsd->num_entries].conn = de->conn; dsd->entries[dsd->num_entries].conn = de->conn;
dsd->num_entries++; dsd->num_entries++;
} }
...@@ -2635,30 +2660,39 @@ static void handle_directory_request(struct mg_connection *conn, ...@@ -2635,30 +2660,39 @@ static void handle_directory_request(struct mg_connection *conn,
} }
// Send len bytes from the opened file to the client. // Send len bytes from the opened file to the client.
static void send_file_data(struct mg_connection *conn, FILE *fp, int64_t len) { static void send_file_data(struct mg_connection *conn, struct file *filep,
int64_t offset, int64_t len) {
char buf[MG_BUF_LEN]; char buf[MG_BUF_LEN];
int to_read, num_read, num_written; int to_read, num_read, num_written;
while (len > 0) { if (len > 0 && filep->membuf != NULL && filep->size > 0) {
// Calculate how much to read from the file in the buffer if (len > filep->size - offset) {
to_read = sizeof(buf); len = filep->size - offset;
if ((int64_t) to_read > len) {
to_read = (int) len;
} }
mg_write(conn, filep->membuf + offset, len);
} else if (len > 0 && filep->fp != NULL) {
fseeko(filep->fp, offset, SEEK_SET);
while (len > 0) {
// Calculate how much to read from the file in the buffer
to_read = sizeof(buf);
if ((int64_t) to_read > len) {
to_read = (int) len;
}
// Read from file, exit the loop on error // Read from file, exit the loop on error
if ((num_read = fread(buf, 1, (size_t)to_read, fp)) <= 0) { if ((num_read = fread(buf, 1, (size_t) to_read, filep->fp)) <= 0) {
break; break;
} }
// Send read bytes to the client, exit the loop on error // Send read bytes to the client, exit the loop on error
if ((num_written = mg_write(conn, buf, (size_t)num_read)) != num_read) { if ((num_written = mg_write(conn, buf, (size_t) num_read)) != num_read) {
break; break;
} }
// Both read and were successful, adjust counters // Both read and were successful, adjust counters
conn->num_bytes_sent += num_written; conn->num_bytes_sent += num_written;
len -= num_written; len -= num_written;
}
} }
} }
...@@ -2671,53 +2705,58 @@ static void gmt_time_string(char *buf, size_t buf_len, time_t *t) { ...@@ -2671,53 +2705,58 @@ static void gmt_time_string(char *buf, size_t buf_len, time_t *t) {
} }
static void construct_etag(char *buf, size_t buf_len, static void construct_etag(char *buf, size_t buf_len,
const struct mgstat *stp) { const struct file *filep) {
snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"", snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"",
(unsigned long) stp->mtime, stp->size); (unsigned long) filep->modification_time, filep->size);
}
static void fclose_on_exec(struct file *filep) {
if (filep != NULL && filep->fp != NULL) {
fcntl(fileno(filep->fp), F_SETFD, FD_CLOEXEC);
}
} }
static void handle_file_request(struct mg_connection *conn, const char *path, static void handle_file_request(struct mg_connection *conn, const char *path,
struct mgstat *stp) { struct file *filep) {
char date[64], lm[64], etag[64], range[64]; char date[64], lm[64], etag[64], range[64];
const char *msg = "OK", *hdr; const char *msg = "OK", *hdr;
time_t curtime = time(NULL); time_t curtime = time(NULL);
int64_t cl, r1, r2; int64_t cl, r1, r2;
struct vec mime_vec; struct vec mime_vec;
FILE *fp;
int n; int n;
get_mime_type(conn->ctx, path, &mime_vec); get_mime_type(conn->ctx, path, &mime_vec);
cl = stp->size; cl = filep->size;
conn->status_code = 200; conn->status_code = 200;
range[0] = '\0'; range[0] = '\0';
if ((fp = mg_fopen(path, "rb")) == NULL) { if (!mg_fopen(conn, path, "rb", filep)) {
send_http_error(conn, 500, http_500_error, send_http_error(conn, 500, http_500_error,
"fopen(%s): %s", path, strerror(ERRNO)); "fopen(%s): %s", path, strerror(ERRNO));
return; return;
} }
set_close_on_exec(fileno(fp)); fclose_on_exec(filep);
// If Range: header specified, act accordingly // If Range: header specified, act accordingly
r1 = r2 = 0; r1 = r2 = 0;
hdr = mg_get_header(conn, "Range"); hdr = mg_get_header(conn, "Range");
if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0) { if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0 &&
r1 >= 0 && r2 > 0) {
conn->status_code = 206; conn->status_code = 206;
(void) fseeko(fp, r1, SEEK_SET); cl = n == 2 ? (r2 > cl ? cl : r2) - r1 + 1: cl - r1;
cl = n == 2 ? r2 - r1 + 1: cl - r1; mg_snprintf(conn, range, sizeof(range),
(void) mg_snprintf(conn, range, sizeof(range), "Content-Range: bytes "
"Content-Range: bytes " "%" INT64_FMT "-%"
"%" INT64_FMT "-%" INT64_FMT "/%" INT64_FMT "\r\n",
INT64_FMT "/%" INT64_FMT "\r\n", r1, r1 + cl - 1, filep->size);
r1, r1 + cl - 1, stp->size);
msg = "Partial Content"; msg = "Partial Content";
} }
// Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to // Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
gmt_time_string(date, sizeof(date), &curtime); gmt_time_string(date, sizeof(date), &curtime);
gmt_time_string(lm, sizeof(lm), &stp->mtime); gmt_time_string(lm, sizeof(lm), &filep->modification_time);
construct_etag(etag, sizeof(etag), stp); construct_etag(etag, sizeof(etag), filep);
(void) mg_printf(conn, (void) mg_printf(conn,
"HTTP/1.1 %d %s\r\n" "HTTP/1.1 %d %s\r\n"
...@@ -2733,15 +2772,15 @@ static void handle_file_request(struct mg_connection *conn, const char *path, ...@@ -2733,15 +2772,15 @@ static void handle_file_request(struct mg_connection *conn, const char *path,
mime_vec.ptr, cl, suggest_connection_header(conn), range); mime_vec.ptr, cl, suggest_connection_header(conn), range);
if (strcmp(conn->request_info.request_method, "HEAD") != 0) { if (strcmp(conn->request_info.request_method, "HEAD") != 0) {
send_file_data(conn, fp, cl); send_file_data(conn, filep, r1, cl);
} }
(void) fclose(fp); mg_fclose(filep);
} }
void mg_send_file(struct mg_connection *conn, const char *path) { void mg_send_file(struct mg_connection *conn, const char *path) {
struct mgstat st; struct file file;
if (mg_stat(path, &st) == 0) { if (mg_stat(conn, path, &file)) {
handle_file_request(conn, path, &st); handle_file_request(conn, path, &file);
} else { } else {
send_http_error(conn, 404, "Not Found", "%s", "File not found"); send_http_error(conn, 404, "Not Found", "%s", "File not found");
} }
...@@ -2839,9 +2878,9 @@ static int read_request(FILE *fp, struct mg_connection *conn, ...@@ -2839,9 +2878,9 @@ static int read_request(FILE *fp, struct mg_connection *conn,
// Return 0 if index file has been found, -1 if not found. // Return 0 if index file has been found, -1 if not found.
// If the file is found, it's stats is returned in stp. // If the file is found, it's stats is returned in stp.
static int substitute_index_file(struct mg_connection *conn, char *path, static int substitute_index_file(struct mg_connection *conn, char *path,
size_t path_len, struct mgstat *stp) { size_t path_len, struct file *filep) {
const char *list = conn->ctx->config[INDEX_FILES]; const char *list = conn->ctx->config[INDEX_FILES];
struct mgstat st; struct file file = STRUCT_FILE_INITIALIZER;
struct vec filename_vec; struct vec filename_vec;
size_t n = strlen(path); size_t n = strlen(path);
int found = 0; int found = 0;
...@@ -2863,12 +2902,12 @@ static int substitute_index_file(struct mg_connection *conn, char *path, ...@@ -2863,12 +2902,12 @@ static int substitute_index_file(struct mg_connection *conn, char *path,
continue; continue;
// Prepare full path to the index file // Prepare full path to the index file
(void) mg_strlcpy(path + n + 1, filename_vec.ptr, filename_vec.len + 1); mg_strlcpy(path + n + 1, filename_vec.ptr, filename_vec.len + 1);
// Does it exist? // Does it exist?
if (mg_stat(path, &st) == 0) { if (mg_stat(conn, path, &file)) {
// Yes it does, break the loop // Yes it does, break the loop
*stp = st; *filep = file;
found = 1; found = 1;
break; break;
} }
...@@ -2884,13 +2923,13 @@ static int substitute_index_file(struct mg_connection *conn, char *path, ...@@ -2884,13 +2923,13 @@ static int substitute_index_file(struct mg_connection *conn, char *path,
// Return True if we should reply 304 Not Modified. // Return True if we should reply 304 Not Modified.
static int is_not_modified(const struct mg_connection *conn, static int is_not_modified(const struct mg_connection *conn,
const struct mgstat *stp) { const struct file *filep) {
char etag[64]; char etag[64];
const char *ims = mg_get_header(conn, "If-Modified-Since"); const char *ims = mg_get_header(conn, "If-Modified-Since");
const char *inm = mg_get_header(conn, "If-None-Match"); const char *inm = mg_get_header(conn, "If-None-Match");
construct_etag(etag, sizeof(etag), stp); construct_etag(etag, sizeof(etag), filep);
return (inm != NULL && !mg_strcasecmp(etag, inm)) || return (inm != NULL && !mg_strcasecmp(etag, inm)) ||
(ims != NULL && stp->mtime <= parse_date_string(ims)); (ims != NULL && filep->modification_time <= parse_date_string(ims));
} }
static int forward_body_data(struct mg_connection *conn, FILE *fp, static int forward_body_data(struct mg_connection *conn, FILE *fp,
...@@ -3119,6 +3158,7 @@ static void handle_cgi_request(struct mg_connection *conn, const char *prog) { ...@@ -3119,6 +3158,7 @@ static void handle_cgi_request(struct mg_connection *conn, const char *prog) {
struct mg_request_info ri; struct mg_request_info ri;
struct cgi_env_block blk; struct cgi_env_block blk;
FILE *in, *out; FILE *in, *out;
struct file fout = STRUCT_FILE_INITIALIZER;
pid_t pid; pid_t pid;
prepare_cgi_environment(conn, prog, &blk); prepare_cgi_environment(conn, prog, &blk);
...@@ -3164,6 +3204,7 @@ static void handle_cgi_request(struct mg_connection *conn, const char *prog) { ...@@ -3164,6 +3204,7 @@ static void handle_cgi_request(struct mg_connection *conn, const char *prog) {
setbuf(in, NULL); setbuf(in, NULL);
setbuf(out, NULL); setbuf(out, NULL);
fout.fp = out;
// Send POST data to the CGI process if needed // Send POST data to the CGI process if needed
if (!strcmp(conn->request_info.request_method, "POST") && if (!strcmp(conn->request_info.request_method, "POST") &&
...@@ -3218,36 +3259,36 @@ static void handle_cgi_request(struct mg_connection *conn, const char *prog) { ...@@ -3218,36 +3259,36 @@ static void handle_cgi_request(struct mg_connection *conn, const char *prog) {
mg_printf(conn, "%s: %s\r\n", mg_printf(conn, "%s: %s\r\n",
ri.http_headers[i].name, ri.http_headers[i].value); ri.http_headers[i].name, ri.http_headers[i].value);
} }
(void) mg_write(conn, "\r\n", 2); mg_write(conn, "\r\n", 2);
// Send chunk of data that may have been read after the headers // Send chunk of data that may have been read after the headers
conn->num_bytes_sent += mg_write(conn, buf + headers_len, conn->num_bytes_sent += mg_write(conn, buf + headers_len,
(size_t)(data_len - headers_len)); (size_t)(data_len - headers_len));
// Read the rest of CGI output and send to the client // Read the rest of CGI output and send to the client
send_file_data(conn, out, INT64_MAX); send_file_data(conn, &fout, 0, INT64_MAX);
done: done:
if (pid != (pid_t) -1) { if (pid != (pid_t) -1) {
kill(pid, SIGKILL); kill(pid, SIGKILL);
} }
if (fd_stdin[0] != -1) { if (fd_stdin[0] != -1) {
(void) close(fd_stdin[0]); close(fd_stdin[0]);
} }
if (fd_stdout[1] != -1) { if (fd_stdout[1] != -1) {
(void) close(fd_stdout[1]); close(fd_stdout[1]);
} }
if (in != NULL) { if (in != NULL) {
(void) fclose(in); fclose(in);
} else if (fd_stdin[1] != -1) { } else if (fd_stdin[1] != -1) {
(void) close(fd_stdin[1]); close(fd_stdin[1]);
} }
if (out != NULL) { if (out != NULL) {
(void) fclose(out); fclose(out);
} else if (fd_stdout[0] != -1) { } else if (fd_stdout[0] != -1) {
(void) close(fd_stdout[0]); close(fd_stdout[0]);
} }
} }
#endif // !NO_CGI #endif // !NO_CGI
...@@ -3255,10 +3296,10 @@ done: ...@@ -3255,10 +3296,10 @@ done:
// For a given PUT path, create all intermediate subdirectories // For a given PUT path, create all intermediate subdirectories
// for given path. Return 0 if the path itself is a directory, // for given path. Return 0 if the path itself is a directory,
// or -1 on error, 1 if OK. // or -1 on error, 1 if OK.
static int put_dir(const char *path) { static int put_dir(struct mg_connection *conn, const char *path) {
char buf[PATH_MAX]; char buf[PATH_MAX];
const char *s, *p; const char *s, *p;
struct mgstat st; struct file file;
int len, res = 1; int len, res = 1;
for (s = p = path + 2; (p = strchr(s, '/')) != NULL; s = ++p) { for (s = p = path + 2; (p = strchr(s, '/')) != NULL; s = ++p) {
...@@ -3272,7 +3313,7 @@ static int put_dir(const char *path) { ...@@ -3272,7 +3313,7 @@ static int put_dir(const char *path) {
// Try to create intermediate directory // Try to create intermediate directory
DEBUG_TRACE(("mkdir(%s)", buf)); DEBUG_TRACE(("mkdir(%s)", buf));
if (mg_stat(buf, &st) == -1 && mg_mkdir(buf, 0755) != 0) { if (!mg_stat(conn, buf, &file) && mg_mkdir(buf, 0755) != 0) {
res = -1; res = -1;
break; break;
} }
...@@ -3287,44 +3328,44 @@ static int put_dir(const char *path) { ...@@ -3287,44 +3328,44 @@ static int put_dir(const char *path) {
} }
static void put_file(struct mg_connection *conn, const char *path) { static void put_file(struct mg_connection *conn, const char *path) {
struct mgstat st; struct file file;
const char *range; const char *range;
int64_t r1, r2; int64_t r1, r2;
FILE *fp;
int rc; int rc;
conn->status_code = mg_stat(path, &st) == 0 ? 200 : 201; conn->status_code = mg_stat(conn, path, &file) ? 200 : 201;
if ((rc = put_dir(path)) == 0) { if ((rc = put_dir(conn, path)) == 0) {
mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n", conn->status_code); mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n", conn->status_code);
} else if (rc == -1) { } else if (rc == -1) {
send_http_error(conn, 500, http_500_error, send_http_error(conn, 500, http_500_error,
"put_dir(%s): %s", path, strerror(ERRNO)); "put_dir(%s): %s", path, strerror(ERRNO));
} else if ((fp = mg_fopen(path, "wb+")) == NULL) { } else if (!mg_fopen(conn, path, "wb+", &file) || file.fp == NULL) {
mg_fclose(&file);
send_http_error(conn, 500, http_500_error, send_http_error(conn, 500, http_500_error,
"fopen(%s): %s", path, strerror(ERRNO)); "fopen(%s): %s", path, strerror(ERRNO));
} else { } else {
set_close_on_exec(fileno(fp)); fclose_on_exec(&file);
range = mg_get_header(conn, "Content-Range"); range = mg_get_header(conn, "Content-Range");
r1 = r2 = 0; r1 = r2 = 0;
if (range != NULL && parse_range_header(range, &r1, &r2) > 0) { if (range != NULL && parse_range_header(range, &r1, &r2) > 0) {
conn->status_code = 206; conn->status_code = 206;
// TODO(lsm): handle seek error fseeko(file.fp, r1, SEEK_SET);
(void) fseeko(fp, r1, SEEK_SET);
} }
if (forward_body_data(conn, fp, INVALID_SOCKET, NULL)) { if (forward_body_data(conn, file.fp, INVALID_SOCKET, NULL)) {
(void) mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n", conn->status_code); mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n", conn->status_code);
} }
(void) fclose(fp); mg_fclose(&file);
} }
} }
static void send_ssi_file(struct mg_connection *, const char *, FILE *, int); static void send_ssi_file(struct mg_connection *, const char *,
struct file *, int);
static void do_ssi_include(struct mg_connection *conn, const char *ssi, static void do_ssi_include(struct mg_connection *conn, const char *ssi,
char *tag, int include_level) { char *tag, int include_level) {
char file_name[MG_BUF_LEN], path[PATH_MAX], *p; char file_name[MG_BUF_LEN], path[PATH_MAX], *p;
FILE *fp; struct file file;
// sscanf() is safe here, since send_ssi_file() also uses buffer // sscanf() is safe here, since send_ssi_file() also uses buffer
// of size MG_BUF_LEN to get the tag. So strlen(tag) is always < MG_BUF_LEN. // of size MG_BUF_LEN to get the tag. So strlen(tag) is always < MG_BUF_LEN.
...@@ -3349,51 +3390,59 @@ static void do_ssi_include(struct mg_connection *conn, const char *ssi, ...@@ -3349,51 +3390,59 @@ static void do_ssi_include(struct mg_connection *conn, const char *ssi,
return; return;
} }
if ((fp = mg_fopen(path, "rb")) == NULL) { if (!mg_fopen(conn, path, "rb", &file)) {
cry(conn, "Cannot open SSI #include: [%s]: fopen(%s): %s", cry(conn, "Cannot open SSI #include: [%s]: fopen(%s): %s",
tag, path, strerror(ERRNO)); tag, path, strerror(ERRNO));
} else { } else {
set_close_on_exec(fileno(fp)); fclose_on_exec(&file);
if (match_prefix(conn->ctx->config[SSI_EXTENSIONS], if (match_prefix(conn->ctx->config[SSI_EXTENSIONS],
strlen(conn->ctx->config[SSI_EXTENSIONS]), path) > 0) { strlen(conn->ctx->config[SSI_EXTENSIONS]), path) > 0) {
send_ssi_file(conn, path, fp, include_level + 1); send_ssi_file(conn, path, &file, include_level + 1);
} else { } else {
send_file_data(conn, fp, INT64_MAX); send_file_data(conn, &file, 0, INT64_MAX);
} }
(void) fclose(fp); mg_fclose(&file);
} }
} }
#if !defined(NO_POPEN) #if !defined(NO_POPEN)
static void do_ssi_exec(struct mg_connection *conn, char *tag) { static void do_ssi_exec(struct mg_connection *conn, char *tag) {
char cmd[MG_BUF_LEN]; char cmd[MG_BUF_LEN];
FILE *fp; struct file file = STRUCT_FILE_INITIALIZER;
if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) { if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) {
cry(conn, "Bad SSI #exec: [%s]", tag); cry(conn, "Bad SSI #exec: [%s]", tag);
} else if ((fp = popen(cmd, "r")) == NULL) { } else if ((file.fp = popen(cmd, "r")) == NULL) {
cry(conn, "Cannot SSI #exec: [%s]: %s", cmd, strerror(ERRNO)); cry(conn, "Cannot SSI #exec: [%s]: %s", cmd, strerror(ERRNO));
} else { } else {
send_file_data(conn, fp, INT64_MAX); send_file_data(conn, &file, 0, INT64_MAX);
(void) pclose(fp); pclose(file.fp);
} }
} }
#endif // !NO_POPEN #endif // !NO_POPEN
static int mg_fgetc(struct file *filep, int offset) {
if (filep->membuf != NULL && offset >=0 && offset < filep->size) {
return ((unsigned char *) filep->membuf)[offset];
} else if (filep->fp != NULL) {
return fgetc(filep->fp);
} else {
return EOF;
}
}
static void send_ssi_file(struct mg_connection *conn, const char *path, static void send_ssi_file(struct mg_connection *conn, const char *path,
FILE *fp, int include_level) { struct file *filep, int include_level) {
char buf[MG_BUF_LEN]; char buf[MG_BUF_LEN];
int ch, len, in_ssi_tag; int ch, offset, len, in_ssi_tag;
if (include_level > 10) { if (include_level > 10) {
cry(conn, "SSI #include level is too deep (%s)", path); cry(conn, "SSI #include level is too deep (%s)", path);
return; return;
} }
in_ssi_tag = 0; in_ssi_tag = len = offset = 0;
len = 0; while ((ch = mg_fgetc(filep, offset)) != EOF) {
while ((ch = fgetc(fp)) != EOF) {
if (in_ssi_tag && ch == '>') { if (in_ssi_tag && ch == '>') {
in_ssi_tag = 0; in_ssi_tag = 0;
buf[len++] = (char) ch; buf[len++] = (char) ch;
...@@ -3401,7 +3450,7 @@ static void send_ssi_file(struct mg_connection *conn, const char *path, ...@@ -3401,7 +3450,7 @@ static void send_ssi_file(struct mg_connection *conn, const char *path,
assert(len <= (int) sizeof(buf)); assert(len <= (int) sizeof(buf));
if (len < 6 || memcmp(buf, "<!--#", 5) != 0) { if (len < 6 || memcmp(buf, "<!--#", 5) != 0) {
// Not an SSI tag, pass it // Not an SSI tag, pass it
(void) mg_write(conn, buf, (size_t)len); (void) mg_write(conn, buf, (size_t) len);
} else { } else {
if (!memcmp(buf + 5, "include", 7)) { if (!memcmp(buf + 5, "include", 7)) {
do_ssi_include(conn, path, buf + 12, include_level); do_ssi_include(conn, path, buf + 12, include_level);
...@@ -3426,14 +3475,14 @@ static void send_ssi_file(struct mg_connection *conn, const char *path, ...@@ -3426,14 +3475,14 @@ static void send_ssi_file(struct mg_connection *conn, const char *path,
} else if (ch == '<') { } else if (ch == '<') {
in_ssi_tag = 1; in_ssi_tag = 1;
if (len > 0) { if (len > 0) {
(void) mg_write(conn, buf, (size_t)len); mg_write(conn, buf, (size_t) len);
} }
len = 0; len = 0;
buf[len++] = ch & 0xff; buf[len++] = ch & 0xff;
} else { } else {
buf[len++] = ch & 0xff; buf[len++] = ch & 0xff;
if (len == (int) sizeof(buf)) { if (len == (int) sizeof(buf)) {
(void) mg_write(conn, buf, (size_t)len); mg_write(conn, buf, (size_t) len);
len = 0; len = 0;
} }
} }
...@@ -3441,42 +3490,41 @@ static void send_ssi_file(struct mg_connection *conn, const char *path, ...@@ -3441,42 +3490,41 @@ static void send_ssi_file(struct mg_connection *conn, const char *path,
// Send the rest of buffered data // Send the rest of buffered data
if (len > 0) { if (len > 0) {
(void) mg_write(conn, buf, (size_t)len); mg_write(conn, buf, (size_t) len);
} }
} }
static void handle_ssi_file_request(struct mg_connection *conn, static void handle_ssi_file_request(struct mg_connection *conn,
const char *path) { const char *path) {
FILE *fp; struct file file;
if ((fp = mg_fopen(path, "rb")) == NULL) { if (!mg_fopen(conn, path, "rb", &file)) {
send_http_error(conn, 500, http_500_error, "fopen(%s): %s", path, send_http_error(conn, 500, http_500_error, "fopen(%s): %s", path,
strerror(ERRNO)); strerror(ERRNO));
} else { } else {
conn->must_close = 1; conn->must_close = 1;
set_close_on_exec(fileno(fp)); fclose_on_exec(&file);
mg_printf(conn, "HTTP/1.1 200 OK\r\n" mg_printf(conn, "HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\nConnection: %s\r\n\r\n", "Content-Type: text/html\r\nConnection: %s\r\n\r\n",
suggest_connection_header(conn)); suggest_connection_header(conn));
send_ssi_file(conn, path, fp, 0); send_ssi_file(conn, path, &file, 0);
(void) fclose(fp); mg_fclose(&file);
} }
} }
static void send_options(struct mg_connection *conn) { static void send_options(struct mg_connection *conn) {
conn->status_code = 200; conn->status_code = 200;
(void) mg_printf(conn, mg_printf(conn, "%s", "HTTP/1.1 200 OK\r\n"
"HTTP/1.1 200 OK\r\n" "Allow: GET, POST, HEAD, CONNECT, PUT, DELETE, OPTIONS\r\n"
"Allow: GET, POST, HEAD, CONNECT, PUT, DELETE, OPTIONS\r\n" "DAV: 1\r\n\r\n");
"DAV: 1\r\n\r\n");
} }
// Writes PROPFIND properties for a collection element // Writes PROPFIND properties for a collection element
static void print_props(struct mg_connection *conn, const char* uri, static void print_props(struct mg_connection *conn, const char* uri,
struct mgstat* st) { struct file *filep) {
char mtime[64]; char mtime[64];
gmt_time_string(mtime, sizeof(mtime), &st->mtime); gmt_time_string(mtime, sizeof(mtime), &filep->modification_time);
conn->num_bytes_sent += mg_printf(conn, conn->num_bytes_sent += mg_printf(conn,
"<d:response>" "<d:response>"
"<d:href>%s</d:href>" "<d:href>%s</d:href>"
...@@ -3490,8 +3538,8 @@ static void print_props(struct mg_connection *conn, const char* uri, ...@@ -3490,8 +3538,8 @@ static void print_props(struct mg_connection *conn, const char* uri,
"</d:propstat>" "</d:propstat>"
"</d:response>\n", "</d:response>\n",
uri, uri,
st->is_directory ? "<d:collection/>" : "", filep->is_directory ? "<d:collection/>" : "",
st->size, filep->size,
mtime); mtime);
} }
...@@ -3500,11 +3548,11 @@ static void print_dav_dir_entry(struct de *de, void *data) { ...@@ -3500,11 +3548,11 @@ static void print_dav_dir_entry(struct de *de, void *data) {
struct mg_connection *conn = (struct mg_connection *) data; struct mg_connection *conn = (struct mg_connection *) data;
mg_snprintf(conn, href, sizeof(href), "%s%s", mg_snprintf(conn, href, sizeof(href), "%s%s",
conn->request_info.uri, de->file_name); conn->request_info.uri, de->file_name);
print_props(conn, href, &de->st); print_props(conn, href, &de->file);
} }
static void handle_propfind(struct mg_connection *conn, const char* path, static void handle_propfind(struct mg_connection *conn, const char *path,
struct mgstat* st) { struct file *filep) {
const char *depth = mg_get_header(conn, "Depth"); const char *depth = mg_get_header(conn, "Depth");
conn->must_close = 1; conn->must_close = 1;
...@@ -3518,10 +3566,10 @@ static void handle_propfind(struct mg_connection *conn, const char* path, ...@@ -3518,10 +3566,10 @@ static void handle_propfind(struct mg_connection *conn, const char* path,
"<d:multistatus xmlns:d='DAV:'>\n"); "<d:multistatus xmlns:d='DAV:'>\n");
// Print properties for the requested resource itself // Print properties for the requested resource itself
print_props(conn, conn->request_info.uri, st); print_props(conn, conn->request_info.uri, filep);
// If it is a directory, print directory entries too if Depth is not 0 // If it is a directory, print directory entries too if Depth is not 0
if (st->is_directory && if (filep->is_directory &&
!mg_strcasecmp(conn->ctx->config[ENABLE_DIRECTORY_LISTING], "yes") && !mg_strcasecmp(conn->ctx->config[ENABLE_DIRECTORY_LISTING], "yes") &&
(depth == NULL || strcmp(depth, "0") != 0)) { (depth == NULL || strcmp(depth, "0") != 0)) {
scan_directory(conn, path, conn, &print_dav_dir_entry); scan_directory(conn, path, conn, &print_dav_dir_entry);
...@@ -3977,8 +4025,8 @@ static void handle_lsp_request(struct mg_connection *conn, const char *path, ...@@ -3977,8 +4025,8 @@ static void handle_lsp_request(struct mg_connection *conn, const char *path,
static void handle_request(struct mg_connection *conn) { static void handle_request(struct mg_connection *conn) {
struct mg_request_info *ri = &conn->request_info; struct mg_request_info *ri = &conn->request_info;
char path[PATH_MAX]; char path[PATH_MAX];
int stat_result, uri_len; int uri_len;
struct mgstat st; struct file file = STRUCT_FILE_INITIALIZER;
if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) { if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) {
* ((char *) conn->request_info.query_string++) = '\0'; * ((char *) conn->request_info.query_string++) = '\0';
...@@ -3987,7 +4035,7 @@ static void handle_request(struct mg_connection *conn) { ...@@ -3987,7 +4035,7 @@ static void handle_request(struct mg_connection *conn) {
url_decode(ri->uri, (size_t)uri_len, (char *) ri->uri, url_decode(ri->uri, (size_t)uri_len, (char *) ri->uri,
(size_t) (uri_len + 1), 0); (size_t) (uri_len + 1), 0);
remove_double_dots_and_double_slashes((char *) ri->uri); remove_double_dots_and_double_slashes((char *) ri->uri);
stat_result = convert_uri_to_file_name(conn, path, sizeof(path), &st); convert_uri_to_file_name(conn, path, sizeof(path), &file);
conn->throttle = set_throttle(conn->ctx->config[THROTTLE], conn->throttle = set_throttle(conn->ctx->config[THROTTLE],
get_remote_ip(conn), ri->uri); get_remote_ip(conn), ri->uri);
...@@ -4018,15 +4066,16 @@ static void handle_request(struct mg_connection *conn) { ...@@ -4018,15 +4066,16 @@ static void handle_request(struct mg_connection *conn) {
send_http_error(conn, 500, http_500_error, "remove(%s): %s", path, send_http_error(conn, 500, http_500_error, "remove(%s): %s", path,
strerror(ERRNO)); strerror(ERRNO));
} }
} else if (stat_result != 0 || must_hide_file(conn, path)) { } else if ((file.membuf == NULL && file.modification_time == (time_t) 0) ||
must_hide_file(conn, path)) {
send_http_error(conn, 404, "Not Found", "%s", "File not found"); send_http_error(conn, 404, "Not Found", "%s", "File not found");
} else if (st.is_directory && ri->uri[uri_len - 1] != '/') { } else if (file.is_directory && ri->uri[uri_len - 1] != '/') {
(void) mg_printf(conn, "HTTP/1.1 301 Moved Permanently\r\n" mg_printf(conn, "HTTP/1.1 301 Moved Permanently\r\n"
"Location: %s/\r\n\r\n", ri->uri); "Location: %s/\r\n\r\n", ri->uri);
} else if (!strcmp(ri->request_method, "PROPFIND")) { } else if (!strcmp(ri->request_method, "PROPFIND")) {
handle_propfind(conn, path, &st); handle_propfind(conn, path, &file);
} else if (st.is_directory && } else if (file.is_directory &&
!substitute_index_file(conn, path, sizeof(path), &st)) { !substitute_index_file(conn, path, sizeof(path), &file)) {
if (!mg_strcasecmp(conn->ctx->config[ENABLE_DIRECTORY_LISTING], "yes")) { if (!mg_strcasecmp(conn->ctx->config[ENABLE_DIRECTORY_LISTING], "yes")) {
handle_directory_request(conn, path); handle_directory_request(conn, path);
} else { } else {
...@@ -4053,10 +4102,10 @@ static void handle_request(struct mg_connection *conn) { ...@@ -4053,10 +4102,10 @@ static void handle_request(struct mg_connection *conn) {
strlen(conn->ctx->config[SSI_EXTENSIONS]), strlen(conn->ctx->config[SSI_EXTENSIONS]),
path) > 0) { path) > 0) {
handle_ssi_file_request(conn, path); handle_ssi_file_request(conn, path);
} else if (is_not_modified(conn, &st)) { } else if (is_not_modified(conn, &file)) {
send_http_error(conn, 304, "Not Modified", "%s", ""); send_http_error(conn, 304, "Not Modified", "%s", "");
} else { } else {
handle_file_request(conn, path, &st); handle_file_request(conn, path, &file);
} }
} }
...@@ -4179,7 +4228,7 @@ static void log_access(const struct mg_connection *conn) { ...@@ -4179,7 +4228,7 @@ static void log_access(const struct mg_connection *conn) {
char date[64], src_addr[20]; char date[64], src_addr[20];
fp = conn->ctx->config[ACCESS_LOG_FILE] == NULL ? NULL : fp = conn->ctx->config[ACCESS_LOG_FILE] == NULL ? NULL :
mg_fopen(conn->ctx->config[ACCESS_LOG_FILE], "a+"); fopen(conn->ctx->config[ACCESS_LOG_FILE], "a+");
if (fp == NULL) if (fp == NULL)
return; return;
...@@ -4400,9 +4449,9 @@ static void uninitialize_ssl(struct mg_context *ctx) { ...@@ -4400,9 +4449,9 @@ static void uninitialize_ssl(struct mg_context *ctx) {
#endif // !NO_SSL #endif // !NO_SSL
static int set_gpass_option(struct mg_context *ctx) { static int set_gpass_option(struct mg_context *ctx) {
struct mgstat mgstat; struct file file;
const char *path = ctx->config[GLOBAL_PASSWORDS_FILE]; const char *path = ctx->config[GLOBAL_PASSWORDS_FILE];
return path == NULL || mg_stat(path, &mgstat) == 0; return path == NULL || !mg_stat(fc(ctx), path, &file);
} }
static int set_acl_option(struct mg_context *ctx) { static int set_acl_option(struct mg_context *ctx) {
......
...@@ -82,6 +82,21 @@ enum mg_event { ...@@ -82,6 +82,21 @@ enum mg_event {
// SSL_CTX *ssl_context = request_info->ev_data; // SSL_CTX *ssl_context = request_info->ev_data;
MG_INIT_SSL, MG_INIT_SSL,
// Mongoose tries to open file.
// If callback returns non-NULL, Mongoose will not try to open it, but
// will use the returned value as a pointer to the file data. This allows
// for example to serve files from memory.
// ev_data contains file path, including document root path.
// Upon return, ev_data should return file size, which should be an int.
//
// const char *file_name = request_info->ev_data;
// if (strcmp(file_name, "foo.txt") == 0) {
// request_info->ev_data = (void *) (int) 4;
// return "data";
// }
// return NULL;
MG_OPEN_FILE,
// Sent on HTTP connect, before websocket handshake. // Sent on HTTP connect, before websocket handshake.
// If user callback returns NULL, then mongoose proceeds // If user callback returns NULL, then mongoose proceeds
// with handshake, otherwise it closes the connection. // with handshake, otherwise it closes the connection.
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
} while (0) } while (0)
#define ASSERT(expr) do { if (!(expr)) FATAL(#expr, __LINE__); } while (0) #define ASSERT(expr) do { if (!(expr)) FATAL(#expr, __LINE__); } while (0)
#define UNUSED_PORT "33796"
static void test_parse_http_request() { static void test_parse_http_request() {
struct mg_request_info ri; struct mg_request_info ri;
char req1[] = "GET / HTTP/1.1\r\n\r\n"; char req1[] = "GET / HTTP/1.1\r\n\r\n";
...@@ -126,15 +128,24 @@ static void test_remove_double_dots() { ...@@ -126,15 +128,24 @@ static void test_remove_double_dots() {
} }
static const char *fetch_data = "hello world!\n"; static const char *fetch_data = "hello world!\n";
static void *event_handler(enum mg_event event, static const char *inmemory_file_data = "hi there";
struct mg_connection *conn) { static void *event_handler(enum mg_event event, struct mg_connection *conn) {
const struct mg_request_info *request_info = mg_get_request_info(conn); const struct mg_request_info *request_info = mg_get_request_info(conn);
if (event == MG_NEW_REQUEST && !strcmp(request_info->uri, "/data")) { if (event == MG_NEW_REQUEST && !strcmp(request_info->uri, "/data")) {
mg_printf(conn, "HTTP/1.1 200 OK\r\n" mg_printf(conn, "HTTP/1.1 200 OK\r\n"
"Content-Length: %d\r\n" "Content-Length: %d\r\n"
"Content-Type: text/plain\r\n\r\n" "Content-Type: text/plain\r\n\r\n"
"%s", (int) strlen(fetch_data), fetch_data); "%s", (int) strlen(fetch_data), fetch_data);
return ""; return "";
} else if (event == MG_OPEN_FILE) {
const char *path = request_info->ev_data;
printf("%s: [%s]\n", __func__, path);
if (strcmp(path, "./blah") == 0) {
mg_get_request_info(conn)->ev_data =
(void *) (int) strlen(inmemory_file_data);
return (void *) inmemory_file_data;
}
} else if (event == MG_EVENT_LOG) { } else if (event == MG_EVENT_LOG) {
printf("%s\n", (const char *) mg_get_request_info(conn)->ev_data); printf("%s\n", (const char *) mg_get_request_info(conn)->ev_data);
} }
...@@ -145,33 +156,33 @@ static void *event_handler(enum mg_event event, ...@@ -145,33 +156,33 @@ static void *event_handler(enum mg_event event,
static void test_mg_fetch(void) { static void test_mg_fetch(void) {
static const char *options[] = { static const char *options[] = {
"document_root", ".", "document_root", ".",
"listening_ports", "33796", "listening_ports", UNUSED_PORT,
NULL, NULL,
}; };
char buf[2000], buf2[2000]; char buf[2000], buf2[2000];
int length; int n, length;
struct mg_context *ctx; struct mg_context *ctx;
struct mg_request_info ri; struct mg_request_info ri;
const char *tmp_file = "temporary_file_name_for_unit_test.txt"; const char *tmp_file = "temporary_file_name_for_unit_test.txt";
struct mgstat st; struct file file;
FILE *fp; FILE *fp;
ASSERT((ctx = mg_start(event_handler, NULL, options)) != NULL); ASSERT((ctx = mg_start(event_handler, NULL, options)) != NULL);
// Failed fetch, pass invalid URL // Failed fetch, pass invalid URL
ASSERT(mg_fetch(ctx, "localhost", tmp_file, buf, sizeof(buf), &ri) == NULL); ASSERT(mg_fetch(ctx, "localhost", tmp_file, buf, sizeof(buf), &ri) == NULL);
ASSERT(mg_fetch(ctx, "localhost:33796", tmp_file, ASSERT(mg_fetch(ctx, "localhost:" UNUSED_PORT, tmp_file,
buf, sizeof(buf), &ri) == NULL); buf, sizeof(buf), &ri) == NULL);
ASSERT(mg_fetch(ctx, "http://$$$.$$$", tmp_file, ASSERT(mg_fetch(ctx, "http://$$$.$$$", tmp_file,
buf, sizeof(buf), &ri) == NULL); buf, sizeof(buf), &ri) == NULL);
// Failed fetch, pass invalid file name // Failed fetch, pass invalid file name
ASSERT(mg_fetch(ctx, "http://localhost:33796/data", ASSERT(mg_fetch(ctx, "http://localhost:" UNUSED_PORT "/data",
"/this/file/must/not/exist/ever", "/this/file/must/not/exist/ever",
buf, sizeof(buf), &ri) == NULL); buf, sizeof(buf), &ri) == NULL);
// Successful fetch // Successful fetch
ASSERT((fp = mg_fetch(ctx, "http://localhost:33796/data", ASSERT((fp = mg_fetch(ctx, "http://localhost:" UNUSED_PORT "/data",
tmp_file, buf, sizeof(buf), &ri)) != NULL); tmp_file, buf, sizeof(buf), &ri)) != NULL);
ASSERT(ri.num_headers == 2); ASSERT(ri.num_headers == 2);
ASSERT(!strcmp(ri.request_method, "HTTP/1.1")); ASSERT(!strcmp(ri.request_method, "HTTP/1.1"));
...@@ -184,12 +195,31 @@ static void test_mg_fetch(void) { ...@@ -184,12 +195,31 @@ static void test_mg_fetch(void) {
fclose(fp); fclose(fp);
// Fetch big file, mongoose.c // Fetch big file, mongoose.c
ASSERT((fp = mg_fetch(ctx, "http://localhost:33796/mongoose.c", ASSERT((fp = mg_fetch(ctx, "http://localhost:" UNUSED_PORT "/mongoose.c",
tmp_file, buf, sizeof(buf), &ri)) != NULL); tmp_file, buf, sizeof(buf), &ri)) != NULL);
ASSERT(mg_stat("mongoose.c", &st) == 0); ASSERT(mg_stat(fc(ctx), "mongoose.c", &file));
ASSERT(st.size == ftell(fp)); ASSERT(file.size == ftell(fp));
ASSERT(!strcmp(ri.request_method, "HTTP/1.1")); ASSERT(!strcmp(ri.request_method, "HTTP/1.1"));
// Fetch nonexistent file, /blah
ASSERT((fp = mg_fetch(ctx, "http://localhost:" UNUSED_PORT "/boo",
tmp_file, buf, sizeof(buf), &ri)) != NULL);
ASSERT(!mg_strcasecmp(ri.uri, "404"));
fclose(fp);
// Fetch existing inmemory file
ASSERT((fp = mg_fetch(ctx, "http://localhost:" UNUSED_PORT "/blah",
tmp_file, buf, sizeof(buf), &ri)) != NULL);
ASSERT(!mg_strcasecmp(ri.uri, "200"));
n = ftell(fp);
fseek(fp, 0, SEEK_SET);
printf("%s %d %d [%.*s]\n", __func__, n, (int) feof(fp), n, buf2);
n = fread(buf2, 1, n, fp);
printf("%s %d %d [%.*s]\n", __func__, n, (int) feof(fp), n, buf2);
ASSERT((size_t) ftell(fp) == (size_t) strlen(inmemory_file_data));
ASSERT(!memcmp(inmemory_file_data, buf2, ftell(fp)));
fclose(fp);
remove(tmp_file); remove(tmp_file);
mg_stop(ctx); mg_stop(ctx);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment