first commit

This commit is contained in:
moneroexamples 2016-04-06 14:53:37 +08:00
commit 98661d9b67
65 changed files with 16558 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
.DS_Store
.sass-cache
*.*~
.idea/

101
CMakeLists.txt Normal file
View file

@ -0,0 +1,101 @@
cmake_minimum_required(VERSION 3.5)
set(PROJECT_NAME
crowxmr)
project(${PROJECT_NAME})
set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -std=c++14")
# find boost
find_package(Boost COMPONENTS
system
filesystem
thread
date_time
chrono
regex
serialization
program_options
date_time
REQUIRED)
# set location of monero static libraries
set(MONERO_LIBS_DIR
/opt/bitmonero-dev/libs)
# set location of moneroheaders
set(MONERO_HEADERS_DIR
/opt/bitmonero-dev/headers)
# include monero headers
include_directories(
${MONERO_HEADERS_DIR}/src
${MONERO_HEADERS_DIR}/external
${MONERO_HEADERS_DIR}/contrib/epee/include
${MONERO_HEADERS_DIR}/external/db_drivers/liblmdb)
# get individual monero static libraries
# that are needed in this project
add_library(common STATIC IMPORTED)
set_property(TARGET common PROPERTY IMPORTED_LOCATION ${MONERO_LIBS_DIR}/libcommon.a)
add_library(blocks STATIC IMPORTED)
set_property(TARGET blocks PROPERTY IMPORTED_LOCATION ${MONERO_LIBS_DIR}/libblocks.a)
add_library(crypto STATIC IMPORTED)
set_property(TARGET crypto
PROPERTY IMPORTED_LOCATION ${MONERO_LIBS_DIR}/libcrypto.a)
add_library(cryptonote_core STATIC IMPORTED)
set_property(TARGET cryptonote_core
PROPERTY IMPORTED_LOCATION ${MONERO_LIBS_DIR}/libcryptonote_core.a)
add_library(blockchain_db STATIC IMPORTED)
set_property(TARGET blockchain_db
PROPERTY IMPORTED_LOCATION ${MONERO_LIBS_DIR}/libblockchain_db.a)
add_library(lmdb STATIC IMPORTED)
set_property(TARGET lmdb
PROPERTY IMPORTED_LOCATION ${MONERO_LIBS_DIR}/liblmdb.a)
# include boost headers
include_directories(${Boost_INCLUDE_DIRS})
include_directories("ext/mstch/include")
# add ext/ subfolder
add_subdirectory(ext/)
# add src/ subfolder
add_subdirectory(src/)
set(SOURCE_FILES
main.cpp)
add_executable(${PROJECT_NAME}
${SOURCE_FILES})
target_link_libraries(${PROJECT_NAME}
myxrm
myext
mstch
cryptonote_core
blockchain_db
crypto
blocks
common
lmdb
${Boost_LIBRARIES}
pthread
unbound)

1
README.md Normal file
View file

@ -0,0 +1 @@
# crow-monero-test

24
ext/CMakeLists.txt Normal file
View file

@ -0,0 +1,24 @@
# first build mstch template library
add_subdirectory("mstch")
# now build myext library from other files
project(myext)
set(SOURCE_HEADERS
minicsv.h
format.h
dateparser.h)
set(SOURCE_FILES
format.cc
dateparser.cpp)
# make static library called libmyxrm
# that we are going to link to
# in the root CMakeLists.txt file
add_library(myext
STATIC
${SOURCE_FILES})

34
ext/crow/ci_map.h Normal file
View file

@ -0,0 +1,34 @@
#pragma once
#include <boost/algorithm/string/predicate.hpp>
#include <boost/functional/hash.hpp>
#include <unordered_map>
namespace crow
{
struct ci_hash
{
size_t operator()(const std::string& key) const
{
std::size_t seed = 0;
std::locale locale;
for(auto c : key)
{
boost::hash_combine(seed, std::toupper(c, locale));
}
return seed;
}
};
struct ci_key_eq
{
bool operator()(const std::string& l, const std::string& r) const
{
return boost::iequals(l, r);
}
};
using ci_map = std::unordered_multimap<std::string, std::string, ci_hash, ci_key_eq>;
}

126
ext/crow/common.h Normal file
View file

@ -0,0 +1,126 @@
#pragma once
#include <vector>
#include <string>
#include <stdexcept>
#include <iostream>
#include "utility.h"
namespace crow
{
enum class HTTPMethod
{
DELETE,
GET,
HEAD,
POST,
PUT,
CONNECT,
OPTIONS,
TRACE,
};
inline std::string method_name(HTTPMethod method)
{
switch(method)
{
case HTTPMethod::DELETE:
return "DELETE";
case HTTPMethod::GET:
return "GET";
case HTTPMethod::HEAD:
return "HEAD";
case HTTPMethod::POST:
return "POST";
case HTTPMethod::PUT:
return "PUT";
case HTTPMethod::CONNECT:
return "CONNECT";
case HTTPMethod::OPTIONS:
return "OPTIONS";
case HTTPMethod::TRACE:
return "TRACE";
}
return "invalid";
}
enum class ParamType
{
INT,
UINT,
DOUBLE,
STRING,
PATH,
MAX
};
struct routing_params
{
std::vector<int64_t> int_params;
std::vector<uint64_t> uint_params;
std::vector<double> double_params;
std::vector<std::string> string_params;
void debug_print() const
{
std::cerr << "routing_params" << std::endl;
for(auto i:int_params)
std::cerr<<i <<", " ;
std::cerr<<std::endl;
for(auto i:uint_params)
std::cerr<<i <<", " ;
std::cerr<<std::endl;
for(auto i:double_params)
std::cerr<<i <<", " ;
std::cerr<<std::endl;
for(auto& i:string_params)
std::cerr<<i <<", " ;
std::cerr<<std::endl;
}
template <typename T>
T get(unsigned) const;
};
template<>
inline int64_t routing_params::get<int64_t>(unsigned index) const
{
return int_params[index];
}
template<>
inline uint64_t routing_params::get<uint64_t>(unsigned index) const
{
return uint_params[index];
}
template<>
inline double routing_params::get<double>(unsigned index) const
{
return double_params[index];
}
template<>
inline std::string routing_params::get<std::string>(unsigned index) const
{
return string_params[index];
}
}
#ifndef CROW_MSVC_WORKAROUND
constexpr crow::HTTPMethod operator "" _method(const char* str, size_t len)
{
return
crow::black_magic::is_equ_p(str, "GET", 3) ? crow::HTTPMethod::GET :
crow::black_magic::is_equ_p(str, "DELETE", 6) ? crow::HTTPMethod::DELETE :
crow::black_magic::is_equ_p(str, "HEAD", 4) ? crow::HTTPMethod::HEAD :
crow::black_magic::is_equ_p(str, "POST", 4) ? crow::HTTPMethod::POST :
crow::black_magic::is_equ_p(str, "PUT", 3) ? crow::HTTPMethod::PUT :
crow::black_magic::is_equ_p(str, "OPTIONS", 7) ? crow::HTTPMethod::OPTIONS :
crow::black_magic::is_equ_p(str, "CONNECT", 7) ? crow::HTTPMethod::CONNECT :
crow::black_magic::is_equ_p(str, "TRACE", 5) ? crow::HTTPMethod::TRACE :
throw std::runtime_error("invalid http method");
};
#endif

199
ext/crow/crow.h Normal file
View file

@ -0,0 +1,199 @@
#pragma once
#include <string>
#include <functional>
#include <memory>
#include <future>
#include <cstdint>
#include <type_traits>
#include <thread>
#include "settings.h"
#include "logging.h"
#include "utility.h"
#include "routing.h"
#include "middleware_context.h"
#include "http_request.h"
#include "http_server.h"
#ifdef CROW_MSVC_WORKAROUND
#define CROW_ROUTE(app, url) app.route_dynamic(url)
#else
#define CROW_ROUTE(app, url) app.route<crow::black_magic::get_parameter_tag(url)>(url)
#endif
namespace crow
{
#ifdef CROW_ENABLE_SSL
using ssl_context_t = boost::asio::ssl::context;
#endif
template <typename ... Middlewares>
class Crow
{
public:
using self_t = Crow;
using server_t = Server<Crow, SocketAdaptor, Middlewares...>;
#ifdef CROW_ENABLE_SSL
using ssl_server_t = Server<Crow, SSLAdaptor, Middlewares...>;
#endif
Crow()
{
}
void handle(const request& req, response& res)
{
router_.handle(req, res);
}
DynamicRule& route_dynamic(std::string&& rule)
{
return router_.new_rule_dynamic(std::move(rule));
}
template <uint64_t Tag>
auto route(std::string&& rule)
-> typename std::result_of<decltype(&Router::new_rule_tagged<Tag>)(Router, std::string&&)>::type
{
return router_.new_rule_tagged<Tag>(std::move(rule));
}
self_t& port(std::uint16_t port)
{
port_ = port;
return *this;
}
self_t& multithreaded()
{
return concurrency(std::thread::hardware_concurrency());
}
self_t& concurrency(std::uint16_t concurrency)
{
if (concurrency < 1)
concurrency = 1;
concurrency_ = concurrency;
return *this;
}
void validate()
{
router_.validate();
}
void run()
{
validate();
#ifdef CROW_ENABLE_SSL
if (use_ssl_)
{
ssl_server_t server(this, port_, &middlewares_, concurrency_, &ssl_context_);
server.run();
}
else
#endif
{
server_t server(this, port_, &middlewares_, concurrency_, nullptr);
server.run();
}
}
void debug_print()
{
CROW_LOG_DEBUG << "Routing:";
router_.debug_print();
}
#ifdef CROW_ENABLE_SSL
self_t& ssl_file(const std::string& crt_filename, const std::string& key_filename)
{
use_ssl_ = true;
ssl_context_.set_verify_mode(boost::asio::ssl::verify_peer);
ssl_context_.use_certificate_file(crt_filename, ssl_context_t::pem);
ssl_context_.use_private_key_file(key_filename, ssl_context_t::pem);
ssl_context_.set_options(
boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::no_sslv3
);
return *this;
}
self_t& ssl_file(const std::string& pem_filename)
{
use_ssl_ = true;
ssl_context_.set_verify_mode(boost::asio::ssl::verify_peer);
ssl_context_.load_verify_file(pem_filename);
ssl_context_.set_options(
boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::no_sslv3
);
return *this;
}
self_t& ssl(boost::asio::ssl::context&& ctx)
{
use_ssl_ = true;
ssl_context_ = std::move(ctx);
return *this;
}
bool use_ssl_{false};
ssl_context_t ssl_context_{boost::asio::ssl::context::sslv23};
#else
template <typename T, typename ... Remain>
self_t& ssl_file(T&& t, Remain&&...)
{
// We can't call .ssl() member function unless CROW_ENABLE_SSL is defined.
static_assert(
// make static_assert dependent to T; always false
std::is_base_of<T, void>::value,
"Define CROW_ENABLE_SSL to enable ssl support.");
return *this;
}
template <typename T>
self_t& ssl(T&& ctx)
{
// We can't call .ssl() member function unless CROW_ENABLE_SSL is defined.
static_assert(
// make static_assert dependent to T; always false
std::is_base_of<T, void>::value,
"Define CROW_ENABLE_SSL to enable ssl support.");
return *this;
}
#endif
// middleware
using context_t = detail::context<Middlewares...>;
template <typename T>
typename T::context& get_context(const request& req)
{
static_assert(black_magic::contains<T, Middlewares...>::value, "App doesn't have the specified middleware type.");
auto& ctx = *reinterpret_cast<context_t*>(req.middleware_context);
return ctx.template get<T>();
}
template <typename T>
T& get_middleware()
{
return utility::get_element_by_type<T, Middlewares...>(middlewares_);
}
private:
uint16_t port_ = 80;
uint16_t concurrency_ = 1;
Router router_;
std::tuple<Middlewares...> middlewares_;
};
template <typename ... Middlewares>
using App = Crow<Middlewares...>;
using SimpleApp = Crow<>;
};

View file

@ -0,0 +1,81 @@
#pragma once
#include <boost/asio.hpp>
#include <deque>
#include <functional>
#include <chrono>
#include <thread>
#include "logging.h"
namespace crow
{
namespace detail
{
// fast timer queue for fixed tick value.
class dumb_timer_queue
{
public:
using key = std::pair<dumb_timer_queue*, int>;
void cancel(key& k)
{
auto self = k.first;
k.first = nullptr;
if (!self)
return;
unsigned int index = (unsigned int)(k.second - self->step_);
if (index < self->dq_.size())
self->dq_[index].second = nullptr;
}
key add(std::function<void()> f)
{
dq_.emplace_back(std::chrono::steady_clock::now(), std::move(f));
int ret = step_+dq_.size()-1;
CROW_LOG_DEBUG << "timer add inside: " << this << ' ' << ret ;
return {this, ret};
}
void process()
{
if (!io_service_)
return;
auto now = std::chrono::steady_clock::now();
while(!dq_.empty())
{
auto& x = dq_.front();
if (now - x.first < std::chrono::seconds(tick))
break;
if (x.second)
{
CROW_LOG_DEBUG << "timer call: " << this << ' ' << step_;
// we know that timer handlers are very simple currenty; call here
x.second();
}
dq_.pop_front();
step_++;
}
}
void set_io_service(boost::asio::io_service& io_service)
{
io_service_ = &io_service;
}
dumb_timer_queue() noexcept
{
}
private:
int tick{5};
boost::asio::io_service* io_service_{};
std::deque<std::pair<decltype(std::chrono::steady_clock::now()), std::function<void()>>> dq_;
int step_{};
};
}
}

575
ext/crow/http_connection.h Normal file
View file

@ -0,0 +1,575 @@
#pragma once
#include <boost/asio.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/array.hpp>
#include <atomic>
#include <chrono>
#include <vector>
#include "http_parser_merged.h"
#include "parser.h"
#include "http_response.h"
#include "logging.h"
#include "settings.h"
#include "dumb_timer_queue.h"
#include "middleware_context.h"
#include "socket_adaptors.h"
namespace crow
{
using namespace boost;
using tcp = asio::ip::tcp;
namespace detail
{
template <typename MW>
struct check_before_handle_arity_3_const
{
template <typename T,
void (T::*)(request&, response&, typename MW::context&) const = &T::before_handle
>
struct get
{ };
};
template <typename MW>
struct check_before_handle_arity_3
{
template <typename T,
void (T::*)(request&, response&, typename MW::context&) = &T::before_handle
>
struct get
{ };
};
template <typename MW>
struct check_after_handle_arity_3_const
{
template <typename T,
void (T::*)(request&, response&, typename MW::context&) const = &T::after_handle
>
struct get
{ };
};
template <typename MW>
struct check_after_handle_arity_3
{
template <typename T,
void (T::*)(request&, response&, typename MW::context&) = &T::after_handle
>
struct get
{ };
};
template <typename T>
struct is_before_handle_arity_3_impl
{
template <typename C>
static std::true_type f(typename check_before_handle_arity_3_const<T>::template get<C>*);
template <typename C>
static std::true_type f(typename check_before_handle_arity_3<T>::template get<C>*);
template <typename C>
static std::false_type f(...);
public:
static const bool value = decltype(f<T>(nullptr))::value;
};
template <typename T>
struct is_after_handle_arity_3_impl
{
template <typename C>
static std::true_type f(typename check_after_handle_arity_3_const<T>::template get<C>*);
template <typename C>
static std::true_type f(typename check_after_handle_arity_3<T>::template get<C>*);
template <typename C>
static std::false_type f(...);
public:
static const bool value = decltype(f<T>(nullptr))::value;
};
template <typename MW, typename Context, typename ParentContext>
typename std::enable_if<!is_before_handle_arity_3_impl<MW>::value>::type
before_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& parent_ctx)
{
mw.before_handle(req, res, ctx.template get<MW>(), ctx);
}
template <typename MW, typename Context, typename ParentContext>
typename std::enable_if<is_before_handle_arity_3_impl<MW>::value>::type
before_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& parent_ctx)
{
mw.before_handle(req, res, ctx.template get<MW>());
}
template <typename MW, typename Context, typename ParentContext>
typename std::enable_if<!is_after_handle_arity_3_impl<MW>::value>::type
after_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& parent_ctx)
{
mw.after_handle(req, res, ctx.template get<MW>(), ctx);
}
template <typename MW, typename Context, typename ParentContext>
typename std::enable_if<is_after_handle_arity_3_impl<MW>::value>::type
after_handler_call(MW& mw, request& req, response& res, Context& ctx, ParentContext& parent_ctx)
{
mw.after_handle(req, res, ctx.template get<MW>());
}
template <int N, typename Context, typename Container, typename CurrentMW, typename ... Middlewares>
bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx)
{
using parent_context_t = typename Context::template partial<N-1>;
before_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
if (res.is_completed())
{
after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
return true;
}
if (middleware_call_helper<N+1, Context, Container, Middlewares...>(middlewares, req, res, ctx))
{
after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
return true;
}
return false;
}
template <int N, typename Context, typename Container>
bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx)
{
return false;
}
template <int N, typename Context, typename Container>
typename std::enable_if<(N<0)>::type
after_handlers_call_helper(Container& middlewares, Context& context, request& req, response& res)
{
}
template <int N, typename Context, typename Container>
typename std::enable_if<(N==0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res)
{
using parent_context_t = typename Context::template partial<N-1>;
using CurrentMW = typename std::tuple_element<N, typename std::remove_reference<Container>::type>::type;
after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
}
template <int N, typename Context, typename Container>
typename std::enable_if<(N>0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res)
{
using parent_context_t = typename Context::template partial<N-1>;
using CurrentMW = typename std::tuple_element<N, typename std::remove_reference<Container>::type>::type;
after_handler_call<CurrentMW, Context, parent_context_t>(std::get<N>(middlewares), req, res, ctx, static_cast<parent_context_t&>(ctx));
after_handlers_call_helper<N-1, Context, Container>(middlewares, ctx, req, res);
}
}
#ifdef CROW_ENABLE_DEBUG
static int connectionCount;
#endif
template <typename Adaptor, typename Handler, typename ... Middlewares>
class Connection
{
public:
Connection(
boost::asio::io_service& io_service,
Handler* handler,
const std::string& server_name,
std::tuple<Middlewares...>* middlewares,
std::function<std::string()>& get_cached_date_str_f,
detail::dumb_timer_queue& timer_queue,
typename Adaptor::context* adaptor_ctx_
)
: adaptor_(io_service, adaptor_ctx_),
handler_(handler),
parser_(this),
server_name_(server_name),
middlewares_(middlewares),
get_cached_date_str(get_cached_date_str_f),
timer_queue(timer_queue)
{
#ifdef CROW_ENABLE_DEBUG
connectionCount ++;
CROW_LOG_DEBUG << "Connection open, total " << connectionCount << ", " << this;
#endif
}
~Connection()
{
res.complete_request_handler_ = nullptr;
cancel_deadline_timer();
#ifdef CROW_ENABLE_DEBUG
connectionCount --;
CROW_LOG_DEBUG << "Connection closed, total " << connectionCount << ", " << this;
#endif
}
decltype(std::declval<Adaptor>().raw_socket())& socket()
{
return adaptor_.raw_socket();
}
void start()
{
adaptor_.start([this](const boost::system::error_code& ec) {
if (!ec)
{
start_deadline();
do_read();
}
else
{
check_destroy();
}
});
}
void handle_header()
{
// HTTP 1.1 Expect: 100-continue
if (parser_.check_version(1, 1) && parser_.headers.count("expect") && get_header_value(parser_.headers, "expect") == "100-continue")
{
buffers_.clear();
static std::string expect_100_continue = "HTTP/1.1 100 Continue\r\n\r\n";
buffers_.emplace_back(expect_100_continue.data(), expect_100_continue.size());
do_write();
}
}
void handle()
{
cancel_deadline_timer();
bool is_invalid_request = false;
add_keep_alive_ = false;
req_ = std::move(parser_.to_request());
request& req = req_;
if (parser_.check_version(1, 0))
{
// HTTP/1.0
if (req.headers.count("connection"))
{
if (boost::iequals(req.get_header_value("connection"),"Keep-Alive"))
add_keep_alive_ = true;
}
else
close_connection_ = true;
}
else if (parser_.check_version(1, 1))
{
// HTTP/1.1
if (req.headers.count("connection"))
{
if (req.get_header_value("connection") == "close")
close_connection_ = true;
else if (boost::iequals(req.get_header_value("connection"),"Keep-Alive"))
add_keep_alive_ = true;
}
if (!req.headers.count("host"))
{
is_invalid_request = true;
res = response(400);
}
}
CROW_LOG_INFO << "Request: " << boost::lexical_cast<std::string>(adaptor_.remote_endpoint()) << " " << this << " HTTP/" << parser_.http_major << "." << parser_.http_minor << ' '
<< method_name(req.method) << " " << req.url;
need_to_call_after_handlers_ = false;
if (!is_invalid_request)
{
res.complete_request_handler_ = []{};
res.is_alive_helper_ = [this]()->bool{ return adaptor_.is_open(); };
ctx_ = detail::context<Middlewares...>();
req.middleware_context = (void*)&ctx_;
detail::middleware_call_helper<0, decltype(ctx_), decltype(*middlewares_), Middlewares...>(*middlewares_, req, res, ctx_);
if (!res.completed_)
{
res.complete_request_handler_ = [this]{ this->complete_request(); };
need_to_call_after_handlers_ = true;
handler_->handle(req, res);
if (add_keep_alive_)
res.set_header("connection", "Keep-Alive");
}
else
{
complete_request();
}
}
else
{
complete_request();
}
}
void complete_request()
{
CROW_LOG_INFO << "Response: " << this << ' ' << req_.raw_url << ' ' << res.code << ' ' << close_connection_;
if (need_to_call_after_handlers_)
{
need_to_call_after_handlers_ = false;
// call all after_handler of middlewares
detail::after_handlers_call_helper<
((int)sizeof...(Middlewares)-1),
decltype(ctx_),
decltype(*middlewares_)>
(*middlewares_, ctx_, req_, res);
}
//auto self = this->shared_from_this();
res.complete_request_handler_ = nullptr;
if (!adaptor_.is_open())
{
//CROW_LOG_DEBUG << this << " delete (socket is closed) " << is_reading << ' ' << is_writing;
//delete this;
return;
}
static std::unordered_map<int, std::string> statusCodes = {
{200, "HTTP/1.1 200 OK\r\n"},
{201, "HTTP/1.1 201 Created\r\n"},
{202, "HTTP/1.1 202 Accepted\r\n"},
{204, "HTTP/1.1 204 No Content\r\n"},
{300, "HTTP/1.1 300 Multiple Choices\r\n"},
{301, "HTTP/1.1 301 Moved Permanently\r\n"},
{302, "HTTP/1.1 302 Moved Temporarily\r\n"},
{304, "HTTP/1.1 304 Not Modified\r\n"},
{400, "HTTP/1.1 400 Bad Request\r\n"},
{401, "HTTP/1.1 401 Unauthorized\r\n"},
{403, "HTTP/1.1 403 Forbidden\r\n"},
{404, "HTTP/1.1 404 Not Found\r\n"},
{500, "HTTP/1.1 500 Internal Server Error\r\n"},
{501, "HTTP/1.1 501 Not Implemented\r\n"},
{502, "HTTP/1.1 502 Bad Gateway\r\n"},
{503, "HTTP/1.1 503 Service Unavailable\r\n"},
};
static std::string seperator = ": ";
static std::string crlf = "\r\n";
buffers_.clear();
buffers_.reserve(4*(res.headers.size()+5)+3);
if (res.body.empty() && res.json_value.t() == json::type::Object)
{
res.body = json::dump(res.json_value);
}
if (!statusCodes.count(res.code))
res.code = 500;
{
auto& status = statusCodes.find(res.code)->second;
buffers_.emplace_back(status.data(), status.size());
}
if (res.code >= 400 && res.body.empty())
res.body = statusCodes[res.code].substr(9);
for(auto& kv : res.headers)
{
buffers_.emplace_back(kv.first.data(), kv.first.size());
buffers_.emplace_back(seperator.data(), seperator.size());
buffers_.emplace_back(kv.second.data(), kv.second.size());
buffers_.emplace_back(crlf.data(), crlf.size());
}
if (!res.headers.count("content-length"))
{
content_length_ = std::to_string(res.body.size());
static std::string content_length_tag = "Content-Length: ";
buffers_.emplace_back(content_length_tag.data(), content_length_tag.size());
buffers_.emplace_back(content_length_.data(), content_length_.size());
buffers_.emplace_back(crlf.data(), crlf.size());
}
if (!res.headers.count("server"))
{
static std::string server_tag = "Server: ";
buffers_.emplace_back(server_tag.data(), server_tag.size());
buffers_.emplace_back(server_name_.data(), server_name_.size());
buffers_.emplace_back(crlf.data(), crlf.size());
}
if (!res.headers.count("date"))
{
static std::string date_tag = "Date: ";
date_str_ = get_cached_date_str();
buffers_.emplace_back(date_tag.data(), date_tag.size());
buffers_.emplace_back(date_str_.data(), date_str_.size());
buffers_.emplace_back(crlf.data(), crlf.size());
}
if (add_keep_alive_)
{
static std::string keep_alive_tag = "Connection: Keep-Alive";
buffers_.emplace_back(keep_alive_tag.data(), keep_alive_tag.size());
buffers_.emplace_back(crlf.data(), crlf.size());
}
buffers_.emplace_back(crlf.data(), crlf.size());
res_body_copy_.swap(res.body);
buffers_.emplace_back(res_body_copy_.data(), res_body_copy_.size());
do_write();
if (need_to_start_read_after_complete_)
{
need_to_start_read_after_complete_ = false;
start_deadline();
do_read();
}
}
private:
void do_read()
{
//auto self = this->shared_from_this();
is_reading = true;
adaptor_.socket().async_read_some(boost::asio::buffer(buffer_),
[this](const boost::system::error_code& ec, std::size_t bytes_transferred)
{
bool error_while_reading = true;
if (!ec)
{
bool ret = parser_.feed(buffer_.data(), bytes_transferred);
if (ret && adaptor_.is_open() && !close_connection_)
{
error_while_reading = false;
}
}
if (error_while_reading)
{
cancel_deadline_timer();
parser_.done();
adaptor_.close();
is_reading = false;
CROW_LOG_DEBUG << this << " from read(1)";
check_destroy();
}
else if (!need_to_call_after_handlers_)
{
start_deadline();
do_read();
}
else
{
// res will be completed later by user
need_to_start_read_after_complete_ = true;
}
});
}
void do_write()
{
//auto self = this->shared_from_this();
is_writing = true;
boost::asio::async_write(adaptor_.socket(), buffers_,
[&](const boost::system::error_code& ec, std::size_t bytes_transferred)
{
is_writing = false;
res.clear();
res_body_copy_.clear();
if (!ec)
{
if (close_connection_)
{
adaptor_.close();
CROW_LOG_DEBUG << this << " from write(1)";
check_destroy();
}
}
else
{
CROW_LOG_DEBUG << this << " from write(2)";
check_destroy();
}
});
}
void check_destroy()
{
CROW_LOG_DEBUG << this << " is_reading " << is_reading << " is_writing " << is_writing;
if (!is_reading && !is_writing)
{
CROW_LOG_DEBUG << this << " delete (idle) ";
delete this;
}
}
void cancel_deadline_timer()
{
CROW_LOG_DEBUG << this << " timer cancelled: " << timer_cancel_key_.first << ' ' << timer_cancel_key_.second;
timer_queue.cancel(timer_cancel_key_);
}
void start_deadline(int timeout = 5)
{
cancel_deadline_timer();
timer_cancel_key_ = timer_queue.add([this]
{
if (!adaptor_.is_open())
{
return;
}
adaptor_.close();
});
CROW_LOG_DEBUG << this << " timer added: " << timer_cancel_key_.first << ' ' << timer_cancel_key_.second;
}
private:
Adaptor adaptor_;
Handler* handler_;
boost::array<char, 4096> buffer_;
HTTPParser<Connection> parser_;
request req_;
response res;
bool close_connection_ = false;
const std::string& server_name_;
std::vector<boost::asio::const_buffer> buffers_;
std::string content_length_;
std::string date_str_;
std::string res_body_copy_;
//boost::asio::deadline_timer deadline_;
detail::dumb_timer_queue::key timer_cancel_key_;
bool is_reading{};
bool is_writing{};
bool need_to_call_after_handlers_{};
bool need_to_start_read_after_complete_{};
bool add_keep_alive_{};
std::tuple<Middlewares...>* middlewares_;
detail::context<Middlewares...> ctx_;
std::function<std::string()>& get_cached_date_str;
detail::dumb_timer_queue& timer_queue;
};
}

