Skip to content

Commit

Permalink
fix static file serving security issue; fix url path encoding issue
Browse files Browse the repository at this point in the history
  • Loading branch information
sprinfall committed Feb 15, 2022
1 parent 50cf424 commit 55a45fd
Show file tree
Hide file tree
Showing 18 changed files with 227 additions and 138 deletions.
2 changes: 1 addition & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ if(WEBCC_ENABLE_SSL)
endif()

if(WIN32)
add_executable(url_unicode url_unicode.cc encoding.cc encoding.h)
add_executable(url_unicode url_unicode.cc)
target_link_libraries(url_unicode ${EXAMPLE_LIBS})
set_target_properties(url_unicode PROPERTIES FOLDER "Examples")
endif()
Expand Down
60 changes: 0 additions & 60 deletions examples/encoding.cc

This file was deleted.

12 changes: 0 additions & 12 deletions examples/encoding.h

This file was deleted.

Binary file modified examples/url_unicode.cc
Binary file not shown.
2 changes: 1 addition & 1 deletion webcc/body.cc
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ Payload FileBody::NextPayload(bool /*free_previous*/) {
}

void FileBody::Dump(std::ostream& os, const std::string& prefix) const {
os << prefix << "<file: " << path_.string() << ">" << std::endl;
os << prefix << "<file: " << path_.u8string() << ">" << std::endl;
}

bool FileBody::Move(const fs::path& new_path) {
Expand Down
4 changes: 4 additions & 0 deletions webcc/fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ using std::filesystem::path;
using std::filesystem::filesystem_error;

// functions
using std::filesystem::absolute;
using std::filesystem::canonical;
using std::filesystem::rename;
using std::filesystem::remove;
using std::filesystem::exists;
Expand All @@ -47,6 +49,8 @@ using boost::filesystem::path;
using boost::filesystem::filesystem_error;

// functions
using boost::filesystem::absolute;
using boost::filesystem::canonical;
using boost::filesystem::rename;
using boost::filesystem::remove;
using boost::filesystem::exists;
Expand Down
9 changes: 4 additions & 5 deletions webcc/request_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,13 @@ void RequestParser::Init(Request* request, ViewMatcher view_matcher) {
}

bool RequestParser::OnHeadersEnd() {
bool matched = view_matcher_(request_->method(), request_->url().path(),
&stream_);

// Decode the URL path before match.
std::string url_path = Url::DecodeUnsafe(request_->url().path());
bool matched = view_matcher_(request_->method(), url_path, &stream_);
if (!matched) {
LOG_WARN("No view matches the request: %s %s", request_->method().c_str(),
request_->url().path().c_str());
url_path.c_str());
}

return matched;
}

Expand Down
1 change: 1 addition & 0 deletions webcc/request_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

