src: Add utility APIs to asio_http2.h; add asio-sv2 example to serve files

This commit is contained in:
Tatsuhiro Tsujikawa 2014-09-24 23:05:13 +09:00
parent 99ca15cae0
commit fd07f5e142
7 changed files with 178 additions and 18 deletions

View File

@ -51,15 +51,24 @@ deflate_SOURCES = deflate.c
if ENABLE_ASIO_LIB if ENABLE_ASIO_LIB
noinst_PROGRAMS += asio-sv noinst_PROGRAMS += asio-sv asio-sv2
asio_sv_SOURCES = asio-sv.cc ASIOCPPFLAGS = ${BOOST_CPPFLAGS} ${AM_CPPFLAGS}
asio_sv_CPPFLAGS = ${BOOST_CPPFLAGS} ${AM_CPPFLAGS} ASIOLDFLAGS = \
asio_sv_LDFLAGS = \
@JEMALLOC_LIBS@ \ @JEMALLOC_LIBS@ \
${BOOST_LDFLAGS} \ ${BOOST_LDFLAGS} \
${BOOST_SYSTEM_LIB} ${BOOST_SYSTEM_LIB}
asio_sv_LDADD = $(top_builddir)/src/libnghttp2_asio.la ASIOLDADD = $(top_builddir)/src/libnghttp2_asio.la
asio_sv_SOURCES = asio-sv.cc
asio_sv_CPPFLAGS = ${ASIOCPPFLAGS}
asio_sv_LDFLAGS = ${ASIOLDFLAGS}
asio_sv_LDADD = ${ASIOLDADD}
asio_sv2_SOURCES = asio-sv2.cc
asio_sv2_CPPFLAGS = ${ASIOCPPFLAGS}
asio_sv2_LDFLAGS = ${ASIOLDFLAGS}
asio_sv2_LDADD = ${ASIOLDADD}
endif # ENABLE_ASIO_LIB endif # ENABLE_ASIO_LIB

110
examples/asio-sv2.cc Normal file
View File

@ -0,0 +1,110 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2014 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// We wrote this code based on the original code which has the
// following license:
//
// main.cpp
// ~~~~~~~~
//
// Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <iostream>
#include <string>
#include <nghttp2/asio_http2.h>
using namespace nghttp2::asio_http2;
using namespace nghttp2::asio_http2::server;
int main(int argc, char* argv[])
{
try {
// Check command line arguments.
if (argc < 4) {
std::cerr << "Usage: asio-sv2 <port> <threads> <doc-root> "
<< "<private-key-file> <cert-file>\n";
return 1;
}
uint16_t port = std::stoi(argv[1]);
std::size_t num_threads = std::stoi(argv[2]);
std::string docroot = argv[3];
http2 server;
server.num_threads(num_threads);
if(argc >= 6) {
server.tls(argv[4], argv[5]);
}
server.listen
("*", port,
[&docroot](std::shared_ptr<request> req, std::shared_ptr<response> res)
{
auto path = percent_decode(req->path());
if(!check_path(path)) {
res->write_head(404);
res->end();
return;
}
if(path == "/") {
path = "/index.html";
}
path = docroot + path;
auto fd = open(path.c_str(), O_RDONLY);
if(fd == -1) {
res->write_head(404);
res->end();
return;
}
auto headers = std::vector<header>();
struct stat stbuf;
if(stat(path.c_str(), &stbuf) == 0) {
headers.push_back
(header{"content-length", std::to_string(stbuf.st_size)});
headers.push_back
(header{"last-modified", http_date(stbuf.st_mtim.tv_sec)});
}
res->write_head(200, std::move(headers));
res->end(file_reader_from_fd(fd));
});
} catch (std::exception& e) {
std::cerr << "exception: " << e.what() << "\n";
}
return 0;
}

View File