File diff suppressed because it is too large Load diff

52
ext/crow/http_request.h Normal file
View file

@ -0,0 +1,52 @@
#pragma once
#include "common.h"
#include "ci_map.h"
#include "query_string.h"
namespace crow
{
template <typename T>
inline const std::string& get_header_value(const T& headers, const std::string& key)
{
if (headers.count(key))
{
return headers.find(key)->second;
}
static std::string empty;
return empty;
}
struct request
{
HTTPMethod method;
std::string raw_url;
std::string url;
query_string url_params;
ci_map headers;
std::string body;
void* middleware_context{};
request()
: method(HTTPMethod::GET)
{
}
request(HTTPMethod method, std::string raw_url, std::string url, query_string url_params, ci_map headers, std::string body)
: method(method), raw_url(std::move(raw_url)), url(std::move(url)), url_params(std::move(url_params)), headers(std::move(headers)), body(std::move(body))
{
}
void add_header(std::string key, std::string value)
{
headers.emplace(std::move(key), std::move(value));
}
const std::string& get_header_value(const std::string& key) const
{
return crow::get_header_value(headers, key);
}
};
}

128
ext/crow/http_response.h Normal file
View file

@ -0,0 +1,128 @@
#pragma once
#include <string>
#include <unordered_map>
#include "json.h"
#include "http_request.h"
#include "ci_map.h"
namespace crow
{
template <typename Adaptor, typename Handler, typename ... Middlewares>
class Connection;
struct response
{
template <typename Adaptor, typename Handler, typename ... Middlewares>
friend class crow::Connection;
std::string body;
json::wvalue json_value;
int code{200};
// `headers' stores HTTP headers.
ci_map headers;
void set_header(std::string key, std::string value)
{
headers.erase(key);
headers.emplace(std::move(key), std::move(value));
}
void add_header(std::string key, std::string value)
{
headers.emplace(std::move(key), std::move(value));
}
const std::string& get_header_value(const std::string& key)
{
return crow::get_header_value(headers, key);
}
response() {}
explicit response(int code) : code(code) {}
response(std::string body) : body(std::move(body)) {}
response(json::wvalue&& json_value) : json_value(std::move(json_value))
{
json_mode();
}
response(int code, std::string body) : body(std::move(body)), code(code) {}
response(const json::wvalue& json_value) : body(json::dump(json_value))
{
json_mode();
}
response(int code, const json::wvalue& json_value) : code(code), body(json::dump(json_value))
{
json_mode();
}
response(response&& r)
{
*this = std::move(r);
}
response& operator = (const response& r) = delete;
response& operator = (response&& r) noexcept
{
body = std::move(r.body);
json_value = std::move(r.json_value);
code = r.code;
headers = std::move(r.headers);
completed_ = r.completed_;
return *this;
}
bool is_completed() const noexcept
{
return completed_;
}
void clear()
{
body.clear();
json_value.clear();
code = 200;
headers.clear();
completed_ = false;
}
void write(const std::string& body_part)
{
body += body_part;
}
void end()
{
if (!completed_)
{
completed_ = true;
if (complete_request_handler_)
{
complete_request_handler_();
}
}
}
void end(const std::string& body_part)
{
body += body_part;
end();
}
bool is_alive()
{
return is_alive_helper_ && is_alive_helper_();
}
private:
bool completed_{};
std::function<void()> complete_request_handler_;
std::function<bool()> is_alive_helper_;
//In case of a JSON object, set the Content-Type header
void json_mode()
{
set_header("Content-Type", "application/json");
}
};
}

184
ext/crow/http_server.h Normal file
View file

@ -0,0 +1,184 @@
#pragma once
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/asio.hpp>
#ifdef CROW_ENABLE_SSL
#include <boost/asio/ssl.hpp>
#endif
#include <cstdint>
#include <atomic>
#include <future>
#include <vector>
#include <memory>
#include "http_connection.h"
#include "logging.h"
#include "dumb_timer_queue.h"
namespace crow
{
using namespace boost;
using tcp = asio::ip::tcp;
template <typename Handler, typename Adaptor = SocketAdaptor, typename ... Middlewares>
class Server
{
public:
Server(Handler* handler, uint16_t port, std::tuple<Middlewares...>* middlewares = nullptr, uint16_t concurrency = 1, typename Adaptor::context* adaptor_ctx = nullptr)
: acceptor_(io_service_, tcp::endpoint(asio::ip::address(), port)),
signals_(io_service_, SIGINT, SIGTERM),
handler_(handler),
concurrency_(concurrency),
port_(port),
middlewares_(middlewares),
adaptor_ctx_(adaptor_ctx)
{
}
void run()
{
if (concurrency_ < 0)
concurrency_ = 1;
for(int i = 0; i < concurrency_; i++)
io_service_pool_.emplace_back(new boost::asio::io_service());
get_cached_date_str_pool_.resize(concurrency_);
timer_queue_pool_.resize(concurrency_);
std::vector<std::future<void>> v;
for(uint16_t i = 0; i < concurrency_; i ++)
v.push_back(
std::async(std::launch::async, [this, i]{
// thread local date string get function
auto last = std::chrono::steady_clock::now();
std::string date_str;
auto update_date_str = [&]
{
auto last_time_t = time(0);
tm my_tm;
#ifdef _MSC_VER
gmtime_s(&my_tm, &last_time_t);
#else
gmtime_r(&last_time_t, &my_tm);
#endif
date_str.resize(100);
size_t date_str_sz = strftime(&date_str[0], 99, "%a, %d %b %Y %H:%M:%S GMT", &my_tm);
date_str.resize(date_str_sz);
};
update_date_str();
get_cached_date_str_pool_[i] = [&]()->std::string
{
if (std::chrono::steady_clock::now() - last >= std::chrono::seconds(1))
{
last = std::chrono::steady_clock::now();
update_date_str();
}
return date_str;
};
// initializing timer queue
detail::dumb_timer_queue timer_queue;
timer_queue_pool_[i] = &timer_queue;
timer_queue.set_io_service(*io_service_pool_[i]);
boost::asio::deadline_timer timer(*io_service_pool_[i]);
timer.expires_from_now(boost::posix_time::seconds(1));
std::function<void(const boost::system::error_code& ec)> handler;
handler = [&](const boost::system::error_code& ec){
if (ec)
return;
timer_queue.process();
timer.expires_from_now(boost::posix_time::seconds(1));
timer.async_wait(handler);
};
timer.async_wait(handler);
io_service_pool_[i]->run();
}));
CROW_LOG_INFO << server_name_ << " server is running, local port " << port_;
signals_.async_wait(
[&](const boost::system::error_code& error, int signal_number){
stop();
});
for (int i = 0; i < concurrency_; i++)
{
while (timer_queue_pool_[i] == nullptr)
std::this_thread::yield();
}
do_accept();
std::thread([this]{
io_service_.run();
CROW_LOG_INFO << "Exiting.";
}).join();
}
void stop()
{
io_service_.stop();
for(auto& io_service:io_service_pool_)
io_service->stop();
}
private:
asio::io_service& pick_io_service()
{
// TODO load balancing
roundrobin_index_++;
if (roundrobin_index_ >= io_service_pool_.size())
roundrobin_index_ = 0;
return *io_service_pool_[roundrobin_index_];
}
void do_accept()
{
asio::io_service& is = pick_io_service();
auto p = new Connection<Adaptor, Handler, Middlewares...>(
is, handler_, server_name_, middlewares_,
get_cached_date_str_pool_[roundrobin_index_], *timer_queue_pool_[roundrobin_index_],
adaptor_ctx_);
acceptor_.async_accept(p->socket(),
[this, p, &is](boost::system::error_code ec)
{
if (!ec)
{
is.post([p]
{
p->start();
});
}
do_accept();
});
}
private:
asio::io_service io_service_;
std::vector<std::unique_ptr<asio::io_service>> io_service_pool_;
std::vector<detail::dumb_timer_queue*> timer_queue_pool_;
std::vector<std::function<std::string()>> get_cached_date_str_pool_;
tcp::acceptor acceptor_;
boost::asio::signal_set signals_;
Handler* handler_;
uint16_t concurrency_{1};
std::string server_name_ = "Crow/0.1";
uint16_t port_;
unsigned int roundrobin_index_{};
std::tuple<Middlewares...>* middlewares_;
#ifdef CROW_ENABLE_SSL
bool use_ssl_{false};
boost::asio::ssl::context ssl_context_{boost::asio::ssl::context::sslv23};
#endif
typename Adaptor::context* adaptor_ctx_;
};
}

1422
ext/crow/json.h Normal file

File diff suppressed because it is too large Load diff

134
ext/crow/logging.h Normal file
View file

@ -0,0 +1,134 @@
#pragma once
#include <string>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <sstream>
#include "settings.h"
namespace crow
{
enum class LogLevel
{
DEBUG,
INFO,
WARNING,
ERROR,
CRITICAL,
};
class ILogHandler {
public:
virtual void log(std::string message, LogLevel level) = 0;
};
class CerrLogHandler : public ILogHandler {
public:
void log(std::string message, LogLevel level) override {
std::cerr << message;
}
};
class logger {
private:
//
static std::string timestamp()
{
char date[32];
time_t t = time(0);
tm my_tm;
#ifdef _MSC_VER
gmtime_s(&my_tm, &t);
#else
gmtime_r(&t, &my_tm);
#endif
size_t sz = strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", &my_tm);
return std::string(date, date+sz);
}
public:
logger(std::string prefix, LogLevel level) : level_(level) {
#ifdef CROW_ENABLE_LOGGING
stringstream_ << "(" << timestamp() << ") [" << prefix << "] ";
#endif
}
~logger() {
#ifdef CROW_ENABLE_LOGGING
if(level_ >= get_current_log_level()) {
stringstream_ << std::endl;
get_handler_ref()->log(stringstream_.str(), level_);
}
#endif
}
//
template <typename T>
logger& operator<<(T const &value) {
#ifdef CROW_ENABLE_LOGGING
if(level_ >= get_current_log_level()) {
stringstream_ << value;
}
#endif
return *this;
}
//
static void setLogLevel(LogLevel level) {
get_log_level_ref() = level;
}
static void setHandler(ILogHandler* handler) {
get_handler_ref() = handler;
}
static LogLevel get_current_log_level() {
return get_log_level_ref();
}
private:
//
static LogLevel& get_log_level_ref()
{
static LogLevel current_level = (LogLevel)CROW_LOG_LEVEL;
return current_level;
}
static ILogHandler*& get_handler_ref()
{
static CerrLogHandler default_handler;
static ILogHandler* current_handler = &default_handler;
return current_handler;
}
//
std::ostringstream stringstream_;
LogLevel level_;
};
}
#define CROW_LOG_CRITICAL \
if (crow::logger::get_current_log_level() <= crow::LogLevel::CRITICAL) \
crow::logger("CRITICAL", crow::LogLevel::CRITICAL)
#define CROW_LOG_ERROR \
if (crow::logger::get_current_log_level() <= crow::LogLevel::ERROR) \
crow::logger("ERROR ", crow::LogLevel::ERROR)
#define CROW_LOG_WARNING \
if (crow::logger::get_current_log_level() <= crow::LogLevel::WARNING) \
crow::logger("WARNING ", crow::LogLevel::WARNING)
#define CROW_LOG_INFO \
if (crow::logger::get_current_log_level() <= crow::LogLevel::INFO) \
crow::logger("INFO ", crow::LogLevel::INFO)
#define CROW_LOG_DEBUG \
if (crow::logger::get_current_log_level() <= crow::LogLevel::DEBUG) \
crow::logger("DEBUG ", crow::LogLevel::DEBUG)

174
ext/crow/middleware.h Normal file
View file

@ -0,0 +1,174 @@
#pragma once
#include <boost/algorithm/string/trim.hpp>
#include "http_request.h"
#include "http_response.h"
namespace crow
{
// Any middleware requires following 3 members:
// struct context;
// storing data for the middleware; can be read from another middleware or handlers
// before_handle
// called before handling the request.
// if res.end() is called, the operation is halted.
// (still call after_handle of this middleware)
// 2 signatures:
// void before_handle(request& req, response& res, context& ctx)
// if you only need to access this middlewares context.
// template <typename AllContext>
// void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
// you can access another middlewares' context by calling `all_ctx.template get<MW>()'
// ctx == all_ctx.template get<CurrentMiddleware>()
// after_handle
// called after handling the request.
// void after_handle(request& req, response& res, context& ctx)
// template <typename AllContext>
// void after_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
struct CookieParser
{
struct context
{
std::unordered_map<std::string, std::string> jar;
std::unordered_map<std::string, std::string> cookies_to_add;
std::string get_cookie(const std::string& key)
{
if (jar.count(key))
return jar[key];
return {};
}
void set_cookie(const std::string& key, const std::string& value)
{
cookies_to_add.emplace(key, value);
}
};
void before_handle(request& req, response& res, context& ctx)
{
int count = req.headers.count("Cookie");
if (!count)
return;
if (count > 1)
{
res.code = 400;
res.end();
return;
}
std::string cookies = req.get_header_value("Cookie");
size_t pos = 0;
while(pos < cookies.size())
{
size_t pos_equal = cookies.find('=', pos);
if (pos_equal == cookies.npos)
break;
std::string name = cookies.substr(pos, pos_equal-pos);
boost::trim(name);
pos = pos_equal+1;
while(pos < cookies.size() && cookies[pos] == ' ') pos++;
if (pos == cookies.size())
break;
std::string value;
if (cookies[pos] == '"')
{
int dquote_meet_count = 0;
pos ++;
size_t pos_dquote = pos-1;
do
{
pos_dquote = cookies.find('"', pos_dquote+1);
dquote_meet_count ++;
} while(pos_dquote < cookies.size() && cookies[pos_dquote-1] == '\\');
if (pos_dquote == cookies.npos)
break;
if (dquote_meet_count == 1)
value = cookies.substr(pos, pos_dquote - pos);
else
{
value.clear();
value.reserve(pos_dquote-pos);
for(size_t p = pos; p < pos_dquote; p++)
{
// FIXME minimal escaping
if (cookies[p] == '\\' && p + 1 < pos_dquote)
{
p++;
if (cookies[p] == '\\' || cookies[p] == '"')
value += cookies[p];
else
{
value += '\\';
value += cookies[p];
}
}
else
value += cookies[p];
}
}
ctx.jar.emplace(std::move(name), std::move(value));
pos = cookies.find(";", pos_dquote+1);
if (pos == cookies.npos)
break;
pos++;
while(pos < cookies.size() && cookies[pos] == ' ') pos++;
if (pos == cookies.size())
break;
}
else
{
size_t pos_semicolon = cookies.find(';', pos);
value = cookies.substr(pos, pos_semicolon - pos);
boost::trim(value);
ctx.jar.emplace(std::move(name), std::move(value));
pos = pos_semicolon;
if (pos == cookies.npos)
break;
pos ++;
while(pos < cookies.size() && cookies[pos] == ' ') pos++;
if (pos == cookies.size())
break;
}
}
}
void after_handle(request& req, response& res, context& ctx)
{
for(auto& cookie:ctx.cookies_to_add)
{
res.add_header("Set-Cookie", cookie.first + "=" + cookie.second);
}
}
};
/*
App<CookieParser, AnotherJarMW> app;
A B C
A::context
int aa;
ctx1 : public A::context
ctx2 : public ctx1, public B::context
ctx3 : public ctx2, public C::context
C depends on A
C::handle
context.aaa
App::context : private CookieParser::contetx, ...
{
jar
}
SimpleApp
*/
}

View file

@ -0,0 +1,59 @@
#pragma once
#include "utility.h"
#include "http_request.h"
#include "http_response.h"
namespace crow
{
namespace detail
{
template <typename ... Middlewares>
struct partial_context
: public black_magic::pop_back<Middlewares...>::template rebind<partial_context>
, public black_magic::last_element_type<Middlewares...>::type::context
{
using parent_context = typename black_magic::pop_back<Middlewares...>::template rebind<::crow::detail::partial_context>;
template <int N>
using partial = typename std::conditional<N == sizeof...(Middlewares)-1, partial_context, typename parent_context::template partial<N>>::type;
template <typename T>
typename T::context& get()
{
return static_cast<typename T::context&>(*this);
}
};
template <>
struct partial_context<>
{
template <int>
using partial = partial_context;
};
template <int N, typename Context, typename Container, typename CurrentMW, typename ... Middlewares>
bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx);
template <typename ... Middlewares>
struct context : private partial_context<Middlewares...>
//struct context : private Middlewares::context... // simple but less type-safe
{
template <int N, typename Context, typename Container>
friend typename std::enable_if<(N==0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res);
template <int N, typename Context, typename Container>
friend typename std::enable_if<(N>0)>::type after_handlers_call_helper(Container& middlewares, Context& ctx, request& req, response& res);
template <int N, typename Context, typename Container, typename CurrentMW, typename ... Middlewares2>
friend bool middleware_call_helper(Container& middlewares, request& req, response& res, Context& ctx);
template <typename T>
typename T::context& get()
{
return static_cast<typename T::context&>(*this);
}
template <int N>
using partial = typename partial_context<Middlewares...>::template partial<N>;
};
}
}

559
ext/crow/mustache.h Normal file
View file

