diff --git a/build/main.c b/build/main.c index 193e74f1955122fc56572cef410b43af5ac81888..d9b5b639dbf2161b9ea993202f6b93e0443eeea1 100644 --- a/build/main.c +++ b/build/main.c @@ -120,7 +120,9 @@ static void show_usage_and_exit(void) { fprintf(stderr, "Mongoose version %s (c) Sergey Lyubka, built on %s\n", MONGOOSE_VERSION, __DATE__); fprintf(stderr, "Usage:\n"); +#ifndef NO_AUTH fprintf(stderr, " mongoose -A <htpasswd_file> <realm> <user> <passwd>\n"); +#endif fprintf(stderr, " mongoose [config_file]\n"); fprintf(stderr, " mongoose [-option value ...]\n"); fprintf(stderr, "\nOPTIONS:\n"); @@ -142,7 +144,7 @@ static const char *config_file_top_comment = "# To make a change, remove leading '#', modify option's value,\n" "# save this file and then restart Mongoose.\n\n"; -static const char *get_url_to_first_open_port(const struct mg_server *server) { +static const char *get_url_to_me(const struct mg_server *server) { static char url[100]; const char *s = mg_get_option(server, "listening_port"); const char *cert = mg_get_option(server, "ssl_certificate"); @@ -335,6 +337,7 @@ static void set_absolute_path(char *options[], const char *option_name, } } +#ifndef NO_AUTH int modify_passwords_file(const char *fname, const char *domain, const char *user, const char *pass) { int found; @@ -397,6 +400,7 @@ int modify_passwords_file(const char *fname, const char *domain, return 1; } +#endif static void start_mongoose(int argc, char *argv[]) { char *options[MAX_OPTIONS]; @@ -406,6 +410,7 @@ static void start_mongoose(int argc, char *argv[]) { die("%s", "Failed to start Mongoose."); } +#ifndef NO_AUTH // Edit passwords file if -A option is specified if (argc > 1 && !strcmp(argv[1], "-A")) { if (argc != 6) { @@ -414,6 +419,7 @@ static void start_mongoose(int argc, char *argv[]) { exit(modify_passwords_file(argv[2], argv[3], argv[4], argv[5]) ? EXIT_SUCCESS : EXIT_FAILURE); } +#endif // Show usage if -h or --help options are specified if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) { @@ -882,8 +888,8 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, manage_service(LOWORD(wParam)); break; case ID_CONNECT: - printf("[%s]\n", get_url_to_first_open_port(server)); - ShellExecute(NULL, "open", get_url_to_first_open_port(server), + printf("[%s]\n", get_url_to_me(server)); + ShellExecute(NULL, "open", get_url_to_me(server), NULL, NULL, SW_SHOW); break; } @@ -980,7 +986,7 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR cmdline, int show) { - (void) openBrowser { [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString: - [NSString stringWithUTF8String:get_url_to_first_open_port(server)]]]; + [NSString stringWithUTF8String:get_url_to_me(server)]]]; } - (void) editConfig { create_config_file(config_file); diff --git a/mongoose.c b/mongoose.c index 53b3c0cbfe2ea148aaa354900debdaef61c3a9fc..f096c9d434bd64c57cd3accda96e4107d03fe0ce 100644 --- a/mongoose.c +++ b/mongoose.c @@ -189,6 +189,14 @@ struct ll { struct ll *prev, *next; }; #define DBG(x) #endif +#ifdef NO_FILESYSTEM +#define NO_AUTH +#define NO_CGI +#define NO_DAV +#define NO_DIRECTORY_LISTING +#define NO_LOGGING +#endif + union socket_address { struct sockaddr sa; struct sockaddr_in sin; @@ -217,10 +225,21 @@ struct dir_entry { // NOTE(lsm): this enum shoulds be in sync with the config_options. enum { - ACCESS_CONTROL_LIST, ACCESS_LOG_FILE, AUTH_DOMAIN, CGI_INTERPRETER, + ACCESS_CONTROL_LIST, +#ifndef NO_FILESYSTEM + ACCESS_LOG_FILE, AUTH_DOMAIN, CGI_INTERPRETER, CGI_PATTERN, DAV_AUTH_FILE, DOCUMENT_ROOT, ENABLE_DIRECTORY_LISTING, - EXTRA_MIME_TYPES, GLOBAL_AUTH_FILE, HIDE_FILES_PATTERN, - IDLE_TIMEOUT_MS, INDEX_FILES, LISTENING_PORT, +#endif + EXTRA_MIME_TYPES, +#ifndef NO_FILESYSTEM + GLOBAL_AUTH_FILE, +#endif + HIDE_FILES_PATTERN, + IDLE_TIMEOUT_MS, +#ifndef NO_FILESYSTEM + INDEX_FILES, +#endif + LISTENING_PORT, #ifndef _WIN32 RUN_AS_USER, #endif @@ -230,6 +249,37 @@ enum { URL_REWRITES, NUM_OPTIONS }; +static const char *static_config_options[] = { + "access_control_list", NULL, +#ifndef NO_FILESYSTEM + "access_log_file", NULL, + "auth_domain", "mydomain.com", + "cgi_interpreter", NULL, + "cgi_pattern", "**.cgi$|**.pl$|**.php$", + "dav_auth_file", NULL, + "document_root", NULL, + "enable_directory_listing", "yes", +#endif + "extra_mime_types", NULL, +#ifndef NO_FILESYSTEM + "global_auth_file", NULL, +#endif + "hide_files_patterns", NULL, + "idle_timeout_ms", "30000", +#ifndef NO_FILESYSTEM + "index_files","index.html,index.htm,index.cgi,index.php,index.lp", +#endif + "listening_port", NULL, +#ifndef _WIN32 + "run_as_user", NULL, +#endif +#ifdef USE_SSL + "ssl_certificate", NULL, +#endif + "url_rewrites", NULL, + NULL +}; + struct mg_server { sock_t listening_sock; union socket_address lsa; // Listening socket address @@ -339,36 +389,6 @@ static const struct { {NULL, 0, NULL} }; -static const char *static_config_options[] = { - "access_control_list", NULL, - "access_log_file", NULL, - "auth_domain", "mydomain.com", - "cgi_interpreter", NULL, - "cgi_pattern", "**.cgi$|**.pl$|**.php$", - "dav_auth_file", NULL, - "document_root", NULL, - "enable_directory_listing", "yes", - "extra_mime_types", NULL, - "global_auth_file", NULL, - "hide_files_patterns", NULL, - "idle_timeout_ms", "30000", - "index_files","index.html,index.htm,index.cgi,index.php,index.lp", - "listening_port", NULL, -#ifndef _WIN32 - "run_as_user", NULL, -#endif -#ifdef USE_SSL - "ssl_certificate", NULL, -#endif - "url_rewrites", NULL, - NULL -}; - -static const char *static_month_names[] = { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" -}; - void *mg_start_thread(void *(*f)(void *), void *p) { #ifdef _WIN32 return (void *) _beginthread((void (__cdecl *)(void *)) f, 0, p); @@ -1413,6 +1433,18 @@ static int mg_strncasecmp(const char *s1, const char *s2, size_t len) { return diff; } +// Return HTTP header value, or NULL if not found. +const char *mg_get_header(const struct mg_connection *ri, const char *s) { + int i; + + for (i = 0; i < ri->num_headers; i++) + if (!mg_strcasecmp(s, ri->http_headers[i].name)) + return ri->http_headers[i].value; + + return NULL; +} + +#ifndef NO_FILESYSTEM // 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; @@ -1453,17 +1485,6 @@ static int match_prefix(const char *pattern, int pattern_len, const char *str) { return j; } -// Return HTTP header value, or NULL if not found. -const char *mg_get_header(const struct mg_connection *ri, const char *s) { - int i; - - for (i = 0; i < ri->num_headers; i++) - if (!mg_strcasecmp(s, ri->http_headers[i].name)) - return ri->http_headers[i].value; - - return NULL; -} - // Return 1 if real file has been found, 0 otherwise static int convert_uri_to_file_name(struct connection *conn, char *buf, size_t buf_len, file_stat_t *st) { @@ -1508,6 +1529,7 @@ static int convert_uri_to_file_name(struct connection *conn, char *buf, return 0; } +#endif // NO_FILESYSTEM static int should_keep_alive(const struct mg_connection *conn) { const char *method = conn->request_method; @@ -1518,17 +1540,6 @@ static int should_keep_alive(const struct mg_connection *conn) { (header == NULL && http_version && !strcmp(http_version, "1.1"))); } -static const char *suggest_connection_header(const struct mg_connection *conn) { - return should_keep_alive(conn) ? "keep-alive" : "close"; -} - -static void mg_strlcpy(register char *dst, register const char *src, size_t n) { - for (; *src != '\0' && n > 1; n--) { - *dst++ = *src++; - } - *dst = '\0'; -} - int mg_write(struct mg_connection *c, const void *buf, int len) { return spool(&((struct connection *) c)->remote_iobuf, buf, len); } @@ -1903,111 +1914,30 @@ const char *mg_get_mime_type(const char *path) { return "text/plain"; } -// Look at the "path" extension and figure what mime type it has. -// Store mime type in the vector. -static void get_mime_type(const struct mg_server *server, const char *path, - struct vec *vec) { - struct vec ext_vec, mime_vec; - const char *list, *ext; - size_t path_len; - - path_len = strlen(path); - - // Scan user-defined mime types first, in case user wants to - // override default mime types. - list = server->config_options[EXTRA_MIME_TYPES]; - while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) { - // ext now points to the path suffix - ext = path + path_len - ext_vec.len; - if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) { - *vec = mime_vec; - return; - } - } - - vec->ptr = mg_get_mime_type(path); - vec->len = strlen(vec->ptr); -} - -static int parse_range_header(const char *header, int64_t *a, int64_t *b) { - return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b); -} - -static void gmt_time_string(char *buf, size_t buf_len, time_t *t) { - strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t)); -} - -static void construct_etag(char *buf, size_t buf_len, const file_stat_t *st) { - mg_snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"", - (unsigned long) st->st_mtime, (int64_t) st->st_size); -} - -static void open_file_endpoint(struct connection *conn, const char *path, - file_stat_t *st) { - char date[64], lm[64], etag[64], range[64], headers[500]; - const char *msg = "OK", *hdr; - time_t curtime = time(NULL); - int64_t r1, r2; - struct vec mime_vec; - int n; - - conn->endpoint_type = EP_FILE; - set_close_on_exec(conn->endpoint.fd); - conn->mg_conn.status_code = 200; - - get_mime_type(conn->server, path, &mime_vec); - conn->cl = st->st_size; - range[0] = '\0'; +static struct uri_handler *find_uri_handler(struct mg_server *server, + const char *uri) { + struct ll *lp, *tmp; + struct uri_handler *uh; - // If Range: header specified, act accordingly - r1 = r2 = 0; - hdr = mg_get_header(&conn->mg_conn, "Range"); - if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0 && - r1 >= 0 && r2 >= 0) { - conn->mg_conn.status_code = 206; - conn->cl = n == 2 ? (r2 > conn->cl ? conn->cl : r2) - r1 + 1: conn->cl - r1; - mg_snprintf(range, sizeof(range), "Content-Range: bytes " - "%" INT64_FMT "-%" INT64_FMT "/%" INT64_FMT "\r\n", - r1, r1 + conn->cl - 1, (int64_t) st->st_size); - msg = "Partial Content"; - lseek(conn->endpoint.fd, r1, SEEK_SET); + LINKED_LIST_FOREACH(&server->uri_handlers, lp, tmp) { + uh = LINKED_LIST_ENTRY(lp, struct uri_handler, link); + if (!strncmp(uh->uri, uri, strlen(uh->uri))) return uh; } - // Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3 - gmt_time_string(date, sizeof(date), &curtime); - gmt_time_string(lm, sizeof(lm), &st->st_mtime); - construct_etag(etag, sizeof(etag), st); - - n = mg_snprintf(headers, sizeof(headers), - "HTTP/1.1 %d %s\r\n" - "Date: %s\r\n" - "Last-Modified: %s\r\n" - "Etag: %s\r\n" - "Content-Type: %.*s\r\n" - "Content-Length: %" INT64_FMT "\r\n" - "Connection: %s\r\n" - "Accept-Ranges: bytes\r\n" - "%s%s\r\n", - conn->mg_conn.status_code, msg, date, lm, etag, - (int) mime_vec.len, mime_vec.ptr, conn->cl, - suggest_connection_header(&conn->mg_conn), - range, USE_EXTRA_HTTP_HEADERS); - spool(&conn->remote_iobuf, headers, n); - - if (!strcmp(conn->mg_conn.request_method, "HEAD")) { - conn->flags |= CONN_SPOOL_DONE; - close(conn->endpoint.fd); - conn->endpoint_type = EP_NONE; - } + return NULL; } +#ifndef NO_FILESYSTEM // Convert month to the month number. Return -1 on error, or month number static int get_month_index(const char *s) { + static const char *month_names[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; int i; - for (i = 0; i < (int) ARRAY_SIZE(static_month_names); i++) - if (!strcmp(s, static_month_names[i])) + for (i = 0; i < (int) ARRAY_SIZE(month_names); i++) + if (!strcmp(s, month_names[i])) return i; return -1; @@ -2045,6 +1975,41 @@ static time_t parse_date_string(const char *datetime) { return result; } +// Look at the "path" extension and figure what mime type it has. +// Store mime type in the vector. +static void get_mime_type(const struct mg_server *server, const char *path, + struct vec *vec) { + struct vec ext_vec, mime_vec; + const char *list, *ext; + size_t path_len; + + path_len = strlen(path); + + // Scan user-defined mime types first, in case user wants to + // override default mime types. + list = server->config_options[EXTRA_MIME_TYPES]; + while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) { + // ext now points to the path suffix + ext = path + path_len - ext_vec.len; + if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) { + *vec = mime_vec; + return; + } + } + + vec->ptr = mg_get_mime_type(path); + vec->len = strlen(vec->ptr); +} + +static const char *suggest_connection_header(const struct mg_connection *conn) { + return should_keep_alive(conn) ? "keep-alive" : "close"; +} + +static void construct_etag(char *buf, size_t buf_len, const file_stat_t *st) { + mg_snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"", + (unsigned long) st->st_mtime, (int64_t) st->st_size); +} + // Return True if we should reply 304 Not Modified. static int is_not_modified(const struct connection *conn, const file_stat_t *stp) { @@ -2056,19 +2021,6 @@ static int is_not_modified(const struct connection *conn, (ims != NULL && stp->st_mtime <= parse_date_string(ims)); } -static struct uri_handler *find_uri_handler(struct mg_server *server, - const char *uri) { - struct ll *lp, *tmp; - struct uri_handler *uh; - - LINKED_LIST_FOREACH(&server->uri_handlers, lp, tmp) { - uh = LINKED_LIST_ENTRY(lp, struct uri_handler, link); - if (!strncmp(uh->uri, uri, strlen(uh->uri))) return uh; - } - - return NULL; -} - // For given directory path, substitute it to valid index file. // Return 0 if index file has been found, -1 if not found. // If the file is found, it's stats is returned in stp. @@ -2096,7 +2048,8 @@ static int find_index_file(struct connection *conn, char *path, continue; // Prepare full path to the index file - mg_strlcpy(path + n + 1, filename_vec.ptr, filename_vec.len + 1); + strncpy(path + n + 1, filename_vec.ptr, filename_vec.len); + path[n + 1 + filename_vec.len] = '\0'; //DBG(("[%s]", path)); @@ -2117,6 +2070,76 @@ static int find_index_file(struct connection *conn, char *path, return found; } +static int parse_range_header(const char *header, int64_t *a, int64_t *b) { + return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b); +} + +static void gmt_time_string(char *buf, size_t buf_len, time_t *t) { + strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t)); +} + +static void open_file_endpoint(struct connection *conn, const char *path, + file_stat_t *st) { + char date[64], lm[64], etag[64], range[64], headers[500]; + const char *msg = "OK", *hdr; + time_t curtime = time(NULL); + int64_t r1, r2; + struct vec mime_vec; + int n; + + conn->endpoint_type = EP_FILE; + set_close_on_exec(conn->endpoint.fd); + conn->mg_conn.status_code = 200; + + get_mime_type(conn->server, path, &mime_vec); + conn->cl = st->st_size; + range[0] = '\0'; + + // If Range: header specified, act accordingly + r1 = r2 = 0; + hdr = mg_get_header(&conn->mg_conn, "Range"); + if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0 && + r1 >= 0 && r2 >= 0) { + conn->mg_conn.status_code = 206; + conn->cl = n == 2 ? (r2 > conn->cl ? conn->cl : r2) - r1 + 1: conn->cl - r1; + mg_snprintf(range, sizeof(range), "Content-Range: bytes " + "%" INT64_FMT "-%" INT64_FMT "/%" INT64_FMT "\r\n", + r1, r1 + conn->cl - 1, (int64_t) st->st_size); + msg = "Partial Content"; + lseek(conn->endpoint.fd, r1, SEEK_SET); + } + + // Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3 + gmt_time_string(date, sizeof(date), &curtime); + gmt_time_string(lm, sizeof(lm), &st->st_mtime); + construct_etag(etag, sizeof(etag), st); + + n = mg_snprintf(headers, sizeof(headers), + "HTTP/1.1 %d %s\r\n" + "Date: %s\r\n" + "Last-Modified: %s\r\n" + "Etag: %s\r\n" + "Content-Type: %.*s\r\n" + "Content-Length: %" INT64_FMT "\r\n" + "Connection: %s\r\n" + "Accept-Ranges: bytes\r\n" + "%s%s\r\n", + conn->mg_conn.status_code, msg, date, lm, etag, + (int) mime_vec.len, mime_vec.ptr, conn->cl, + suggest_connection_header(&conn->mg_conn), + range, USE_EXTRA_HTTP_HEADERS); + spool(&conn->remote_iobuf, headers, n); + + if (!strcmp(conn->mg_conn.request_method, "HEAD")) { + conn->flags |= CONN_SPOOL_DONE; + close(conn->endpoint.fd); + conn->endpoint_type = EP_NONE; + } +} + +#endif // NO_FILESYSTEM + static void write_terminating_chunk(struct connection *conn) { mg_write(&conn->mg_conn, "0\r\n\r\n", 5); } @@ -2584,7 +2607,6 @@ static void forward_put_data(struct connection *conn) { } } } -#endif // NO_DAV static void send_options(struct connection *conn) { static const char reply[] = "HTTP/1.1 200 OK\r\nAllow: GET, POST, HEAD, " @@ -2592,6 +2614,7 @@ static void send_options(struct connection *conn) { spool(&conn->remote_iobuf, reply, sizeof(reply) - 1); conn->flags |= CONN_SPOOL_DONE; } +#endif // NO_DAV #ifndef NO_AUTH static void send_authorization_request(struct connection *conn) { @@ -2630,7 +2653,7 @@ static FILE *open_auth_file(struct connection *conn, const char *path) { return fp; } -#ifndef HAVE_MD5 +#if !defined(HAVE_MD5) && !defined(NO_AUTH) typedef struct MD5Context { uint32_t buf[4]; uint32_t bits[2]; @@ -3234,13 +3257,15 @@ static void handle_lsp_request(struct connection *conn, const char *path, #endif // USE_LUA static void open_local_endpoint(struct connection *conn) { + const char *cl_hdr = mg_get_header(&conn->mg_conn, "Content-Length"); +#ifndef NO_FILESYSTEM static const char lua_pat[] = LUA_SCRIPT_PATTERN; - char path[MAX_PATH_SIZE]; file_stat_t st; + char path[MAX_PATH_SIZE]; int exists = 0, is_directory = 0; - const char *cl_hdr = mg_get_header(&conn->mg_conn, "Content-Length"); const char *cgi_pat = conn->server->config_options[CGI_PATTERN]; const char *dir_lst = conn->server->config_options[ENABLE_DIRECTORY_LISTING]; +#endif conn->mg_conn.content_len = cl_hdr == NULL ? 0 : (int) to64(cl_hdr); @@ -3262,6 +3287,9 @@ static void open_local_endpoint(struct connection *conn) { return; } +#ifdef NO_FILESYSTEM + send_http_error(conn, 404, NULL); +#else exists = convert_uri_to_file_name(conn, path, sizeof(path), &st); is_directory = S_ISDIR(st.st_mode); @@ -3329,6 +3357,7 @@ static void open_local_endpoint(struct connection *conn) { } else { send_http_error(conn, 404, NULL); } +#endif // NO_FILESYSTEM } static void send_continue_if_expected(struct connection *conn) {