/
zOMGdev
/
cppcms
Обзор
Документация
Войти
/
zOMGdev
/
cppcms
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
v1.2.0
src/internal_file_server.cpp
558 строк
14 KB
Artyom Beilis
File server now works in non-blocking mode
23 дек 2015, 14:19
23 дек 2015, 14:19
ae65815
Код
Авторство
О чём код?
/////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2008-2012 Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com> // // See accompanying file COPYING.TXT file for licensing details. // /////////////////////////////////////////////////////////////////////////////// #define CPPCMS_SOURCE // make sure we all defines are given #include "dir.h" #include <cppcms/config.h> # if defined(CPPCMS_HAVE_CANONICALIZE_FILE_NAME) && !defined(_GNU_SOURCE) # define _GNU_SOURCE #endif #include <stdlib.h> #include <booster/callback.h> #include <cppcms/application.h> #include <cppcms/service.h> #include <cppcms/http_response.h> #include <cppcms/http_context.h> #include "internal_file_server.h" #include <cppcms/cppcms_error.h> #include <cppcms/json.h> #include <cppcms/util.h> #include <sstream> #include <booster/nowide/fstream.h> #include <booster/locale/encoding.h> #include <booster/locale/formatting.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #ifndef CPPCMS_WIN_NATIVE #include <unistd.h> #include <limits.h> #endif namespace cppcms { namespace impl { file_server::file_server(cppcms::service &srv,bool async) : application(srv), async_(async) { if(!canonical(settings().get("file_server.document_root","."),document_root_)) throw cppcms_error("Invalid document root"); list_directories_ = settings().get("file_server.listing",false); index_file_ = settings().get("file_server.index","index.html"); check_symlinks_ = settings().get("file_server.check_symlink",true); std::string mime_file=settings().get("file_server.mime_types",""); allow_deflate_ = settings().get("file_server.allow_deflate",false); if(settings().find("file_server.alias").type()==json::is_array) { json::array const &alias = settings().find("file_server.alias").array(); for(unsigned i=0;i<alias.size();i++) { std::string url = alias[i].get<std::string>("url"); if(url.size() < 2 || url[0]!='/') { throw cppcms_error("Invalid alias URL: " + url); } if(url[url.size()-1]=='/') url.resize(url.size()-1); std::string input_path = alias[i].get<std::string>("path"); std::string canon_path; if(!canonical(input_path,canon_path)) { throw cppcms_error("Invalid alias path: " + input_path); } alias_.push_back(std::make_pair(url,canon_path)); } } if(mime_file.empty()) { mime_[".pdf"] = "application/pdf"; mime_[".sig"] = "application/pgp-signature"; mime_[".spl"] = "application/futuresplash"; mime_[".ps"] = "application/postscript"; mime_[".torrent"]= "application/x-bittorrent"; mime_[".dvi"] = "application/x-dvi"; mime_[".gz"] = "application/x-gzip"; mime_[".pac"] = "application/x-ns-proxy-autoconfig"; mime_[".swf"] = "application/x-shockwave-flash"; mime_[".tgz"] = "application/x-tgz"; mime_[".tar"] = "application/x-tar"; mime_[".zip"] = "application/zip"; mime_[".mp3"] = "audio/mpeg"; mime_[".m3u"] = "audio/x-mpegurl"; mime_[".wma"] = "audio/x-ms-wma"; mime_[".wax"] = "audio/x-ms-wax"; mime_[".ogg"] = "application/ogg"; mime_[".wav"] = "audio/x-wav"; mime_[".gif"] = "image/gif"; mime_[".jpg"] = "image/jpeg"; mime_[".jpeg"] = "image/jpeg"; mime_[".png"] = "image/png"; mime_[".xbm"] = "image/x-xbitmap"; mime_[".xpm"] = "image/x-xpixmap"; mime_[".xwd"] = "image/x-xwindowdump"; mime_[".css"] = "text/css"; mime_[".html"] = "text/html"; mime_[".htm"] = "text/html"; mime_[".js"] = "text/javascript"; mime_[".asc"] = "text/plain"; mime_[".c"] = "text/plain"; mime_[".cpp"] = "text/plain"; mime_[".log"] = "text/plain"; mime_[".conf"] = "text/plain"; mime_[".text"] = "text/plain"; mime_[".txt"] = "text/plain"; mime_[".dtd"] = "text/xml"; mime_[".xml"] = "text/xml"; mime_[".mpeg"] = "video/mpeg"; mime_[".mpg"] = "video/mpeg"; mime_[".mov"] = "video/quicktime"; mime_[".qt"] = "video/quicktime"; mime_[".avi"] = "video/x-msvideo"; mime_[".asf"] = "video/x-ms-asf"; mime_[".asx"] = "video/x-ms-asf"; mime_[".wmv"] = "video/x-ms-wmv"; mime_[".bz2"] = "application/x-bzip"; mime_[".tbz"] = "application/x-bzip-compressed-tar"; } else { load_mime_types(mime_file); } } void file_server::load_mime_types(std::string file_name) { booster::nowide::ifstream inp(file_name.c_str()); if(!inp) { return; } std::string line; while(!inp.eof() && getline(inp,line)) { if(line.empty() || line[0]=='#') continue; std::istringstream ss(line); std::string mime; std::string ext; if(ss>>mime) { while(ss>>ext) { mime_["."+ext]=mime; } } } } file_server::~file_server() { } bool file_server::canonical(std::string normal,std::string &real) { #ifndef CPPCMS_WIN_NATIVE #ifdef CPPCMS_HAVE_CANONICALIZE_FILE_NAME char *canon=::canonicalize_file_name(normal.c_str()); if(!canon) return false; try { real=canon; } catch(...) { free(canon); throw; } free(canon); canon=0; #else #if defined(PATH_MAX) int len = PATH_MAX; #else int len = pathconf(normal.c_str(),_PC_PATH_MAX); if(len <= 0) len = 32768; // Hope it is enough #endif std::vector<char> buffer; try { // Size may be not feasible for allocation according to POSIX buffer.resize(len,0); } catch(std::bad_alloc const &e) { buffer.resize(32768); } char *canon = ::realpath(normal.c_str(),&buffer.front()); if(!canon) return false; real = canon; #endif #else wchar_t *wreal = 0; try { std::wstring wnormal = booster::locale::conv::utf_to_utf<wchar_t>(normal,booster::locale::conv::stop); wchar_t *wreal = _wfullpath(0,wnormal.c_str(),0); if(!wreal) return false; real = booster::locale::conv::utf_to_utf<char>(wreal,booster::locale::conv::stop); free(wreal); wreal = 0; } catch(booster::locale::conv::conversion_error const &) { if(wreal) free(wreal); return false; } // stat would not work on files like foo/ so remove the last slash as realpath // and canonicalize does if(real.size()>1 && real[real.size()-1]=='\\') real.resize(real.size()-1); #endif return true; } static bool is_directory_separator(char c) { #ifdef CPPCMS_WIN32 return c=='\\' || c=='/'; #else return c=='/'; #endif } static bool is_file_prefix(std::string const &prefix,std::string const &full) { size_t prefix_size = prefix.size(); if(prefix_size > full.size()) return false; if(memcmp(prefix.c_str(),full.c_str(),prefix_size) != 0) return false; if(prefix_size == 0 || is_directory_separator(prefix[prefix_size-1])) return true; if(full.size() > prefix_size && !is_directory_separator(full[prefix_size])) return false; return true; } bool file_server::is_in_root(std::string const &input_path,std::string const &root,std::string &real) { std::string normal=root + "/" + input_path; if(!canonical(normal,real)) return false; if(!is_file_prefix(root,real)) return false; return true; } void file_server::normalize_path(std::string &path) { #ifdef CPPCMS_WIN32 for(size_t i=0;i<path.size();i++) if(path[i]=='\\') path[i]='/'; #endif if(path.empty() || path[0]!='/') path = "/" + path; std::string::iterator out = path.begin() + 1; std::string::iterator start = path.begin() + 1; while(start < path.end()) { std::string::iterator end = std::find(start,path.end(),'/'); if(end==start || (end-start == 1 && *start == '.')) { // case of "//" and "/./" // nothing to do } else if(end-start == 2 && *start == '.' && *(start+1) == '.') { std::string::iterator min_pos = path.begin() + 1; if(out > min_pos) out --; while(out > min_pos) { out --; if(*out == '/') break; } } else { out = std::copy(start,end,out); if(end != path.end()) *out++ = '/'; } if(end == path.end()) break; start = end; ++start; } if(*(out-1) == '/' && out > path.begin()+1) out--; path.resize(out - path.begin()); } bool file_server::check_in_document_root(std::string normal,std::string &real) { normalize_path(normal); std::string root = document_root_; for(unsigned i=0;i<alias_.size();i++) { std::string const &ref=alias_[i].first; if(is_file_prefix(ref,normal)) { root = alias_[i].second; normal = normal.substr(ref.size()); if(normal.empty()) normal="/"; break; } } if(normal.empty()) return false; if(normal[0]!='/') return false; if(check_symlinks_) { if(!is_in_root(normal,root,real)) return false; } else { real = root + normal; // remove trailing '/' from file name size_t real_size = real.size(); if(real_size > 0 && is_directory_separator(real[real_size-1])) { real.resize(real_size-1); } } return true; } namespace { #ifdef CPPCMS_WIN_NATIVE typedef struct _stat port_stat; int get_stat(char const *name,port_stat *st) { try { std::wstring wname = booster::locale::conv::utf_to_utf<wchar_t>(name,booster::locale::conv::stop); return ::_wstat(wname.c_str(),st); } catch(booster::locale::conv::conversion_error const &) { return -1; } } #else typedef struct stat port_stat; int get_stat(char const *name,port_stat *st) { return ::stat(name,st); } #endif } int file_server::file_mode(std::string const &file_name) { port_stat st; if(get_stat(file_name.c_str(),&st) < 0) return 0; return st.st_mode; } void file_server::list_dir(std::string const &url,std::string const &path) { cppcms::impl::directory d; if(!d.open(path)) { show404(); return; } #ifdef CPPCMS_WIN_NATIVE response().content_type("text/html; charset=UTF-8"); #endif std::ostream &out = response().out(); out << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n" " \"http://www.w3.org/TR/html4/loose.dtd\">\n"; out << "<html><head><title>Directory Listing</title></head>\n" "<body><h1>Index of " << util::escape(url) << "</h1>\n"; out << booster::locale::as::gmt; //out <<"<table cellpadding='0' cellspacing='2' border='0' >\n"; out <<"<table>\n"; out <<"<thead><tr><td width='60%'>File</td><td width='20%' >Date</td><td width='5%'> </td><td width='15%'>Size</td></tr></thead>\n" "<tbody>\n"; if(url!="/" && !url.empty()) { out << "<tr><td><code><a href='../' >..</a></code></td><td> </td><td> </td><td> </td></tr>\n"; } out << booster::locale::as::ftime("%Y-%m-%d %H:%M:%S"); while(d.next()) { if(memcmp(d.name(),".",1) == 0) continue; port_stat st; if(get_stat((path + "/" + d.name()).c_str(),&st) < 0) continue; char const *add=""; if(st.st_mode & S_IFDIR) add="/"; else if(st.st_mode & S_IFREG) ; else continue; out << "<tr>"; out << "<td><code><a href='" << util::urlencode(d.name()) << add << "'>" << util::escape(d.name()) << add << "</a></code></td>"; out << "<td>" << booster::locale::as::strftime << st.st_mtime <<"</td><td> </td>"; if(st.st_mode & S_IFREG) out << "<td>" << booster::locale::as::number << st.st_size <<"</td>"; else out << "<td> <strong>-</strong> </td>"; out <<"</tr>\n"; } out <<"</tbody>\n</table>\n"; out <<"<p>CppCMS-Embedded/" CPPCMS_PACKAGE_VERSION "</p>\n"; out <<"</body>\n"; } namespace file_server_detail { class async_file_handler : public booster::callable<void(cppcms::http::context::completion_type)> { public: async_file_handler(std::string const &path,booster::shared_ptr<cppcms::http::context> c) : f(path.c_str(),std::ios_base::binary), ctx(c) { } typedef booster::intrusive_ptr<async_file_handler> pointer_type; void go() { if(!f) { ctx->response().set_html_header(); ctx->response().make_error_response(404); ctx->async_complete_response(); } else { ctx->response().full_asynchronous_buffering(false); ctx->response().out(); ctx->response().setbuf(0); (*this)(cppcms::http::context::operation_completed); } } void operator()(cppcms::http::context::completion_type c) { if(c!=cppcms::http::context::operation_completed) return; char buf[8192]; size_t total = 0; while(!f.eof() && !ctx->response().pending_blocked_output()) { f.read(buf,sizeof(buf)); size_t n = f.gcount(); total += n; ctx->response().out().write(buf,n); } if(f.eof()) ctx->async_complete_response(); else ctx->async_flush_output(pointer_type(this)); } private: booster::nowide::ifstream f; booster::shared_ptr<cppcms::http::context> ctx; }; } // file_server_detail void file_server::main(std::string file_name) { std::string path; if(!check_in_document_root(file_name,path)) { show404(); return; } int s=file_mode(path); if((s & S_IFDIR)) { std::string path2; int mode_2=0; bool have_index = check_in_document_root(file_name+"/" + index_file_ ,path2); if(have_index) { mode_2 = file_mode(path2); have_index = (mode_2 & S_IFREG) != 0; } if( !file_name.empty() && file_name[file_name.size()-1]!='/' // not ending with "/" as should && (have_index || list_directories_) ) { response().set_redirect_header(file_name + "/"); response().out()<<std::flush; return; } if(have_index) { path = path2; s=mode_2; } else { if(list_directories_) list_dir(file_name,path); else show404(); return; } } if(!(s & S_IFREG)) { show404(); return; } std::string ext; size_t pos = path.rfind('.'); if(pos != std::string::npos) ext=path.substr(pos); mime_type::const_iterator p=mime_.find(ext); if(p!=mime_.end()) response().content_type(p->second); else response().content_type("application/octet-stream"); if(!allow_deflate_ && !async_) { response().io_mode(http::response::nogzip); } if(async_) { file_server_detail::async_file_handler::pointer_type p=new file_server_detail::async_file_handler(path,release_context()); p->go(); } else { booster::nowide::ifstream file(path.c_str(),std::ios_base::binary); if(!file) { show404(); return; } response().out()<<file.rdbuf(); // write stream to stream } } void file_server::show404() { response().set_html_header(); response().make_error_response(404); } } // impl } // cppcms