@ -0,0 +1,559 @@
#pragma once
#include <string>
#include <vector>
#include <fstream>
#include <iterator>
#include <functional>
#include "json.h"
namespace crow
{
namespace mustache
{
using context = json::wvalue;
template_t load(const std::string& filename);
class invalid_template_exception : public std::exception
{
public:
invalid_template_exception(const std::string& msg)
: msg("crow::mustache error: " + msg)
{
}
virtual const char* what() const throw()
{
return msg.c_str();
}
std::string msg;
};
enum class ActionType
{
Ignore,
Tag,
UnescapeTag,
OpenBlock,
CloseBlock,
ElseBlock,
Partial,
};
struct Action
{
int start;
int end;
int pos;
ActionType t;
Action(ActionType t, int start, int end, int pos = 0)
: start(start), end(end), pos(pos), t(t)
{}
};
class template_t
{
public:
template_t(std::string body)
: body_(std::move(body))
{
// {{ {{# {{/ {{^ {{! {{> {{=
parse();
}
private:
std::string tag_name(const Action& action)
{
return body_.substr(action.start, action.end - action.start);
}
auto find_context(const std::string& name, const std::vector<context*>& stack)->std::pair<bool, context&>
{
if (name == ".")
{
return {true, *stack.back()};
}
int dotPosition = name.find(".");
if (dotPosition == (int)name.npos)
{
for(auto it = stack.rbegin(); it != stack.rend(); ++it)
{
if ((*it)->t() == json::type::Object)
{
if ((*it)->count(name))
return {true, (**it)[name]};
}
}
}
else
{
std::vector<int> dotPositions;
dotPositions.push_back(-1);
while(dotPosition != (int)name.npos)
{
dotPositions.push_back(dotPosition);
dotPosition = name.find(".", dotPosition+1);
}
dotPositions.push_back(name.size());
std::vector<std::string> names;
names.reserve(dotPositions.size()-1);
for(int i = 1; i < (int)dotPositions.size(); i ++)
names.emplace_back(name.substr(dotPositions[i-1]+1, dotPositions[i]-dotPositions[i-1]-1));
for(auto it = stack.rbegin(); it != stack.rend(); ++it)
{
context* view = *it;
bool found = true;
for(auto jt = names.begin(); jt != names.end(); ++jt)
{
if (view->t() == json::type::Object &&
view->count(*jt))
{
view = &(*view)[*jt];
}
else
{
found = false;
break;
}
}
if (found)
return {true, *view};
}
}
static json::wvalue empty_str;
empty_str = "";
return {false, empty_str};
}
void escape(const std::string& in, std::string& out)
{
out.reserve(out.size() + in.size());
for(auto it = in.begin(); it != in.end(); ++it)
{
switch(*it)
{
case '&': out += "&amp;"; break;
case '<': out += "&lt;"; break;
case '>': out += "&gt;"; break;
case '"': out += "&quot;"; break;
case '\'': out += "&#39;"; break;
case '/': out += "&#x2F;"; break;
default: out += *it; break;
}
}
}
void render_internal(int actionBegin, int actionEnd, std::vector<context*>& stack, std::string& out, int indent)
{
int current = actionBegin;
if (indent)
out.insert(out.size(), indent, ' ');
while(current < actionEnd)
{
auto& fragment = fragments_[current];
auto& action = actions_[current];
render_fragment(fragment, indent, out);
switch(action.t)
{
case ActionType::Ignore:
// do nothing
break;
case ActionType::Partial:
{
std::string partial_name = tag_name(action);
auto partial_templ = load(partial_name);
int partial_indent = action.pos;
partial_templ.render_internal(0, partial_templ.fragments_.size()-1, stack, out, partial_indent?indent+partial_indent:0);
}
break;
case ActionType::UnescapeTag:
case ActionType::Tag:
{
auto optional_ctx = find_context(tag_name(action), stack);
auto& ctx = optional_ctx.second;
switch(ctx.t())
{
case json::type::Number:
out += json::dump(ctx);
break;
case json::type::String:
if (action.t == ActionType::Tag)
escape(ctx.s, out);
else
out += ctx.s;
break;
default:
throw std::runtime_error("not implemented tag type" + boost::lexical_cast<std::string>((int)ctx.t()));
}
}
break;
case ActionType::ElseBlock:
{
static context nullContext;
auto optional_ctx = find_context(tag_name(action), stack);
if (!optional_ctx.first)
{
stack.emplace_back(&nullContext);
break;
}
auto& ctx = optional_ctx.second;
switch(ctx.t())
{
case json::type::List:
if (ctx.l && !ctx.l->empty())
current = action.pos;
else
stack.emplace_back(&nullContext);
break;
case json::type::False:
case json::type::Null:
stack.emplace_back(&nullContext);
break;
default:
current = action.pos;
break;
}
break;
}
case ActionType::OpenBlock:
{
auto optional_ctx = find_context(tag_name(action), stack);
if (!optional_ctx.first)
{
current = action.pos;
break;
}
auto& ctx = optional_ctx.second;
switch(ctx.t())
{
case json::type::List:
if (ctx.l)
for(auto it = ctx.l->begin(); it != ctx.l->end(); ++it)
{
stack.push_back(&*it);
render_internal(current+1, action.pos, stack, out, indent);
stack.pop_back();
}
current = action.pos;
break;
case json::type::Number:
case json::type::String:
case json::type::Object:
case json::type::True:
stack.push_back(&ctx);
break;
case json::type::False:
case json::type::Null:
current = action.pos;
break;
default:
throw std::runtime_error("{{#: not implemented context type: " + boost::lexical_cast<std::string>((int)ctx.t()));
break;
}
break;
}
case ActionType::CloseBlock:
stack.pop_back();
break;
default:
throw std::runtime_error("not implemented " + boost::lexical_cast<std::string>((int)action.t));
}
current++;
}
auto& fragment = fragments_[actionEnd];
render_fragment(fragment, indent, out);
}
void render_fragment(const std::pair<int, int> fragment, int indent, std::string& out)
{
if (indent)
{
for(int i = fragment.first; i < fragment.second; i ++)
{
out += body_[i];
if (body_[i] == '\n' && i+1 != (int)body_.size())
out.insert(out.size(), indent, ' ');
}
}
else
out.insert(out.size(), body_, fragment.first, fragment.second-fragment.first);
}
public:
std::string render()
{
context empty_ctx;
std::vector<context*> stack;
stack.emplace_back(&empty_ctx);
std::string ret;
render_internal(0, fragments_.size()-1, stack, ret, 0);
return ret;
}
std::string render(context& ctx)
{
std::vector<context*> stack;
stack.emplace_back(&ctx);
std::string ret;
render_internal(0, fragments_.size()-1, stack, ret, 0);
return ret;
}
private:
void parse()
{
std::string tag_open = "{{";
std::string tag_close = "}}";
std::vector<int> blockPositions;
size_t current = 0;
while(1)
{
size_t idx = body_.find(tag_open, current);
if (idx == body_.npos)
{
fragments_.emplace_back(current, body_.size());
actions_.emplace_back(ActionType::Ignore, 0, 0);
break;
}
fragments_.emplace_back(current, idx);
idx += tag_open.size();
size_t endIdx = body_.find(tag_close, idx);
if (endIdx == idx)
{
throw invalid_template_exception("empty tag is not allowed");
}
if (endIdx == body_.npos)
{
// error, no matching tag
throw invalid_template_exception("not matched opening tag");
}
current = endIdx + tag_close.size();
switch(body_[idx])
{
case '#':
idx++;
while(body_[idx] == ' ') idx++;
while(body_[endIdx-1] == ' ') endIdx--;
blockPositions.emplace_back(actions_.size());
actions_.emplace_back(ActionType::OpenBlock, idx, endIdx);
break;
case '/':
idx++;
while(body_[idx] == ' ') idx++;
while(body_[endIdx-1] == ' ') endIdx--;
{
auto& matched = actions_[blockPositions.back()];
if (body_.compare(idx, endIdx-idx,
body_, matched.start, matched.end - matched.start) != 0)
{
throw invalid_template_exception("not matched {{# {{/ pair: " +
body_.substr(matched.start, matched.end - matched.start) + ", " +
body_.substr(idx, endIdx-idx));
}
matched.pos = actions_.size();
}
actions_.emplace_back(ActionType::CloseBlock, idx, endIdx, blockPositions.back());
blockPositions.pop_back();
break;
case '^':
idx++;
while(body_[idx] == ' ') idx++;
while(body_[endIdx-1] == ' ') endIdx--;
blockPositions.emplace_back(actions_.size());
actions_.emplace_back(ActionType::ElseBlock, idx, endIdx);
break;
case '!':
// do nothing action
actions_.emplace_back(ActionType::Ignore, idx+1, endIdx);
break;
case '>': // partial
idx++;
while(body_[idx] == ' ') idx++;
while(body_[endIdx-1] == ' ') endIdx--;
actions_.emplace_back(ActionType::Partial, idx, endIdx);
break;
case '{':
if (tag_open != "{{" || tag_close != "}}")
throw invalid_template_exception("cannot use triple mustache when delimiter changed");
idx ++;
if (body_[endIdx+2] != '}')
{
throw invalid_template_exception("{{{: }}} not matched");
}
while(body_[idx] == ' ') idx++;
while(body_[endIdx-1] == ' ') endIdx--;
actions_.emplace_back(ActionType::UnescapeTag, idx, endIdx);
current++;
break;
case '&':
idx ++;
while(body_[idx] == ' ') idx++;
while(body_[endIdx-1] == ' ') endIdx--;
actions_.emplace_back(ActionType::UnescapeTag, idx, endIdx);
break;
case '=':
// tag itself is no-op
idx ++;
actions_.emplace_back(ActionType::Ignore, idx, endIdx);
endIdx --;
if (body_[endIdx] != '=')
throw invalid_template_exception("{{=: not matching = tag: "+body_.substr(idx, endIdx-idx));
endIdx --;
while(body_[idx] == ' ') idx++;
while(body_[endIdx] == ' ') endIdx--;
endIdx++;
{
bool succeeded = false;
for(size_t i = idx; i < endIdx; i++)
{
if (body_[i] == ' ')
{
tag_open = body_.substr(idx, i-idx);
while(body_[i] == ' ') i++;
tag_close = body_.substr(i, endIdx-i);
if (tag_open.empty())
throw invalid_template_exception("{{=: empty open tag");
if (tag_close.empty())
throw invalid_template_exception("{{=: empty close tag");
if (tag_close.find(" ") != tag_close.npos)
throw invalid_template_exception("{{=: invalid open/close tag: "+tag_open+" " + tag_close);
succeeded = true;
break;
}
}
if (!succeeded)
throw invalid_template_exception("{{=: cannot find space between new open/close tags");
}
break;
default:
// normal tag case;
while(body_[idx] == ' ') idx++;
while(body_[endIdx-1] == ' ') endIdx--;
actions_.emplace_back(ActionType::Tag, idx, endIdx);
break;
}
}
// removing standalones
for(int i = actions_.size()-2; i >= 0; i --)
{
if (actions_[i].t == ActionType::Tag || actions_[i].t == ActionType::UnescapeTag)
continue;
auto& fragment_before = fragments_[i];
auto& fragment_after = fragments_[i+1];
bool is_last_action = i == (int)actions_.size()-2;
bool all_space_before = true;
int j, k;
for(j = fragment_before.second-1;j >= fragment_before.first;j--)
{
if (body_[j] != ' ')
{
all_space_before = false;
break;
}
}
if (all_space_before && i > 0)
continue;
if (!all_space_before && body_[j] != '\n')
continue;
bool all_space_after = true;
for(k = fragment_after.first; k < (int)body_.size() && k < fragment_after.second; k ++)
{
if (body_[k] != ' ')
{
all_space_after = false;
break;
}
}
if (all_space_after && !is_last_action)
continue;
if (!all_space_after &&
!(
body_[k] == '\n'
||
(body_[k] == '\r' &&
k + 1 < (int)body_.size() &&
body_[k+1] == '\n')))
continue;
if (actions_[i].t == ActionType::Partial)
{
actions_[i].pos = fragment_before.second - j - 1;
}
fragment_before.second = j+1;
if (!all_space_after)
{
if (body_[k] == '\n')
k++;
else
k += 2;
fragment_after.first = k;
}
}
}
std::vector<std::pair<int,int>> fragments_;
std::vector<Action> actions_;
std::string body_;
};
inline template_t compile(const std::string& body)
{
return template_t(body);
}
namespace detail
{
inline std::string& get_template_base_directory_ref()
{
static std::string template_base_directory = "templates";
return template_base_directory;
}
}
inline std::string default_loader(const std::string& filename)
{
std::ifstream inf(detail::get_template_base_directory_ref() + filename);
if (!inf)
return {};
return {std::istreambuf_iterator<char>(inf), std::istreambuf_iterator<char>()};
}
namespace detail
{
inline std::function<std::string (std::string)>& get_loader_ref()
{
static std::function<std::string (std::string)> loader = default_loader;
return loader;
}
}
inline void set_base(const std::string& path)
{
auto& base = detail::get_template_base_directory_ref();
base = path;
if (base.back() != '\\' &&
base.back() != '/')
{
base += '/';
}
}
inline void set_loader(std::function<std::string(std::string)> loader)
{
detail::get_loader_ref() = std::move(loader);
}
inline template_t load(const std::string& filename)
{
return compile(detail::get_loader_ref()(filename));
}
}
}

163
ext/crow/parser.h Normal file
View file

@ -0,0 +1,163 @@
#pragma once
#include <string>
#include <unordered_map>
#include <boost/algorithm/string.hpp>
#include <boost/tokenizer.hpp>
#include <algorithm>
#include "http_parser_merged.h"
#include "http_request.h"
namespace crow
{
template <typename Handler>
struct HTTPParser : public http_parser
{
static int on_message_begin(http_parser* self_)
{
HTTPParser* self = static_cast<HTTPParser*>(self_);
self->clear();
return 0;
}
static int on_url(http_parser* self_, const char* at, size_t length)
{
HTTPParser* self = static_cast<HTTPParser*>(self_);
self->raw_url.insert(self->raw_url.end(), at, at+length);
return 0;
}
static int on_header_field(http_parser* self_, const char* at, size_t length)
{
HTTPParser* self = static_cast<HTTPParser*>(self_);
switch (self->header_building_state)
{
case 0:
if (!self->header_value.empty())
{
self->headers.emplace(std::move(self->header_field), std::move(self->header_value));
}
self->header_field.assign(at, at+length);
self->header_building_state = 1;
break;
case 1:
self->header_field.insert(self->header_field.end(), at, at+length);
break;
}
return 0;
}
static int on_header_value(http_parser* self_, const char* at, size_t length)
{
HTTPParser* self = static_cast<HTTPParser*>(self_);
switch (self->header_building_state)
{
case 0:
self->header_value.insert(self->header_value.end(), at, at+length);
break;
case 1:
self->header_building_state = 0;
self->header_value.assign(at, at+length);
break;
}
return 0;
}
static int on_headers_complete(http_parser* self_)
{
HTTPParser* self = static_cast<HTTPParser*>(self_);
if (!self->header_field.empty())
{
self->headers.emplace(std::move(self->header_field), std::move(self->header_value));
}
self->process_header();
return 0;
}
static int on_body(http_parser* self_, const char* at, size_t length)
{
HTTPParser* self = static_cast<HTTPParser*>(self_);
self->body.insert(self->body.end(), at, at+length);
return 0;
}
static int on_message_complete(http_parser* self_)
{
HTTPParser* self = static_cast<HTTPParser*>(self_);
// url params
self->url = self->raw_url.substr(0, self->raw_url.find("?"));
self->url_params = query_string(self->raw_url);
self->process_message();
return 0;
}
HTTPParser(Handler* handler) :
handler_(handler)
{
http_parser_init(this, HTTP_REQUEST);
}
// return false on error
bool feed(const char* buffer, int length)
{
const static http_parser_settings settings_{
on_message_begin,
on_url,
nullptr,
on_header_field,
on_header_value,
on_headers_complete,
on_body,
on_message_complete,
};
int nparsed = http_parser_execute(this, &settings_, buffer, length);
return nparsed == length;
}
bool done()
{
return feed(nullptr, 0);
}
void clear()
{
url.clear();
raw_url.clear();
header_building_state = 0;
header_field.clear();
header_value.clear();
headers.clear();
url_params.clear();
body.clear();
}
void process_header()
{
handler_->handle_header();
}
void process_message()
{
handler_->handle();
}
request to_request() const
{
return request{(HTTPMethod)method, std::move(raw_url), std::move(url), std::move(url_params), std::move(headers), std::move(body)};
}
bool check_version(int major, int minor) const
{
return http_major == major && http_minor == minor;
}
std::string raw_url;
std::string url;
int header_building_state = 0;
std::string header_field;
std::string header_value;
ci_map headers;
query_string url_params;
std::string body;
Handler* handler_;
};
}

342
ext/crow/query_string.h Normal file
View file

@ -0,0 +1,342 @@
#pragma once
#include <stdio.h>
#include <string.h>
#include <string>
#include <vector>
#include <iostream>
// ----------------------------------------------------------------------------
// qs_parse (modified)
// https://github.com/bartgrantham/qs_parse
// ----------------------------------------------------------------------------
/* Similar to strncmp, but handles URL-encoding for either string */
int qs_strncmp(const char * s, const char * qs, size_t n);
/* Finds the beginning of each key/value pair and stores a pointer in qs_kv.
* Also decodes the value portion of the k/v pair *in-place*. In a future
* enhancement it will also have a compile-time option of sorting qs_kv
* alphabetically by key. */
int qs_parse(char * qs, char * qs_kv[], int qs_kv_size);
/* Used by qs_parse to decode the value portion of a k/v pair */
int qs_decode(char * qs);
/* Looks up the value according to the key on a pre-processed query string
* A future enhancement will be a compile-time option to look up the key
* in a pre-sorted qs_kv array via a binary search. */
//char * qs_k2v(const char * key, char * qs_kv[], int qs_kv_size);
char * qs_k2v(const char * key, char * const * qs_kv, int qs_kv_size, int nth);
/* Non-destructive lookup of value, based on key. User provides the
* destinaton string and length. */
char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len);
// TODO: implement sorting of the qs_kv array; for now ensure it's not compiled
#undef _qsSORTING
// isxdigit _is_ available in <ctype.h>, but let's avoid another header instead
#define CROW_QS_ISHEX(x) ((((x)>='0'&&(x)<='9') || ((x)>='A'&&(x)<='F') || ((x)>='a'&&(x)<='f')) ? 1 : 0)
#define CROW_QS_HEX2DEC(x) (((x)>='0'&&(x)<='9') ? (x)-48 : ((x)>='A'&&(x)<='F') ? (x)-55 : ((x)>='a'&&(x)<='f') ? (x)-87 : 0)
#define CROW_QS_ISQSCHR(x) ((((x)=='=')||((x)=='#')||((x)=='&')||((x)=='\0')) ? 0 : 1)
inline int qs_strncmp(const char * s, const char * qs, size_t n)
{
int i=0;
unsigned char u1, u2, unyb, lnyb;
while(n-- > 0)
{
u1 = (unsigned char) *s++;
u2 = (unsigned char) *qs++;
if ( ! CROW_QS_ISQSCHR(u1) ) { u1 = '\0'; }
if ( ! CROW_QS_ISQSCHR(u2) ) { u2 = '\0'; }
if ( u1 == '+' ) { u1 = ' '; }
if ( u1 == '%' ) // easier/safer than scanf
{
unyb = (unsigned char) *s++;
lnyb = (unsigned char) *s++;
if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) )
u1 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
else
u1 = '\0';
}
if ( u2 == '+' ) { u2 = ' '; }
if ( u2 == '%' ) // easier/safer than scanf
{
unyb = (unsigned char) *qs++;
lnyb = (unsigned char) *qs++;
if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) )
u2 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
else
u2 = '\0';
}
if ( u1 != u2 )
return u1 - u2;
if ( u1 == '\0' )
return 0;
i++;
}
if ( CROW_QS_ISQSCHR(*qs) )
return -1;
else
return 0;
}
inline int qs_parse(char * qs, char * qs_kv[], int qs_kv_size)
{
int i, j;
char * substr_ptr;
for(i=0; i<qs_kv_size; i++) qs_kv[i] = NULL;
// find the beginning of the k/v substrings or the fragment
substr_ptr = qs + strcspn(qs, "?#");
if (substr_ptr[0] != '\0')
substr_ptr++;
else
return 0; // no query or fragment
i=0;
while(i<qs_kv_size)
{
qs_kv[i] = substr_ptr;
j = strcspn(substr_ptr, "&");
if ( substr_ptr[j] == '\0' ) { break; }
substr_ptr += j + 1;
i++;
}
i++; // x &'s -> means x iterations of this loop -> means *x+1* k/v pairs
// we only decode the values in place, the keys could have '='s in them
// which will hose our ability to distinguish keys from values later
for(j=0; j<i; j++)
{
substr_ptr = qs_kv[j] + strcspn(qs_kv[j], "=&#");
if ( substr_ptr[0] == '&' || substr_ptr[0] == '\0') // blank value: skip decoding
substr_ptr[0] = '\0';
else
qs_decode(++substr_ptr);
}
#ifdef _qsSORTING
// TODO: qsort qs_kv, using qs_strncmp() for the comparison
#endif
return i;
}
inline int qs_decode(char * qs)
{
int i=0, j=0;
while( CROW_QS_ISQSCHR(qs[j]) )
{
if ( qs[j] == '+' ) { qs[i] = ' '; }
else if ( qs[j] == '%' ) // easier/safer than scanf
{
if ( ! CROW_QS_ISHEX(qs[j+1]) || ! CROW_QS_ISHEX(qs[j+2]) )
{
qs[i] = '\0';
return i;
}
qs[i] = (CROW_QS_HEX2DEC(qs[j+1]) * 16) + CROW_QS_HEX2DEC(qs[j+2]);
j+=2;
}
else
{
qs[i] = qs[j];
}
i++; j++;
}
qs[i] = '\0';
return i;
}
inline char * qs_k2v(const char * key, char * const * qs_kv, int qs_kv_size, int nth = 0)
{
int i;
size_t key_len, skip;
key_len = strlen(key);
#ifdef _qsSORTING
// TODO: binary search for key in the sorted qs_kv
#else // _qsSORTING
for(i=0; i<qs_kv_size; i++)
{
// we rely on the unambiguous '=' to find the value in our k/v pair
if ( qs_strncmp(key, qs_kv[i], key_len) == 0 )
{
skip = strcspn(qs_kv[i], "=");
if ( qs_kv[i][skip] == '=' )
skip++;
// return (zero-char value) ? ptr to trailing '\0' : ptr to value
if(nth == 0)
return qs_kv[i] + skip;
else
--nth;
}
}
#endif // _qsSORTING
return NULL;
}
inline char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len)
{
size_t i, key_len;
const char * tmp;
// find the beginning of the k/v substrings
if ( (tmp = strchr(qs, '?')) != NULL )
qs = tmp + 1;
key_len = strlen(key);
while(qs[0] != '#' && qs[0] != '\0')
{
if ( qs_strncmp(key, qs, key_len) == 0 )
break;
qs += strcspn(qs, "&") + 1;
}
if ( qs[0] == '\0' ) return NULL;
qs += strcspn(qs, "=&#");
if ( qs[0] == '=' )
{
qs++;
i = strcspn(qs, "&=#");
strncpy(val, qs, (val_len-1)<(i+1) ? (val_len-1) : (i+1));
qs_decode(val);
}
else
{
if ( val_len > 0 )
val[0] = '\0';
}
return val;
}
// ----------------------------------------------------------------------------
namespace crow
{
class query_string
{
public:
static const int MAX_KEY_VALUE_PAIRS_COUNT = 256;
query_string()
{
}
query_string(const query_string& qs)
: url_(qs.url_)
{
for(auto p:qs.key_value_pairs_)
{
key_value_pairs_.push_back((char*)(p-qs.url_.c_str()+url_.c_str()));
}
}
query_string& operator = (const query_string& qs)
{
url_ = qs.url_;
key_value_pairs_.clear();
for(auto p:qs.key_value_pairs_)
{
key_value_pairs_.push_back((char*)(p-qs.url_.c_str()+url_.c_str()));
}
return *this;
}
query_string& operator = (query_string&& qs)
{
key_value_pairs_ = std::move(qs.key_value_pairs_);
char* old_data = (char*)qs.url_.c_str();
url_ = std::move(qs.url_);
for(auto& p:key_value_pairs_)
{
p += (char*)url_.c_str() - old_data;
}
return *this;
}
query_string(std::string url)
: url_(std::move(url))
{
if (url_.empty())
return;
key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT);
int count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT);
key_value_pairs_.resize(count);
}
void clear()
{
key_value_pairs_.clear();
url_.clear();
}
friend std::ostream& operator<<(std::ostream& os, const query_string& qs)
{
os << "[ ";
for(size_t i = 0; i < qs.key_value_pairs_.size(); ++i) {
if (i)
os << ", ";
os << qs.key_value_pairs_[i];
}
os << " ]";
return os;
}
char* get (const std::string& name) const
{
char* ret = qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size());
return ret;
}
std::vector<char*> get_list (const std::string& name) const
{
std::vector<char*> ret;
std::string plus = name + "[]";
char* element = nullptr;
int count = 0;
while(1)
{
element = qs_k2v(plus.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++);
if (!element)
break;
ret.push_back(element);
}
return ret;
}
private:
std::string url_;
std::vector<char*> key_value_pairs_;
};
} // end namespace

