diff --git a/build/Makefile b/build/Makefile index 92c7e82a53bd55ede4fc8f557e7651171ac583d9..618896db17713af885062a526d81e53d2dfd5c8f 100644 --- a/build/Makefile +++ b/build/Makefile @@ -24,7 +24,7 @@ EXE_SUFFIX = CFLAGS = -std=c99 -O2 -W -Wall -pedantic -pthread -pipe -I.. $(CFLAGS_EXTRA) VERSION = $(shell perl -lne \ 'print $$1 if /define\s+MONGOOSE_VERSION\s+"(\S+)"/' ../mongoose.c) -SOURCES = src/mongoose.c +SOURCES = src/internal.h src/string.c src/mongoose.c TINY_SOURCES = ../mongoose.c main.c LUA_SOURCES = $(TINY_SOURCES) sqlite3.c lsqlite3.c lua_5.2.1.c @@ -57,8 +57,8 @@ endif all: @echo "make (unix|windows|macos)" -../mongoose.c: mod_lua.c ../mongoose.h Makefile src/internal.h $(SOURCES) - cat src/internal.h src/mongoose.c | sed '/#include "internal.h"/d' > $@ +../mongoose.c: mod_lua.c ../mongoose.h Makefile $(SOURCES) + cat $(SOURCES) | sed '/#include "internal.h"/d' > $@ unix_unit_test: $(LUA_SOURCES) Makefile ../test/unit_test.c $(CC) ../test/unit_test.c lua_5.2.1.c $(CFLAGS) -g -O0 -o t && ./t diff --git a/build/src/mongoose.c b/build/src/mongoose.c index 7f73e6649de1a30963609013ea5625f20a412000..3b26a277e8280fe857c765a55863438bf314b0d0 100644 --- a/build/src/mongoose.c +++ b/build/src/mongoose.c @@ -159,254 +159,6 @@ const char *mg_version(void) { return MONGOOSE_VERSION; } -static void mg_strlcpy(register char *dst, register const char *src, size_t n) { - for (; *src != '\0' && n > 1; n--) { - *dst++ = *src++; - } - *dst = '\0'; -} - -static int lowercase(const char *s) { - return tolower(* (const unsigned char *) s); -} - -static int mg_strncasecmp(const char *s1, const char *s2, size_t len) { - int diff = 0; - - if (len > 0) - do { - diff = lowercase(s1++) - lowercase(s2++); - } while (diff == 0 && s1[-1] != '\0' && --len > 0); - - return diff; -} - -static int mg_strcasecmp(const char *s1, const char *s2) { - int diff; - - do { - diff = lowercase(s1++) - lowercase(s2++); - } while (diff == 0 && s1[-1] != '\0'); - - return diff; -} - -static char * mg_strndup(const char *ptr, size_t len) { - char *p; - - if ((p = (char *) malloc(len + 1)) != NULL) { - mg_strlcpy(p, ptr, len + 1); - } - - return p; -} - -static char * mg_strdup(const char *str) { - return mg_strndup(str, strlen(str)); -} - -static const char *mg_strcasestr(const char *big_str, const char *small_str) { - int i, big_len = strlen(big_str), small_len = strlen(small_str); - - for (i = 0; i <= big_len - small_len; i++) { - if (mg_strncasecmp(big_str + i, small_str, small_len) == 0) { - return big_str + i; - } - } - - return NULL; -} - -// Like snprintf(), but never returns negative value, or a value -// that is larger than a supplied buffer. -// Thanks to Adam Zeldis to pointing snprintf()-caused vulnerability -// in his audit report. -static int mg_vsnprintf(struct mg_connection *conn, char *buf, size_t buflen, - const char *fmt, va_list ap) { - int n; - - if (buflen == 0) - return 0; - - n = vsnprintf(buf, buflen, fmt, ap); - - if (n < 0) { - cry(conn, "vsnprintf error"); - n = 0; - } else if (n >= (int) buflen) { - cry(conn, "truncating vsnprintf buffer: [%.*s]", - n > 200 ? 200 : n, buf); - n = (int) buflen - 1; - } - buf[n] = '\0'; - - return n; -} - -static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen, - PRINTF_FORMAT_STRING(const char *fmt), ...) - PRINTF_ARGS(4, 5); - -static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen, - const char *fmt, ...) { - va_list ap; - int n; - - va_start(ap, fmt); - n = mg_vsnprintf(conn, buf, buflen, fmt, ap); - va_end(ap); - - return n; -} - -// Skip the characters until one of the delimiters characters found. -// 0-terminate resulting word. Skip the delimiter and following whitespaces. -// Advance pointer to buffer to the next word. Return found 0-terminated word. -// Delimiters can be quoted with quotechar. -static char *skip_quoted(char **buf, const char *delimiters, - const char *whitespace, char quotechar) { - char *p, *begin_word, *end_word, *end_whitespace; - - begin_word = *buf; - end_word = begin_word + strcspn(begin_word, delimiters); - - // Check for quotechar - if (end_word > begin_word) { - p = end_word - 1; - while (*p == quotechar) { - // If there is anything beyond end_word, copy it - if (*end_word == '\0') { - *p = '\0'; - break; - } else { - 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; - } - } - for (p++; p < end_word; p++) { - *p = '\0'; - } - } - - if (*end_word == '\0') { - *buf = end_word; - } else { - end_whitespace = end_word + 1 + strspn(end_word + 1, whitespace); - - for (p = end_word; p < end_whitespace; p++) { - *p = '\0'; - } - - *buf = end_whitespace; - } - - return begin_word; -} - -// Simplified version of skip_quoted without quote char -// and whitespace == delimiters -static char *skip(char **buf, const char *delimiters) { - return skip_quoted(buf, delimiters, delimiters, 0); -} - - -// Return HTTP header value, or NULL if not found. -static const char *get_header(const struct mg_request_info *ri, - const char *name) { - int i; - - for (i = 0; i < ri->num_headers; i++) - if (!mg_strcasecmp(name, ri->http_headers[i].name)) - return ri->http_headers[i].value; - - return NULL; -} - -const char *mg_get_header(const struct mg_connection *conn, const char *name) { - return get_header(&conn->request_info, name); -} - -// A helper function for traversing a comma separated list of values. -// It returns a list pointer shifted to the next value, or NULL if the end -// of the list found. -// Value is stored in val vector. If value has form "x=y", then eq_val -// vector is initialized to point to the "y" part, and val vector length -// is adjusted to point only to "x". -static const char *next_option(const char *list, struct vec *val, - struct vec *eq_val) { - if (list == NULL || *list == '\0') { - // End of the list - list = NULL; - } else { - val->ptr = list; - if ((list = strchr(val->ptr, ',')) != NULL) { - // Comma found. Store length and shift the list ptr - val->len = list - val->ptr; - list++; - } else { - // This value is the last one - list = val->ptr + strlen(val->ptr); - val->len = list - val->ptr; - } - - if (eq_val != NULL) { - // Value has form "x=y", adjust pointers and lengths - // so that val points to "x", and eq_val points to "y". - eq_val->len = 0; - eq_val->ptr = (const char *) memchr(val->ptr, '=', val->len); - if (eq_val->ptr != NULL) { - eq_val->ptr++; // Skip over '=' character - eq_val->len = val->ptr + val->len - eq_val->ptr; - val->len = (eq_val->ptr - val->ptr) - 1; - } - } - } - - return list; -} - -// Perform case-insensitive match of string against pattern -static int match_prefix(const char *pattern, int pattern_len, const char *str) { - const char *or_str; - int i, j, len, res; - - if ((or_str = (const char *) memchr(pattern, '|', pattern_len)) != NULL) { - res = match_prefix(pattern, or_str - pattern, str); - return res > 0 ? res : - match_prefix(or_str + 1, (pattern + pattern_len) - (or_str + 1), str); - } - - i = j = 0; - res = -1; - for (; i < pattern_len; i++, j++) { - if (pattern[i] == '?' && str[j] != '\0') { - continue; - } else if (pattern[i] == '$') { - return str[j] == '\0' ? j : -1; - } else if (pattern[i] == '*') { - i++; - if (pattern[i] == '*') { - i++; - len = (int) strlen(str + j); - } else { - len = (int) strcspn(str + j, "/"); - } - if (i == pattern_len) { - return j + len; - } - do { - res = match_prefix(pattern + i, pattern_len - i, str + j + len); - } while (res == -1 && len-- > 0); - return res == -1 ? -1 : j + res + len; - } else if (lowercase(&pattern[i]) != lowercase(&str[j])) { - return -1; - } - } - return j; -} - // HTTP 1.1 assumes keep alive if "Connection:" header is not set // This function must tolerate situations when connection info is not // set up, for example if request parsing failed. @@ -443,11 +195,11 @@ static void send_http_error(struct mg_connection *conn, int status, // Errors 1xx, 204 and 304 MUST NOT send a body if (status > 199 && status != 204 && status != 304) { - len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason); + len = mg_snprintf(buf, sizeof(buf), "Error %d: %s", status, reason); buf[len++] = '\n'; va_start(ap, fmt); - len += mg_vsnprintf(conn, buf + len, sizeof(buf) - len, fmt, ap); + len += mg_vsnprintf(buf + len, sizeof(buf) - len, fmt, ap); va_end(ap); } DEBUG_TRACE(("[%s]", buf)); @@ -834,7 +586,7 @@ static pid_t spawn_process(struct mg_connection *conn, const char *prog, } GetFullPathNameA(dir, sizeof(full_dir), full_dir, NULL); - mg_snprintf(conn, cmdline, sizeof(cmdline), "%s%s\"%s\\%s\"", + mg_snprintf(cmdline, sizeof(cmdline), "%s%s\"%s\\%s\"", interp, interp[0] == '\0' ? "" : " ", full_dir, prog); DEBUG_TRACE(("Running [%s]", cmdline)); @@ -1311,12 +1063,12 @@ static int convert_uri_to_file_name(struct mg_connection *conn, char *buf, // 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, buf, buf_len - 1, "%s%s", root, uri); + mg_snprintf(buf, buf_len - 1, "%s%s", root, uri); 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, buf, buf_len - 1, "%.*s%s", (int) b.len, b.ptr, + mg_snprintf(buf, buf_len - 1, "%.*s%s", (int) b.len, b.ptr, uri + match_len); break; } @@ -1827,7 +1579,7 @@ static FILE *open_auth_file(struct mg_connection *conn, const char *path) { // Important: using local struct file to test path for is_directory flag. // If filep is used, mg_stat() makes it appear as if auth file was opened. } else if (mg_stat(path, &file) && file.is_directory) { - mg_snprintf(conn, name, sizeof(name), "%s%c%s", + mg_snprintf(name, sizeof(name), "%s%c%s", path, '/', PASSWORDS_FILE_NAME); fp = mg_fopen(name, "r"); } else { @@ -1835,7 +1587,7 @@ static FILE *open_auth_file(struct mg_connection *conn, const char *path) { for (p = path, e = p + strlen(p) - 1; e > p; e--) if (e[0] == '/') break; - mg_snprintf(conn, name, sizeof(name), "%.*s%c%s", + mg_snprintf(name, sizeof(name), "%.*s%c%s", (int) (e - p), p, '/', PASSWORDS_FILE_NAME); fp = mg_fopen(name, "r"); } @@ -1947,7 +1699,7 @@ static int check_authorization(struct mg_connection *conn, const char *path) { list = conn->ctx->config[PROTECT_URI]; while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) { if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) { - mg_snprintf(conn, fname, sizeof(fname), "%.*s", + mg_snprintf(fname, sizeof(fname), "%.*s", (int) filename_vec.len, filename_vec.ptr); if ((fp = mg_fopen(fname, "r")) == NULL) { cry(conn, "%s: cannot open %s: %s", __func__, fname, strerror(errno)); @@ -2112,20 +1864,20 @@ static void print_dir_entry(const struct de *de) { const char *slash = de->file.is_directory ? "/" : ""; if (de->file.is_directory) { - mg_snprintf(de->conn, size, sizeof(size), "%s", "[DIRECTORY]"); + mg_snprintf(size, sizeof(size), "%s", "[DIRECTORY]"); } else { // We use (signed) cast below because MSVC 6 compiler cannot // convert unsigned __int64 to double. Sigh. if (de->file.size < 1024) { - mg_snprintf(de->conn, size, sizeof(size), "%d", (int) de->file.size); + mg_snprintf(size, sizeof(size), "%d", (int) de->file.size); } else if (de->file.size < 0x100000) { - mg_snprintf(de->conn, size, sizeof(size), + mg_snprintf(size, sizeof(size), "%.1fk", (double) de->file.size / 1024.0); } else if (de->file.size < 0x40000000) { - mg_snprintf(de->conn, size, sizeof(size), + mg_snprintf(size, sizeof(size), "%.1fM", (double) de->file.size / 1048576); } else { - mg_snprintf(de->conn, size, sizeof(size), + mg_snprintf(size, sizeof(size), "%.1fG", (double) de->file.size / 1073741824); } } @@ -2195,7 +1947,7 @@ static int scan_directory(struct mg_connection *conn, const char *dir, continue; } - mg_snprintf(conn, path, sizeof(path), "%s%c%s", dir, '/', dp->d_name); + mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name); // If we don't memset stat structure to zero, mtime will have // garbage and strftime() will segfault later on in @@ -2231,7 +1983,7 @@ static int remove_directory(struct mg_connection *conn, const char *dir) { continue; } - mg_snprintf(conn, path, sizeof(path), "%s%c%s", dir, '/', dp->d_name); + mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name); // If we don't memset stat structure to zero, mtime will have // garbage and strftime() will segfault later on in @@ -2447,7 +2199,7 @@ static void handle_file_request(struct mg_connection *conn, const char *path, } conn->status_code = 206; cl = n == 2 ? (r2 > cl ? cl : r2) - r1 + 1: cl - r1; - mg_snprintf(conn, range, sizeof(range), + mg_snprintf(range, sizeof(range), "Content-Range: bytes " "%" INT64_FMT "-%" INT64_FMT "/%" INT64_FMT "\r\n", @@ -2724,7 +2476,7 @@ static char *addenv(struct cgi_env_block *block, const char *fmt, ...) { // Copy VARIABLE=VALUE\0 string into the free space va_start(ap, fmt); - n = mg_vsnprintf(block->conn, added, (size_t) space, fmt, ap); + n = mg_vsnprintf(added, (size_t) space, fmt, ap); va_end(ap); // Make sure we do not overflow buffer and the envp array @@ -2877,7 +2629,7 @@ static void handle_cgi_request(struct mg_connection *conn, const char *prog) { // CGI must be executed in its own directory. 'dir' must point to the // directory containing executable program, 'p' must point to the // executable program name relative to 'dir'. - (void) mg_snprintf(conn, dir, sizeof(dir), "%s", prog); + (void) mg_snprintf(dir, sizeof(dir), "%s", prog); if ((p = strrchr(dir, '/')) != NULL) { *p++ = '\0'; } else { @@ -3130,20 +2882,20 @@ static void do_ssi_include(struct mg_connection *conn, const char *ssi, // of size MG_BUF_LEN to get the tag. So strlen(tag) is always < MG_BUF_LEN. if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) { // File name is relative to the webserver root - (void) mg_snprintf(conn, path, sizeof(path), "%s%c%s", + (void) mg_snprintf(path, sizeof(path), "%s%c%s", conn->ctx->config[DOCUMENT_ROOT], '/', file_name); } else if (sscanf(tag, " abspath=\"%[^\"]\"", file_name) == 1) { // File name is relative to the webserver working directory // or it is absolute system path - (void) mg_snprintf(conn, path, sizeof(path), "%s", file_name); + (void) mg_snprintf(path, sizeof(path), "%s", file_name); } else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1 || sscanf(tag, " \"%[^\"]\"", file_name) == 1) { // File name is relative to the currect document - (void) mg_snprintf(conn, path, sizeof(path), "%s", ssi); + (void) mg_snprintf(path, sizeof(path), "%s", ssi); if ((p = strrchr(path, '/')) != NULL) { p[1] = '\0'; } - (void) mg_snprintf(conn, path + strlen(path), + (void) mg_snprintf(path + strlen(path), sizeof(path) - strlen(path), "%s", file_name); } else { cry(conn, "Bad SSI #include: [%s]", tag); @@ -3301,7 +3053,7 @@ static void print_dav_dir_entry(struct de *de, void *data) { char href[PATH_MAX]; char href_encoded[PATH_MAX]; struct mg_connection *conn = (struct mg_connection *) data; - mg_snprintf(conn, href, sizeof(href), "%s%s", + mg_snprintf(href, sizeof(href), "%s%s", conn->request_info.uri, de->file_name); mg_url_encode(href, href_encoded, PATH_MAX-1); print_props(conn, href_encoded, &de->file); @@ -3493,7 +3245,7 @@ void mg_websocket_handshake(struct mg_connection *conn) { char buf[100], sha[20], b64_sha[sizeof(sha) * 2]; SHA1_CTX sha_ctx; - mg_snprintf(conn, buf, sizeof(buf), "%s%s", + mg_snprintf(buf, sizeof(buf), "%s%s", mg_get_header(conn, "Sec-WebSocket-Key"), magic); SHA1Init(&sha_ctx); SHA1Update(&sha_ctx, (unsigned char *) buf, strlen(buf)); diff --git a/build/src/string.c b/build/src/string.c new file mode 100644 index 0000000000000000000000000000000000000000..226d8f3631fc855915b6c9642d2f2583b010f529 --- /dev/null +++ b/build/src/string.c @@ -0,0 +1,245 @@ +#include "internal.h" + +static void mg_strlcpy(register char *dst, register const char *src, size_t n) { + for (; *src != '\0' && n > 1; n--) { + *dst++ = *src++; + } + *dst = '\0'; +} + +static int lowercase(const char *s) { + return tolower(* (const unsigned char *) s); +} + +static int mg_strncasecmp(const char *s1, const char *s2, size_t len) { + int diff = 0; + + if (len > 0) + do { + diff = lowercase(s1++) - lowercase(s2++); + } while (diff == 0 && s1[-1] != '\0' && --len > 0); + + return diff; +} + +static int mg_strcasecmp(const char *s1, const char *s2) { + int diff; + + do { + diff = lowercase(s1++) - lowercase(s2++); + } while (diff == 0 && s1[-1] != '\0'); + + return diff; +} + +static char * mg_strndup(const char *ptr, size_t len) { + char *p; + + if ((p = (char *) malloc(len + 1)) != NULL) { + mg_strlcpy(p, ptr, len + 1); + } + + return p; +} + +static char * mg_strdup(const char *str) { + return mg_strndup(str, strlen(str)); +} + +static const char *mg_strcasestr(const char *big_str, const char *small_str) { + int i, big_len = strlen(big_str), small_len = strlen(small_str); + + for (i = 0; i <= big_len - small_len; i++) { + if (mg_strncasecmp(big_str + i, small_str, small_len) == 0) { + return big_str + i; + } + } + + return NULL; +} + +// Like snprintf(), but never returns negative value, or a value +// that is larger than a supplied buffer. +// Thanks to Adam Zeldis to pointing snprintf()-caused vulnerability +// in his audit report. +static int mg_vsnprintf(char *buf, size_t buflen, const char *fmt, va_list ap) { + int n; + + if (buflen == 0) { + return 0; + } + + n = vsnprintf(buf, buflen, fmt, ap); + + if (n < 0) { + n = 0; + } else if (n >= (int) buflen) { + n = (int) buflen - 1; + } + buf[n] = '\0'; + + return n; +} + +static int mg_snprintf(char *buf, size_t buflen, + PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(3, 4); + +static int mg_snprintf(char *buf, size_t buflen, const char *fmt, ...) { + va_list ap; + int n; + + va_start(ap, fmt); + n = mg_vsnprintf(buf, buflen, fmt, ap); + va_end(ap); + + return n; +} + +// Skip the characters until one of the delimiters characters found. +// 0-terminate resulting word. Skip the delimiter and following whitespaces. +// Advance pointer to buffer to the next word. Return found 0-terminated word. +// Delimiters can be quoted with quotechar. +static char *skip_quoted(char **buf, const char *delimiters, + const char *whitespace, char quotechar) { + char *p, *begin_word, *end_word, *end_whitespace; + + begin_word = *buf; + end_word = begin_word + strcspn(begin_word, delimiters); + + // Check for quotechar + if (end_word > begin_word) { + p = end_word - 1; + while (*p == quotechar) { + // If there is anything beyond end_word, copy it + if (*end_word == '\0') { + *p = '\0'; + break; + } else { + 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; + } + } + for (p++; p < end_word; p++) { + *p = '\0'; + } + } + + if (*end_word == '\0') { + *buf = end_word; + } else { + end_whitespace = end_word + 1 + strspn(end_word + 1, whitespace); + + for (p = end_word; p < end_whitespace; p++) { + *p = '\0'; + } + + *buf = end_whitespace; + } + + return begin_word; +} + +// Simplified version of skip_quoted without quote char +// and whitespace == delimiters +static char *skip(char **buf, const char *delimiters) { + return skip_quoted(buf, delimiters, delimiters, 0); +} + + +// Return HTTP header value, or NULL if not found. +static const char *get_header(const struct mg_request_info *ri, + const char *name) { + int i; + + for (i = 0; i < ri->num_headers; i++) + if (!mg_strcasecmp(name, ri->http_headers[i].name)) + return ri->http_headers[i].value; + + return NULL; +} + +const char *mg_get_header(const struct mg_connection *conn, const char *name) { + return get_header(&conn->request_info, name); +} + +// A helper function for traversing a comma separated list of values. +// It returns a list pointer shifted to the next value, or NULL if the end +// of the list found. +// Value is stored in val vector. If value has form "x=y", then eq_val +// vector is initialized to point to the "y" part, and val vector length +// is adjusted to point only to "x". +static const char *next_option(const char *list, struct vec *val, + struct vec *eq_val) { + if (list == NULL || *list == '\0') { + // End of the list + list = NULL; + } else { + val->ptr = list; + if ((list = strchr(val->ptr, ',')) != NULL) { + // Comma found. Store length and shift the list ptr + val->len = list - val->ptr; + list++; + } else { + // This value is the last one + list = val->ptr + strlen(val->ptr); + val->len = list - val->ptr; + } + + if (eq_val != NULL) { + // Value has form "x=y", adjust pointers and lengths + // so that val points to "x", and eq_val points to "y". + eq_val->len = 0; + eq_val->ptr = (const char *) memchr(val->ptr, '=', val->len); + if (eq_val->ptr != NULL) { + eq_val->ptr++; // Skip over '=' character + eq_val->len = val->ptr + val->len - eq_val->ptr; + val->len = (eq_val->ptr - val->ptr) - 1; + } + } + } + + return list; +} + +// Perform case-insensitive match of string against pattern +static int match_prefix(const char *pattern, int pattern_len, const char *str) { + const char *or_str; + int i, j, len, res; + + if ((or_str = (const char *) memchr(pattern, '|', pattern_len)) != NULL) { + res = match_prefix(pattern, or_str - pattern, str); + return res > 0 ? res : + match_prefix(or_str + 1, (pattern + pattern_len) - (or_str + 1), str); + } + + i = j = 0; + res = -1; + for (; i < pattern_len; i++, j++) { + if (pattern[i] == '?' && str[j] != '\0') { + continue; + } else if (pattern[i] == '$') { + return str[j] == '\0' ? j : -1; + } else if (pattern[i] == '*') { + i++; + if (pattern[i] == '*') { + i++; + len = (int) strlen(str + j); + } else { + len = (int) strcspn(str + j, "/"); + } + if (i == pattern_len) { + return j + len; + } + do { + res = match_prefix(pattern + i, pattern_len - i, str + j + len); + } while (res == -1 && len-- > 0); + return res == -1 ? -1 : j + res + len; + } else if (lowercase(&pattern[i]) != lowercase(&str[j])) { + return -1; + } + } + return j; +} + diff --git a/mongoose.c b/mongoose.c index d2be1a2062f2caa0676184b5809fc0a96aaa8170..9cc8604149bdf7718d6f57444ff2f9c20deea9b2 100644 --- a/mongoose.c +++ b/mongoose.c @@ -458,6 +458,250 @@ struct de { struct file file; }; + +static void mg_strlcpy(register char *dst, register const char *src, size_t n) { + for (; *src != '\0' && n > 1; n--) { + *dst++ = *src++; + } + *dst = '\0'; +} + +static int lowercase(const char *s) { + return tolower(* (const unsigned char *) s); +} + +static int mg_strncasecmp(const char *s1, const char *s2, size_t len) { + int diff = 0; + + if (len > 0) + do { + diff = lowercase(s1++) - lowercase(s2++); + } while (diff == 0 && s1[-1] != '\0' && --len > 0); + + return diff; +} + +static int mg_strcasecmp(const char *s1, const char *s2) { + int diff; + + do { + diff = lowercase(s1++) - lowercase(s2++); + } while (diff == 0 && s1[-1] != '\0'); + + return diff; +} + +static char * mg_strndup(const char *ptr, size_t len) { + char *p; + + if ((p = (char *) malloc(len + 1)) != NULL) { + mg_strlcpy(p, ptr, len + 1); + } + + return p; +} + +static char * mg_strdup(const char *str) { + return mg_strndup(str, strlen(str)); +} + +static const char *mg_strcasestr(const char *big_str, const char *small_str) { + int i, big_len = strlen(big_str), small_len = strlen(small_str); + + for (i = 0; i <= big_len - small_len; i++) { + if (mg_strncasecmp(big_str + i, small_str, small_len) == 0) { + return big_str + i; + } + } + + return NULL; +} + +// Like snprintf(), but never returns negative value, or a value +// that is larger than a supplied buffer. +// Thanks to Adam Zeldis to pointing snprintf()-caused vulnerability +// in his audit report. +static int mg_vsnprintf(char *buf, size_t buflen, const char *fmt, va_list ap) { + int n; + + if (buflen == 0) { + return 0; + } + + n = vsnprintf(buf, buflen, fmt, ap); + + if (n < 0) { + n = 0; + } else if (n >= (int) buflen) { + n = (int) buflen - 1; + } + buf[n] = '\0'; + + return n; +} + +static int mg_snprintf(char *buf, size_t buflen, + PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(3, 4); + +static int mg_snprintf(char *buf, size_t buflen, const char *fmt, ...) { + va_list ap; + int n; + + va_start(ap, fmt); + n = mg_vsnprintf(buf, buflen, fmt, ap); + va_end(ap); + + return n; +} + +// Skip the characters until one of the delimiters characters found. +// 0-terminate resulting word. Skip the delimiter and following whitespaces. +// Advance pointer to buffer to the next word. Return found 0-terminated word. +// Delimiters can be quoted with quotechar. +static char *skip_quoted(char **buf, const char *delimiters, + const char *whitespace, char quotechar) { + char *p, *begin_word, *end_word, *end_whitespace; + + begin_word = *buf; + end_word = begin_word + strcspn(begin_word, delimiters); + + // Check for quotechar + if (end_word > begin_word) { + p = end_word - 1; + while (*p == quotechar) { + // If there is anything beyond end_word, copy it + if (*end_word == '\0') { + *p = '\0'; + break; + } else { + 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; + } + } + for (p++; p < end_word; p++) { + *p = '\0'; + } + } + + if (*end_word == '\0') { + *buf = end_word; + } else { + end_whitespace = end_word + 1 + strspn(end_word + 1, whitespace); + + for (p = end_word; p < end_whitespace; p++) { + *p = '\0'; + } + + *buf = end_whitespace; + } + + return begin_word; +} + +// Simplified version of skip_quoted without quote char +// and whitespace == delimiters +static char *skip(char **buf, const char *delimiters) { + return skip_quoted(buf, delimiters, delimiters, 0); +} + + +// Return HTTP header value, or NULL if not found. +static const char *get_header(const struct mg_request_info *ri, + const char *name) { + int i; + + for (i = 0; i < ri->num_headers; i++) + if (!mg_strcasecmp(name, ri->http_headers[i].name)) + return ri->http_headers[i].value; + + return NULL; +} + +const char *mg_get_header(const struct mg_connection *conn, const char *name) { + return get_header(&conn->request_info, name); +} + +// A helper function for traversing a comma separated list of values. +// It returns a list pointer shifted to the next value, or NULL if the end +// of the list found. +// Value is stored in val vector. If value has form "x=y", then eq_val +// vector is initialized to point to the "y" part, and val vector length +// is adjusted to point only to "x". +static const char *next_option(const char *list, struct vec *val, + struct vec *eq_val) { + if (list == NULL || *list == '\0') { + // End of the list + list = NULL; + } else { + val->ptr = list; + if ((list = strchr(val->ptr, ',')) != NULL) { + // Comma found. Store length and shift the list ptr + val->len = list - val->ptr; + list++; + } else { + // This value is the last one + list = val->ptr + strlen(val->ptr); + val->len = list - val->ptr; + } + + if (eq_val != NULL) { + // Value has form "x=y", adjust pointers and lengths + // so that val points to "x", and eq_val points to "y". + eq_val->len = 0; + eq_val->ptr = (const char *) memchr(val->ptr, '=', val->len); + if (eq_val->ptr != NULL) { + eq_val->ptr++; // Skip over '=' character + eq_val->len = val->ptr + val->len - eq_val->ptr; + val->len = (eq_val->ptr - val->ptr) - 1; + } + } + } + + return list; +} + +// Perform case-insensitive match of string against pattern +static int match_prefix(const char *pattern, int pattern_len, const char *str) { + const char *or_str; + int i, j, len, res; + + if ((or_str = (const char *) memchr(pattern, '|', pattern_len)) != NULL) { + res = match_prefix(pattern, or_str - pattern, str); + return res > 0 ? res : + match_prefix(or_str + 1, (pattern + pattern_len) - (or_str + 1), str); + } + + i = j = 0; + res = -1; + for (; i < pattern_len; i++, j++) { + if (pattern[i] == '?' && str[j] != '\0') { + continue; + } else if (pattern[i] == '$') { + return str[j] == '\0' ? j : -1; + } else if (pattern[i] == '*') { + i++; + if (pattern[i] == '*') { + i++; + len = (int) strlen(str + j); + } else { + len = (int) strcspn(str + j, "/"); + } + if (i == pattern_len) { + return j + len; + } + do { + res = match_prefix(pattern + i, pattern_len - i, str + j + len); + } while (res == -1 && len-- > 0); + return res == -1 ? -1 : j + res + len; + } else if (lowercase(&pattern[i]) != lowercase(&str[j])) { + return -1; + } + } + return j; +} + static const char *month_names[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", @@ -618,254 +862,6 @@ const char *mg_version(void) { return MONGOOSE_VERSION; } -static void mg_strlcpy(register char *dst, register const char *src, size_t n) { - for (; *src != '\0' && n > 1; n--) { - *dst++ = *src++; - } - *dst = '\0'; -} - -static int lowercase(const char *s) { - return tolower(* (const unsigned char *) s); -} - -static int mg_strncasecmp(const char *s1, const char *s2, size_t len) { - int diff = 0; - - if (len > 0) - do { - diff = lowercase(s1++) - lowercase(s2++); - } while (diff == 0 && s1[-1] != '\0' && --len > 0); - - return diff; -} - -static int mg_strcasecmp(const char *s1, const char *s2) { - int diff; - - do { - diff = lowercase(s1++) - lowercase(s2++); - } while (diff == 0 && s1[-1] != '\0'); - - return diff; -} - -static char * mg_strndup(const char *ptr, size_t len) { - char *p; - - if ((p = (char *) malloc(len + 1)) != NULL) { - mg_strlcpy(p, ptr, len + 1); - } - - return p; -} - -static char * mg_strdup(const char *str) { - return mg_strndup(str, strlen(str)); -} - -static const char *mg_strcasestr(const char *big_str, const char *small_str) { - int i, big_len = strlen(big_str), small_len = strlen(small_str); - - for (i = 0; i <= big_len - small_len; i++) { - if (mg_strncasecmp(big_str + i, small_str, small_len) == 0) { - return big_str + i; - } - } - - return NULL; -} - -// Like snprintf(), but never returns negative value, or a value -// that is larger than a supplied buffer. -// Thanks to Adam Zeldis to pointing snprintf()-caused vulnerability -// in his audit report. -static int mg_vsnprintf(struct mg_connection *conn, char *buf, size_t buflen, - const char *fmt, va_list ap) { - int n; - - if (buflen == 0) - return 0; - - n = vsnprintf(buf, buflen, fmt, ap); - - if (n < 0) { - cry(conn, "vsnprintf error"); - n = 0; - } else if (n >= (int) buflen) { - cry(conn, "truncating vsnprintf buffer: [%.*s]", - n > 200 ? 200 : n, buf); - n = (int) buflen - 1; - } - buf[n] = '\0'; - - return n; -} - -static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen, - PRINTF_FORMAT_STRING(const char *fmt), ...) - PRINTF_ARGS(4, 5); - -static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen, - const char *fmt, ...) { - va_list ap; - int n; - - va_start(ap, fmt); - n = mg_vsnprintf(conn, buf, buflen, fmt, ap); - va_end(ap); - - return n; -} - -// Skip the characters until one of the delimiters characters found. -// 0-terminate resulting word. Skip the delimiter and following whitespaces. -// Advance pointer to buffer to the next word. Return found 0-terminated word. -// Delimiters can be quoted with quotechar. -static char *skip_quoted(char **buf, const char *delimiters, - const char *whitespace, char quotechar) { - char *p, *begin_word, *end_word, *end_whitespace; - - begin_word = *buf; - end_word = begin_word + strcspn(begin_word, delimiters); - - // Check for quotechar - if (end_word > begin_word) { - p = end_word - 1; - while (*p == quotechar) { - // If there is anything beyond end_word, copy it - if (*end_word == '\0') { - *p = '\0'; - break; - } else { - 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; - } - } - for (p++; p < end_word; p++) { - *p = '\0'; - } - } - - if (*end_word == '\0') { - *buf = end_word; - } else { - end_whitespace = end_word + 1 + strspn(end_word + 1, whitespace); - - for (p = end_word; p < end_whitespace; p++) { - *p = '\0'; - } - - *buf = end_whitespace; - } - - return begin_word; -} - -// Simplified version of skip_quoted without quote char -// and whitespace == delimiters -static char *skip(char **buf, const char *delimiters) { - return skip_quoted(buf, delimiters, delimiters, 0); -} - - -// Return HTTP header value, or NULL if not found. -static const char *get_header(const struct mg_request_info *ri, - const char *name) { - int i; - - for (i = 0; i < ri->num_headers; i++) - if (!mg_strcasecmp(name, ri->http_headers[i].name)) - return ri->http_headers[i].value; - - return NULL; -} - -const char *mg_get_header(const struct mg_connection *conn, const char *name) { - return get_header(&conn->request_info, name); -} - -// A helper function for traversing a comma separated list of values. -// It returns a list pointer shifted to the next value, or NULL if the end -// of the list found. -// Value is stored in val vector. If value has form "x=y", then eq_val -// vector is initialized to point to the "y" part, and val vector length -// is adjusted to point only to "x". -static const char *next_option(const char *list, struct vec *val, - struct vec *eq_val) { - if (list == NULL || *list == '\0') { - // End of the list - list = NULL; - } else { - val->ptr = list; - if ((list = strchr(val->ptr, ',')) != NULL) { - // Comma found. Store length and shift the list ptr - val->len = list - val->ptr; - list++; - } else { - // This value is the last one - list = val->ptr + strlen(val->ptr); - val->len = list - val->ptr; - } - - if (eq_val != NULL) { - // Value has form "x=y", adjust pointers and lengths - // so that val points to "x", and eq_val points to "y". - eq_val->len = 0; - eq_val->ptr = (const char *) memchr(val->ptr, '=', val->len); - if (eq_val->ptr != NULL) { - eq_val->ptr++; // Skip over '=' character - eq_val->len = val->ptr + val->len - eq_val->ptr; - val->len = (eq_val->ptr - val->ptr) - 1; - } - } - } - - return list; -} - -// Perform case-insensitive match of string against pattern -static int match_prefix(const char *pattern, int pattern_len, const char *str) { - const char *or_str; - int i, j, len, res; - - if ((or_str = (const char *) memchr(pattern, '|', pattern_len)) != NULL) { - res = match_prefix(pattern, or_str - pattern, str); - return res > 0 ? res : - match_prefix(or_str + 1, (pattern + pattern_len) - (or_str + 1), str); - } - - i = j = 0; - res = -1; - for (; i < pattern_len; i++, j++) { - if (pattern[i] == '?' && str[j] != '\0') { - continue; - } else if (pattern[i] == '$') { - return str[j] == '\0' ? j : -1; - } else if (pattern[i] == '*') { - i++; - if (pattern[i] == '*') { - i++; - len = (int) strlen(str + j); - } else { - len = (int) strcspn(str + j, "/"); - } - if (i == pattern_len) { - return j + len; - } - do { - res = match_prefix(pattern + i, pattern_len - i, str + j + len); - } while (res == -1 && len-- > 0); - return res == -1 ? -1 : j + res + len; - } else if (lowercase(&pattern[i]) != lowercase(&str[j])) { - return -1; - } - } - return j; -} - // HTTP 1.1 assumes keep alive if "Connection:" header is not set // This function must tolerate situations when connection info is not // set up, for example if request parsing failed. @@ -902,11 +898,11 @@ static void send_http_error(struct mg_connection *conn, int status, // Errors 1xx, 204 and 304 MUST NOT send a body if (status > 199 && status != 204 && status != 304) { - len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason); + len = mg_snprintf(buf, sizeof(buf), "Error %d: %s", status, reason); buf[len++] = '\n'; va_start(ap, fmt); - len += mg_vsnprintf(conn, buf + len, sizeof(buf) - len, fmt, ap); + len += mg_vsnprintf(buf + len, sizeof(buf) - len, fmt, ap); va_end(ap); } DEBUG_TRACE(("[%s]", buf)); @@ -1293,7 +1289,7 @@ static pid_t spawn_process(struct mg_connection *conn, const char *prog, } GetFullPathNameA(dir, sizeof(full_dir), full_dir, NULL); - mg_snprintf(conn, cmdline, sizeof(cmdline), "%s%s\"%s\\%s\"", + mg_snprintf(cmdline, sizeof(cmdline), "%s%s\"%s\\%s\"", interp, interp[0] == '\0' ? "" : " ", full_dir, prog); DEBUG_TRACE(("Running [%s]", cmdline)); @@ -1770,12 +1766,12 @@ static int convert_uri_to_file_name(struct mg_connection *conn, char *buf, // 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, buf, buf_len - 1, "%s%s", root, uri); + mg_snprintf(buf, buf_len - 1, "%s%s", root, uri); 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, buf, buf_len - 1, "%.*s%s", (int) b.len, b.ptr, + mg_snprintf(buf, buf_len - 1, "%.*s%s", (int) b.len, b.ptr, uri + match_len); break; } @@ -2286,7 +2282,7 @@ static FILE *open_auth_file(struct mg_connection *conn, const char *path) { // Important: using local struct file to test path for is_directory flag. // If filep is used, mg_stat() makes it appear as if auth file was opened. } else if (mg_stat(path, &file) && file.is_directory) { - mg_snprintf(conn, name, sizeof(name), "%s%c%s", + mg_snprintf(name, sizeof(name), "%s%c%s", path, '/', PASSWORDS_FILE_NAME); fp = mg_fopen(name, "r"); } else { @@ -2294,7 +2290,7 @@ static FILE *open_auth_file(struct mg_connection *conn, const char *path) { for (p = path, e = p + strlen(p) - 1; e > p; e--) if (e[0] == '/') break; - mg_snprintf(conn, name, sizeof(name), "%.*s%c%s", + mg_snprintf(name, sizeof(name), "%.*s%c%s", (int) (e - p), p, '/', PASSWORDS_FILE_NAME); fp = mg_fopen(name, "r"); } @@ -2406,7 +2402,7 @@ static int check_authorization(struct mg_connection *conn, const char *path) { list = conn->ctx->config[PROTECT_URI]; while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) { if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) { - mg_snprintf(conn, fname, sizeof(fname), "%.*s", + mg_snprintf(fname, sizeof(fname), "%.*s", (int) filename_vec.len, filename_vec.ptr); if ((fp = mg_fopen(fname, "r")) == NULL) { cry(conn, "%s: cannot open %s: %s", __func__, fname, strerror(errno)); @@ -2571,20 +2567,20 @@ static void print_dir_entry(const struct de *de) { const char *slash = de->file.is_directory ? "/" : ""; if (de->file.is_directory) { - mg_snprintf(de->conn, size, sizeof(size), "%s", "[DIRECTORY]"); + mg_snprintf(size, sizeof(size), "%s", "[DIRECTORY]"); } else { // We use (signed) cast below because MSVC 6 compiler cannot // convert unsigned __int64 to double. Sigh. if (de->file.size < 1024) { - mg_snprintf(de->conn, size, sizeof(size), "%d", (int) de->file.size); + mg_snprintf(size, sizeof(size), "%d", (int) de->file.size); } else if (de->file.size < 0x100000) { - mg_snprintf(de->conn, size, sizeof(size), + mg_snprintf(size, sizeof(size), "%.1fk", (double) de->file.size / 1024.0); } else if (de->file.size < 0x40000000) { - mg_snprintf(de->conn, size, sizeof(size), + mg_snprintf(size, sizeof(size), "%.1fM", (double) de->file.size / 1048576); } else { - mg_snprintf(de->conn, size, sizeof(size), + mg_snprintf(size, sizeof(size), "%.1fG", (double) de->file.size / 1073741824); } } @@ -2654,7 +2650,7 @@ static int scan_directory(struct mg_connection *conn, const char *dir, continue; } - mg_snprintf(conn, path, sizeof(path), "%s%c%s", dir, '/', dp->d_name); + mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name); // If we don't memset stat structure to zero, mtime will have // garbage and strftime() will segfault later on in @@ -2690,7 +2686,7 @@ static int remove_directory(struct mg_connection *conn, const char *dir) { continue; } - mg_snprintf(conn, path, sizeof(path), "%s%c%s", dir, '/', dp->d_name); + mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name); // If we don't memset stat structure to zero, mtime will have // garbage and strftime() will segfault later on in @@ -2906,7 +2902,7 @@ static void handle_file_request(struct mg_connection *conn, const char *path, } conn->status_code = 206; cl = n == 2 ? (r2 > cl ? cl : r2) - r1 + 1: cl - r1; - mg_snprintf(conn, range, sizeof(range), + mg_snprintf(range, sizeof(range), "Content-Range: bytes " "%" INT64_FMT "-%" INT64_FMT "/%" INT64_FMT "\r\n", @@ -3183,7 +3179,7 @@ static char *addenv(struct cgi_env_block *block, const char *fmt, ...) { // Copy VARIABLE=VALUE\0 string into the free space va_start(ap, fmt); - n = mg_vsnprintf(block->conn, added, (size_t) space, fmt, ap); + n = mg_vsnprintf(added, (size_t) space, fmt, ap); va_end(ap); // Make sure we do not overflow buffer and the envp array @@ -3336,7 +3332,7 @@ static void handle_cgi_request(struct mg_connection *conn, const char *prog) { // CGI must be executed in its own directory. 'dir' must point to the // directory containing executable program, 'p' must point to the // executable program name relative to 'dir'. - (void) mg_snprintf(conn, dir, sizeof(dir), "%s", prog); + (void) mg_snprintf(dir, sizeof(dir), "%s", prog); if ((p = strrchr(dir, '/')) != NULL) { *p++ = '\0'; } else { @@ -3589,20 +3585,20 @@ static void do_ssi_include(struct mg_connection *conn, const char *ssi, // of size MG_BUF_LEN to get the tag. So strlen(tag) is always < MG_BUF_LEN. if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) { // File name is relative to the webserver root - (void) mg_snprintf(conn, path, sizeof(path), "%s%c%s", + (void) mg_snprintf(path, sizeof(path), "%s%c%s", conn->ctx->config[DOCUMENT_ROOT], '/', file_name); } else if (sscanf(tag, " abspath=\"%[^\"]\"", file_name) == 1) { // File name is relative to the webserver working directory // or it is absolute system path - (void) mg_snprintf(conn, path, sizeof(path), "%s", file_name); + (void) mg_snprintf(path, sizeof(path), "%s", file_name); } else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1 || sscanf(tag, " \"%[^\"]\"", file_name) == 1) { // File name is relative to the currect document - (void) mg_snprintf(conn, path, sizeof(path), "%s", ssi); + (void) mg_snprintf(path, sizeof(path), "%s", ssi); if ((p = strrchr(path, '/')) != NULL) { p[1] = '\0'; } - (void) mg_snprintf(conn, path + strlen(path), + (void) mg_snprintf(path + strlen(path), sizeof(path) - strlen(path), "%s", file_name); } else { cry(conn, "Bad SSI #include: [%s]", tag); @@ -3760,7 +3756,7 @@ static void print_dav_dir_entry(struct de *de, void *data) { char href[PATH_MAX]; char href_encoded[PATH_MAX]; struct mg_connection *conn = (struct mg_connection *) data; - mg_snprintf(conn, href, sizeof(href), "%s%s", + mg_snprintf(href, sizeof(href), "%s%s", conn->request_info.uri, de->file_name); mg_url_encode(href, href_encoded, PATH_MAX-1); print_props(conn, href_encoded, &de->file); @@ -3952,7 +3948,7 @@ void mg_websocket_handshake(struct mg_connection *conn) { char buf[100], sha[20], b64_sha[sizeof(sha) * 2]; SHA1_CTX sha_ctx; - mg_snprintf(conn, buf, sizeof(buf), "%s%s", + mg_snprintf(buf, sizeof(buf), "%s%s", mg_get_header(conn, "Sec-WebSocket-Key"), magic); SHA1Init(&sha_ctx); SHA1Update(&sha_ctx, (unsigned char *) buf, strlen(buf));