#include "uwsgi.h" extern struct uwsgi_server uwsgi; int uwsgi_static_want_gzip(struct wsgi_request *wsgi_req, char *filename, size_t *filename_len, struct stat *st) { // check for filename size if (*filename_len + 4 > PATH_MAX) return 0; // check for supported encodings if (!uwsgi_contains_n(wsgi_req->encoding, wsgi_req->encoding_len, "gzip", 4) ) return 0; // check for 'all' if (uwsgi.static_gzip_all) goto gzip; // check for dirs/prefix struct uwsgi_string_list *usl = uwsgi.static_gzip_dir; while(usl) { if (!uwsgi_starts_with(filename, *filename_len, usl->value, usl->len)) { goto gzip; } usl = usl->next; } // check for ext/suffix usl = uwsgi.static_gzip_ext; while(usl) { if (!uwsgi_strncmp(filename + (*filename_len - usl->len), usl->len, usl->value, usl->len)) { goto gzip; } usl = usl->next; } #ifdef UWSGI_PCRE // check for regexp struct uwsgi_regexp_list *url = uwsgi.static_gzip; while(url) { if (uwsgi_regexp_match(url->pattern, url->pattern_extra, filename, *filename_len) >= 0) { goto gzip; } url = url->next; } #endif return 0; gzip: memcpy(filename + *filename_len, ".gz\0", 4); *filename_len += 3; if (stat(filename, st)) { *filename_len -= 3; filename[*filename_len] = 0; return 0; } return 1; } int uwsgi_http_date(time_t t, char *dst) { static char *week[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; struct tm *hdtm = gmtime(&t); int ret = snprintf(dst, 31, "%s, %02d %s %4d %02d:%02d:%02d GMT", week[hdtm->tm_wday], hdtm->tm_mday, months[hdtm->tm_mon], hdtm->tm_year + 1900, hdtm->tm_hour, hdtm->tm_min, hdtm->tm_sec); if (ret <= 0 || ret > 31) { return 0; } return ret; } // only RFC 1123 is supported static time_t parse_http_date(char *date, uint16_t len) { struct tm hdtm; if (len != 29 && date[3] != ',') return 0; hdtm.tm_mday = uwsgi_str2_num(date + 5); switch (date[8]) { case 'J': if (date[9] == 'a') { hdtm.tm_mon = 0; break; } if (date[9] == 'u') { if (date[10] == 'n') { hdtm.tm_mon = 5; break; } if (date[10] == 'l') { hdtm.tm_mon = 6; break; } return 0; } return 0; case 'F': hdtm.tm_mon = 1; break; case 'M': if (date[9] != 'a') return 0; if (date[10] == 'r') { hdtm.tm_mon = 2; break; } if (date[10] == 'y') { hdtm.tm_mon = 4; break; } return 0; case 'A': if (date[10] == 'r') { hdtm.tm_mon = 3; break; } if (date[10] == 'g') { hdtm.tm_mon = 7; break; } return 0; case 'S': hdtm.tm_mon = 8; break; case 'O': hdtm.tm_mon = 9; break; case 'N': hdtm.tm_mon = 10; break; case 'D': hdtm.tm_mon = 11; break; default: return 0; } hdtm.tm_year = uwsgi_str4_num(date + 12) - 1900; hdtm.tm_hour = uwsgi_str2_num(date + 17); hdtm.tm_min = uwsgi_str2_num(date + 20); hdtm.tm_sec = uwsgi_str2_num(date + 23); return timegm(&hdtm); } time_t uwsgi_parse_http_date(char *buf, uint16_t len) { return parse_http_date(buf, len); } int uwsgi_add_expires_type(struct wsgi_request *wsgi_req, char *mime_type, int mime_type_len, struct stat *st) { struct uwsgi_dyn_dict *udd = uwsgi.static_expires_type; time_t now = wsgi_req->start_of_request / 1000000; // 30+1 char expires[31]; while (udd) { if (!uwsgi_strncmp(udd->key, udd->keylen, mime_type, mime_type_len)) { int delta = uwsgi_str_num(udd->value, udd->vallen); int size = uwsgi_http_date(now + delta, expires); if (size > 0) { if (uwsgi_response_add_header(wsgi_req, "Expires", 7, expires, size)) return -1; } return 0; } udd = udd->next; } udd = uwsgi.static_expires_type_mtime; while (udd) { if (!uwsgi_strncmp(udd->key, udd->keylen, mime_type, mime_type_len)) { int delta = uwsgi_str_num(udd->value, udd->vallen); int size = uwsgi_http_date(st->st_mtime + delta, expires); if (size > 0) { if (uwsgi_response_add_header(wsgi_req, "Expires", 7, expires, size)) return -1; } return 0; } udd = udd->next; } return 0; } #ifdef UWSGI_PCRE int uwsgi_add_expires(struct wsgi_request *wsgi_req, char *filename, int filename_len, struct stat *st) { struct uwsgi_dyn_dict *udd = uwsgi.static_expires; time_t now = wsgi_req->start_of_request / 1000000; // 30+1 char expires[31]; while (udd) { if (uwsgi_regexp_match(udd->pattern, udd->pattern_extra, filename, filename_len) >= 0) { int delta = uwsgi_str_num(udd->value, udd->vallen); int size = uwsgi_http_date(now + delta, expires); if (size > 0) { if (uwsgi_response_add_header(wsgi_req, "Expires", 7, expires, size)) return -1; } return 0; } udd = udd->next; } udd = uwsgi.static_expires_mtime; while (udd) { if (uwsgi_regexp_match(udd->pattern, udd->pattern_extra, filename, filename_len) >= 0) { int delta = uwsgi_str_num(udd->value, udd->vallen); int size = uwsgi_http_date(st->st_mtime + delta, expires); if (size > 0) { if (uwsgi_response_add_header(wsgi_req, "Expires", 7, expires, size)) return -1; } return 0; } udd = udd->next; } return 0; } int uwsgi_add_expires_path_info(struct wsgi_request *wsgi_req, struct stat *st) { struct uwsgi_dyn_dict *udd = uwsgi.static_expires_path_info; time_t now = wsgi_req->start_of_request / 1000000; // 30+1 char expires[31]; while (udd) { if (uwsgi_regexp_match(udd->pattern, udd->pattern_extra, wsgi_req->path_info, wsgi_req->path_info_len) >= 0) { int delta = uwsgi_str_num(udd->value, udd->vallen); int size = uwsgi_http_date(now + delta, expires); if (size > 0) { if (uwsgi_response_add_header(wsgi_req, "Expires", 7, expires, size)) return -1; } return 0; } udd = udd->next; } udd = uwsgi.static_expires_path_info_mtime; while (udd) { if (uwsgi_regexp_match(udd->pattern, udd->pattern_extra, wsgi_req->path_info, wsgi_req->path_info_len) >= 0) { int delta = uwsgi_str_num(udd->value, udd->vallen); int size = uwsgi_http_date(st->st_mtime + delta, expires); if (size > 0) { if (uwsgi_response_add_header(wsgi_req, "Expires", 7, expires, size)) return -1; } return 0; } udd = udd->next; } return 0; } int uwsgi_add_expires_uri(struct wsgi_request *wsgi_req, struct stat *st) { struct uwsgi_dyn_dict *udd = uwsgi.static_expires_uri; time_t now = wsgi_req->start_of_request / 1000000; // 30+1 char expires[31]; while (udd) { if (uwsgi_regexp_match(udd->pattern, udd->pattern_extra, wsgi_req->uri, wsgi_req->uri_len) >= 0) { int delta = uwsgi_str_num(udd->value, udd->vallen); int size = uwsgi_http_date(now + delta, expires); if (size > 0) { if (uwsgi_response_add_header(wsgi_req, "Expires", 7, expires, size)) return -1; } return 0; } udd = udd->next; } udd = uwsgi.static_expires_uri_mtime; while (udd) { if (uwsgi_regexp_match(udd->pattern, udd->pattern_extra, wsgi_req->uri, wsgi_req->uri_len) >= 0) { int delta = uwsgi_str_num(udd->value, udd->vallen); int size = uwsgi_http_date(st->st_mtime + delta, expires); if (size > 0) { if (uwsgi_response_add_header(wsgi_req, "Expires", 7, expires, size)) return -1; } return 0; } udd = udd->next; } return 0; } #endif char *uwsgi_get_mime_type(char *name, int namelen, size_t *size) { int i; int count = 0; char *ext = NULL; for (i = namelen - 1; i >= 0; i--) { if (!isalnum((int) name[i])) { if (name[i] == '.') { ext = name + (namelen - count); break; } } count++; } if (!ext) return NULL; if (uwsgi.threads > 1) pthread_mutex_lock(&uwsgi.lock_static); struct uwsgi_dyn_dict *udd = uwsgi.mimetypes; while (udd) { if (!uwsgi_strncmp(ext, count, udd->key, udd->keylen)) { udd->hits++; // auto optimization if (udd->prev) { if (udd->hits > udd->prev->hits) { struct uwsgi_dyn_dict *udd_parent = udd->prev->prev, *udd_prev = udd->prev; if (udd_parent) { udd_parent->next = udd; } if (udd->next) { udd->next->prev = udd_prev; } udd_prev->prev = udd; udd_prev->next = udd->next; udd->prev = udd_parent; udd->next = udd_prev; if (udd->prev == NULL) { uwsgi.mimetypes = udd; } } } *size = udd->vallen; if (uwsgi.threads > 1) pthread_mutex_unlock(&uwsgi.lock_static); return udd->value; } udd = udd->next; } if (uwsgi.threads > 1) pthread_mutex_unlock(&uwsgi.lock_static); return NULL; } ssize_t uwsgi_append_static_path(char *dir, size_t dir_len, char *file, size_t file_len) { size_t len = dir_len; if (len + 1 + file_len > PATH_MAX) { return -1; } if (dir[len - 1] == '/') { memcpy(dir + len, file, file_len); dir[len + file_len] = 0; len += file_len; } else { dir[len] = '/'; memcpy(dir + len + 1, file, file_len); dir[len + 1 + file_len] = 0; len += 1 + file_len; } return len; } static int uwsgi_static_stat(struct wsgi_request *wsgi_req, char *filename, size_t *filename_len, struct stat *st, struct uwsgi_string_list **index) { int ret = stat(filename, st); // if non-existant return -1 if (ret < 0) return -1; if (S_ISREG(st->st_mode)) return 0; // check for index if (S_ISDIR(st->st_mode)) { struct uwsgi_string_list *usl = uwsgi.static_index; while (usl) { ssize_t new_len = uwsgi_append_static_path(filename, *filename_len, usl->value, usl->len); if (new_len >= 0) { #ifdef UWSGI_DEBUG uwsgi_log("checking for %s\n", filename); #endif if (uwsgi_is_file2(filename, st)) { *index = usl; *filename_len = new_len; return 0; } // reset to original name filename[*filename_len] = 0; } usl = usl->next; } } return -1; } int uwsgi_real_file_serve(struct wsgi_request *wsgi_req, char *real_filename, size_t real_filename_len, struct stat *st) { size_t mime_type_size = 0; char http_last_modified[49]; int use_gzip = 0; char *mime_type = uwsgi_get_mime_type(real_filename, real_filename_len, &mime_type_size); // here we need to choose if we want the gzip variant; if (uwsgi_static_want_gzip(wsgi_req, real_filename, &real_filename_len, st)) use_gzip = 1; if (wsgi_req->if_modified_since_len) { time_t ims = parse_http_date(wsgi_req->if_modified_since, wsgi_req->if_modified_since_len); if (st->st_mtime <= ims) { if (uwsgi_response_prepare_headers(wsgi_req, "304 Not Modified", 16)) return -1; return uwsgi_response_write_headers_do(wsgi_req); } } #ifdef UWSGI_DEBUG uwsgi_log("[uwsgi-fileserve] file %s found, mimetype %s\n", real_filename, mime_type); #endif // static file - don't update avg_rt after request wsgi_req->do_not_account_avg_rt = 1; size_t fsize = st->st_size; // security check if (wsgi_req->range_from > fsize) { wsgi_req->range_from = 0; wsgi_req->range_to = 0; } else { fsize -= wsgi_req->range_from; } if (wsgi_req->range_to) { fsize = (wsgi_req->range_to - wsgi_req->range_from)+1; if (fsize + wsgi_req->range_from > (size_t) (st->st_size)) { fsize = st->st_size - wsgi_req->range_from; } } // HTTP status if (fsize > 0 && (wsgi_req->range_from || wsgi_req->range_to)) { if (uwsgi_response_prepare_headers(wsgi_req, "206 Partial Content", 19)) return -1; } else { if (uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6)) return -1; } #ifdef UWSGI_PCRE uwsgi_add_expires(wsgi_req, real_filename, real_filename_len, st); uwsgi_add_expires_path_info(wsgi_req, st); uwsgi_add_expires_uri(wsgi_req, st); #endif if (use_gzip) { if (uwsgi_response_add_header(wsgi_req, "Content-Encoding", 16, "gzip", 4)) return -1; } // Content-Type (if available) if (mime_type_size > 0 && mime_type) { if (uwsgi_response_add_content_type(wsgi_req, mime_type, mime_type_size)) return -1; // check for content-type related headers uwsgi_add_expires_type(wsgi_req, mime_type, mime_type_size, st); } // increase static requests counter uwsgi.workers[uwsgi.mywid].cores[wsgi_req->async_id].static_requests++; // nginx if (uwsgi.file_serve_mode == 1) { if (uwsgi_response_add_header(wsgi_req, "X-Accel-Redirect", 16, real_filename, real_filename_len)) return -1; // this is the final header (\r\n added) int size = uwsgi_http_date(st->st_mtime, http_last_modified); if (uwsgi_response_add_header(wsgi_req, "Last-Modified", 13, http_last_modified, size)) return -1; } // apache else if (uwsgi.file_serve_mode == 2) { if (uwsgi_response_add_header(wsgi_req, "X-Sendfile", 10, real_filename, real_filename_len)) return -1; // this is the final header (\r\n added) int size = uwsgi_http_date(st->st_mtime, http_last_modified); if (uwsgi_response_add_header(wsgi_req, "Last-Modified", 13, http_last_modified, size)) return -1; } // raw else { // set Content-Length (to fsize NOT st->st_size) if (uwsgi_response_add_content_length(wsgi_req, fsize)) return -1; if (fsize > 0 && (wsgi_req->range_from || wsgi_req->range_to)) { // here use the original size !!! if (uwsgi_response_add_content_range(wsgi_req, wsgi_req->range_from, wsgi_req->range_to, st->st_size)) return -1; } int size = uwsgi_http_date(st->st_mtime, http_last_modified); if (uwsgi_response_add_header(wsgi_req, "Last-Modified", 13, http_last_modified, size)) return -1; // if it is a HEAD request just skip transfer if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "HEAD", 4)) { wsgi_req->status = 200; return 0; } // Ok, the file must be transferred from uWSGI // offloading will be automatically managed int fd = open(real_filename, O_RDONLY); if (fd < 0) return -1; // fd will be closed in the following function uwsgi_response_sendfile_do(wsgi_req, fd, wsgi_req->range_from, fsize); } wsgi_req->status = 200; return 0; } int uwsgi_file_serve(struct wsgi_request *wsgi_req, char *document_root, uint16_t document_root_len, char *path_info, uint16_t path_info_len, int is_a_file) { struct stat st; char real_filename[PATH_MAX + 1]; size_t real_filename_len = 0; char *filename = NULL; size_t filename_len = 0; struct uwsgi_string_list *index = NULL; if (!is_a_file) { filename = uwsgi_concat3n(document_root, document_root_len, "/", 1, path_info, path_info_len); filename_len = document_root_len + 1 + path_info_len; } else { filename = uwsgi_concat2n(document_root, document_root_len, "", 0); filename_len = document_root_len; } #ifdef UWSGI_DEBUG uwsgi_log("[uwsgi-fileserve] checking for %s\n", filename); #endif if (uwsgi.static_cache_paths) { uwsgi_rlock(uwsgi.static_cache_paths->lock); uint64_t item_len; char *item = uwsgi_cache_get2(uwsgi.static_cache_paths, filename, filename_len, &item_len); if (item && item_len > 0 && item_len <= PATH_MAX) { memcpy(real_filename, item, item_len); real_filename_len = item_len; real_filename[real_filename_len] = 0; uwsgi_rwunlock(uwsgi.static_cache_paths->lock); goto found; } uwsgi_rwunlock(uwsgi.static_cache_paths->lock); } if (!realpath(filename, real_filename)) { #ifdef UWSGI_DEBUG uwsgi_log("[uwsgi-fileserve] unable to get realpath() of the static file\n"); #endif free(filename); return -1; } real_filename_len = strlen(real_filename); if (uwsgi.static_cache_paths) { uwsgi_wlock(uwsgi.static_cache_paths->lock); uwsgi_cache_set2(uwsgi.static_cache_paths, filename, filename_len, real_filename, real_filename_len, uwsgi.use_static_cache_paths, UWSGI_CACHE_FLAG_UPDATE); uwsgi_rwunlock(uwsgi.static_cache_paths->lock); } found: free(filename); if (uwsgi_starts_with(real_filename, real_filename_len, document_root, document_root_len)) { struct uwsgi_string_list *safe = uwsgi.static_safe; while(safe) { if (!uwsgi_starts_with(real_filename, real_filename_len, safe->value, safe->len)) { goto safe; } safe = safe->next; } uwsgi_log("[uwsgi-fileserve] security error: %s is not under %.*s or a safe path\n", real_filename, document_root_len, document_root); return -1; } safe: if (!uwsgi_static_stat(wsgi_req, real_filename, &real_filename_len, &st, &index)) { if (index) { // if we are here the PATH_INFO need to be changed if (uwsgi_req_append_path_info_with_index(wsgi_req, index->value, index->len)) { return -1; } } // skip methods other than GET and HEAD if (uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "GET", 3) && uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "HEAD", 4)) { return -1; } // check for skippable ext struct uwsgi_string_list *sse = uwsgi.static_skip_ext; while (sse) { if (real_filename_len >= sse->len) { if (!uwsgi_strncmp(real_filename + (real_filename_len - sse->len), sse->len, sse->value, sse->len)) { return -1; } } sse = sse->next; } #ifdef UWSGI_ROUTING // before sending the file, we need to check if some rule applies if (!wsgi_req->is_routing && uwsgi_apply_routes_do(uwsgi.routes, wsgi_req, NULL, 0) == UWSGI_ROUTE_BREAK) { return 0; } wsgi_req->routes_applied = 1; #endif return uwsgi_real_file_serve(wsgi_req, real_filename, real_filename_len, &st); } return -1; }