|
- ///////////////////////////////////////////////////////////////////////////////
- //
- // 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;
- }
- return true;
- }
-
- namespace {
-
- #ifdef CPPCMS_WIN_NATIVE
- typedef struct _stat port_stat;
- int get_stat(char const *name,port_stat *st)
- {
- std::wstring wname = booster::locale::conv::utf_to_utf<wchar_t>(name,booster::locale::conv::stop);
- return ::_wstat(wname.c_str(),st);
- }
- #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();
- (*this)(cppcms::http::context::operation_completed);
- }
-
- }
- void operator()(cppcms::http::context::completion_type c)
- {
- if(c!=cppcms::http::context::operation_completed)
- return;
- char buf[4096];
- size_t total = 0;
- while(!f.eof() && total < 65536) {
- 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
|