diff --git a/mongoose.c b/mongoose.c index 174c7bcbed017b8a4dd57d4cfb32927e7b766711..8b8e4ccd0f3e5b894ff776ca37d9b25a469b15f3 100644 --- a/mongoose.c +++ b/mongoose.c @@ -420,8 +420,11 @@ struct file { int64_t size; FILE *fp; const char *membuf; // Non-NULL if file data is in memory + // set to 1 if the content is gzipped + // in which case we need a content-encoding: gzip header + int gzipped; }; -#define STRUCT_FILE_INITIALIZER {0, 0, 0, NULL, NULL} +#define STRUCT_FILE_INITIALIZER {0, 0, 0, NULL, NULL, 0} // Describes listening socket, or socket which was accept()-ed by the master // thread and queued for future handling by the worker thread. @@ -1772,6 +1775,8 @@ static void convert_uri_to_file_name(struct mg_connection *conn, char *buf, const char *rewrite, *uri = conn->request_info.uri; char *p; int match_len; + char gz_path[PATH_MAX]; + char const* accept_encoding; // Using buf_len - 1 because memmove() for PATH_INFO may shift part // of the path one byte on the right. @@ -1787,26 +1792,42 @@ static void convert_uri_to_file_name(struct mg_connection *conn, char *buf, } } - if (!mg_stat(conn, buf, filep)) { - // Support PATH_INFO for CGI scripts. - for (p = buf + strlen(buf); p > buf + 1; p--) { - if (*p == '/') { - *p = '\0'; - if (match_prefix(conn->ctx->config[CGI_EXTENSIONS], - strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0 && - mg_stat(conn, buf, filep)) { - // Shift PATH_INFO block one character right, e.g. - // "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00" - // conn->path_info is pointing to the local variable "path" declared - // in handle_request(), so PATH_INFO is not valid after - // handle_request returns. - conn->path_info = p + 1; - memmove(p + 2, p + 1, strlen(p + 1) + 1); // +1 is for trailing \0 - p[1] = '/'; - break; - } else { - *p = '/'; - } + if (mg_stat(conn, buf, filep)) return; + + // if we can't find the actual file, look for the file + // with the same name but a .gz extension. If we find it, + // use that and set the gzipped flag in the file struct + // to indicate that the response need to have the content- + // encoding: gzip header + // we can only do this if the browser declares support + if ((accept_encoding = mg_get_header(conn, "Accept-Encoding")) != NULL) { + if (strstr(accept_encoding,"gzip") != NULL) { + snprintf(gz_path, sizeof(gz_path), "%s.gz", buf); + if (mg_stat(conn, gz_path, filep)) { + filep->gzipped = 1; + return; + } + } + } + + // Support PATH_INFO for CGI scripts. + for (p = buf + strlen(buf); p > buf + 1; p--) { + if (*p == '/') { + *p = '\0'; + if (match_prefix(conn->ctx->config[CGI_EXTENSIONS], + strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0 && + mg_stat(conn, buf, filep)) { + // Shift PATH_INFO block one character right, e.g. + // "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00" + // conn->path_info is pointing to the local variable "path" declared + // in handle_request(), so PATH_INFO is not valid after + // handle_request returns. + conn->path_info = p + 1; + memmove(p + 2, p + 1, strlen(p + 1) + 1); // +1 is for trailing \0 + p[1] = '/'; + break; + } else { + *p = '/'; } } } @@ -2828,17 +2849,29 @@ static void handle_file_request(struct mg_connection *conn, const char *path, int64_t cl, r1, r2; struct vec mime_vec; int n; + char gz_path[PATH_MAX]; + char const* encoding = ""; get_mime_type(conn->ctx, path, &mime_vec); cl = filep->size; conn->status_code = 200; range[0] = '\0'; + // if this file is in fact a pre-gzipped file, rewrite its filename + // it's important to rewrite the filename after resolving + // the mime type from it, to preserve the actual file's type + if (filep->gzipped) { + snprintf(gz_path, sizeof(gz_path), "%s.gz", path); + path = gz_path; + encoding = "Content-Encoding: gzip\r\n"; + } + if (!mg_fopen(conn, path, "rb", filep)) { send_http_error(conn, 500, http_500_error, "fopen(%s): %s", path, strerror(ERRNO)); return; } + fclose_on_exec(filep); // If Range: header specified, act accordingly @@ -2846,6 +2879,12 @@ static void handle_file_request(struct mg_connection *conn, const char *path, hdr = mg_get_header(conn, "Range"); if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0 && r1 >= 0 && r2 >= 0) { + // actually, range requests don't play well with a pre-gzipped + // file (since the range is specified in the uncmpressed space) + if (filep->gzipped) { + send_http_error(conn, 501, "Not Implemented", "range requests in gzipped files are not supported"); + return; + } conn->status_code = 206; cl = n == 2 ? (r2 > cl ? cl : r2) - r1 + 1: cl - r1; mg_snprintf(conn, range, sizeof(range), @@ -2871,9 +2910,9 @@ static void handle_file_request(struct mg_connection *conn, const char *path, "Content-Length: %" INT64_FMT "\r\n" "Connection: %s\r\n" "Accept-Ranges: bytes\r\n" - "%s\r\n", + "%s%s\r\n", conn->status_code, msg, date, lm, etag, (int) mime_vec.len, - mime_vec.ptr, cl, suggest_connection_header(conn), range); + mime_vec.ptr, cl, suggest_connection_header(conn), range, encoding); if (strcmp(conn->request_info.request_method, "HEAD") != 0) { send_file_data(conn, filep, r1, cl);