also improvement overall path handlingmaster
@@ -831,6 +831,12 @@ add_test(file_server_test | |||
"-U" "${CNF}" | |||
) | |||
add_test(file_server_test_no_links | |||
file_server_test "-c" "${CNF}/file_server_test.js" "--file_server-check_symlink=false" | |||
"--test-exec=${PYTHON} ${CNF}/file_server_test.py no_links" | |||
"-U" "${CNF}" | |||
) | |||
add_test(file_server_with_listing_test | |||
file_server_test "-c" "${CNF}/file_server_test.js" | |||
"--file_server-listing=true" | |||
@@ -13,9 +13,11 @@ | |||
namespace cppcms { | |||
class service; | |||
namespace impl { | |||
class file_server : public application | |||
class CPPCMS_API file_server : public application | |||
{ | |||
public: | |||
static void normalize_path(std::string &path); | |||
file_server(cppcms::service &srv); | |||
~file_server(); | |||
virtual void main(std::string file_name); | |||
@@ -35,6 +37,7 @@ namespace impl { | |||
typedef std::map<std::string,std::string> mime_type; | |||
mime_type mime_; | |||
bool list_directories_; | |||
bool check_symlinks_; | |||
std::string index_file_; | |||
}; | |||
@@ -48,6 +48,7 @@ file_server::file_server(cppcms::service &srv) : application(srv) | |||
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",""); | |||
@@ -253,13 +254,51 @@ bool file_server::is_in_root(std::string const &input_path,std::string const &ro | |||
return true; | |||
} | |||
bool file_server::check_in_document_root(std::string normal,std::string &real) | |||
void file_server::normalize_path(std::string &path) | |||
{ | |||
// Use only Unix file names | |||
for(size_t i=0;i<normal.size();i++) | |||
if(is_directory_separator(normal[i])) | |||
normal[i]='/'; | |||
#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; | |||
@@ -276,21 +315,13 @@ bool file_server::check_in_document_root(std::string normal,std::string &real) | |||
return false; | |||
if(normal[0]!='/') | |||
return false; | |||
// Prevent the access to any valid file below like | |||
// detecting that the files placed in /var/www | |||
// by providing a path /../../var/www/known.txt | |||
// whuch would be valid as known is placed in /var/www | |||
// but yet we don't want user to detect that files | |||
// exist in /var/www | |||
for(size_t pos = 1;pos != std::string::npos; pos = normal.find('/',pos)) { | |||
std::string sub_path = normal.substr(0,pos); | |||
std::string tmp; | |||
if(!is_in_root(sub_path,root,tmp)) | |||
if(check_symlinks_) { | |||
if(!is_in_root(normal,root,real)) | |||
return false; | |||
pos++; | |||
} | |||
if(!is_in_root(normal,root,real)) | |||
return false; | |||
else { | |||
real = root + normal; | |||
} | |||
return true; | |||
} | |||
@@ -15,6 +15,7 @@ | |||
#include <cppcms/mount_point.h> | |||
#include <cppcms/json.h> | |||
#include <cppcms/copy_filter.h> | |||
#include "internal_file_server.h" | |||
#include <iostream> | |||
#include "client.h" | |||
#include "test.h" | |||
@@ -24,9 +25,42 @@ | |||
#include <unistd.h> | |||
#endif | |||
int test(std::string const &left,std::string const &expected) | |||
{ | |||
std::string tmp=left; | |||
cppcms::impl::file_server::normalize_path(tmp); | |||
if(tmp!=expected) { | |||
std::cout << "[" << left << "] -> expected [" << expected << "] got [" << tmp << "]\n"; | |||
return 1; | |||
} | |||
return 0; | |||
} | |||
int test_normalize() | |||
{ | |||
std::cout << "- Tesing path normalization "<< std::endl; | |||
int r = 0 | |||
+ test("/foo/bar","/foo/bar") | |||
+ test("/foo/bar/","/foo/bar") | |||
+ test("/foo////bar","/foo/bar") | |||
+ test("///foo/./bar","/foo/bar") | |||
+ test("///foo/./bar///","/foo/bar") | |||
+ test("///foo/./bar/","/foo/bar") | |||
+ test("/../bar","/bar") | |||
+ test("/..","/") | |||
+ test("../test/./","/test") | |||
+ test("/var/www/../../../xx","/xx") | |||
+ test("/var/www/../../xx","/xx") | |||
+ test("../test/./x","/test/x") | |||
+ test("/test/..","/") | |||
+ test("/test/xx/../","/test") | |||
+ test("/test/xx/..","/test") | |||
; | |||
return r; | |||
} | |||
int main(int argc,char **argv) | |||
{ | |||
if(test_normalize()!=0) | |||
return 1; | |||
if(argc<5 || argv[argc-2]!=std::string("-U")) { | |||
std::cerr <<"Usage -c config -U dir"; | |||
return 1; | |||
@@ -48,8 +48,11 @@ def test_request(url,status,content='ignore',valid=[],notvalid=[]): | |||
do_listing = False | |||
check_links = True | |||
if len(sys.argv) == 2: | |||
do_listing = sys.argv[1] == 'listing' | |||
do_listing = sys.argv[1] == 'listing' | |||
if sys.argv[1] == 'no_links': | |||
check_links = False | |||
@@ -85,10 +88,6 @@ test_request('/alias/',200,'/al/index.html') | |||
test_request('/alias/test.txt',200,'/al/test.txt') | |||
test_request('/alias/foo/test.txt',200,'/al/foo/test.txt') | |||
if os.name == 'posix': | |||
print "- Testing symlinks" | |||
test_request('/no.txt',404) | |||
test_request('/yes.txt',200,'/yes') | |||
print "- Testing directory traversal" | |||
@@ -103,3 +102,11 @@ test_request('/../alias/never.txt','404') | |||
test_request('/%2e%2e/never.txt','404') | |||
test_request('/..%c0%afnever.txt','404') | |||
if os.name == 'posix': | |||
print "- Testing symlinks" | |||
test_request('/yes.txt',200,'/yes') | |||
if check_links: | |||
test_request('/no.txt',404) | |||
else: | |||
test_request('/no.txt',200,'never.txt') | |||