namespace webcc {

// Parameters: http_method, url_path, [out]stream
using ViewMatcher =
std::function<bool(const std::string&, const std::string&, bool*)>;

Expand Down
7 changes: 3 additions & 4 deletions webcc/router.cc
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ ViewPtr Router::FindView(const std::string& method, const std::string& url,
return ViewPtr();
}

bool Router::MatchView(const std::string& method, const std::string& url,
bool Router::MatchView(const std::string& method, const std::string& url_path,
bool* stream) {
assert(stream != nullptr);
*stream = false;
Expand All @@ -80,13 +80,12 @@ bool Router::MatchView(const std::string& method, const std::string& url,

if (route.url.empty()) {
std::smatch match;

if (std::regex_match(url, match, route.url_regex)) {
if (std::regex_match(url_path, match, route.url_regex)) {
*stream = route.view->Stream(method);
return true;
}
} else {
if (boost::iequals(route.url, url)) {
if (boost::iequals(route.url, url_path)) {
*stream = route.view->Stream(method);
return true;
}
Expand Down
4 changes: 2 additions & 2 deletions webcc/router.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ class Router {
ViewPtr FindView(const std::string& method, const std::string& url,
UrlArgs* args);

// Match the view by HTTP method and URL (path).
// Match the view by HTTP method and URL path.
// Return if a view is matched or not.
// If the view asks for data streaming, |stream| will be set to true.
bool MatchView(const std::string& method, const std::string& url,
bool MatchView(const std::string& method, const std::string& url_path,
bool* stream);

private:
Expand Down
36 changes: 32 additions & 4 deletions webcc/server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <fstream>
#include <utility>

#include "boost/algorithm/string/trim.hpp"

#include "webcc/body.h"
#include "webcc/logger.h"
#include "webcc/request.h"
Expand Down Expand Up @@ -32,6 +34,7 @@ Server::Server(boost::asio::ip::tcp protocol, std::uint16_t port,
doc_root_(doc_root),
acceptor_(io_context_),
signals_(io_context_) {
CheckDocRoot();
AddSignals();
}

Expand Down Expand Up @@ -105,6 +108,27 @@ bool Server::IsRunning() const {
return running_ && !io_context_.stopped();
}

void Server::CheckDocRoot() {
try {
if (!fs::exists(doc_root_) || !fs::is_directory(doc_root_)) {
LOG_ERRO("Doc root is not an existing directory!");
return;
}

if (doc_root_.is_relative()) {
doc_root_ = fs::absolute(doc_root_);
}

doc_root_ = fs::canonical(doc_root_);

} catch (fs::filesystem_error& e) {
LOG_ERRO("Doc root error: %s", e.what());
doc_root_.clear();
}

LOG_INFO("Doc root: %s", doc_root_.u8string().c_str());
}

void Server::AddSignals() {
signals_.add(SIGINT); // Ctrl+C
signals_.add(SIGTERM);
Expand Down Expand Up @@ -314,14 +338,16 @@ void Server::Handle(ConnectionPtr connection) {
}

bool Server::MatchViewOrStatic(const std::string& method,
const std::string& url, bool* stream) {
if (Router::MatchView(method, url, stream)) {
const std::string& url_path, bool* stream) {
if (Router::MatchView(method, url_path, stream)) {
return true;
}

// Try to match a static file.
if (method == methods::kGet && !doc_root_.empty()) {
fs::path path = doc_root_ / url;
fs::path sub_path = utility::TranslatePath(url_path);
//LOG_INFO("Translated URL path: %s", sub_path.u8string().c_str());
fs::path path = doc_root_ / sub_path;

fs::error_code ec;
if (!fs::is_directory(path, ec) && fs::exists(path, ec)) {
Expand All @@ -340,7 +366,9 @@ ResponsePtr Server::ServeStatic(RequestPtr request) {
return {};
}

fs::path path = doc_root_ / request->url().path();
std::string url_path = Url::DecodeUnsafe(request->url().path());
fs::path sub_path = utility::TranslatePath(url_path);
fs::path path = doc_root_ / sub_path;

try {
// NOTE: FileBody might throw Error::kFileError.
Expand Down
11 changes: 9 additions & 2 deletions webcc/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ class Server : public Router {
bool IsRunning() const;

private:
// Check if doc root is valid.
// Absolute it if necessary.
void CheckDocRoot();

// Register signals which indicate when the server should exit.
void AddSignals();

Expand Down Expand Up @@ -90,10 +94,13 @@ class Server : public Router {
// request comes, this connection will be put back to the queue again.
virtual void Handle(ConnectionPtr connection);

// Match the view by HTTP method and URL (path).
// Match the view by HTTP method and URL path.
// Return if a view or static file is matched or not.
// The |url_path| has already been decoded.
// The |url_path| is UTF8 encoded by itself, and this is taken into account
// when match the static files.
// If the view asks for data streaming, |stream| will be set to true.
bool MatchViewOrStatic(const std::string& method, const std::string& url,
bool MatchViewOrStatic(const std::string& method, const std::string& url_path,
bool* stream);

// Serve static files from the doc root.
Expand Down
61 changes: 61 additions & 0 deletions webcc/string.cc
Original file line number Diff line number Diff line change
@@ -1,11 +1,72 @@
#include "webcc/string.h"

#if (defined(_WIN32) || defined(_WIN64))
#include <Windows.h>
#endif

#include <random>

#include "boost/algorithm/string/trim.hpp"

namespace webcc {

#if (defined(_WIN32) || defined(_WIN64))

// Wrapper for Windows API MultiByteToWideChar.
static std::wstring MB2WC(const std::string& input, unsigned int code_page) {
if (input.empty()) {
return L"";
}

int length = ::MultiByteToWideChar(code_page, 0, &input[0],
static_cast<int>(input.size()),
NULL, 0);

std::wstring output(length, '\0');

::MultiByteToWideChar(code_page, 0, &input[0], static_cast<int>(input.size()),
&output[0], static_cast<int>(output.size()));

return output;
}

// Wrapper for Windows API WideCharToMultiByte.
static std::string WC2MB(const std::wstring& input, unsigned int code_page) {
if (input.empty()) {
return "";
}

// There do have other code pages which require the flags to be 0, e.g.,
// 50220, 50211, and so on. But they are not included in our charset
// dictionary. So, only consider 65001 (UTF-8) and 54936 (GB18030).
DWORD flags = 0;
if (code_page != 65001 && code_page != 54936) {
flags = WC_NO_BEST_FIT_CHARS | WC_COMPOSITECHECK | WC_DEFAULTCHAR;
}

int length = ::WideCharToMultiByte(code_page, flags, &input[0],
static_cast<int>(input.size()), NULL, 0,
NULL, NULL);

std::string output(length, '\0');

::WideCharToMultiByte(code_page, flags, &input[0],
static_cast<int>(input.size()), &output[0],
static_cast<int>(output.size()), NULL, NULL);

return output;
}

std::string Utf16To8(const std::wstring& utf16_string) {
return WC2MB(utf16_string, CP_UTF8);
}

std::wstring Utf8To16(const std::string& utf8_string) {
return MB2WC(utf8_string, CP_UTF8);
}

#endif // defined(_WIN32) || defined(_WIN64)

// Ref: https://stackoverflow.com/a/24586587
std::string RandomString(std::size_t length) {
static const char chrs[] =
Expand Down
5 changes: 5 additions & 0 deletions webcc/string.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

namespace webcc {

#if (defined(_WIN32) || defined(_WIN64))
std::string Utf16To8(const std::wstring& utf16_string);
std::wstring Utf8To16(const std::string& utf8_string);
#endif

// Get a randomly generated string with the given length.
std::string RandomString(std::size_t length);

Expand Down
Loading

0 comments on commit 55a45fd

Please sign in to comment.