892
ext/crow/routing.h Normal file
View file

@ -0,0 +1,892 @@
#pragma once
#include <cstdint>
#include <utility>
#include <tuple>
#include <unordered_map>
#include <memory>
#include <boost/lexical_cast.hpp>
#include <vector>
#include "common.h"
#include "http_response.h"
#include "http_request.h"
#include "utility.h"
#include "logging.h"
namespace crow
{
class BaseRule
{
public:
BaseRule(std::string rule)
: rule_(std::move(rule))
{
}
virtual ~BaseRule()
{
}
virtual void validate() = 0;
virtual void handle(const request&, response&, const routing_params&) = 0;
uint32_t get_methods()
{
return methods_;
}
protected:
uint32_t methods_{1<<(int)HTTPMethod::GET};
std::string rule_;
std::string name_;
friend class Router;
template <typename T>
friend struct RuleParameterTraits;
};
namespace detail
{
namespace routing_handler_call_helper
{
template <typename T, int Pos>
struct call_pair
{
using type = T;
static const int pos = Pos;
};
template <typename H1>
struct call_params
{
H1& handler;
const routing_params& params;
const request& req;
response& res;
};
template <typename F, int NInt, int NUint, int NDouble, int NString, typename S1, typename S2>
struct call
{
};
template <typename F, int NInt, int NUint, int NDouble, int NString, typename ... Args1, typename ... Args2>
struct call<F, NInt, NUint, NDouble, NString, black_magic::S<int64_t, Args1...>, black_magic::S<Args2...>>
{
void operator()(F cparams)
{
using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<int64_t, NInt>>;
call<F, NInt+1, NUint, NDouble, NString,
black_magic::S<Args1...>, pushed>()(cparams);
}
};
template <typename F, int NInt, int NUint, int NDouble, int NString, typename ... Args1, typename ... Args2>
struct call<F, NInt, NUint, NDouble, NString, black_magic::S<uint64_t, Args1...>, black_magic::S<Args2...>>
{
void operator()(F cparams)
{
using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<uint64_t, NUint>>;
call<F, NInt, NUint+1, NDouble, NString,
black_magic::S<Args1...>, pushed>()(cparams);
}
};
template <typename F, int NInt, int NUint, int NDouble, int NString, typename ... Args1, typename ... Args2>
struct call<F, NInt, NUint, NDouble, NString, black_magic::S<double, Args1...>, black_magic::S<Args2...>>
{
void operator()(F cparams)
{
using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<double, NDouble>>;
call<F, NInt, NUint, NDouble+1, NString,
black_magic::S<Args1...>, pushed>()(cparams);
}
};
template <typename F, int NInt, int NUint, int NDouble, int NString, typename ... Args1, typename ... Args2>
struct call<F, NInt, NUint, NDouble, NString, black_magic::S<std::string, Args1...>, black_magic::S<Args2...>>
{
void operator()(F cparams)
{
using pushed = typename black_magic::S<Args2...>::template push_back<call_pair<std::string, NString>>;
call<F, NInt, NUint, NDouble, NString+1,
black_magic::S<Args1...>, pushed>()(cparams);
}
};
template <typename F, int NInt, int NUint, int NDouble, int NString, typename ... Args1>
struct call<F, NInt, NUint, NDouble, NString, black_magic::S<>, black_magic::S<Args1...>>
{
void operator()(F cparams)
{
cparams.handler(
cparams.req,
cparams.res,
cparams.params.template get<typename Args1::type>(Args1::pos)...
);
}
};
template <typename Func, typename ... ArgsWrapped>
struct Wrapped
{
template <typename ... Args>
void set(Func f, typename std::enable_if<
!std::is_same<typename std::tuple_element<0, std::tuple<Args..., void>>::type, const request&>::value
, int>::type = 0)
{
handler_ = (
#ifdef CROW_CAN_USE_CPP14
[f = std::move(f)]
#else
[f]
#endif
(const request&, response& res, Args... args){
res = response(f(args...));
res.end();
});
}
template <typename Req, typename ... Args>
struct req_handler_wrapper
{
req_handler_wrapper(Func f)
: f(std::move(f))
{
}
void operator()(const request& req, response& res, Args... args)
{
res = response(f(req, args...));
res.end();
}
Func f;
};
template <typename ... Args>
void set(Func f, typename std::enable_if<
std::is_same<typename std::tuple_element<0, std::tuple<Args..., void>>::type, const request&>::value &&
!std::is_same<typename std::tuple_element<1, std::tuple<Args..., void, void>>::type, response&>::value
, int>::type = 0)
{
handler_ = req_handler_wrapper<Args...>(std::move(f));
/*handler_ = (
[f = std::move(f)]
(const request& req, response& res, Args... args){
res = response(f(req, args...));
res.end();
});*/
}
template <typename ... Args>
void set(Func f, typename std::enable_if<
std::is_same<typename std::tuple_element<0, std::tuple<Args..., void>>::type, const request&>::value &&
std::is_same<typename std::tuple_element<1, std::tuple<Args..., void, void>>::type, response&>::value
, int>::type = 0)
{
handler_ = std::move(f);
}
template <typename ... Args>
struct handler_type_helper
{
using type = std::function<void(const crow::request&, crow::response&, Args...)>;
using args_type = black_magic::S<typename black_magic::promote_t<Args>...>;
};
template <typename ... Args>
struct handler_type_helper<const request&, Args...>
{
using type = std::function<void(const crow::request&, crow::response&, Args...)>;
using args_type = black_magic::S<typename black_magic::promote_t<Args>...>;
};
template <typename ... Args>
struct handler_type_helper<const request&, response&, Args...>
{
using type = std::function<void(const crow::request&, crow::response&, Args...)>;
using args_type = black_magic::S<typename black_magic::promote_t<Args>...>;
};
typename handler_type_helper<ArgsWrapped...>::type handler_;
void operator()(const request& req, response& res, const routing_params& params)
{
detail::routing_handler_call_helper::call<
detail::routing_handler_call_helper::call_params<
decltype(handler_)>,
0, 0, 0, 0,
typename handler_type_helper<ArgsWrapped...>::args_type,
black_magic::S<>
>()(
detail::routing_handler_call_helper::call_params<
decltype(handler_)>
{handler_, params, req, res}
);
}
};
}
}
template <typename T>
struct RuleParameterTraits
{
using self_t = T;
self_t& name(std::string name) noexcept
{
((self_t*)this)->name_ = std::move(name);
return (self_t&)*this;
}
self_t& methods(HTTPMethod method)
{
((self_t*)this)->methods_ = 1 << (int)method;
return (self_t&)*this;
}
template <typename ... MethodArgs>
self_t& methods(HTTPMethod method, MethodArgs ... args_method)
{
methods(args_method...);
((self_t*)this)->methods_ |= 1 << (int)method;
return (self_t&)*this;
}
};
class DynamicRule : public BaseRule, public RuleParameterTraits<DynamicRule>
{
public:
DynamicRule(std::string rule)
: BaseRule(std::move(rule))
{
}
void validate() override
{
if (!erased_handler_)
{
throw std::runtime_error(name_ + (!name_.empty() ? ": " : "") + "no handler for url " + rule_);
}
}
void handle(const request& req, response& res, const routing_params& params) override
{
erased_handler_(req, res, params);
}
template <typename Func>
void operator()(Func f)
{
#ifdef CROW_MSVC_WORKAROUND
using function_t = utility::function_traits<decltype(&Func::operator())>;
#else
using function_t = utility::function_traits<Func>;
#endif
erased_handler_ = wrap(std::move(f), black_magic::gen_seq<function_t::arity>());
}
// enable_if Arg1 == request && Arg2 == response
// enable_if Arg1 == request && Arg2 != resposne
// enable_if Arg1 != request
#ifdef CROW_MSVC_WORKAROUND
template <typename Func, size_t ... Indices>
#else
template <typename Func, unsigned ... Indices>
#endif
std::function<void(const request&, response&, const routing_params&)>
wrap(Func f, black_magic::seq<Indices...>)
{
#ifdef CROW_MSVC_WORKAROUND
using function_t = utility::function_traits<decltype(&Func::operator())>;
#else
using function_t = utility::function_traits<Func>;
#endif
if (!black_magic::is_parameter_tag_compatible(
black_magic::get_parameter_tag_runtime(rule_.c_str()),
black_magic::compute_parameter_tag_from_args_list<
typename function_t::template arg<Indices>...>::value))
{
throw std::runtime_error("route_dynamic: Handler type is mismatched with URL parameters: " + rule_);
}
auto ret = detail::routing_handler_call_helper::Wrapped<Func, typename function_t::template arg<Indices>...>();
ret.template set<
typename function_t::template arg<Indices>...
>(std::move(f));
return ret;
}
template <typename Func>
void operator()(std::string name, Func&& f)
{
name_ = std::move(name);
(*this).template operator()<Func>(std::forward(f));
}
private:
std::function<void(const request&, response&, const routing_params&)> erased_handler_;
};
template <typename ... Args>
class TaggedRule : public BaseRule, public RuleParameterTraits<TaggedRule<Args...>>
{
public:
using self_t = TaggedRule<Args...>;
TaggedRule(std::string rule)
: BaseRule(std::move(rule))
{
}
void validate()
{
if (!handler_)
{
throw std::runtime_error(name_ + (!name_.empty() ? ": " : "") + "no handler for url " + rule_);
}
}
template <typename Func>
typename std::enable_if<black_magic::CallHelper<Func, black_magic::S<Args...>>::value, void>::type
operator()(Func&& f)
{
static_assert(black_magic::CallHelper<Func, black_magic::S<Args...>>::value ||
black_magic::CallHelper<Func, black_magic::S<crow::request, Args...>>::value ,
"Handler type is mismatched with URL parameters");
static_assert(!std::is_same<void, decltype(f(std::declval<Args>()...))>::value,
"Handler function cannot have void return type; valid return types: string, int, crow::resposne, crow::json::wvalue");
handler_ = [f = std::move(f)](const request&, response& res, Args ... args){
res = response(f(args...));
res.end();
};
}
template <typename Func>
typename std::enable_if<
!black_magic::CallHelper<Func, black_magic::S<Args...>>::value &&
black_magic::CallHelper<Func, black_magic::S<crow::request, Args...>>::value,
void>::type
operator()(Func&& f)
{
static_assert(black_magic::CallHelper<Func, black_magic::S<Args...>>::value ||
black_magic::CallHelper<Func, black_magic::S<crow::request, Args...>>::value,
"Handler type is mismatched with URL parameters");
static_assert(!std::is_same<void, decltype(f(std::declval<crow::request>(), std::declval<Args>()...))>::value,
"Handler function cannot have void return type; valid return types: string, int, crow::resposne, crow::json::wvalue");
handler_ = [f = std::move(f)](const crow::request& req, crow::response& res, Args ... args){
res = response(f(req, args...));
res.end();
};
}
template <typename Func>
typename std::enable_if<
!black_magic::CallHelper<Func, black_magic::S<Args...>>::value &&
!black_magic::CallHelper<Func, black_magic::S<crow::request, Args...>>::value,
void>::type
operator()(Func&& f)
{
static_assert(black_magic::CallHelper<Func, black_magic::S<Args...>>::value ||
black_magic::CallHelper<Func, black_magic::S<crow::request, Args...>>::value ||
black_magic::CallHelper<Func, black_magic::S<crow::request, crow::response&, Args...>>::value
,
"Handler type is mismatched with URL parameters");
static_assert(std::is_same<void, decltype(f(std::declval<crow::request>(), std::declval<crow::response&>(), std::declval<Args>()...))>::value,
"Handler function with response argument should have void return type");
handler_ = std::move(f);
}
template <typename Func>
void operator()(std::string name, Func&& f)
{
name_ = std::move(name);
(*this).template operator()<Func>(std::forward(f));
}
void handle(const request& req, response& res, const routing_params& params) override
{
detail::routing_handler_call_helper::call<
detail::routing_handler_call_helper::call_params<
decltype(handler_)>,
0, 0, 0, 0,
black_magic::S<Args...>,
black_magic::S<>
>()(
detail::routing_handler_call_helper::call_params<
decltype(handler_)>
{handler_, params, req, res}
);
}
private:
std::function<void(const crow::request&, crow::response&, Args...)> handler_;
};
const int RULE_SPECIAL_REDIRECT_SLASH = 1;
class Trie
{
public:
struct Node
{
unsigned rule_index{};
std::array<unsigned, (int)ParamType::MAX> param_childrens{};
std::unordered_map<std::string, unsigned> children;
bool IsSimpleNode() const
{
return
!rule_index &&
std::all_of(
std::begin(param_childrens),
std::end(param_childrens),
[](unsigned x){ return !x; });
}
};
Trie() : nodes_(1)
{
}
private:
void optimizeNode(Node* node)
{
for(auto x : node->param_childrens)
{
if (!x)
continue;
Node* child = &nodes_[x];
optimizeNode(child);
}
if (node->children.empty())
return;
bool mergeWithChild = true;
for(auto& kv : node->children)
{
Node* child = &nodes_[kv.second];
if (!child->IsSimpleNode())
{
mergeWithChild = false;
break;
}
}
if (mergeWithChild)
{
decltype(node->children) merged;
for(auto& kv : node->children)
{
Node* child = &nodes_[kv.second];
for(auto& child_kv : child->children)
{
merged[kv.first + child_kv.first] = child_kv.second;
}
}
node->children = std::move(merged);
optimizeNode(node);
}
else
{
for(auto& kv : node->children)
{
Node* child = &nodes_[kv.second];
optimizeNode(child);
}
}
}
void optimize()
{
optimizeNode(head());
}
public:
void validate()
{
if (!head()->IsSimpleNode())
throw std::runtime_error("Internal error: Trie header should be simple!");
optimize();
}
std::pair<unsigned, routing_params> find(const std::string& req_url, const Node* node = nullptr, unsigned pos = 0, routing_params* params = nullptr) const
{
routing_params empty;
if (params == nullptr)
params = &empty;
unsigned found{};
routing_params match_params;
if (node == nullptr)
node = head();
if (pos == req_url.size())
return {node->rule_index, *params};
auto update_found = [&found, &match_params](std::pair<unsigned, routing_params>& ret)
{
if (ret.first && (!found || found > ret.first))
{
found = ret.first;
match_params = std::move(ret.second);
}
};
if (node->param_childrens[(int)ParamType::INT])
{
char c = req_url[pos];
if ((c >= '0' && c <= '9') || c == '+' || c == '-')
{
char* eptr;
errno = 0;
long long int value = strtoll(req_url.data()+pos, &eptr, 10);
if (errno != ERANGE && eptr != req_url.data()+pos)
{
params->int_params.push_back(value);
auto ret = find(req_url, &nodes_[node->param_childrens[(int)ParamType::INT]], eptr - req_url.data(), params);
update_found(ret);
params->int_params.pop_back();
}
}
}
if (node->param_childrens[(int)ParamType::UINT])
{
char c = req_url[pos];
if ((c >= '0' && c <= '9') || c == '+')
{
char* eptr;
errno = 0;
unsigned long long int value = strtoull(req_url.data()+pos, &eptr, 10);
if (errno != ERANGE && eptr != req_url.data()+pos)
{
params->uint_params.push_back(value);
auto ret = find(req_url, &nodes_[node->param_childrens[(int)ParamType::UINT]], eptr - req_url.data(), params);
update_found(ret);
params->uint_params.pop_back();
}
}
}
if (node->param_childrens[(int)ParamType::DOUBLE])
{
char c = req_url[pos];
if ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.')
{
char* eptr;
errno = 0;
double value = strtod(req_url.data()+pos, &eptr);
if (errno != ERANGE && eptr != req_url.data()+pos)
{
params->double_params.push_back(value);
auto ret = find(req_url, &nodes_[node->param_childrens[(int)ParamType::DOUBLE]], eptr - req_url.data(), params);
update_found(ret);
params->double_params.pop_back();
}
}
}
if (node->param_childrens[(int)ParamType::STRING])
{
size_t epos = pos;
for(; epos < req_url.size(); epos ++)
{
if (req_url[epos] == '/')
break;
}
if (epos != pos)
{
params->string_params.push_back(req_url.substr(pos, epos-pos));
auto ret = find(req_url, &nodes_[node->param_childrens[(int)ParamType::STRING]], epos, params);
update_found(ret);
params->string_params.pop_back();
}
}
if (node->param_childrens[(int)ParamType::PATH])
{
size_t epos = req_url.size();
if (epos != pos)
{
params->string_params.push_back(req_url.substr(pos, epos-pos));
auto ret = find(req_url, &nodes_[node->param_childrens[(int)ParamType::PATH]], epos, params);
update_found(ret);
params->string_params.pop_back();
}
}
for(auto& kv : node->children)
{
const std::string& fragment = kv.first;
const Node* child = &nodes_[kv.second];
if (req_url.compare(pos, fragment.size(), fragment) == 0)
{
auto ret = find(req_url, child, pos + fragment.size(), params);
update_found(ret);
}
}
return {found, match_params};
}
void add(const std::string& url, unsigned rule_index)
{
unsigned idx{0};
for(unsigned i = 0; i < url.size(); i ++)
{
char c = url[i];
if (c == '<')
{
static struct ParamTraits
{
ParamType type;
std::string name;
} paramTraits[] =
{
{ ParamType::INT, "<int>" },
{ ParamType::UINT, "<uint>" },
{ ParamType::DOUBLE, "<float>" },
{ ParamType::DOUBLE, "<double>" },
{ ParamType::STRING, "<str>" },
{ ParamType::STRING, "<string>" },
{ ParamType::PATH, "<path>" },
};
for(auto& x:paramTraits)
{
if (url.compare(i, x.name.size(), x.name) == 0)
{
if (!nodes_[idx].param_childrens[(int)x.type])
{
auto new_node_idx = new_node();
nodes_[idx].param_childrens[(int)x.type] = new_node_idx;
}
idx = nodes_[idx].param_childrens[(int)x.type];
i += x.name.size();
break;
}
}
i --;
}
else
{
std::string piece(&c, 1);
if (!nodes_[idx].children.count(piece))
{
auto new_node_idx = new_node();
nodes_[idx].children.emplace(piece, new_node_idx);
}
idx = nodes_[idx].children[piece];
}
}
if (nodes_[idx].rule_index)
throw std::runtime_error("handler already exists for " + url);
nodes_[idx].rule_index = rule_index;
}
private:
void debug_node_print(Node* n, int level)
{
for(int i = 0; i < (int)ParamType::MAX; i ++)
{
if (n->param_childrens[i])
{
CROW_LOG_DEBUG << std::string(2*level, ' ') /*<< "("<<n->param_childrens[i]<<") "*/;
switch((ParamType)i)
{
case ParamType::INT:
CROW_LOG_DEBUG << "<int>";
break;
case ParamType::UINT:
CROW_LOG_DEBUG << "<uint>";
break;
case ParamType::DOUBLE:
CROW_LOG_DEBUG << "<float>";
break;
case ParamType::STRING:
CROW_LOG_DEBUG << "<str>";
break;
case ParamType::PATH:
CROW_LOG_DEBUG << "<path>";
break;
default:
CROW_LOG_DEBUG << "<ERROR>";
break;
}
debug_node_print(&nodes_[n->param_childrens[i]], level+1);
}
}
for(auto& kv : n->children)
{
CROW_LOG_DEBUG << std::string(2*level, ' ') /*<< "(" << kv.second << ") "*/ << kv.first;
debug_node_print(&nodes_[kv.second], level+1);
}
}
public:
void debug_print()
{
debug_node_print(head(), 0);
}
private:
const Node* head() const
{
return &nodes_.front();
}
Node* head()
{
return &nodes_.front();
}
unsigned new_node()
{
nodes_.resize(nodes_.size()+1);
return nodes_.size() - 1;
}
std::vector<Node> nodes_;
};
class Router
{
public:
Router() : rules_(2)
{
}
DynamicRule& new_rule_dynamic(const std::string& rule)
{
auto ruleObject = new DynamicRule(rule);
internal_add_rule_object(rule, ruleObject);
return *ruleObject;
}
template <uint64_t N>
typename black_magic::arguments<N>::type::template rebind<TaggedRule>& new_rule_tagged(const std::string& rule)
{
using RuleT = typename black_magic::arguments<N>::type::template rebind<TaggedRule>;
auto ruleObject = new RuleT(rule);
internal_add_rule_object(rule, ruleObject);
return *ruleObject;
}
void internal_add_rule_object(const std::string& rule, BaseRule* ruleObject)
{
rules_.emplace_back(ruleObject);
trie_.add(rule, rules_.size() - 1);
// directory case:
// request to `/about' url matches `/about/' rule
if (rule.size() > 1 && rule.back() == '/')
{
std::string rule_without_trailing_slash = rule;
rule_without_trailing_slash.pop_back();
trie_.add(rule_without_trailing_slash, RULE_SPECIAL_REDIRECT_SLASH);
}
}
void validate()
{
trie_.validate();
for(auto& rule:rules_)
{
if (rule)
rule->validate();
}
}
void handle(const request& req, response& res)
{
auto found = trie_.find(req.url);
unsigned rule_index = found.first;
CROW_LOG_DEBUG << "???" << rule_index;
if (!rule_index)
{
CROW_LOG_DEBUG << "Cannot match rules " << req.url;
res = response(404);
res.end();
return;
}
if (rule_index >= rules_.size())
throw std::runtime_error("Trie internal structure corrupted!");
if (rule_index == RULE_SPECIAL_REDIRECT_SLASH)
{
CROW_LOG_INFO << "Redirecting to a url with trailing slash: " << req.url;
res = response(301);
// TODO absolute url building
if (req.get_header_value("Host").empty())
{
res.add_header("Location", req.url + "/");
}
else
{
res.add_header("Location", "http://" + req.get_header_value("Host") + req.url + "/");
}
res.end();
return;
}
if ((rules_[rule_index]->get_methods() & (1<<(uint32_t)req.method)) == 0)
{
CROW_LOG_DEBUG << "Rule found but method mismatch: " << req.url << " with " << method_name(req.method) << "(" << (uint32_t)req.method << ") / " << rules_[rule_index]->get_methods();
res = response(404);
res.end();
return;
}
CROW_LOG_DEBUG << "Matched rule '" << rules_[rule_index]->rule_ << "' " << (uint32_t)req.method << " / " << rules_[rule_index]->get_methods();
// any uncaught exceptions become 500s
try
{
rules_[rule_index]->handle(req, res, found.second);
}
catch(std::exception& e)
{
CROW_LOG_ERROR << "An uncaught exception occurred: " << e.what();
res = response(500);
res.end();
return;
}
catch(...)
{
CROW_LOG_ERROR << "An uncaught exception occurred. The type was unknown so no information was available.";
res = response(500);
res.end();
return;
}
}
void debug_print()
{
trie_.debug_print();
}
private:
std::vector<std::unique_ptr<BaseRule>> rules_;
Trie trie_;
};
}