@ -796,18 +796,6 @@ ssize_t file_read_callback
return nread; return nread;
} }
namespace {
bool check_url(const std::string& url)
{
// We don't like '\' in url.
return !url.empty() && url[0] == '/' &&
url.find('\\') == std::string::npos &&
url.find("/../") == std::string::npos &&
url.find("/./") == std::string::npos &&
!util::endsWith(url, "/..") && !util::endsWith(url, "/.");
}
} // namespace
namespace { namespace {
void prepare_status_response(Stream *stream, Http2Handler *hd, void prepare_status_response(Stream *stream, Http2Handler *hd,
const std::string& status) const std::string& status)
@ -885,7 +873,7 @@ void prepare_response(Stream *stream, Http2Handler *hd, bool allow_push = true)
url = url.substr(0, query_pos); url = url.substr(0, query_pos);
} }
url = util::percentDecode(url.begin(), url.end()); url = util::percentDecode(url.begin(), url.end());
if(!check_url(url)) { if(!util::check_path(url)) {
prepare_status_response(stream, hd, STATUS_404); prepare_status_response(stream, hd, STATUS_404);
return; return;
} }

View File

@ -155,6 +155,11 @@ read_cb file_reader(const std::string& path)
return read_cb(); return read_cb();
} }
return file_reader_from_fd(fd);
}
read_cb file_reader_from_fd(int fd)
{
auto d = defer_shared(static_cast<int>(fd), close); auto d = defer_shared(static_cast<int>(fd), close);
return [fd, d](uint8_t *buf, size_t len) -> read_cb::result_type return [fd, d](uint8_t *buf, size_t len) -> read_cb::result_type
@ -174,6 +179,21 @@ read_cb file_reader(const std::string& path)
}; };
} }
bool check_path(const std::string& path)
{
return util::check_path(path);
}
std::string percent_decode(const std::string& s)
{
return util::percentDecode(std::begin(s), std::end(s));
}
std::string http_date(time_t t)
{
return util::http_date(t);
}
} // namespace server } // namespace server
} // namespace asio_http2 } // namespace asio_http2

View File

@ -168,6 +168,23 @@ private:
// |path|. This can be passed to response::end(). // |path|. This can be passed to response::end().
read_cb file_reader(const std::string& path); read_cb file_reader(const std::string& path);
// Like file_reader(const std::string&), but it takes opened file
// descriptor. The passed descriptor will be closed when returned
// function object is destroyed.
read_cb file_reader_from_fd(int fd);
// Validates path so that it does not contain directory traversal
// vector. Returns true if path is safe. The |path| must start with
// "/" otherwise returns false. This function should be called after
// percent-decode was performed.
bool check_path(const std::string& path);
// Performs percent-decode against string |s|.
std::string percent_decode(const std::string& s);
// Returns HTTP date representation of current posix time |t|.
std::string http_date(time_t t);
} // namespace server } // namespace server
} // namespace asio_http2 } // namespace asio_http2

View File

@ -593,6 +593,16 @@ char* get_exec_path(int argc, char **const argv, const char *cwd)
return path; return path;
} }
bool check_path(const std::string& path)
{
// We don't like '\' in path.
return !path.empty() && path[0] == '/' &&
path.find('\\') == std::string::npos &&
path.find("/../") == std::string::npos &&
path.find("/./") == std::string::npos &&
!util::endsWith(path, "/..") && !util::endsWith(path, "/.");
}
} // namespace util } // namespace util
} // namespace nghttp2 } // namespace nghttp2

View File

@ -464,6 +464,12 @@ std::string ascii_dump(const uint8_t *data, size_t len);
// it. // it.
char* get_exec_path(int argc, char **const argv, const char *cwd); char* get_exec_path(int argc, char **const argv, const char *cwd);
// Validates path so that it does not contain directory traversal
// vector. Returns true if path is safe. The |path| must start with
// "/" otherwise returns false. This function should be called after
// percent-decode was performed.
bool check_path(const std::string& path);
} // namespace util } // namespace util
} // namespace nghttp2 } // namespace nghttp2