38
ext/crow/settings.h Normal file
View file

@ -0,0 +1,38 @@
#pragma once
// settings for crow
// TODO - replace with runtime config. libucl?
/* #ifdef - enables debug mode */
#define CROW_ENABLE_DEBUG
/* #ifdef - enables logging */
#define CROW_ENABLE_LOGGING
/* #ifdef - enables SSL */
//#define CROW_ENABLE_SSL
/* #define - specifies log level */
/*
DEBUG = 0
INFO = 1
WARNING = 2
ERROR = 3
CRITICAL = 4
default to INFO
*/
#define CROW_LOG_LEVEL 1
// compiler flags
#if __cplusplus >= 201402L
#define CROW_CAN_USE_CPP14
#endif
#if defined(_MSC_VER)
#if _MSC_VER < 1900
#define CROW_MSVC_WORKAROUND
#define constexpr const
#define noexcept throw()
#endif
#endif

View file

@ -0,0 +1,98 @@
#pragma once
#include <boost/asio.hpp>
#include "settings.h"
namespace crow
{
using namespace boost;
using tcp = asio::ip::tcp;
struct SocketAdaptor
{
using context = void;
SocketAdaptor(boost::asio::io_service& io_service, context*)
: socket_(io_service)
{
}
tcp::socket& raw_socket()
{
return socket_;
}
tcp::socket& socket()
{
return socket_;
}
tcp::endpoint remote_endpoint()
{
return socket_.remote_endpoint();
}
bool is_open()
{
return socket_.is_open();
}
void close()
{
socket_.close();
}
template <typename F>
void start(F f)
{
f(boost::system::error_code());
}
tcp::socket socket_;
};
#ifdef CROW_ENABLE_SSL
struct SSLAdaptor
{
using context = boost::asio::ssl::context;
SSLAdaptor(boost::asio::io_service& io_service, context* ctx)
: ssl_socket_(io_service, *ctx)
{
}
boost::asio::ssl::stream<tcp::socket>& socket()
{
return ssl_socket_;
}
tcp::socket::lowest_layer_type&
raw_socket()
{
return ssl_socket_.lowest_layer();
}
tcp::endpoint remote_endpoint()
{
return raw_socket().remote_endpoint();
}
bool is_open()
{
return raw_socket().is_open();
}
void close()
{
raw_socket().close();
}
template <typename F>
void start(F f)
{
ssl_socket_.async_handshake(boost::asio::ssl::stream_base::server,
[f](const boost::system::error_code& ec) {
f(ec);
});
}
boost::asio::ssl::stream<tcp::socket> ssl_socket_;
};
#endif
}

503
ext/crow/utility.h Normal file
View file

@ -0,0 +1,503 @@
#pragma once
#include <cstdint>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <cstring>
#include <functional>
namespace crow
{
namespace black_magic
{
#ifndef CROW_MSVC_WORKAROUND
struct OutOfRange
{
OutOfRange(unsigned pos, unsigned length) {}
};
constexpr unsigned requires_in_range( unsigned i, unsigned len )
{
return i >= len ? throw OutOfRange(i, len) : i;
}
class const_str
{
const char * const begin_;
unsigned size_;
public:
template< unsigned N >
constexpr const_str( const char(&arr)[N] ) : begin_(arr), size_(N - 1) {
static_assert( N >= 1, "not a string literal");
}
constexpr char operator[]( unsigned i ) const {
return requires_in_range(i, size_), begin_[i];
}
constexpr operator const char *() const {
return begin_;
}
constexpr const char* begin() const { return begin_; }
constexpr const char* end() const { return begin_ + size_; }
constexpr unsigned size() const {
return size_;
}
};
constexpr unsigned find_closing_tag(const_str s, unsigned p)
{
return s[p] == '>' ? p : find_closing_tag(s, p+1);
}
constexpr bool is_valid(const_str s, unsigned i = 0, int f = 0)
{
return
i == s.size()
? f == 0 :
f < 0 || f >= 2
? false :
s[i] == '<'
? is_valid(s, i+1, f+1) :
s[i] == '>'
? is_valid(s, i+1, f-1) :
is_valid(s, i+1, f);
}
constexpr bool is_equ_p(const char* a, const char* b, unsigned n)
{
return
*a == 0 && *b == 0 && n == 0
? true :
(*a == 0 || *b == 0)
? false :
n == 0
? true :
*a != *b
? false :
is_equ_p(a+1, b+1, n-1);
}
constexpr bool is_equ_n(const_str a, unsigned ai, const_str b, unsigned bi, unsigned n)
{
return
ai + n > a.size() || bi + n > b.size()
? false :
n == 0
? true :
a[ai] != b[bi]
? false :
is_equ_n(a,ai+1,b,bi+1,n-1);
}
constexpr bool is_int(const_str s, unsigned i)
{
return is_equ_n(s, i, "<int>", 0, 5);
}
constexpr bool is_uint(const_str s, unsigned i)
{
return is_equ_n(s, i, "<uint>", 0, 6);
}
constexpr bool is_float(const_str s, unsigned i)
{
return is_equ_n(s, i, "<float>", 0, 7) ||
is_equ_n(s, i, "<double>", 0, 8);
}
constexpr bool is_str(const_str s, unsigned i)
{
return is_equ_n(s, i, "<str>", 0, 5) ||
is_equ_n(s, i, "<string>", 0, 8);
}
constexpr bool is_path(const_str s, unsigned i)
{
return is_equ_n(s, i, "<path>", 0, 6);
}
#endif
template <typename T>
struct parameter_tag
{
static const int value = 0;
};
#define CROW_INTERNAL_PARAMETER_TAG(t, i) \
template <> \
struct parameter_tag<t> \
{ \
static const int value = i; \
};
CROW_INTERNAL_PARAMETER_TAG(int, 1);
CROW_INTERNAL_PARAMETER_TAG(char, 1);
CROW_INTERNAL_PARAMETER_TAG(short, 1);
CROW_INTERNAL_PARAMETER_TAG(long, 1);
CROW_INTERNAL_PARAMETER_TAG(long long, 1);
CROW_INTERNAL_PARAMETER_TAG(unsigned int, 2);
CROW_INTERNAL_PARAMETER_TAG(unsigned char, 2);
CROW_INTERNAL_PARAMETER_TAG(unsigned short, 2);
CROW_INTERNAL_PARAMETER_TAG(unsigned long, 2);
CROW_INTERNAL_PARAMETER_TAG(unsigned long long, 2);
CROW_INTERNAL_PARAMETER_TAG(double, 3);
CROW_INTERNAL_PARAMETER_TAG(std::string, 4);
#undef CROW_INTERNAL_PARAMETER_TAG
template <typename ... Args>
struct compute_parameter_tag_from_args_list;
template <>
struct compute_parameter_tag_from_args_list<>
{
static const int value = 0;
};
template <typename Arg, typename ... Args>
struct compute_parameter_tag_from_args_list<Arg, Args...>
{
static const int sub_value =
compute_parameter_tag_from_args_list<Args...>::value;
static const int value =
parameter_tag<typename std::decay<Arg>::type>::value
? sub_value* 6 + parameter_tag<typename std::decay<Arg>::type>::value
: sub_value;
};
static inline bool is_parameter_tag_compatible(uint64_t a, uint64_t b)
{
if (a == 0)
return b == 0;
if (b == 0)
return a == 0;
int sa = a%6;
int sb = a%6;
if (sa == 5) sa = 4;
if (sb == 5) sb = 4;
if (sa != sb)
return false;
return is_parameter_tag_compatible(a/6, b/6);
}
static inline unsigned find_closing_tag_runtime(const char* s, unsigned p)
{
return
s[p] == 0
? throw std::runtime_error("unmatched tag <") :
s[p] == '>'
? p : find_closing_tag_runtime(s, p + 1);
}
static inline uint64_t get_parameter_tag_runtime(const char* s, unsigned p = 0)
{
return
s[p] == 0
? 0 :
s[p] == '<' ? (
std::strncmp(s+p, "<int>", 5) == 0
? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 1 :
std::strncmp(s+p, "<uint>", 6) == 0
? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 2 :
(std::strncmp(s+p, "<float>", 7) == 0 ||
std::strncmp(s+p, "<double>", 8) == 0)
? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 3 :
(std::strncmp(s+p, "<str>", 5) == 0 ||
std::strncmp(s+p, "<string>", 8) == 0)
? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 4 :
std::strncmp(s+p, "<path>", 6) == 0
? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 5 :
throw std::runtime_error("invalid parameter type")
) :
get_parameter_tag_runtime(s, p+1);
}
#ifndef CROW_MSVC_WORKAROUND
constexpr uint64_t get_parameter_tag(const_str s, unsigned p = 0)
{
return
p == s.size()
? 0 :
s[p] == '<' ? (
is_int(s, p)
? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 1 :
is_uint(s, p)
? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 2 :
is_float(s, p)
? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 3 :
is_str(s, p)
? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 4 :
is_path(s, p)
? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 5 :
throw std::runtime_error("invalid parameter type")
) :
get_parameter_tag(s, p+1);
}
#endif
template <typename ... T>
struct S
{
template <typename U>
using push = S<U, T...>;
template <typename U>
using push_back = S<T..., U>;
template <template<typename ... Args> class U>
using rebind = U<T...>;
};
template <typename F, typename Set>
struct CallHelper;
template <typename F, typename ...Args>
struct CallHelper<F, S<Args...>>
{
template <typename F1, typename ...Args1, typename =
decltype(std::declval<F1>()(std::declval<Args1>()...))
>
static char __test(int);
template <typename ...>
static int __test(...);
static constexpr bool value = sizeof(__test<F, Args...>(0)) == sizeof(char);
};
template <int N>
struct single_tag_to_type
{
};
template <>
struct single_tag_to_type<1>
{
using type = int64_t;
};
template <>
struct single_tag_to_type<2>
{
using type = uint64_t;
};
template <>
struct single_tag_to_type<3>
{
using type = double;
};
template <>
struct single_tag_to_type<4>
{
using type = std::string;
};
template <>
struct single_tag_to_type<5>
{
using type = std::string;
};
template <uint64_t Tag>
struct arguments
{
using subarguments = typename arguments<Tag/6>::type;
using type =
typename subarguments::template push<typename single_tag_to_type<Tag%6>::type>;
};
template <>
struct arguments<0>
{
using type = S<>;
};
template <typename ... T>
struct last_element_type
{
using type = typename std::tuple_element<sizeof...(T)-1, std::tuple<T...>>::type;
};
template <>
struct last_element_type<>
{
};
// from http://stackoverflow.com/questions/13072359/c11-compile-time-array-with-logarithmic-evaluation-depth
template<class T> using Invoke = typename T::type;
template<unsigned...> struct seq{ using type = seq; };
template<class S1, class S2> struct concat;
template<unsigned... I1, unsigned... I2>
struct concat<seq<I1...>, seq<I2...>>
: seq<I1..., (sizeof...(I1)+I2)...>{};
template<class S1, class S2>
using Concat = Invoke<concat<S1, S2>>;
template<unsigned N> struct gen_seq;
template<unsigned N> using GenSeq = Invoke<gen_seq<N>>;
template<unsigned N>
struct gen_seq : Concat<GenSeq<N/2>, GenSeq<N - N/2>>{};
template<> struct gen_seq<0> : seq<>{};
template<> struct gen_seq<1> : seq<0>{};
template <typename Seq, typename Tuple>
struct pop_back_helper;
template <unsigned ... N, typename Tuple>
struct pop_back_helper<seq<N...>, Tuple>
{
template <template <typename ... Args> class U>
using rebind = U<typename std::tuple_element<N, Tuple>::type...>;
};
template <typename ... T>
struct pop_back //: public pop_back_helper<typename gen_seq<sizeof...(T)-1>::type, std::tuple<T...>>
{
template <template <typename ... Args> class U>
using rebind = typename pop_back_helper<typename gen_seq<sizeof...(T)-1>::type, std::tuple<T...>>::template rebind<U>;
};
template <>
struct pop_back<>
{
template <template <typename ... Args> class U>
using rebind = U<>;
};
// from http://stackoverflow.com/questions/2118541/check-if-c0x-parameter-pack-contains-a-type
template < typename Tp, typename... List >
struct contains : std::true_type {};
template < typename Tp, typename Head, typename... Rest >
struct contains<Tp, Head, Rest...>
: std::conditional< std::is_same<Tp, Head>::value,
std::true_type,
contains<Tp, Rest...>
>::type {};
template < typename Tp >
struct contains<Tp> : std::false_type {};
template <typename T>
struct empty_context
{
};
template <typename T>
struct promote
{
using type = T;
};
#define CROW_INTERNAL_PROMOTE_TYPE(t1, t2) \
template<> \
struct promote<t1> \
{ \
using type = t2; \
}
CROW_INTERNAL_PROMOTE_TYPE(char, int64_t);
CROW_INTERNAL_PROMOTE_TYPE(short, int64_t);
CROW_INTERNAL_PROMOTE_TYPE(int, int64_t);
CROW_INTERNAL_PROMOTE_TYPE(long, int64_t);
CROW_INTERNAL_PROMOTE_TYPE(long long, int64_t);
CROW_INTERNAL_PROMOTE_TYPE(unsigned char, uint64_t);
CROW_INTERNAL_PROMOTE_TYPE(unsigned short, uint64_t);
CROW_INTERNAL_PROMOTE_TYPE(unsigned int, uint64_t);
CROW_INTERNAL_PROMOTE_TYPE(unsigned long, uint64_t);
CROW_INTERNAL_PROMOTE_TYPE(unsigned long long, uint64_t);
CROW_INTERNAL_PROMOTE_TYPE(float, double);
#undef CROW_INTERNAL_PROMOTE_TYPE
template <typename T>
using promote_t = typename promote<T>::type;
} // namespace black_magic
namespace detail
{
template <class T, std::size_t N, class... Args>
struct get_index_of_element_from_tuple_by_type_impl
{
static constexpr auto value = N;
};
template <class T, std::size_t N, class... Args>
struct get_index_of_element_from_tuple_by_type_impl<T, N, T, Args...>
{
static constexpr auto value = N;
};
template <class T, std::size_t N, class U, class... Args>
struct get_index_of_element_from_tuple_by_type_impl<T, N, U, Args...>
{
static constexpr auto value = get_index_of_element_from_tuple_by_type_impl<T, N + 1, Args...>::value;
};
} // namespace detail
namespace utility
{
template <class T, class... Args>
T& get_element_by_type(std::tuple<Args...>& t)
{
return std::get<detail::get_index_of_element_from_tuple_by_type_impl<T, 0, Args...>::value>(t);
}
template<typename T>
struct function_traits;
#ifndef CROW_MSVC_WORKAROUND
template<typename T>
struct function_traits : public function_traits<decltype(&T::operator())>
{
using parent_t = function_traits<decltype(&T::operator())>;
static const size_t arity = parent_t::arity;
using result_type = typename parent_t::result_type;
template <size_t i>
using arg = typename parent_t::template arg<i>;
};
#endif
template<typename ClassType, typename R, typename ...Args>
struct function_traits<R(ClassType::*)(Args...) const>
{
static const size_t arity = sizeof...(Args);
typedef R result_type;
template <size_t i>
using arg = typename std::tuple_element<i, std::tuple<Args...>>::type;
};
template<typename ClassType, typename R, typename ...Args>
struct function_traits<R(ClassType::*)(Args...)>
{
static const size_t arity = sizeof...(Args);
typedef R result_type;
template <size_t i>
using arg = typename std::tuple_element<i, std::tuple<Args...>>::type;
};
template<typename R, typename ...Args>
struct function_traits<std::function<R(Args...)>>
{
static const size_t arity = sizeof...(Args);
typedef R result_type;
template <size_t i>
using arg = typename std::tuple_element<i, std::tuple<Args...>>::type;
};
} // namespace utility
}

35
ext/dateparser.cpp Normal file
View file

@ -0,0 +1,35 @@
//
// Created by marcin on 22/11/15.
//
#include "dateparser.h"
dateparser::dateparser(std::string fmt)
{
// set format
using namespace boost::local_time;
local_time_input_facet* input_facet = new local_time_input_facet();
input_facet->format(fmt.c_str());
ss.imbue(std::locale(ss.getloc(), input_facet));
}
bool
dateparser::operator()(std::string const& text)
{
ss.clear();
ss.str(text);
bool ok = bool(ss >> pt);
if (ok)
{
auto tm = to_tm(pt);
year = tm.tm_year;
month = tm.tm_mon + 1; // for 1-based (1:jan, .. 12:dec)
day = tm.tm_mday;
}
return ok;
}

27
ext/dateparser.h Normal file
View file

@ -0,0 +1,27 @@
//
// Created by marcin on 22/11/15.
//
#ifndef XMR2CSV_DATEPARSER_H
#define XMR2CSV_DATEPARSER_H
#include <iostream>
#include <boost/date_time/local_time/local_time.hpp>
// taken from: http://stackoverflow.com/a/19482908/248823
struct dateparser
{
boost::posix_time::ptime pt;
unsigned year, month, day;
dateparser(std::string fmt);
bool
operator()(std::string const& text);
private:
std::stringstream ss;
};
#endif //XMR2CSV_DATEPARSER_H

1378
ext/format.cc Normal file

File diff suppressed because it is too large Load diff

3157
ext/format.h Normal file

File diff suppressed because it is too large Load diff

762
ext/minicsv.h Normal file
View file

@ -0,0 +1,762 @@
// The MIT License (MIT)
// Minimalistic CSV Streams 1.6
// Copyright (C) 2014, by Wong Shao Voon (shaovoon@yahoo.com)
//
// http://opensource.org/licenses/MIT
//
// version 1.2 : make use of make_shared
// version 1.3 : fixed: to when reading the last line and it does not have linefeed
// added: skip_1st_line and skip_line functions to ifstream class
// version 1.4 : Removed the use of smart ptr.
// version 1.5 : Performance increase on writing without flushing every line.
// version 1.6 : Add string streams
// version 1.7 : You MUST specify the escape/unescape string when calling set_delimiter. Option to surround/trim string with quotes
// version 1.7.1 : Add stream operator overload usage in example.cpp
// Disable the surround/trim quote on text by default
// version 1.7.2 : Stream operator overload for const char*
// version 1.7.3 : Add num_of_delimiter method to ifstream and istringstream
// Fix g++ compilation errors
// version 1.7.4 : Add get_rest_of_line
// version 1.7.5 : Add terminate_on_blank_line variable. Set to false if your file format has blank lines in between.
// version 1.7.6 : Ignore delimiters within quotes during reading when enable_trim_quote_on_str is true;
// version 1.7.7 : Fixed multiple symbol linkage errors
//#define USE_BOOST_LEXICAL_CAST
#ifndef MiniCSV_H
#define MiniCSV_H
#include <string>
#include <sstream>
#include <fstream>
#ifdef USE_BOOST_LEXICAL_CAST
# include <boost/lexical_cast.hpp>
#endif
#define NEWLINE '\n'
namespace csv
{
inline std::string const & replace(std::string & src, std::string const & to_find, std::string const & to_replace)
{
size_t pos = 0;
while (std::string::npos != pos)
{
pos = src.find(to_find, pos);
if (std::string::npos != pos)
{
src.erase(pos, to_find.size());
src.insert(pos, to_replace);
pos += to_replace.size();
}
}
return src;
}
inline std::string trim_right(const std::string& str, const std::string& trimChars)
{
std::string result = "";
size_t endpos = str.find_last_not_of(trimChars);
if (std::string::npos != endpos)
{
result = str.substr(0, endpos + 1);
}
else
result = str;
return result;
}
inline std::string trim_left(const std::string& str, const std::string& trimChars)
{
std::string result = "";
size_t startpos = str.find_first_not_of(trimChars);
if (std::string::npos != startpos)
{
result = str.substr(startpos);
}
else
result = str;
return result;
}
inline std::string trim(const std::string& str, const std::string& trimChars)
{
return trim_left(trim_right(str, trimChars), trimChars);
}
class ifstream
{
public:
ifstream() : str(""), pos(0), delimiter(","), unescape_str("##"), trim_quote_on_str(false), trim_quote('\"'), terminate_on_blank_line(true)
{
}
ifstream(const char * file)
{
open(file);
}
void open(const char * file)
{
init();
istm.open(file, std::ios_base::in);
}
void init()
{
str = "";
pos = 0;
delimiter = ',';
unescape_str = "##";
trim_quote_on_str = false;
trim_quote = '\"';
terminate_on_blank_line = true;
}
void close()
{
istm.close();
}
bool is_open()
{
return istm.is_open();
}
void enable_trim_quote_on_str(bool enable, char quote)
{
trim_quote_on_str = enable;
trim_quote = quote;
}
// eof is replaced by read_line
//bool eof() const
void set_delimiter(char delimiter_, std::string const & unescape_str_)
{
delimiter = delimiter_;
unescape_str = unescape_str_;
}
std::string const & get_delimiter() const
{
return delimiter;
}
std::string const & get_unescape_str() const
{
return unescape_str;
}
void skip_line()
{
if(!istm.eof())
{
std::getline(istm, str);
pos = 0;
}
}
bool read_line()
{
this->str = "";
while(!istm.eof())
{
std::getline(istm, this->str);
pos = 0;
if (this->str.empty())
{
if (terminate_on_blank_line)
break;
else
continue;
}
return true;
}
return false;
}
std::string get_delimited_str()
{
std::string str = "";
char ch = '\0';
bool within_quote = false;
do
{
if(pos>=this->str.size())
{
this->str = "";
return unescape(str);
}
ch = this->str[pos];
if (trim_quote_on_str)
{
if (within_quote == false && ch == trim_quote && ((pos > 0 && this->str[pos - 1] == delimiter[0]) || pos == 0))
within_quote = true;
else if (within_quote && ch == trim_quote)
within_quote = false;
}
++(pos);
if (ch == delimiter[0] && within_quote == false)
break;
if (ch == '\r' || ch == '\n')
break;
str += ch;
}
while(true);
return unescape(str);
}
std::string unescape(std::string & src)
{
src = unescape_str.empty() ? src : replace(src, unescape_str, delimiter);
return trim_quote_on_str ? trim(src, std::string(1, trim_quote)) : src;
}
size_t num_of_delimiter() const
{
if (delimiter.size() == 0)
return 0;
size_t cnt = 0;
for (size_t i = 0; i < str.size(); ++i)
{
if (str[i] == delimiter[0])
++cnt;
}
return cnt;
}
std::string get_rest_of_line() const
{
return str.substr(pos);
}
const std::string& get_line() const
{
return str;
}
void enable_terminate_on_blank_line(bool enable)
{
terminate_on_blank_line = enable;
}
bool is_terminate_on_blank_line() const
{
return terminate_on_blank_line;
}
private:
std::ifstream istm;
std::string str;
size_t pos;
std::string delimiter;
std::string unescape_str;
bool trim_quote_on_str;
char trim_quote;
bool terminate_on_blank_line;
};
class ofstream
{
public:
ofstream() : after_newline(true), delimiter(","), escape_str("##"), surround_quote_on_str(false), surround_quote('\"')
{
}
ofstream(const char * file)
{
open(file);
}
void open(const char * file)
{
init();
ostm.open(file, std::ios_base::out);
}
void init()
{
after_newline = true;
delimiter = ',';
escape_str = "##";
surround_quote_on_str = false;
surround_quote = '\"';
}
void flush()
{
ostm.flush();
}
void close()
{
ostm.close();
}
bool is_open()
{
return ostm.is_open();
}
void enable_surround_quote_on_str(bool enable, char quote)
{
surround_quote_on_str = enable;
surround_quote = quote;
}
void set_delimiter(char delimiter_, std::string const & escape_str_)
{
delimiter = delimiter_;
escape_str = escape_str_;
}
std::string const & get_delimiter() const
{
return delimiter;
}
std::string const & get_escape_str() const
{
return escape_str;
}
void set_after_newline(bool after_newline_)
{
after_newline = after_newline_;
}
bool get_after_newline() const
{
return after_newline;
}
std::ofstream& get_ofstream()
{
return ostm;
}
void escape_and_output(std::string src)
{
ostm << ((escape_str.empty()) ? src : replace(src, delimiter, escape_str));
}
void escape_str_and_output(std::string src)
{
src = ((escape_str.empty()) ? src : replace(src, delimiter, escape_str));
if (surround_quote_on_str)
{
ostm << surround_quote << src << surround_quote;
}
else
{
ostm << src;
}
}
private:
std::ofstream ostm;
bool after_newline;
std::string delimiter;
std::string escape_str;
bool surround_quote_on_str;
char surround_quote;
};
} // ns csv
template<typename T>
csv::ifstream& operator >> (csv::ifstream& istm, T& val)
{
std::string str = istm.get_delimited_str();
#ifdef USE_BOOST_LEXICAL_CAST
val = boost::lexical_cast<T>(str);
#else
std::istringstream is(str);
is >> val;
#endif
return istm;
}
template<>
inline csv::ifstream& operator >> (csv::ifstream& istm, std::string& val)
{
val = istm.get_delimited_str();
return istm;
}
template<typename T>
csv::ofstream& operator << (csv::ofstream& ostm, const T& val)
{
if(!ostm.get_after_newline())
ostm.get_ofstream() << ostm.get_delimiter();
std::ostringstream os_temp;
os_temp << val;
ostm.escape_and_output(os_temp.str());
ostm.set_after_newline(false);
return ostm;
}
template<typename T>
csv::ofstream& operator << (csv::ofstream& ostm, const T* val)
{
if (!ostm.get_after_newline())
ostm.get_ofstream() << ostm.get_delimiter();
std::ostringstream os_temp;
os_temp << *val;
ostm.escape_and_output(os_temp.str());
ostm.set_after_newline(false);
return ostm;
}
template<>
inline csv::ofstream& operator << (csv::ofstream& ostm, const std::string& val)
{
if (!ostm.get_after_newline())
ostm.get_ofstream() << ostm.get_delimiter();
std::string temp = val;
ostm.escape_str_and_output(temp);
ostm.set_after_newline(false);
return ostm;
}
template<>
inline csv::ofstream& operator << (csv::ofstream& ostm, const char& val)
{
if(val==NEWLINE)
{
ostm.get_ofstream() << NEWLINE;
ostm.set_after_newline(true);
}
else
{
std::ostringstream os_temp;
os_temp << val;
ostm.escape_and_output(os_temp.str());
}
return ostm;
}
template<>
inline csv::ofstream& operator << (csv::ofstream& ostm, const char* val)
{
const std::string temp = val;
ostm << temp;
return ostm;
}
namespace csv
{
class istringstream
{
public:
istringstream(const char * text)
: str("")
, pos(0)
, delimiter(",")
, unescape_str("##")
, trim_quote_on_str(false)
, trim_quote('\"')
, terminate_on_blank_line(true)
{
istm.str(text);
}
void enable_trim_quote_on_str(bool enable, char quote)
{
trim_quote_on_str = enable;
trim_quote = quote;
}
void set_delimiter(char delimiter_, std::string const & unescape_str_)
{
delimiter = delimiter_;
unescape_str = unescape_str_;
}
std::string const & get_delimiter() const
{
return delimiter;
}
std::string const & get_unescape_str() const
{
return unescape_str;
}
void skip_line()
{
std::getline(istm, str);
pos = 0;
}
bool read_line()
{
this->str = "";
while (!istm.eof())
{
std::getline(istm, this->str);
pos = 0;
if (this->str.empty())
{
if (terminate_on_blank_line)
break;
else
continue;
}
return true;
}
return false;
return false;
}
std::string get_delimited_str()
{
std::string str = "";
char ch = '\0';
bool within_quote = false;
do
{
if (pos >= this->str.size())
{
this->str = "";
return unescape(str);
}
ch = this->str[pos];
if (trim_quote_on_str)
{
if (within_quote == false && ch == trim_quote && ((pos > 0 && this->str[pos - 1] == delimiter[0]) || pos == 0))
within_quote = true;
else if (within_quote && ch == trim_quote)
within_quote = false;
}
++(pos);
if (ch == delimiter[0] && within_quote == false)
break;
if (ch == '\r' || ch == '\n')
break;
str += ch;
} while (true);
return unescape(str);
}
std::string unescape(std::string & src)
{
src = unescape_str.empty() ? src : replace(src, unescape_str, delimiter);
return trim_quote_on_str ? trim(src, std::string(1, trim_quote)) : src;
}
size_t num_of_delimiter() const
{
if (delimiter.size() == 0)
return 0;
size_t cnt = 0;
for (size_t i = 0; i < str.size(); ++i)
{
if (str[i] == delimiter[0])
++cnt;
}
return cnt;
}
std::string get_rest_of_line() const
{
return str.substr(pos);
}
const std::string& get_line() const
{
return str;
}
void enable_terminate_on_blank_line(bool enable)
{
terminate_on_blank_line = enable;
}
bool is_terminate_on_blank_line() const
{
return terminate_on_blank_line;
}
private:
std::istringstream istm;
std::string str;
size_t pos;
std::string delimiter;
std::string unescape_str;
bool trim_quote_on_str;
char trim_quote;
bool terminate_on_blank_line;
};
class ostringstream
{
public:
ostringstream() : after_newline(true), delimiter(","), escape_str("##"), surround_quote_on_str(false), surround_quote('\"')
{
}
void enable_surround_quote_on_str(bool enable, char quote)
{
surround_quote_on_str = enable;
surround_quote = quote;
}
void set_delimiter(char delimiter_, std::string const & escape_str_)
{
delimiter = delimiter_;
escape_str = escape_str_;
}
std::string const & get_delimiter() const
{
return delimiter;
}
std::string const & get_escape_str() const
{
return escape_str;
}
void set_after_newline(bool after_newline_)
{
after_newline = after_newline_;
}
bool get_after_newline() const
{
return after_newline;
}
std::ostringstream& get_ostringstream()
{
return ostm;
}
std::string get_text()
{
return ostm.str();
}
void escape_and_output(std::string src)
{
ostm << ((escape_str.empty()) ? src : replace(src, delimiter, escape_str));
}
void escape_str_and_output(std::string src)
{
src = ((escape_str.empty()) ? src : replace(src, delimiter, escape_str));
if (surround_quote_on_str)
{
ostm << surround_quote << src << surround_quote;
}
else
{
ostm << src;
}
}
private:
std::ostringstream ostm;
bool after_newline;
std::string delimiter;
std::string escape_str;
bool surround_quote_on_str;
char surround_quote;
};
} // ns csv
template<typename T>
csv::istringstream& operator >> (csv::istringstream& istm, T& val)
{
std::string str = istm.get_delimited_str();
#ifdef USE_BOOST_LEXICAL_CAST
val = boost::lexical_cast<T>(str);
#else
std::istringstream is(str);
is >> val;
#endif
return istm;
}
template<>
inline csv::istringstream& operator >> (csv::istringstream& istm, std::string& val)
{
val = istm.get_delimited_str();
return istm;
}
template<typename T>
csv::ostringstream& operator << (csv::ostringstream& ostm, const T& val)
{
if (!ostm.get_after_newline())
ostm.get_ostringstream() << ostm.get_delimiter();
std::ostringstream os_temp;
os_temp << val;
ostm.escape_and_output(os_temp.str());
ostm.set_after_newline(false);
return ostm;
}
template<typename T>
csv::ostringstream& operator << (csv::ostringstream& ostm, const T* val)
{
if (!ostm.get_after_newline())
ostm.get_ostringstream() << ostm.get_delimiter();
std::ostringstream os_temp;
os_temp << val;
ostm.escape_and_output(os_temp.str());
ostm.set_after_newline(false);
return ostm;
}
template<>
inline csv::ostringstream& operator << (csv::ostringstream& ostm, const std::string& val)
{
if (!ostm.get_after_newline())
ostm.get_ostringstream() << ostm.get_delimiter();
std::string temp = val;
ostm.escape_str_and_output(temp);
ostm.set_after_newline(false);
return ostm;
}
template<>
inline csv::ostringstream& operator << (csv::ostringstream& ostm, const char& val)
{
if (val == NEWLINE)
{
ostm.get_ostringstream() << NEWLINE;
ostm.set_after_newline(true);
}
else
{
std::ostringstream os_temp;
os_temp << val;
ostm.escape_and_output(os_temp.str());
}
return ostm;
}
template<>
inline csv::ostringstream& operator << (csv::ostringstream& ostm, const char* val)
{
const std::string temp = val;
ostm << temp;
return ostm;
}
#endif // MiniCSV_H

17
ext/mstch/CMakeLists.txt Normal file
View file

@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.0.2)
project(mstch)
option(WITH_UNIT_TESTS "enable building unit test executable" OFF)
option(WITH_BENCHMARK "enable building benchmark executable" OFF)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON)
set(CMAKE_BUILD_TYPE Debug)
set(mstch_VERSION 1.0.1)
if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -O3")
endif()
add_subdirectory(src)

22
ext/mstch/LICENSE Normal file
View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Daniel Sipka
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.

281
ext/mstch/README.md Normal file
View file

@ -0,0 +1,281 @@
# mstch - {{mustache}} templates in C++11
![mstch logo](http://i.imgur.com/MRyStO5.png)
mstch is a complete implementation of [{{mustache}}](http://mustache.github.io/)
templates using modern C++. It's compliant with [specifications](https://github.com/mustache/spec)
v1.1.3, including the lambda module.
[![Try it online](https://img.shields.io/badge/try%20it-online-blue.svg)](http://melpon.org/wandbox/permlink/EqyOe7IBRYPGVk5f)
[![GitHub version](https://badge.fury.io/gh/no1msd%2Fmstch.svg)](http://badge.fury.io/gh/no1msd%2Fmstch)
[![Build Status](https://travis-ci.org/no1msd/mstch.svg?branch=master)](https://travis-ci.org/no1msd/mstch)
[![Build status](https://ci.appveyor.com/api/projects/status/d6mxp0uba5646x16?svg=true)](https://ci.appveyor.com/project/no1msd/mstch)
## Supported features
mstch supports the complete feature set described in the `mustache(5)` [manpage](http://mustache.github.com/mustache.5.html):
- JSON-like data structure using [Boost.Variant](http://www.boost.org/libs/variant)
- variables, sections, inverted sections
- partials
- changing the delimiter
- C++11 lambdas
- C++ objects as view models
## Basic usage
```c++
#include <iostream>
#include <mstch/mstch.hpp>
int main() {
std::string view{"{{#names}}Hi {{name}}!\n{{/names}}"};
mstch::map context{
{"names", mstch::array{
mstch::map{{"name", std::string{"Chris"}}},
mstch::map{{"name", std::string{"Mark"}}},
mstch::map{{"name", std::string{"Scott"}}},
}}
};
std::cout << mstch::render(view, context) << std::endl;
return 0;
}
```
The output of this example will be:
```html
Hi Chris!
Hi Mark!
Hi Scott!
```
### Data structure
The types in the example above, `mstch::array` and `mstch::map` are actually
aliases for standard types:
```c++
using map = std::map<const std::string, node>;
using array = std::vector<node>;
```
`mstch::node` is a `boost::variant` that can hold a `std::string`, `int`,
`double`, `bool`, `mstch::lambda` or a `std::shared_ptr<mstch::object>`
(see below), also a map or an array recursively. Essentially it works just like
a JSON object.
Note that when using a `std::string` as value you must explicitly specify the
type, since a `const char*` literal like `"foobar"` would be implicitly
converted to `bool`. Alternatively you can use [C++14 string_literals](http://en.cppreference.com/w/cpp/string/basic_string/operator%22%22s)
if your compiler supports it.
## Advanced usage
### Partials
Partials can be passed in a `std::map` as the third parameter of the
`mstch::render` function:
```c++
std::string view{"{{#names}}{{> user}}{{/names}}"};
std::string user_view{"<strong>{{name}}\n</strong>"};
mstch::map context{
{"names", mstch::array{
mstch::map{{"name", std::string{"Chris"}}},
mstch::map{{"name", std::string{"Mark"}}},
mstch::map{{"name", std::string{"Scott"}}},
}}
};
std::cout << mstch::render(view, context, {{"user", user_view}}) << std::endl;
```
Output:
```html
<strong>Chris</strong>
<strong>Mark</strong>
<strong>Scott</strong>
```
### Lambdas
C++11 lambda expressions can be used to add logic to your templates. Like a
`const char*` literal, lambdas can be implicitly converted to `bool`, so they
must be wrapped in a `mstch::lambda` object when used in a `mstch::node`. The
lambda expression passed to `mstch::lambda` must itself return a `mstch::node`.
The returned node will be rendered to a string, then it will be parsed as a
template.
The lambda expression accepts either no parameters:
```c++
std::string view{"Hello {{lambda}}!"};
mstch::map context{
{"lambda", mstch::lambda{[]() -> mstch::node {
return std::string{"World"};
}}}
};
std::cout << mstch::render(view, context) << std::endl;
```
Output:
```html
Hello World!
```
Or it accepts a `const std::string&` that gets the unrendered literal block:
```c++
std::string view{"{{#bold}}{{yay}} :){{/bold}}"};
mstch::map context{
{"yay", std::string{"Yay!"}},
{"bold", mstch::lambda{[](const std::string& text) -> mstch::node {
return "<b>" + text + "</b>";
}}}
};
std::cout << mstch::render(view, context) << std::endl;
```
Output:
```html
<b>Yay! :)</b>
```
### Objects
Custom objects can also be used as context for rendering templates. The class
must inherit from `mstch::object`, and register it's exported methods with
`register_methods`. Exported methods must have the return type of `mstch::node`.
Objects must be created as a `std::shared_ptr`.
```c++
class example: public mstch::object {
public:
example(): m_value(1) {
register_methods(this, {
{"count", &example::count},
{"names", &example::names}
});
}
mstch::node count() {
return m_value++;
}
mstch::node names() {
return mstch::array{
std::string{"Chris"}, std::string{"Mark"}, std::string{"Scott"}};
}
private:
int m_value;
};
std::string view{"{{#names}}<b>{{count}}</b>: {{.}}\n{{/names}}"};
const auto context = std::make_shared<example>();
std::cout << mstch::render(view, context) << std::endl;
```
Output:
```html
<b>1</b>: Chris
<b>2</b>: Mark
<b>3</b>: Scott
```
### Custom escape function
By default, mstch uses HTML escaping on the output, as per specification. This
is not useful if your output is not HTML, so mstch provides a way to supply
your own escape implementation. Just assign any callable object to the static
`mstch::config::escape`, which is an initially empty
`std::function<std::string(const std::string&)>`.
For example you can turn off escaping entirely with a lambda:
```c++
mstch::config::escape = [](const std::string& str) -> std::string {
return str;
};
```
## Requirements
- A C++ compiler with decent C++11 support. Currently tested with:
- GCC 4.7, 4.8, 4.9, 5.1
- clang 3.5, 3.6, 3.7 (both libstdc++ and libc++ are supported)
- MSVC 2013, 2015
- Boost 1.54+ for [Boost.Variant](http://www.boost.org/libs/variant)
- CMake 3.0+ for building
## Using mstch in your project
If you are using CMake, the easiest way to include mstch in your project is to
copy the whole directory to your source tree, and use `add_subdirectory` in your
CMakeLists.txt. This will set a variable named `mstch_INCLUDE_DIR` that contains
its include path, and add a static library target named `mstch`. For example:
```cmake
add_subdirectory(external/mstch)
include_directories(${mstch_INCLUDE_DIR})
target_link_libraries(your_project mstch)
```
If you prefer to install the library globally, you can simply do the following
from the root of the source tree:
```bash
$ mkdir build
$ cd build
$ cmake ..
$ make
$ make install
```
The install command may require root privileges. This will also install CMake
config files, so you can use use `find_package` in your CMakeLists.txt:
```cmake
find_package(mstch)
target_link_libraries(your_project mstch::mstch)
```
## Running the unit tests
Unit tests are using the [Catch](https://github.com/philsquared/Catch) framework
and [rapidjson](http://rapidjson.org/) to parse the
[Mustache specifications](https://github.com/mustache/spec), all of which are
included in the repository as git submodules. Various
[Boost](http://www.boost.org/) libraries are also required to build them.
Don't forget to initialize submodules:
```bash
$ git submodule init
$ git submodule update
```
To build and run the unit tests:
```bash
$ mkdir build
$ cd build
$ cmake -DWITH_UNIT_TESTS=ON ..
$ make
$ make test
```
## License
mstch is licensed under the [MIT license](https://github.com/no1msd/mstch/blob/master/LICENSE).

View file

@ -0,0 +1 @@
include("${CMAKE_CURRENT_LIST_DIR}/mstch-targets.cmake")

View file

@ -0,0 +1,113 @@
#pragma once
#include <vector>
#include <map>
#include <string>
#include <memory>
#include <functional>
#include <boost/variant.hpp>
namespace mstch {
struct config {
static std::function<std::string(const std::string&)> escape;
};
namespace internal {
template<class N>
class object_t {
public:
const N& at(const std::string& name) const {
cache[name] = (methods.at(name))();
return cache[name];
}
bool has(const std::string name) const {
return methods.count(name) != 0;
}
protected:
template<class S>
void register_methods(S* s, std::map<std::string,N(S::*)()> methods) {
for(auto& item: methods)
this->methods.insert({item.first, std::bind(item.second, s)});
}
private:
std::map<std::string, std::function<N()>> methods;
mutable std::map<std::string, N> cache;
};
template<class T, class N>
class is_fun {
private:
using not_fun = char;
using fun_without_args = char[2];
using fun_with_args = char[3];
template <typename U, U> struct really_has;
template <typename C> static fun_without_args& test(
really_has<N(C::*)() const, &C::operator()>*);
template <typename C> static fun_with_args& test(
really_has<N(C::*)(const std::string&) const,
&C::operator()>*);
template <typename> static not_fun& test(...);
public:
static bool const no_args = sizeof(test<T>(0)) == sizeof(fun_without_args);
static bool const has_args = sizeof(test<T>(0)) == sizeof(fun_with_args);
};
template<class N>
using node_renderer = std::function<std::string(const N& n)>;
template<class N>
class lambda_t {
public:
template<class F>
lambda_t(F f, typename std::enable_if<is_fun<F, N>::no_args>::type* = 0):
fun([f](node_renderer<N> renderer, const std::string&) {
return renderer(f());
})
{
}
template<class F>
lambda_t(F f, typename std::enable_if<is_fun<F, N>::has_args>::type* = 0):
fun([f](node_renderer<N> renderer, const std::string& text) {
return renderer(f(text));
})
{
}
std::string operator()(node_renderer<N> renderer,
const std::string& text = "") const
{
return fun(renderer, text);
}
private:
std::function<std::string(node_renderer<N> renderer, const std::string&)> fun;
};
}
using node = boost::make_recursive_variant<
std::nullptr_t, std::string, int, double, bool,
internal::lambda_t<boost::recursive_variant_>,
std::shared_ptr<internal::object_t<boost::recursive_variant_>>,
std::map<const std::string, boost::recursive_variant_>,
std::vector<boost::recursive_variant_>>::type;
using object = internal::object_t<node>;
using lambda = internal::lambda_t<node>;
using map = std::map<const std::string, node>;
using array = std::vector<node>;
std::string render(
const std::string& tmplt,
const node& root,
const std::map<std::string,std::string>& partials =
std::map<std::string,std::string>());
}

View file

@ -0,0 +1,66 @@
find_package(Boost 1.54 REQUIRED)
set(mstch_INCLUDE_DIR
${PROJECT_SOURCE_DIR}/include CACHE STRING "mstch include directory")
# /home/mwo/crow-monero-test/ext/mstch
message(${PROJECT_SOURCE_DIR})
#
include_directories(
${Boost_INCLUDE_DIR})
set(SRC
state/in_section.cpp
state/outside_section.cpp
state/render_state.hpp
visitor/get_token.hpp
visitor/has_token.hpp
visitor/is_node_empty.hpp
visitor/render_node.hpp
visitor/render_section.hpp
mstch.cpp
render_context.cpp
template_type.cpp
token.cpp
utils.cpp)
add_library(mstch STATIC ${SRC})
#
set_property(TARGET mstch PROPERTY VERSION ${mstch_VERSION})
#
#install(
# TARGETS mstch EXPORT mstchTargets
# LIBRARY DESTINATION lib
# ARCHIVE DESTINATION lib)
#
#install(
# FILES "${PROJECT_SOURCE_DIR}/include/mstch/mstch.hpp"
# DESTINATION include/mstch
# COMPONENT Devel)
#
#include(CMakePackageConfigHelpers)
#write_basic_package_version_file(
# "${CMAKE_CURRENT_BINARY_DIR}/mstch/mstch-config-version.cmake"
# VERSION ${mstch_VERSION}
# COMPATIBILITY AnyNewerVersion)
#
#export(
# EXPORT mstchTargets
# FILE "${CMAKE_CURRENT_BINARY_DIR}/mstch/mstch-targets.cmake"
# NAMESPACE mstch::)
#
#configure_file(
# "${PROJECT_SOURCE_DIR}/cmake/mstch-config.cmake"
# "${CMAKE_CURRENT_BINARY_DIR}/mstch/mstch-config.cmake")
#
#install(
# EXPORT mstchTargets
# FILE mstch-targets.cmake
# NAMESPACE mstch::
# DESTINATION lib/cmake/mstch)
#
#install(FILES
# "${PROJECT_SOURCE_DIR}/cmake/mstch-config.cmake"
# "${CMAKE_CURRENT_BINARY_DIR}/mstch/mstch-config-version.cmake"
# DESTINATION lib/cmake/mstch
# COMPONENT Devel)

20
ext/mstch/src/mstch.cpp Normal file
View file

@ -0,0 +1,20 @@
#include <iostream>
#include "mstch/mstch.hpp"
#include "render_context.hpp"
using namespace mstch;
std::function<std::string(const std::string&)> mstch::config::escape;
std::string mstch::render(
const std::string& tmplt,
const node& root,
const std::map<std::string,std::string>& partials)
{
std::map<std::string, template_type> partial_templates;
for (auto& partial: partials)
partial_templates.insert({partial.first, {partial.second}});
return render_context(root, partial_templates).render(tmplt);
}

View file

@ -0,0 +1,72 @@
#include "render_context.hpp"
#include "state/outside_section.hpp"
#include "visitor/get_token.hpp"
using namespace mstch;
const mstch::node render_context::null_node;
render_context::push::push(render_context& context, const mstch::node& node):
m_context(context)
{
context.m_nodes.emplace_front(node);
context.m_node_ptrs.emplace_front(&node);
context.m_state.push(std::unique_ptr<render_state>(new outside_section));
}
render_context::push::~push() {
m_context.m_nodes.pop_front();
m_context.m_node_ptrs.pop_front();
m_context.m_state.pop();
}
std::string render_context::push::render(const template_type& templt) {
return m_context.render(templt);
}
render_context::render_context(
const mstch::node& node,
const std::map<std::string, template_type>& partials):
m_partials(partials), m_nodes(1, node), m_node_ptrs(1, &node)
{
m_state.push(std::unique_ptr<render_state>(new outside_section));
}
const mstch::node& render_context::find_node(
const std::string& token,
std::list<node const*> current_nodes)
{
if (token != "." && token.find('.') != std::string::npos)
return find_node(token.substr(token.rfind('.') + 1),
{&find_node(token.substr(0, token.rfind('.')), current_nodes)});
else
for (auto& node: current_nodes)
if (visit(has_token(token), *node))
return visit(get_token(token, *node), *node);
return null_node;
}
const mstch::node& render_context::get_node(const std::string& token) {
return find_node(token, m_node_ptrs);
}
std::string render_context::render(
const template_type& templt, const std::string& prefix)
{
std::string output;
bool prev_eol = true;
for (auto& token: templt) {
if (prev_eol && prefix.length() != 0)
output += m_state.top()->render(*this, {prefix});
output += m_state.top()->render(*this, token);
prev_eol = token.eol();
}
return output;
}
std::string render_context::render_partial(
const std::string& partial_name, const std::string& prefix)
{
return m_partials.count(partial_name) ?
render(m_partials.at(partial_name), prefix) : "";
}

View file

@ -0,0 +1,51 @@
#pragma once
#include <deque>
#include <list>
#include <sstream>
#include <string>
#include <stack>
#include "mstch/mstch.hpp"
#include "state/render_state.hpp"
#include "template_type.hpp"
namespace mstch {
class render_context {
public:
class push {
public:
push(render_context& context, const mstch::node& node = {});
~push();
std::string render(const template_type& templt);
private:
render_context& m_context;
};
render_context(
const mstch::node& node,
const std::map<std::string, template_type>& partials);
const mstch::node& get_node(const std::string& token);
std::string render(
const template_type& templt, const std::string& prefix = "");
std::string render_partial(
const std::string& partial_name, const std::string& prefix);
template<class T, class... Args>
void set_state(Args&& ... args) {
m_state.top() = std::unique_ptr<render_state>(
new T(std::forward<Args>(args)...));
}
private:
static const mstch::node null_node;
const mstch::node& find_node(
const std::string& token,
std::list<node const*> current_nodes);
std::map<std::string, template_type> m_partials;
std::deque<mstch::node> m_nodes;
std::list<const mstch::node*> m_node_ptrs;
std::stack<std::unique_ptr<render_state>> m_state;
};
}

View file

@ -0,0 +1,34 @@
#include "in_section.hpp"
#include "outside_section.hpp"
#include "visitor/is_node_empty.hpp"
#include "visitor/render_section.hpp"
using namespace mstch;
in_section::in_section(type type, const token& start_token):
m_type(type), m_start_token(start_token), m_skipped_openings(0)
{
}
std::string in_section::render(render_context& ctx, const token& token) {
if (token.token_type() == token::type::section_close)
if (token.name() == m_start_token.name() && m_skipped_openings == 0) {
auto& node = ctx.get_node(m_start_token.name());
std::string out;
if (m_type == type::normal && !visit(is_node_empty(), node))
out = visit(render_section(ctx, m_section, m_start_token.delims()), node);
else if (m_type == type::inverted && visit(is_node_empty(), node))
out = render_context::push(ctx).render(m_section);
ctx.set_state<outside_section>();
return out;
} else
m_skipped_openings--;
else if (token.token_type() == token::type::inverted_section_open ||
token.token_type() == token::type::section_open)
m_skipped_openings++;
m_section << token;
return "";
}

View file

@ -0,0 +1,24 @@
#pragma once
#include <sstream>
#include <vector>
#include "render_state.hpp"
#include "template_type.hpp"
namespace mstch {
class in_section: public render_state {
public:
enum class type { inverted, normal };
in_section(type type, const token& start_token);
std::string render(render_context& context, const token& token) override;
private:
const type m_type;
const token& m_start_token;
template_type m_section;
int m_skipped_openings;
};
}

View file

@ -0,0 +1,32 @@
#include "outside_section.hpp"
#include "visitor/render_node.hpp"
#include "in_section.hpp"
#include "render_context.hpp"
using namespace mstch;
std::string outside_section::render(
render_context& ctx, const token& token)
{
using flag = render_node::flag;
switch (token.token_type()) {
case token::type::section_open:
ctx.set_state<in_section>(in_section::type::normal, token);
break;
case token::type::inverted_section_open:
ctx.set_state<in_section>(in_section::type::inverted, token);
break;
case token::type::variable:
return visit(render_node(ctx, flag::escape_html), ctx.get_node(token.name()));
case token::type::unescaped_variable:
return visit(render_node(ctx, flag::none), ctx.get_node(token.name()));
case token::type::text:
return token.raw();
case token::type::partial:
return ctx.render_partial(token.name(), token.partial_prefix());
default:
break;
}
return "";
}

View file

@ -0,0 +1,12 @@
#pragma once
#include "render_state.hpp"
namespace mstch {
class outside_section: public render_state {
public:
std::string render(render_context& context, const token& token) override;
};
}

View file

@ -0,0 +1,17 @@
#pragma once
#include <memory>
#include "token.hpp"
namespace mstch {
class render_context;
class render_state {
public:
virtual ~render_state() {}
virtual std::string render(render_context& context, const token& token) = 0;
};
}

View file

@ -0,0 +1,104 @@
#include "template_type.hpp"
using namespace mstch;
template_type::template_type(const std::string& str, const delim_type& delims):
m_open(delims.first), m_close(delims.second)
{
tokenize(str);
strip_whitespace();
}
template_type::template_type(const std::string& str):
m_open("{{"), m_close("}}")
{
tokenize(str);
strip_whitespace();
}
void template_type::process_text(citer begin, citer end) {
if (begin == end)
return;
auto start = begin;
for (auto it = begin; it != end; ++it)
if (*it == '\n' || it == end - 1) {
m_tokens.push_back({{start, it + 1}});
start = it + 1;
}
}
void template_type::tokenize(const std::string& tmp) {
citer beg = tmp.begin();
auto npos = std::string::npos;
for (std::size_t cur_pos = 0; cur_pos < tmp.size();) {
auto open_pos = tmp.find(m_open, cur_pos);
auto close_pos = tmp.find(
m_close, open_pos == npos ? open_pos : open_pos + 1);
if (close_pos != npos && open_pos != npos) {
if (*(beg + open_pos + m_open.size()) == '{' &&
*(beg + close_pos + m_close.size()) == '}')
++close_pos;
process_text(beg + cur_pos, beg + open_pos);
cur_pos = close_pos + m_close.size();
m_tokens.push_back({{beg + open_pos, beg + close_pos + m_close.size()},
m_open.size(), m_close.size()});
if (cur_pos == tmp.size()) {
m_tokens.push_back({{""}});
m_tokens.back().eol(true);
}
if (*(beg + open_pos + m_open.size()) == '=' &&
*(beg + close_pos - 1) == '=')
{
auto tok_beg = beg + open_pos + m_open.size() + 1;
auto tok_end = beg + close_pos - 1;
auto front_skip = first_not_ws(tok_beg, tok_end);
auto back_skip = first_not_ws(reverse(tok_end), reverse(tok_beg));
m_open = {front_skip, beg + tmp.find(' ', front_skip - beg)};
m_close = {beg + tmp.rfind(' ', back_skip - beg) + 1, back_skip + 1};
}
} else {
process_text(beg + cur_pos, tmp.end());
cur_pos = close_pos;
}
}
}
void template_type::strip_whitespace() {
auto line_begin = m_tokens.begin();
bool has_tag = false, non_space = false;
for (auto it = m_tokens.begin(); it != m_tokens.end(); ++it) {
auto type = (*it).token_type();
if (type != token::type::text && type != token::type::variable &&
type != token::type::unescaped_variable)
has_tag = true;
else if (!(*it).ws_only())
non_space = true;
if ((*it).eol()) {
if (has_tag && !non_space) {
store_prefixes(line_begin);
auto c = line_begin;
for (bool end = false; !end; (*c).ws_only() ? c = m_tokens.erase(c) : ++c)
if ((end = (*c).eol()))
it = c - 1;
}
non_space = has_tag = false;
line_begin = it + 1;
}
}
}
void template_type::store_prefixes(std::vector<token>::iterator beg) {
for (auto cur = beg; !(*cur).eol(); ++cur)
if ((*cur).token_type() == token::type::partial &&
cur != beg && (*(cur - 1)).ws_only())
(*cur).partial_prefix((*(cur - 1)).raw());
}

View file

@ -0,0 +1,30 @@
#pragma once
#include <string>
#include <vector>
#include "token.hpp"
#include "utils.hpp"
namespace mstch {
class template_type {
public:
template_type() = default;
template_type(const std::string& str);
template_type(const std::string& str, const delim_type& delims);
std::vector<token>::const_iterator begin() const { return m_tokens.begin(); }
std::vector<token>::const_iterator end() const { return m_tokens.end(); }
void operator<<(const token& token) { m_tokens.push_back(token); }
private:
std::vector<token> m_tokens;
std::string m_open;
std::string m_close;
void strip_whitespace();
void process_text(citer beg, citer end);
void tokenize(const std::string& tmp);
void store_prefixes(std::vector<token>::iterator beg);
};
}

42
ext/mstch/src/token.cpp Normal file
View file

@ -0,0 +1,42 @@
#include "token.hpp"
#include "utils.hpp"
using namespace mstch;
token::type token::token_info(char c) {
switch (c) {
case '>': return type::partial;
case '^': return type::inverted_section_open;
case '/': return type::section_close;
case '&': return type::unescaped_variable;
case '#': return type::section_open;
case '!': return type::comment;
default: return type::variable;
}
}
token::token(const std::string& str, std::size_t left, std::size_t right):
m_raw(str), m_eol(false), m_ws_only(false)
{
if (left != 0 && right != 0) {
if (str[left] == '=' && str[str.size() - right - 1] == '=') {
m_type = type::delimiter_change;
} else if (str[left] == '{' && str[str.size() - right - 1] == '}') {
m_type = type::unescaped_variable;
m_name = {first_not_ws(str.begin() + left + 1, str.end() - right),
first_not_ws(str.rbegin() + 1 + right, str.rend() - left) + 1};
} else {
auto c = first_not_ws(str.begin() + left, str.end() - right);
m_type = token_info(*c);
if (m_type != type::variable)
c = first_not_ws(c + 1, str.end() - right);
m_name = {c, first_not_ws(str.rbegin() + right, str.rend() - left) + 1};
m_delims = {{str.begin(), str.begin() + left},
{str.end() - right, str.end()}};
}
} else {
m_type = type::text;
m_eol = (str.size() > 0 && str[str.size() - 1] == '\n');
m_ws_only = (str.find_first_not_of(" \r\n\t") == std::string::npos);
}
}

39
ext/mstch/src/token.hpp Normal file
View file

@ -0,0 +1,39 @@
#pragma once
#include <string>
namespace mstch {
using delim_type = std::pair<std::string, std::string>;
class token {
public:
enum class type {
text, variable, section_open, section_close, inverted_section_open,
unescaped_variable, comment, partial, delimiter_change
};
token(const std::string& str, std::size_t left = 0, std::size_t right = 0);
type token_type() const { return m_type; };
const std::string& raw() const { return m_raw; };
const std::string& name() const { return m_name; };
const std::string& partial_prefix() const { return m_partial_prefix; };
const delim_type& delims() const { return m_delims; };
void partial_prefix(const std::string& p_partial_prefix) {
m_partial_prefix = p_partial_prefix;
};
bool eol() const { return m_eol; }
void eol(bool eol) { m_eol = eol; }
bool ws_only() const { return m_ws_only; }
private:
type m_type;
std::string m_name;
std::string m_raw;
std::string m_partial_prefix;
delim_type m_delims;
bool m_eol;
bool m_ws_only;
type token_info(char c);
};
}

44
ext/mstch/src/utils.cpp Normal file
View file

@ -0,0 +1,44 @@
#include "utils.hpp"
#include "mstch/mstch.hpp"
mstch::citer mstch::first_not_ws(mstch::citer begin, mstch::citer end) {
for (auto it = begin; it != end; ++it)
if (*it != ' ') return it;
return end;
}
mstch::citer mstch::first_not_ws(mstch::criter begin, mstch::criter end) {
for (auto rit = begin; rit != end; ++rit)
if (*rit != ' ') return --(rit.base());
return --(end.base());
}
mstch::criter mstch::reverse(mstch::citer it) {
return std::reverse_iterator<mstch::citer>(it);
}
std::string mstch::html_escape(const std::string& str) {
if (mstch::config::escape)
return mstch::config::escape(str);
std::string out;
citer start = str.begin();
auto add_escape = [&out, &start](const std::string& escaped, citer& it) {
out += std::string{start, it} + escaped;
start = it + 1;
};
for (auto it = str.begin(); it != str.end(); ++it)
switch (*it) {
case '&': add_escape("&amp;", it); break;
case '\'': add_escape("&#39;", it); break;
case '"': add_escape("&quot;", it); break;
case '<': add_escape("&lt;", it); break;
case '>': add_escape("&gt;", it); break;
case '/': add_escape("&#x2F;", it); break;
default: break;
}
return out + std::string{start, str.end()};
}

23
ext/mstch/src/utils.hpp Normal file
View file

@ -0,0 +1,23 @@
#pragma once
#include <string>
#include <boost/variant/apply_visitor.hpp>
namespace mstch {
using citer = std::string::const_iterator;
using criter = std::string::const_reverse_iterator;
citer first_not_ws(citer begin, citer end);
citer first_not_ws(criter begin, criter end);
std::string html_escape(const std::string& str);
criter reverse(citer it);
template<class... Args>
auto visit(Args&&... args) -> decltype(boost::apply_visitor(
std::forward<Args>(args)...))
{
return boost::apply_visitor(std::forward<Args>(args)...);
}
}

View file

@ -0,0 +1,35 @@
#pragma once
#include <boost/variant/static_visitor.hpp>
#include "mstch/mstch.hpp"
#include "has_token.hpp"
namespace mstch {
class get_token: public boost::static_visitor<const mstch::node&> {
public:
get_token(const std::string& token, const mstch::node& node):
m_token(token), m_node(node)
{
}
template<class T>
const mstch::node& operator()(const T&) const {
return m_node;
}
const mstch::node& operator()(const map& map) const {
return map.at(m_token);
}
const mstch::node& operator()(const std::shared_ptr<object>& object) const {
return object->at(m_token);
}
private:
const std::string& m_token;
const mstch::node& m_node;
};
}

View file

@ -0,0 +1,31 @@
#pragma once
#include <boost/variant/static_visitor.hpp>
#include "mstch/mstch.hpp"
namespace mstch {
class has_token: public boost::static_visitor<bool> {
public:
has_token(const std::string& token): m_token(token) {
}
template<class T>
bool operator()(const T&) const {
return m_token == ".";
}
bool operator()(const map& map) const {
return map.count(m_token) == 1;
}
bool operator()(const std::shared_ptr<object>& object) const {
return object->has(m_token);
}
private:
const std::string& m_token;
};
}

View file

@ -0,0 +1,41 @@
#pragma once
#include <boost/variant/static_visitor.hpp>
#include "mstch/mstch.hpp"
namespace mstch {
class is_node_empty: public boost::static_visitor<bool> {
public:
template<class T>
bool operator()(const T&) const {
return false;
}
bool operator()(const std::nullptr_t&) const {
return true;
}
bool operator()(const int& value) const {
return value == 0;
}
bool operator()(const double& value) const {
return value == 0;
}
bool operator()(const bool& value) const {
return !value;
}
bool operator()(const std::string& value) const {
return value == "";
}
bool operator()(const array& array) const {
return array.size() == 0;
}
};
}

View file

@ -0,0 +1,56 @@
#pragma once
#include <sstream>
#include <boost/variant/static_visitor.hpp>
#include "render_context.hpp"
#include "mstch/mstch.hpp"
#include "utils.hpp"
namespace mstch {
class render_node: public boost::static_visitor<std::string> {
public:
enum class flag { none, escape_html };
render_node(render_context& ctx, flag p_flag = flag::none):
m_ctx(ctx), m_flag(p_flag)
{
}
template<class T>
std::string operator()(const T&) const {
return "";
}
std::string operator()(const int& value) const {
return std::to_string(value);
}
std::string operator()(const double& value) const {
std::stringstream ss;
ss << value;
return ss.str();
}
std::string operator()(const bool& value) const {
return value ? "true" : "false";
}
std::string operator()(const lambda& value) const {
template_type interpreted{value([this](const mstch::node& n) {
return visit(render_node(m_ctx), n);
})};
auto rendered = render_context::push(m_ctx).render(interpreted);
return (m_flag == flag::escape_html) ? html_escape(rendered) : rendered;
}
std::string operator()(const std::string& value) const {
return (m_flag == flag::escape_html) ? html_escape(value) : value;
}
private:
render_context& m_ctx;
flag m_flag;
};
}

View file

@ -0,0 +1,57 @@
#pragma once
#include <boost/variant/static_visitor.hpp>
#include "render_context.hpp"
#include "mstch/mstch.hpp"
#include "utils.hpp"
#include "render_node.hpp"
namespace mstch {
class render_section: public boost::static_visitor<std::string> {
public:
enum class flag { none, keep_array };
render_section(
render_context& ctx,
const template_type& section,
const delim_type& delims,
flag p_flag = flag::none):
m_ctx(ctx), m_section(section), m_delims(delims), m_flag(p_flag)
{
}
template<class T>
std::string operator()(const T& t) const {
return render_context::push(m_ctx, t).render(m_section);
}
std::string operator()(const lambda& fun) const {
std::string section_str;
for (auto& token: m_section)
section_str += token.raw();
template_type interpreted{fun([this](const mstch::node& n) {
return visit(render_node(m_ctx), n);
}, section_str), m_delims};
return render_context::push(m_ctx).render(interpreted);
}
std::string operator()(const array& array) const {
std::string out;
if (m_flag == flag::keep_array)
return render_context::push(m_ctx, array).render(m_section);
else
for (auto& item: array)
out += visit(render_section(
m_ctx, m_section, m_delims, flag::keep_array), item);
return out;
}
private:
render_context& m_ctx;
const template_type& m_section;
const delim_type& m_delims;
flag m_flag;
};
}

42
main.cpp Normal file
View file

@ -0,0 +1,42 @@
#include "src/MicroCore.h"
#include "ext/crow/crow.h"
#include "mstch/mstch.hpp"
#include "ext/format.h"
#include <iostream>
using boost::filesystem::path;
using namespace std;
int main() {
path blockchain_path {"/home/mwo/.bitmonero/lmdb"};
fmt::print("Blockchain path : {}\n", blockchain_path);
std::string view{"{{#names}}Hi {{name}}!\n{{/names}}"};
mstch::map context{
{"names", mstch::array{
mstch::map{{"name", std::string{"Chris"}}},
mstch::map{{"name", std::string{"Mark"}}},
mstch::map{{"name", std::string{"Scott"}}},
}}
};
crow::SimpleApp app;
CROW_ROUTE(app, "/")
([&]() {
return mstch::render(view, context);
});
app.port(8080).multithreaded().run();
return 0;
}

22
src/CMakeLists.txt Normal file
View file

@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.5)
project(myxrm)
set(SOURCE_HEADERS
MicroCore.h
tools.h
monero_headers.h
tx_details.h)
set(SOURCE_FILES
MicroCore.cpp
tools.cpp
CmdLineOptions.cpp
tx_details.cpp)
# make static library called libmyxrm
# that we are going to link to
# in the root CMakeLists.txt file
add_library(myxrm
STATIC
${SOURCE_FILES})

80
src/CmdLineOptions.cpp Normal file
View file

@ -0,0 +1,80 @@
//
// Created by mwo on 6/11/15.
//
#include "CmdLineOptions.h"
namespace xmreg
{
/**
* Take the acc and *avv[] from the main() and check and parse
* all the options given
*/
CmdLineOptions::CmdLineOptions(int acc, const char *avv[]) {
positional_options_description p;
p.add("txhash", -1);
options_description desc(
"showmixins, shows mixin outputs used for each input in a given transaction");
desc.add_options()
("help,h", value<bool>()->default_value(false)->implicit_value(true),
"produce help message")
("txhash,t", value<string>(),
"transaction hash")
("viewkey,v", value<string>(),
"private view key string")
("address,a", value<string>(),
"monero address string")
("bc-path,b", value<string>(),
"path to lmdb blockchain")
("testnet", value<bool>()->default_value(false)->implicit_value(true),
"is the address from testnet network");
store(command_line_parser(acc, avv)
.options(desc)
.positional(p)
.run(), vm);
notify(vm);
if (vm.count("help"))
{
if (vm["help"].as<bool>())
cout << desc << "\n";
}
}
/**
* Return the value of the argument passed to the program
* in wrapped around boost::optional
*/
template<typename T>
boost::optional<T>
CmdLineOptions::get_option(const string & opt_name) const
{
if (!vm.count(opt_name))
{
return boost::none;
}
return vm[opt_name].as<T>();
}
// explicit instantiations of get_option template function
template boost::optional<string>
CmdLineOptions::get_option<string>(const string & opt_name) const;
template boost::optional<bool>
CmdLineOptions::get_option<bool>(const string & opt_name) const;
template boost::optional<size_t>
CmdLineOptions::get_option<size_t>(const string & opt_name) const;
}

37
src/CmdLineOptions.h Normal file
View file

@ -0,0 +1,37 @@
//
// Created by mwo on 6/11/15.
//
#ifndef XMREG01_CMDLINEOPTIONS_H
#define XMREG01_CMDLINEOPTIONS_H
#include <iostream>
#include <string>
#include <boost/program_options.hpp>
#include <boost/optional.hpp>
namespace xmreg
{
using namespace std;
using namespace boost::program_options;
/**
* Manages program options of this example program.
*
* Basically a wrapper for boost::program_options
*/
class CmdLineOptions {
variables_map vm;
public:
CmdLineOptions(int acc, const char *avv[]);
template<typename T>
boost::optional<T> get_option(const string & opt_name) const;
};
}
#endif //XMREG01_CMDLINEOPTIONS_H

276
src/MicroCore.cpp Normal file
View file

@ -0,0 +1,276 @@
//
// Created by mwo on 5/11/15.
//
#include "MicroCore.h"
namespace xmreg
{
/**
* The constructor is interesting, as
* m_mempool and m_blockchain_storage depend
* on each other.
*
* So basically m_mempool is initialized with
* reference to Blockchain (i.e., Blockchain&)
* and m_blockchain_storage is initialized with
* reference to m_mempool (i.e., tx_memory_pool&)
*
* The same is done in cryptonode::core.
*/
MicroCore::MicroCore():
m_mempool(m_blockchain_storage),
m_blockchain_storage(m_mempool)
{}
/**
* Initialized the MicroCore object.
*
* Create BlockchainLMDB on the heap.
* Open database files located in blockchain_path.
* Initialize m_blockchain_storage with the BlockchainLMDB object.
*/
bool
MicroCore::init(const string& blockchain_path)
{
int db_flags = 0;
//db_flags |= MDB_RDONLY;
//db_flags |= MDB_NOLOCK;
db_flags |= MDB_NOSYNC;
BlockchainDB* db = nullptr;
db = new BlockchainLMDB();
try
{
// try opening lmdb database files
db->open(blockchain_path, db_flags);
}
catch (const std::exception& e)
{
cerr << "Error opening database: " << e.what();
return false;
}
// check if the blockchain database
// is successful opened
if(!db->is_open())
{
return false;
}
// initialize Blockchain object to manage
// the database.
return m_blockchain_storage.init(db, false);
}
/**
* Get m_blockchain_storage.
* Initialize m_blockchain_storage with the BlockchainLMDB object.
*/
Blockchain&
MicroCore::get_core()
{
return m_blockchain_storage;
}
/**
* Get block by its height
*
* returns true if success
*/
bool
MicroCore::get_block_by_height(const uint64_t& height, block& blk)
{
crypto::hash block_id;
try
{
block_id = m_blockchain_storage.get_block_id_by_height(height);
}
catch (const exception& e)
{
cerr << e.what() << endl;
return false;
}
if (!m_blockchain_storage.get_block_by_hash(block_id, blk))
{
cerr << "Block with hash " << block_id
<< "and height " << height << " not found!"
<< endl;
return false;
}
return true;
}
/**
* Get transaction tx from the blockchain using it hash
*/
bool
MicroCore::get_tx(const crypto::hash& tx_hash, transaction& tx)
{
try
{
// get transaction with given hash
tx = m_blockchain_storage.get_db().get_tx(tx_hash);
}
catch (const exception& e)
{
cerr << e.what() << endl;
return false;
}
return true;
}
/**
* Find output with given public key in a given transaction
*/
bool
MicroCore::find_output_in_tx(const transaction& tx,
const public_key& output_pubkey,
tx_out& out,
size_t& output_index)
{
size_t idx {0};
// search in the ouputs for an output which
// public key matches to what we want
auto it = std::find_if(tx.vout.begin(), tx.vout.end(),
[&](const tx_out& o)
{
const txout_to_key& tx_in_to_key
= boost::get<txout_to_key>(o.target);
++idx;
return tx_in_to_key.key == output_pubkey;
});
if (it != tx.vout.end())
{
// we found the desired public key
out = *it;
output_index = idx > 0 ? idx - 1 : idx;
//cout << idx << " " << output_index << endl;
return true;
}
return false;
}
/**
* Returns tx hash in a given block which
* contains given output's public key
*/
bool
MicroCore::get_tx_hash_from_output_pubkey(const public_key& output_pubkey,
const uint64_t& block_height,
crypto::hash& tx_hash,
cryptonote::transaction& tx_found)
{
tx_hash = null_hash;
// get block of given height
block blk;
if (!get_block_by_height(block_height, blk))
{
cerr << "Cant get block of height: " << block_height << endl;
return false;
}
// get all transactions in the block found
// initialize the first list with transaction for solving
// the block i.e. coinbase.
list<transaction> txs {blk.miner_tx};
list<crypto::hash> missed_txs;
if (!m_blockchain_storage.get_transactions(blk.tx_hashes, txs, missed_txs))
{
cerr << "Cant find transcations in block: " << block_height << endl;
return false;
}
if (!missed_txs.empty())
{
cerr << "Transactions not found in blk: " << block_height << endl;
for (const crypto::hash& h : missed_txs)
{
cerr << " - tx hash: " << h << endl;
}
return false;
}
// search outputs in each transactions
// until output with pubkey of interest is found
for (const transaction& tx : txs)
{
tx_out found_out;
// we dont need here output_index
size_t output_index;
if (find_output_in_tx(tx, output_pubkey, found_out, output_index))
{
// we found the desired public key
tx_hash = get_transaction_hash(tx);
tx_found = tx;
return true;
}
}
return false;
}
uint64_t
MicroCore::get_blk_timestamp(uint64_t blk_height)
{
cryptonote::block blk;
if (!get_block_by_height(blk_height, blk))
{
cerr << "Cant get block by height: " << blk_height << endl;
return 0;
}
return blk.timestamp;
}
/**
* De-initialized Blockchain.
*
* since blockchain is opened as MDB_RDONLY
* need to manually free memory taken on heap
* by BlockchainLMDB
*/
MicroCore::~MicroCore()
{
delete &m_blockchain_storage.get_db();
}
}

72
src/MicroCore.h Normal file
View file

@ -0,0 +1,72 @@
//
// Created by mwo on 5/11/15.
//
#ifndef XMREG01_MICROCORE_H
#define XMREG01_MICROCORE_H
#include <iostream>
#include "monero_headers.h"
#include "tx_details.h"
namespace xmreg
{
using namespace cryptonote;
using namespace crypto;
using namespace std;
/**
* Micro version of cryptonode::core class
* Micro version of constructor,
* init and destructor are implemted.
*
* Just enough to read the blockchain
* database for use in the example.
*/
class MicroCore {
tx_memory_pool m_mempool;
Blockchain m_blockchain_storage;
public:
MicroCore();
bool
init(const string& blockchain_path);
Blockchain&
get_core();
bool
get_block_by_height(const uint64_t& height, block& blk);
bool
get_tx(const crypto::hash& tx_hash, transaction& tx);
bool
find_output_in_tx(const transaction& tx,
const public_key& output_pubkey,
tx_out& out,
size_t& output_index);
bool
get_tx_hash_from_output_pubkey(const public_key& output_pubkey,
const uint64_t& block_height,
crypto::hash& tx_hash,
transaction& tx_found);
uint64_t
get_blk_timestamp(uint64_t blk_height);
virtual ~MicroCore();
};
}
#endif //XMREG01_MICROCORE_H

22
src/monero_headers.h Normal file
View file

@ -0,0 +1,22 @@
//
// Created by mwo on 5/11/15.
//
#ifndef XMREG01_MONERO_HEADERS_H_H
#define XMREG01_MONERO_HEADERS_H_H
#define DB_LMDB 2
#define BLOCKCHAIN_DB DB_LMDB
#include "cryptonote_core/cryptonote_basic.h"
#include "cryptonote_core/blockchain_storage.h"
#include "cryptonote_core/blockchain.h"
#include "blockchain_db/lmdb/db_lmdb.h"
#include "common/base58.h"
#endif //XMREG01_MONERO_HEADERS_H_H

337
src/tools.cpp Normal file
View file

@ -0,0 +1,337 @@
//
// Created by marcin on 5/11/15.
//
#include "tools.h"
namespace xmreg
{
/**
* Parse key string, e.g., a viewkey in a string
* into crypto::secret_key or crypto::public_key
* depending on the template argument.
*/
template <typename T>
bool
parse_str_secret_key(const string& key_str, T& secret_key)
{
// hash and keys have same structure, so to parse string of
// a key, e.g., a view key, we can first parse it into the hash
// object using parse_hash256 function, and then copy the reslting
// hash data into secret key.
crypto::hash hash_;
if(!parse_hash256(key_str, hash_))
{
cerr << "Cant parse a key (e.g. viewkey): " << key_str << endl;
return false;
}
// crypto::hash and crypto::secret_key have basicly same
// structure. They both keep they key/hash as c-style char array
// of fixed size. Thus we can just copy data from hash
// to key
copy(begin(hash_.data), end(hash_.data), secret_key.data);
return true;
}
// explicit instantiations of get template function
template bool parse_str_secret_key<crypto::secret_key>(const string& key_str, crypto::secret_key& secret_key);
template bool parse_str_secret_key<crypto::public_key>(const string& key_str, crypto::public_key& secret_key);
template bool parse_str_secret_key<crypto::hash>(const string& key_str, crypto::hash& secret_key);
/**
* Get transaction tx using given tx hash. Hash is represent as string here,
* so before we can tap into the blockchain, we need to pare it into
* crypto::hash object.
*/
bool
get_tx_pub_key_from_str_hash(Blockchain& core_storage, const string& hash_str, transaction& tx)
{
crypto::hash tx_hash;
parse_hash256(hash_str, tx_hash);
try
{
// get transaction with given hash
tx = core_storage.get_db().get_tx(tx_hash);
}
catch (const TX_DNE& e)
{
cerr << e.what() << endl;
return false;
}
return true;
}
/**
* Parse monero address in a string form into
* cryptonote::account_public_address object
*/
bool
parse_str_address(const string& address_str,
account_public_address& address,
bool testnet)
{
if (!get_account_address_from_str(address, testnet, address_str))
{
cerr << "Error getting address: " << address_str << endl;
return false;
}
return true;
}
/**
* Return string representation of monero address
*/
string
print_address(const account_public_address& address, bool testnet)
{
return "<" + get_account_address_as_str(testnet, address) + ">";
}
string
print_sig (const signature& sig)
{
stringstream ss;
ss << "c: <" << epee::string_tools::pod_to_hex(sig.c) << "> "
<< "r: <" << epee::string_tools::pod_to_hex(sig.r) << ">";
return ss.str();
}
/**
* Check if a character is a path seprator
*/
inline bool
is_separator(char c)
{
// default linux path separator
const char separator = PATH_SEPARARTOR;
return c == separator;
}
/**
* Remove trailinig path separator.
*/
string
remove_trailing_path_separator(const string& in_path)
{
string new_string = in_path;
if (!new_string.empty() && is_separator(new_string[new_string.size() - 1]))
new_string.erase(new_string.size() - 1);
return new_string;
}
bf::path
remove_trailing_path_separator(const bf::path& in_path)
{
string path_str = in_path.native();
return bf::path(remove_trailing_path_separator(path_str));
}
string
timestamp_to_str(time_t timestamp, const char* format)
{
const int TIME_LENGTH = 60;
char str_buff[TIME_LENGTH];
tm *tm_ptr;
tm_ptr = localtime(&timestamp);
size_t len;
len = std::strftime(str_buff, TIME_LENGTH, format, tm_ptr);
return string(str_buff, len);
}
ostream&
operator<< (ostream& os, const account_public_address& addr)
{
os << get_account_address_as_str(false, addr);
return os;
}
/*
* Generate key_image of foran ith output
*/
bool
generate_key_image(const crypto::key_derivation& derivation,
const std::size_t i,
const crypto::secret_key& sec_key,
const crypto::public_key& pub_key,
crypto::key_image& key_img)
{
cryptonote::keypair in_ephemeral;
if (!crypto::derive_public_key(derivation, i,
pub_key,
in_ephemeral.pub))
{
cerr << "Error generating publick key " << pub_key << endl;
return false;
}
try
{
crypto::derive_secret_key(derivation, i,
sec_key,
in_ephemeral.sec);
}
catch(const std::exception& e)
{
cerr << "Error generate secret image: " << e.what() << endl;
return false;
}
try
{
crypto::generate_key_image(in_ephemeral.pub,
in_ephemeral.sec,
key_img);
}
catch(const std::exception& e)
{
cerr << "Error generate key image: " << e.what() << endl;
return false;
}
return true;
}
string
get_default_lmdb_folder()
{
// default path to monero folder
// on linux this is /home/<username>/.bitmonero
string default_monero_dir = tools::get_default_data_dir();
// the default folder of the lmdb blockchain database
// is therefore as follows
return default_monero_dir + string("/lmdb");
}
/*
* Ge blockchain exception from command line option
*
* If not given, provide default path
*/
bool
get_blockchain_path(const boost::optional<string>& bc_path, bf::path& blockchain_path )
{
// the default folder of the lmdb blockchain database
string default_lmdb_dir = xmreg::get_default_lmdb_folder();
blockchain_path = bc_path
? bf::path(*bc_path)
: bf::path(default_lmdb_dir);
if (!bf::is_directory(blockchain_path))
{
cerr << "Given path \"" << blockchain_path << "\" "
<< "is not a folder or does not exist" << " "
<< endl;
return false;
}
blockchain_path = xmreg::remove_trailing_path_separator(blockchain_path);
return true;
}
/**
* Rough estimate of block height from the time provided
*
*/
uint64_t
estimate_bc_height(const string& date, const char* format)
{
const pt::ptime MONERO_START {gt::date(2014,04,18)};
const uint64_t MONERO_BLOCK_TIME {60}; // seconds
dateparser parser {format};
if (!parser(date))
{
throw runtime_error(string("Date format is incorrect: ") + date);
}
pt::ptime requested_date = parser.pt;
if (requested_date < MONERO_START)
{
return 0;
}
pt::time_duration td = requested_date - MONERO_START;
return static_cast<uint64_t>(td.total_seconds()) / MONERO_BLOCK_TIME;
}
array<size_t, 5>
timestamp_difference(uint64_t t1, uint64_t t2)
{
uint64_t timestamp_diff = t1 - t2;
// calculate difference of timestamps from current block to the mixin one
if (t2 > t1)
{
timestamp_diff = t2 - t1;
}
uint64_t time_diff_years = timestamp_diff / 31536000;
timestamp_diff -= time_diff_years * 31536000;
uint64_t time_diff_days = timestamp_diff / 86400;
timestamp_diff -= time_diff_days * 86400;
uint64_t time_diff_hours = timestamp_diff / 3600;
timestamp_diff -= time_diff_hours * 3600;
uint64_t time_diff_minutes = timestamp_diff / 60;
timestamp_diff -= time_diff_minutes * 60;
uint64_t time_diff_seconds = timestamp_diff ;
return array<size_t, 5> {time_diff_years, time_diff_days,
time_diff_hours, time_diff_minutes,
time_diff_seconds};
};
}

151
src/tools.h Normal file
View file

@ -0,0 +1,151 @@
//
// Created by marcin on 5/11/15.
//
#ifndef XMREG01_TOOLS_H
#define XMREG01_TOOLS_H
#define PATH_SEPARARTOR '/'
#include "monero_headers.h"
#include "tx_details.h"
#include "../ext/dateparser.h"
#include <boost/filesystem.hpp>
#include <boost/optional.hpp>
#include "boost/date_time/posix_time/posix_time.hpp"
#include <string>
#include <vector>
#include <array>
/**
* Some helper functions used in the example.
* Names are rather self-explanatory, so I think
* there is no reason for any detailed explanations here
*/
namespace xmreg
{
using namespace cryptonote;
using namespace crypto;
using namespace std;
namespace bf = boost::filesystem;
namespace pt = boost::posix_time;
namespace gt = boost::gregorian;
struct outputs_visitor
{
std::vector<crypto::public_key >& m_output_keys;
const Blockchain& m_bch;
outputs_visitor(std::vector<crypto::public_key>& output_keys, const Blockchain& bch) :
m_output_keys(output_keys), m_bch(bch)
{
}
bool handle_output(uint64_t unlock_time, const crypto::public_key &pubkey)
{
//check tx unlock time
// if (!m_bch.is_tx_spendtime_unlocked(unlock_time))
// {
// LOG_PRINT_L1("One of outputs for one of inputs has wrong tx.unlock_time = " << unlock_time);
// return false;
// }
m_output_keys.push_back(pubkey);
return true;
}
};
template <typename T>
bool
parse_str_secret_key(const string& key_str, T& secret_key);
bool
get_tx_pub_key_from_str_hash(Blockchain& core_storage,
const string& hash_str,
transaction& tx);
bool
parse_str_address(const string& address_str,
account_public_address& address,
bool testnet = false);
inline bool
is_separator(char c);
string
print_address(const account_public_address& address,
bool testnet = false);
string
print_sig (const signature& sig);
string
remove_trailing_path_separator(const string& in_path);
bf::path
remove_trailing_path_separator(const bf::path& in_path);
string
timestamp_to_str(time_t timestamp, const char* format = "%F %T");
ostream&
operator<< (ostream& os, const account_public_address& addr);
string
get_default_lmdb_folder();
bool
generate_key_image(const crypto::key_derivation& derivation,
const std::size_t output_index,
const crypto::secret_key& sec_key,
const crypto::public_key& pub_key,
crypto::key_image& key_img);
bool
get_blockchain_path(const boost::optional<string>& bc_path,
bf::path& blockchain_path);
inline void
enable_monero_log() {
uint32_t log_level = 0;
epee::log_space::get_set_log_detalisation_level(true, log_level);
epee::log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL);
}
uint64_t
estimate_bc_height(const string& date, const char* format = "%Y-%m-%d");
inline double
get_xmr(uint64_t core_amount)
{
return static_cast<double>(core_amount) / 1e12;
}
array<size_t, 5>
timestamp_difference(uint64_t t1, uint64_t t2);
}
#endif //XMREG01_TOOLS_H

292
src/tx_details.cpp Normal file
View file

@ -0,0 +1,292 @@
//
// Created by mwo on 14/11/15.
//
#include "tx_details.h"
namespace xmreg
{
crypto::hash
transfer_details::tx_hash() const
{
return get_transaction_hash(m_tx);
};
uint64_t
transfer_details::amount() const
{
return m_tx.vout[m_internal_output_index].amount;
}
ostream&
operator<<(ostream& os, const transfer_details& td)
{
os << "Block: " << td.m_block_height
<< " time: " << timestamp_to_str(td.m_block_timestamp)
<< " tx hash: " << td.tx_hash()
<< " out idx: " << td.m_internal_output_index
<< " amount: " << print_money(td.amount());
return os;
}
/**
* Get tx outputs associated with the given private view and public spend keys
*
*
*/
vector<xmreg::transfer_details>
get_belonging_outputs(const block& blk,
const transaction& tx,
const secret_key& private_view_key,
const public_key& public_spend_key,
uint64_t block_height)
{
// vector to be returned
vector<xmreg::transfer_details> our_outputs;
// get transaction's public key
public_key pub_tx_key = get_tx_pub_key_from_extra(tx);
// check if transaction has valid public key
// if no, then skip
if (pub_tx_key == null_pkey)
{
return our_outputs;
}
// get the total number of outputs in a transaction.
size_t output_no = tx.vout.size();
// check if the given transaction has any outputs
// if no, then finish
if (output_no == 0)
{
return our_outputs;
}
// public transaction key is combined with our viewkey
// to create, so called, derived key.
key_derivation derivation;
if (!generate_key_derivation(pub_tx_key, private_view_key, derivation))
{
cerr << "Cant get dervied key for: " << "\n"
<< "pub_tx_key: " << private_view_key << " and "
<< "prv_view_key" << private_view_key << endl;
return our_outputs;
}
// each tx that we (or the address we are checking) received
// contains a number of outputs.
// some of them are ours, some not. so we need to go through
// all of them in a given tx block, to check which outputs are ours.
// sum amount of xmr sent to us
// in the given transaction
uint64_t money_transfered {0};
// loop through outputs in the given tx
// to check which outputs our ours. we compare outputs'
// public keys with the public key that would had been
// generated for us if we had gotten the outputs.
// not sure this is the case though, but that's my understanding.
for (size_t i = 0; i < output_no; ++i)
{
// get the tx output public key
// that normally would be generated for us,
// if someone had sent us some xmr.
public_key pubkey;
derive_public_key(derivation,
i,
public_spend_key,
pubkey);
// get tx output public key
const txout_to_key tx_out_to_key
= boost::get<txout_to_key>(tx.vout[i].target);
//cout << "Output no: " << i << ", " << tx_out_to_key.key;
// check if the output's public key is ours
if (tx_out_to_key.key == pubkey)
{
// if so, then add this output to the
// returned vector
//our_outputs.push_back(tx.vout[i]);
our_outputs.push_back(
xmreg::transfer_details {block_height,
blk.timestamp,
tx, i, false}
);
}
}
return our_outputs;
}
/**
* Check if given output (specified by output_index)
* belongs is ours based
* on our private view key and public spend key
*/
bool
is_output_ours(const size_t& output_index,
const transaction& tx,
const secret_key& private_view_key,
const public_key& public_spend_key)
{
// get transaction's public key
public_key pub_tx_key = get_tx_pub_key_from_extra(tx);
// check if transaction has valid public key
// if no, then skip
if (pub_tx_key == null_pkey)
{
return false;
}
// public transaction key is combined with our viewkey
// to create, so called, derived key.
key_derivation derivation;
if (!generate_key_derivation(pub_tx_key, private_view_key, derivation))
{
cerr << "Cant get dervied key for: " << "\n"
<< "pub_tx_key: " << pub_tx_key << " and "
<< "prv_view_key" << private_view_key << endl;
return false;
}
// get the tx output public key
// that normally would be generated for us,
// if someone had sent us some xmr.
public_key pubkey;
derive_public_key(derivation,
output_index,
public_spend_key,
pubkey);
//cout << "\n" << tx.vout.size() << " " << output_index << endl;
// get tx output public key
const txout_to_key tx_out_to_key
= boost::get<txout_to_key>(tx.vout[output_index].target);
if (tx_out_to_key.key == pubkey)
{
return true;
}
return false;
}
bool
get_payment_id(const transaction& tx,
crypto::hash& payment_id)
{
payment_id = null_hash;
std::vector<tx_extra_field> tx_extra_fields;
if(!parse_tx_extra(tx.extra, tx_extra_fields))
{
return false;
}
tx_extra_nonce extra_nonce;
if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
{
if (!get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id))
{
return false;
}
}
else
{
return false;
}
return true;
}
bool
get_encrypted_payment_id(const transaction& tx,
crypto::hash8& payment_id8)
{
payment_id8 = null_hash8;
std::vector<tx_extra_field> tx_extra_fields;
if(!parse_tx_extra(tx.extra, tx_extra_fields))
{
return false;
}
tx_extra_nonce extra_nonce;
if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
{
if (!get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8))
{
// if (decrypt_payment_id(payment_id8, addr.m_view_public_key, ptx.tx_key))
// {
// memcpy(payment_id8.data, payment_id8.data, 8);
// }
return false;
}
}
else
{
return false;
}
return true;
}
}
template<>
csv::ofstream&
operator<<(csv::ofstream& ostm, const xmreg::transfer_details& td)
{
ostm << xmreg::timestamp_to_str(td.m_block_timestamp, "%F");
ostm << xmreg::timestamp_to_str(td.m_block_timestamp, "%T");
ostm << td.m_block_height;
ostm << td.tx_hash();
ostm << td.m_internal_output_index;
ostm << cryptonote::print_money(td.amount());
return ostm;
}

71
src/tx_details.h Normal file
View file

@ -0,0 +1,71 @@
//
// Created by mwo on 14/11/15.
//
#ifndef XMR2CSV_TXDATA_H
#define XMR2CSV_TXDATA_H
#include "../ext/minicsv.h"
#include "monero_headers.h"
#include "tools.h"
namespace xmreg
{
using namespace cryptonote;
using namespace crypto;
using namespace std;
struct transfer_details
{
uint64_t m_block_height;
uint64_t m_block_timestamp;
transaction m_tx;
size_t m_internal_output_index;
bool m_spent;
crypto::hash tx_hash() const;
uint64_t amount() const;
};
ostream&
operator<<(ostream& os, const transfer_details& dt);
vector<xmreg::transfer_details>
get_belonging_outputs(const block& blk,
const transaction& tx,
const secret_key& private_view_key,
const public_key& public_spend_key,
uint64_t block_height = 0);
bool
is_output_ours(const size_t& output_index,
const transaction& tx,
const secret_key& private_view_key,
const public_key& public_spend_key);
bool
get_payment_id(const transaction& tx,
crypto::hash& payment_id);
bool
get_encrypted_payment_id(const transaction& tx,
crypto::hash8& payment_id);
}
template<>
csv::ostringstream&
operator<<(csv::ostringstream& ostm, const xmreg::transfer_details& td);
#endif //XMR2CSV_TXDATA_H