From 06fc879371cb81a1b2920896c2ddb1b66b8fbb13 Mon Sep 17 00:00:00 2001 From: yhirose Date: Sat, 9 Feb 2019 08:55:28 -0500 Subject: [PATCH] Removed server mode from peglint and updated documentation --- README.md | 6 +- lint/CMakeLists.txt | 11 +- lint/README.md | 2 +- lint/httplib.h | 2497 ------------------------------------------- lint/peglint.cc | 30 +- lint/server.cc | 371 ------- 6 files changed, 7 insertions(+), 2910 deletions(-) delete mode 100644 lint/httplib.h delete mode 100644 lint/server.cc diff --git a/README.md b/README.md index 6224eae..f18e0d2 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ cpp-peglib [![Build Status](https://travis-ci.org/yhirose/cpp-peglib.svg?branch=master)](https://travis-ci.org/yhirose/cpp-peglib) [![Bulid Status](https://ci.appveyor.com/api/projects/status/github/yhirose/cpp-peglib?branch=master&svg=true)](https://ci.appveyor.com/project/yhirose/cpp-peglib) -C++11 header-only [PEG](http://en.wikipedia.org/wiki/Parsing_expression_grammar) (Parsing Expression Grammars) library. +C++11 header-only [PEG](http://en.wikipedia.org/wiki/Parsing_expression_grammar) (Parsing Expression Grammars) library. You can start using it right away just by including `peglib.h` in your project. -*cpp-peglib* tries to provide more expressive parsing experience in a simple way. This library depends on only one header file. So, you can start using it right away just by including `peglib.h` in your project. +You can also try the online version at https://yhirose.github.io/cpp-peglib. The PEG syntax is well described on page 2 in the [document](http://www.brynosaurus.com/pub/lang/peg.pdf). *cpp-peglib* also supports the following additional syntax for now: @@ -433,7 +433,7 @@ peglint - PEG syntax lint utility > cmake .. > make > ./peglint -usage: peglint [--ast] [--optimize_ast_nodes|--opt] [--source text] [--server [PORT]] [--trace] [grammar file path] [source file path] +usage: peglint [--ast] [--optimize_ast_nodes|--opt] [--source text] [--trace] [grammar file path] [source file path] ``` ### Lint grammar diff --git a/lint/CMakeLists.txt b/lint/CMakeLists.txt index cefd0db..3c434fd 100644 --- a/lint/CMakeLists.txt +++ b/lint/CMakeLists.txt @@ -1,13 +1,4 @@ cmake_minimum_required(VERSION 2.8) include_directories(..) add_definitions("-std=c++11") -add_executable(peglint peglint.cc server.cc) - -if(WIN32) - target_link_libraries(peglint wsock32 ws2_32) -endif() - -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - #set(add_link_deps pthread) - target_link_libraries(peglint pthread) -endif() +add_executable(peglint peglint.cc) diff --git a/lint/README.md b/lint/README.md index 9f48c1c..7300fe4 100644 --- a/lint/README.md +++ b/lint/README.md @@ -4,5 +4,5 @@ peglint The lint utility for PEG. ``` -usage: peglint [--ast] [--optimize_ast_nodes|--opt] [--server [PORT]] [--trace] [grammar file path] [source file path] +usage: peglint [--ast] [--optimize_ast_nodes|--opt] [--trace] [grammar file path] [source file path] ``` diff --git a/lint/httplib.h b/lint/httplib.h deleted file mode 100644 index 701132f..0000000 --- a/lint/httplib.h +++ /dev/null @@ -1,2497 +0,0 @@ -// -// httplib.h -// -// Copyright (c) 2017 Yuji Hirose. All rights reserved. -// MIT License -// - -#ifndef CPPHTTPLIB_HTTPLIB_H -#define CPPHTTPLIB_HTTPLIB_H - -#ifdef _WIN32 -#ifndef _CRT_SECURE_NO_WARNINGS -#define _CRT_SECURE_NO_WARNINGS -#endif //_CRT_SECURE_NO_WARNINGS - -#ifndef _CRT_NONSTDC_NO_DEPRECATE -#define _CRT_NONSTDC_NO_DEPRECATE -#endif //_CRT_NONSTDC_NO_DEPRECATE - -#if defined(_MSC_VER) && _MSC_VER < 1900 -#define snprintf _snprintf_s -#endif // _MSC_VER - -#ifndef S_ISREG -#define S_ISREG(m) (((m)&S_IFREG)==S_IFREG) -#endif //S_ISREG - -#ifndef S_ISDIR -#define S_ISDIR(m) (((m)&S_IFDIR)==S_IFDIR) -#endif //S_ISDIR - -#define NOMINMAX - -#include -#include -#include - -#ifndef strcasecmp -#define strcasecmp _stricmp -#endif //strcasecmp - -typedef SOCKET socket_t; -#else -#include -#include -#include -#include -#include -#include -#include -#include -#include - -typedef int socket_t; -#define INVALID_SOCKET (-1) -#endif //_WIN32 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -#include -#endif - -#ifdef CPPHTTPLIB_ZLIB_SUPPORT -#include -#endif - -/* - * Configuration - */ -#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 -#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND 0 - -namespace httplib -{ - -namespace detail { - -struct ci { - bool operator() (const std::string & s1, const std::string & s2) const { - return std::lexicographical_compare( - s1.begin(), s1.end(), - s2.begin(), s2.end(), - [](char c1, char c2) { - return ::tolower(c1) < ::tolower(c2); - }); - } -}; - -} // namespace detail - -enum class HttpVersion { v1_0 = 0, v1_1 }; - -typedef std::multimap Headers; - -template -std::pair make_range_header(uint64_t value, Args... args); - -typedef std::multimap Params; -typedef std::smatch Match; -typedef std::function Progress; - -struct MultipartFile { - std::string filename; - std::string content_type; - size_t offset = 0; - size_t length = 0; -}; -typedef std::multimap MultipartFiles; - -struct Request { - std::string version; - std::string method; - std::string target; - std::string path; - Headers headers; - std::string body; - Params params; - MultipartFiles files; - Match matches; - - Progress progress; - - bool has_header(const char* key) const; - std::string get_header_value(const char* key, size_t id = 0) const; - size_t get_header_value_count(const char* key) const; - void set_header(const char* key, const char* val); - - bool has_param(const char* key) const; - std::string get_param_value(const char* key, size_t id = 0) const; - size_t get_param_value_count(const char* key) const; - - bool has_file(const char* key) const; - MultipartFile get_file_value(const char* key) const; -}; - -struct Response { - std::string version; - int status; - Headers headers; - std::string body; - std::function streamcb; - - bool has_header(const char* key) const; - std::string get_header_value(const char* key, size_t id = 0) const; - size_t get_header_value_count(const char* key) const; - void set_header(const char* key, const char* val); - - void set_redirect(const char* uri); - void set_content(const char* s, size_t n, const char* content_type); - void set_content(const std::string& s, const char* content_type); - - Response() : status(-1) {} -}; - -class Stream { -public: - virtual ~Stream() {} - virtual int read(char* ptr, size_t size) = 0; - virtual int write(const char* ptr, size_t size1) = 0; - virtual int write(const char* ptr) = 0; - virtual std::string get_remote_addr() const = 0; - - template - void write_format(const char* fmt, const Args& ...args); -}; - -class SocketStream : public Stream { -public: - SocketStream(socket_t sock); - virtual ~SocketStream(); - - virtual int read(char* ptr, size_t size); - virtual int write(const char* ptr, size_t size); - virtual int write(const char* ptr); - virtual std::string get_remote_addr() const; - -private: - socket_t sock_; -}; - -class BufferStream : public Stream { -public: - BufferStream() {} - virtual ~BufferStream() {} - - virtual int read(char* ptr, size_t size); - virtual int write(const char* ptr, size_t size); - virtual int write(const char* ptr); - virtual std::string get_remote_addr() const; - - const std::string& get_buffer() const; - -private: - std::string buffer; -}; - -class Server { -public: - typedef std::function Handler; - typedef std::function Logger; - - Server(); - - virtual ~Server(); - - virtual bool is_valid() const; - - Server& Get(const char* pattern, Handler handler); - Server& Post(const char* pattern, Handler handler); - - Server& Put(const char* pattern, Handler handler); - Server& Delete(const char* pattern, Handler handler); - Server& Options(const char* pattern, Handler handler); - - bool set_base_dir(const char* path); - - void set_error_handler(Handler handler); - void set_logger(Logger logger); - - void set_keep_alive_max_count(size_t count); - - int bind_to_any_port(const char* host, int socket_flags = 0); - bool listen_after_bind(); - - bool listen(const char* host, int port, int socket_flags = 0); - - bool is_running() const; - void stop(); - -protected: - bool process_request(Stream& strm, bool last_connection, bool& connection_close); - - size_t keep_alive_max_count_; - -private: - typedef std::vector> Handlers; - - socket_t create_server_socket(const char* host, int port, int socket_flags) const; - int bind_internal(const char* host, int port, int socket_flags); - bool listen_internal(); - - bool routing(Request& req, Response& res); - bool handle_file_request(Request& req, Response& res); - bool dispatch_request(Request& req, Response& res, Handlers& handlers); - - bool parse_request_line(const char* s, Request& req); - void write_response(Stream& strm, bool last_connection, const Request& req, Response& res); - - virtual bool read_and_close_socket(socket_t sock); - - bool is_running_; - socket_t svr_sock_; - std::string base_dir_; - Handlers get_handlers_; - Handlers post_handlers_; - Handlers put_handlers_; - Handlers delete_handlers_; - Handlers options_handlers_; - Handler error_handler_; - Logger logger_; - - // TODO: Use thread pool... - std::mutex running_threads_mutex_; - int running_threads_; -}; - -class Client { -public: - Client( - const char* host, - int port = 80, - time_t timeout_sec = 300); - - virtual ~Client(); - - virtual bool is_valid() const; - - std::shared_ptr Get(const char* path, Progress progress = nullptr); - std::shared_ptr Get(const char* path, const Headers& headers, Progress progress = nullptr); - - std::shared_ptr Head(const char* path); - std::shared_ptr Head(const char* path, const Headers& headers); - - std::shared_ptr Post(const char* path, const std::string& body, const char* content_type); - std::shared_ptr Post(const char* path, const Headers& headers, const std::string& body, const char* content_type); - - std::shared_ptr Post(const char* path, const Params& params); - std::shared_ptr Post(const char* path, const Headers& headers, const Params& params); - - std::shared_ptr Put(const char* path, const std::string& body, const char* content_type); - std::shared_ptr Put(const char* path, const Headers& headers, const std::string& body, const char* content_type); - - std::shared_ptr Delete(const char* path); - std::shared_ptr Delete(const char* path, const Headers& headers); - - std::shared_ptr Options(const char* path); - std::shared_ptr Options(const char* path, const Headers& headers); - - bool send(Request& req, Response& res); - -protected: - bool process_request(Stream& strm, Request& req, Response& res, bool& connection_close); - - const std::string host_; - const int port_; - time_t timeout_sec_; - const std::string host_and_port_; - -private: - socket_t create_client_socket() const; - bool read_response_line(Stream& strm, Response& res); - void write_request(Stream& strm, Request& req); - - virtual bool read_and_close_socket(socket_t sock, Request& req, Response& res); - virtual bool is_ssl() const; -}; - -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -class SSLSocketStream : public Stream { -public: - SSLSocketStream(socket_t sock, SSL* ssl); - virtual ~SSLSocketStream(); - - virtual int read(char* ptr, size_t size); - virtual int write(const char* ptr, size_t size); - virtual int write(const char* ptr); - virtual std::string get_remote_addr() const; - -private: - socket_t sock_; - SSL* ssl_; -}; - -class SSLServer : public Server { -public: - SSLServer( - const char* cert_path, const char* private_key_path); - - virtual ~SSLServer(); - - virtual bool is_valid() const; - -private: - virtual bool read_and_close_socket(socket_t sock); - - SSL_CTX* ctx_; - std::mutex ctx_mutex_; -}; - -class SSLClient : public Client { -public: - SSLClient( - const char* host, - int port = 443, - time_t timeout_sec = 300); - - virtual ~SSLClient(); - - virtual bool is_valid() const; - -private: - virtual bool read_and_close_socket(socket_t sock, Request& req, Response& res); - virtual bool is_ssl() const; - - SSL_CTX* ctx_; - std::mutex ctx_mutex_; -}; -#endif - -/* - * Implementation - */ -namespace detail { - -template -void split(const char* b, const char* e, char d, Fn fn) -{ - int i = 0; - int beg = 0; - - while (e ? (b + i != e) : (b[i] != '\0')) { - if (b[i] == d) { - fn(&b[beg], &b[i]); - beg = i + 1; - } - i++; - } - - if (i) { - fn(&b[beg], &b[i]); - } -} - -// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` -// to store data. The call can set memory on stack for performance. -class stream_line_reader { -public: - stream_line_reader(Stream& strm, char* fixed_buffer, size_t fixed_buffer_size) - : strm_(strm) - , fixed_buffer_(fixed_buffer) - , fixed_buffer_size_(fixed_buffer_size) { - } - - const char* ptr() const { - if (glowable_buffer_.empty()) { - return fixed_buffer_; - } else { - return glowable_buffer_.data(); - } - } - - bool getline() { - fixed_buffer_used_size_ = 0; - glowable_buffer_.clear(); - - for (size_t i = 0; ; i++) { - char byte; - auto n = strm_.read(&byte, 1); - - if (n < 0) { - return false; - } else if (n == 0) { - if (i == 0) { - return false; - } else { - break; - } - } - - append(byte); - - if (byte == '\n') { - break; - } - } - - return true; - } - -private: - void append(char c) { - if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { - fixed_buffer_[fixed_buffer_used_size_++] = c; - fixed_buffer_[fixed_buffer_used_size_] = '\0'; - } else { - if (glowable_buffer_.empty()) { - assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); - glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); - } - glowable_buffer_ += c; - } - } - - Stream& strm_; - char* fixed_buffer_; - const size_t fixed_buffer_size_; - size_t fixed_buffer_used_size_; - std::string glowable_buffer_; -}; - -inline int close_socket(socket_t sock) -{ -#ifdef _WIN32 - return closesocket(sock); -#else - return close(sock); -#endif -} - -inline int select_read(socket_t sock, time_t sec, time_t usec) -{ - fd_set fds; - FD_ZERO(&fds); - FD_SET(sock, &fds); - - timeval tv; - tv.tv_sec = sec; - tv.tv_usec = usec; - - return select(sock + 1, &fds, NULL, NULL, &tv); -} - -inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) -{ - fd_set fdsr; - FD_ZERO(&fdsr); - FD_SET(sock, &fdsr); - - auto fdsw = fdsr; - auto fdse = fdsr; - - timeval tv; - tv.tv_sec = sec; - tv.tv_usec = usec; - - if (select(sock + 1, &fdsr, &fdsw, &fdse, &tv) < 0) { - return false; - } else if (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw)) { - int error = 0; - socklen_t len = sizeof(error); - if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)&error, &len) < 0 || error) { - return false; - } - } else { - return false; - } - - return true; -} - -template -inline bool read_and_close_socket(socket_t sock, size_t keep_alive_max_count, T callback) -{ - bool ret = false; - - if (keep_alive_max_count > 0) { - auto count = keep_alive_max_count; - while (count > 0 && - detail::select_read(sock, - CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND, - CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0) { - SocketStream strm(sock); - auto last_connection = count == 1; - auto connection_close = false; - - ret = callback(strm, last_connection, connection_close); - if (!ret || connection_close) { - break; - } - - count--; - } - } else { - SocketStream strm(sock); - auto dummy_connection_close = false; - ret = callback(strm, true, dummy_connection_close); - } - - close_socket(sock); - return ret; -} - -inline int shutdown_socket(socket_t sock) -{ -#ifdef _WIN32 - return shutdown(sock, SD_BOTH); -#else - return shutdown(sock, SHUT_RDWR); -#endif -} - -template -socket_t create_socket(const char* host, int port, Fn fn, int socket_flags = 0) -{ -#ifdef _WIN32 -#define SO_SYNCHRONOUS_NONALERT 0x20 -#define SO_OPENTYPE 0x7008 - - int opt = SO_SYNCHRONOUS_NONALERT; - setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, (char*)&opt, sizeof(opt)); -#endif - - // Get address info - struct addrinfo hints; - struct addrinfo *result; - - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = socket_flags; - hints.ai_protocol = 0; - - auto service = std::to_string(port); - - if (getaddrinfo(host, service.c_str(), &hints, &result)) { - return INVALID_SOCKET; - } - - for (auto rp = result; rp; rp = rp->ai_next) { - // Create a socket - auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); - if (sock == INVALID_SOCKET) { - continue; - } - - // Make 'reuse address' option available - int yes = 1; - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&yes, sizeof(yes)); - - // bind or connect - if (fn(sock, *rp)) { - freeaddrinfo(result); - return sock; - } - - close_socket(sock); - } - - freeaddrinfo(result); - return INVALID_SOCKET; -} - -inline void set_nonblocking(socket_t sock, bool nonblocking) -{ -#ifdef _WIN32 - auto flags = nonblocking ? 1UL : 0UL; - ioctlsocket(sock, FIONBIO, &flags); -#else - auto flags = fcntl(sock, F_GETFL, 0); - fcntl(sock, F_SETFL, nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); -#endif -} - -inline bool is_connection_error() -{ -#ifdef _WIN32 - return WSAGetLastError() != WSAEWOULDBLOCK; -#else - return errno != EINPROGRESS; -#endif -} - -inline std::string get_remote_addr(socket_t sock) { - struct sockaddr_storage addr; - socklen_t len = sizeof(addr); - - if (!getpeername(sock, (struct sockaddr*)&addr, &len)) { - char ipstr[NI_MAXHOST]; - - if (!getnameinfo((struct sockaddr*)&addr, len, - ipstr, sizeof(ipstr), nullptr, 0, NI_NUMERICHOST)) { - return ipstr; - } - } - - return std::string(); -} - -inline bool is_file(const std::string& path) -{ - struct stat st; - return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode); -} - -inline bool is_dir(const std::string& path) -{ - struct stat st; - return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode); -} - -inline bool is_valid_path(const std::string& path) { - size_t level = 0; - size_t i = 0; - - // Skip slash - while (i < path.size() && path[i] == '/') { - i++; - } - - while (i < path.size()) { - // Read component - auto beg = i; - while (i < path.size() && path[i] != '/') { - i++; - } - - auto len = i - beg; - assert(len > 0); - - if (!path.compare(beg, len, ".")) { - ; - } else if (!path.compare(beg, len, "..")) { - if (level == 0) { - return false; - } - level--; - } else { - level++; - } - - // Skip slash - while (i < path.size() && path[i] == '/') { - i++; - } - } - - return true; -} - -inline void read_file(const std::string& path, std::string& out) -{ - std::ifstream fs(path, std::ios_base::binary); - fs.seekg(0, std::ios_base::end); - auto size = fs.tellg(); - fs.seekg(0); - out.resize(static_cast(size)); - fs.read(&out[0], size); -} - -inline std::string file_extension(const std::string& path) -{ - std::smatch m; - auto pat = std::regex("\\.([a-zA-Z0-9]+)$"); - if (std::regex_search(path, m, pat)) { - return m[1].str(); - } - return std::string(); -} - -inline const char* find_content_type(const std::string& path) -{ - auto ext = file_extension(path); - if (ext == "txt") { - return "text/plain"; - } else if (ext == "html") { - return "text/html"; - } else if (ext == "css") { - return "text/css"; - } else if (ext == "jpeg" || ext == "jpg") { - return "image/jpg"; - } else if (ext == "png") { - return "image/png"; - } else if (ext == "gif") { - return "image/gif"; - } else if (ext == "svg") { - return "image/svg+xml"; - } else if (ext == "ico") { - return "image/x-icon"; - } else if (ext == "json") { - return "application/json"; - } else if (ext == "pdf") { - return "application/pdf"; - } else if (ext == "js") { - return "application/javascript"; - } else if (ext == "xml") { - return "application/xml"; - } else if (ext == "xhtml") { - return "application/xhtml+xml"; - } - return nullptr; -} - -inline const char* status_message(int status) -{ - switch (status) { - case 200: return "OK"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 303: return "See Other"; - case 304: return "Not Modified"; - case 400: return "Bad Request"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 415: return "Unsupported Media Type"; - default: - case 500: return "Internal Server Error"; - } -} - -inline bool has_header(const Headers& headers, const char* key) -{ - return headers.find(key) != headers.end(); -} - -inline const char* get_header_value( - const Headers& headers, const char* key, size_t id = 0, const char* def = nullptr) -{ - auto it = headers.find(key); - std::advance(it, id); - if (it != headers.end()) { - return it->second.c_str(); - } - return def; -} - -inline int get_header_value_int(const Headers& headers, const char* key, int def = 0) -{ - auto it = headers.find(key); - if (it != headers.end()) { - return std::stoi(it->second); - } - return def; -} - -inline bool read_headers(Stream& strm, Headers& headers) -{ - static std::regex re(R"((.+?):\s*(.+?)\s*\r\n)"); - - const auto bufsiz = 2048; - char buf[bufsiz]; - - stream_line_reader reader(strm, buf, bufsiz); - - for (;;) { - if (!reader.getline()) { - return false; - } - if (!strcmp(reader.ptr(), "\r\n")) { - break; - } - std::cmatch m; - if (std::regex_match(reader.ptr(), m, re)) { - auto key = std::string(m[1]); - auto val = std::string(m[2]); - headers.emplace(key, val); - } - } - - return true; -} - -inline bool read_content_with_length(Stream& strm, std::string& out, size_t len, Progress progress) -{ - out.assign(len, 0); - size_t r = 0; - while (r < len){ - auto n = strm.read(&out[r], len - r); - if (n <= 0) { - return false; - } - - r += n; - - if (progress) { - if (!progress(r, len)) { - return false; - } - } - } - - return true; -} - -inline bool read_content_without_length(Stream& strm, std::string& out) -{ - for (;;) { - char byte; - auto n = strm.read(&byte, 1); - if (n < 0) { - return false; - } else if (n == 0) { - return true; - } - out += byte; - } - - return true; -} - -inline bool read_content_chunked(Stream& strm, std::string& out) -{ - const auto bufsiz = 16; - char buf[bufsiz]; - - stream_line_reader reader(strm, buf, bufsiz); - - if (!reader.getline()) { - return false; - } - - auto chunk_len = std::stoi(reader.ptr(), 0, 16); - - while (chunk_len > 0){ - std::string chunk; - if (!read_content_with_length(strm, chunk, chunk_len, nullptr)) { - return false; - } - - if (!reader.getline()) { - return false; - } - - if (strcmp(reader.ptr(), "\r\n")) { - break; - } - - out += chunk; - - if (!reader.getline()) { - return false; - } - - chunk_len = std::stoi(reader.ptr(), 0, 16); - } - - if (chunk_len == 0) { - // Reader terminator after chunks - if (!reader.getline() || strcmp(reader.ptr(), "\r\n")) - return false; - } - - return true; -} - -template -bool read_content(Stream& strm, T& x, Progress progress = Progress()) -{ - if (has_header(x.headers, "Content-Length")) { - auto len = get_header_value_int(x.headers, "Content-Length", 0); - if (len == 0) { - const auto& encoding = get_header_value(x.headers, "Transfer-Encoding", 0, ""); - if (!strcasecmp(encoding, "chunked")) { - return read_content_chunked(strm, x.body); - } - } - return read_content_with_length(strm, x.body, len, progress); - } else { - const auto& encoding = get_header_value(x.headers, "Transfer-Encoding", 0, ""); - if (!strcasecmp(encoding, "chunked")) { - return read_content_chunked(strm, x.body); - } - return read_content_without_length(strm, x.body); - } - return true; -} - -template -inline void write_headers(Stream& strm, const T& info) -{ - for (const auto& x: info.headers) { - strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); - } - strm.write("\r\n"); -} - -inline std::string encode_url(const std::string& s) -{ - std::string result; - - for (auto i = 0; s[i]; i++) { - switch (s[i]) { - case ' ': result += "%20"; break; - case '+': result += "%2B"; break; - case '\'': result += "%27"; break; - case ',': result += "%2C"; break; - case ':': result += "%3A"; break; - case ';': result += "%3B"; break; - default: - auto c = static_cast(s[i]); - if (c >= 0x80) { - result += '%'; - char hex[4]; - size_t len = snprintf(hex, sizeof(hex) - 1, "%02X", c); - assert(len == 2); - result.append(hex, len); - } else { - result += s[i]; - } - break; - } - } - - return result; -} - -inline bool is_hex(char c, int& v) -{ - if (0x20 <= c && isdigit(c)) { - v = c - '0'; - return true; - } else if ('A' <= c && c <= 'F') { - v = c - 'A' + 10; - return true; - } else if ('a' <= c && c <= 'f') { - v = c - 'a' + 10; - return true; - } - return false; -} - -inline bool from_hex_to_i(const std::string& s, size_t i, size_t cnt, int& val) -{ - if (i >= s.size()) { - return false; - } - - val = 0; - for (; cnt; i++, cnt--) { - if (!s[i]) { - return false; - } - int v = 0; - if (is_hex(s[i], v)) { - val = val * 16 + v; - } else { - return false; - } - } - return true; -} - -inline std::string from_i_to_hex(uint64_t n) -{ - const char *charset = "0123456789abcdef"; - std::string ret; - do { - ret = charset[n & 15] + ret; - n >>= 4; - } while (n > 0); - return ret; -} - -inline size_t to_utf8(int code, char* buff) -{ - if (code < 0x0080) { - buff[0] = (code & 0x7F); - return 1; - } else if (code < 0x0800) { - buff[0] = (0xC0 | ((code >> 6) & 0x1F)); - buff[1] = (0x80 | (code & 0x3F)); - return 2; - } else if (code < 0xD800) { - buff[0] = (0xE0 | ((code >> 12) & 0xF)); - buff[1] = (0x80 | ((code >> 6) & 0x3F)); - buff[2] = (0x80 | (code & 0x3F)); - return 3; - } else if (code < 0xE000) { // D800 - DFFF is invalid... - return 0; - } else if (code < 0x10000) { - buff[0] = (0xE0 | ((code >> 12) & 0xF)); - buff[1] = (0x80 | ((code >> 6) & 0x3F)); - buff[2] = (0x80 | (code & 0x3F)); - return 3; - } else if (code < 0x110000) { - buff[0] = (0xF0 | ((code >> 18) & 0x7)); - buff[1] = (0x80 | ((code >> 12) & 0x3F)); - buff[2] = (0x80 | ((code >> 6) & 0x3F)); - buff[3] = (0x80 | (code & 0x3F)); - return 4; - } - - // NOTREACHED - return 0; -} - -inline std::string decode_url(const std::string& s) -{ - std::string result; - - for (size_t i = 0; i < s.size(); i++) { - if (s[i] == '%' && i + 1 < s.size()) { - if (s[i + 1] == 'u') { - int val = 0; - if (from_hex_to_i(s, i + 2, 4, val)) { - // 4 digits Unicode codes - char buff[4]; - size_t len = to_utf8(val, buff); - if (len > 0) { - result.append(buff, len); - } - i += 5; // 'u0000' - } else { - result += s[i]; - } - } else { - int val = 0; - if (from_hex_to_i(s, i + 1, 2, val)) { - // 2 digits hex codes - result += val; - i += 2; // '00' - } else { - result += s[i]; - } - } - } else if (s[i] == '+') { - result += ' '; - } else { - result += s[i]; - } - } - - return result; -} - -inline void parse_query_text(const std::string& s, Params& params) -{ - split(&s[0], &s[s.size()], '&', [&](const char* b, const char* e) { - std::string key; - std::string val; - split(b, e, '=', [&](const char* b, const char* e) { - if (key.empty()) { - key.assign(b, e); - } else { - val.assign(b, e); - } - }); - params.emplace(key, decode_url(val)); - }); -} - -inline bool parse_multipart_boundary(const std::string& content_type, std::string& boundary) -{ - auto pos = content_type.find("boundary="); - if (pos == std::string::npos) { - return false; - } - - boundary = content_type.substr(pos + 9); - return true; -} - -inline bool parse_multipart_formdata( - const std::string& boundary, const std::string& body, MultipartFiles& files) -{ - static std::string dash = "--"; - static std::string crlf = "\r\n"; - - static std::regex re_content_type( - "Content-Type: (.*?)", std::regex_constants::icase); - - static std::regex re_content_disposition( - "Content-Disposition: form-data; name=\"(.*?)\"(?:; filename=\"(.*?)\")?", - std::regex_constants::icase); - - auto dash_boundary = dash + boundary; - - auto pos = body.find(dash_boundary); - if (pos != 0) { - return false; - } - - pos += dash_boundary.size(); - - auto next_pos = body.find(crlf, pos); - if (next_pos == std::string::npos) { - return false; - } - - pos = next_pos + crlf.size(); - - while (pos < body.size()) { - next_pos = body.find(crlf, pos); - if (next_pos == std::string::npos) { - return false; - } - - std::string name; - MultipartFile file; - - auto header = body.substr(pos, (next_pos - pos)); - - while (pos != next_pos) { - std::smatch m; - if (std::regex_match(header, m, re_content_type)) { - file.content_type = m[1]; - } else if (std::regex_match(header, m, re_content_disposition)) { - name = m[1]; - file.filename = m[2]; - } - - pos = next_pos + crlf.size(); - - next_pos = body.find(crlf, pos); - if (next_pos == std::string::npos) { - return false; - } - - header = body.substr(pos, (next_pos - pos)); - } - - pos = next_pos + crlf.size(); - - next_pos = body.find(crlf + dash_boundary, pos); - - if (next_pos == std::string::npos) { - return false; - } - - file.offset = pos; - file.length = next_pos - pos; - - pos = next_pos + crlf.size() + dash_boundary.size(); - - next_pos = body.find(crlf, pos); - if (next_pos == std::string::npos) { - return false; - } - - files.emplace(name, file); - - pos = next_pos + crlf.size(); - } - - return true; -} - -inline std::string to_lower(const char* beg, const char* end) -{ - std::string out; - auto it = beg; - while (it != end) { - out += ::tolower(*it); - it++; - } - return out; -} - -inline void make_range_header_core(std::string&) {} - -template -inline void make_range_header_core(std::string& field, uint64_t value) -{ - if (!field.empty()) { - field += ", "; - } - field += std::to_string(value) + "-"; -} - -template -inline void make_range_header_core(std::string& field, uint64_t value1, uint64_t value2, Args... args) -{ - if (!field.empty()) { - field += ", "; - } - field += std::to_string(value1) + "-" + std::to_string(value2); - make_range_header_core(field, args...); -} - -#ifdef CPPHTTPLIB_ZLIB_SUPPORT -inline bool can_compress(const std::string& content_type) { - return !content_type.find("text/") || - content_type == "image/svg+xml" || - content_type == "application/javascript" || - content_type == "application/json" || - content_type == "application/xml" || - content_type == "application/xhtml+xml"; -} - -inline void compress(std::string& content) -{ - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - - auto ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY); - if (ret != Z_OK) { - return; - } - - strm.avail_in = content.size(); - strm.next_in = (Bytef *)content.data(); - - std::string compressed; - - const auto bufsiz = 16384; - char buff[bufsiz]; - do { - strm.avail_out = bufsiz; - strm.next_out = (Bytef *)buff; - deflate(&strm, Z_FINISH); - compressed.append(buff, bufsiz - strm.avail_out); - } while (strm.avail_out == 0); - - content.swap(compressed); - - deflateEnd(&strm); -} - -inline void decompress(std::string& content) -{ - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - - // 15 is the value of wbits, which should be at the maximum possible value to ensure - // that any gzip stream can be decoded. The offset of 16 specifies that the stream - // to decompress will be formatted with a gzip wrapper. - auto ret = inflateInit2(&strm, 16 + 15); - if (ret != Z_OK) { - return; - } - - strm.avail_in = content.size(); - strm.next_in = (Bytef *)content.data(); - - std::string decompressed; - - const auto bufsiz = 16384; - char buff[bufsiz]; - do { - strm.avail_out = bufsiz; - strm.next_out = (Bytef *)buff; - inflate(&strm, Z_NO_FLUSH); - decompressed.append(buff, bufsiz - strm.avail_out); - } while (strm.avail_out == 0); - - content.swap(decompressed); - - inflateEnd(&strm); -} -#endif - -#ifdef _WIN32 -class WSInit { -public: - WSInit() { - WSADATA wsaData; - WSAStartup(0x0002, &wsaData); - } - - ~WSInit() { - WSACleanup(); - } -}; - -static WSInit wsinit_; -#endif - -} // namespace detail - -// Header utilities -template -inline std::pair make_range_header(uint64_t value, Args... args) -{ - std::string field; - detail::make_range_header_core(field, value, args...); - field.insert(0, "bytes="); - return std::make_pair("Range", field); -} - -// Request implementation -inline bool Request::has_header(const char* key) const -{ - return detail::has_header(headers, key); -} - -inline std::string Request::get_header_value(const char* key, size_t id) const -{ - return detail::get_header_value(headers, key, id, ""); -} - -inline size_t Request::get_header_value_count(const char* key) const -{ - auto r = headers.equal_range(key); - return std::distance(r.first, r.second); -} - -inline void Request::set_header(const char* key, const char* val) -{ - headers.emplace(key, val); -} - -inline bool Request::has_param(const char* key) const -{ - return params.find(key) != params.end(); -} - -inline std::string Request::get_param_value(const char* key, size_t id) const -{ - auto it = params.find(key); - std::advance(it, id); - if (it != params.end()) { - return it->second; - } - return std::string(); -} - -inline size_t Request::get_param_value_count(const char* key) const -{ - auto r = params.equal_range(key); - return std::distance(r.first, r.second); -} - -inline bool Request::has_file(const char* key) const -{ - return files.find(key) != files.end(); -} - -inline MultipartFile Request::get_file_value(const char* key) const -{ - auto it = files.find(key); - if (it != files.end()) { - return it->second; - } - return MultipartFile(); -} - -// Response implementation -inline bool Response::has_header(const char* key) const -{ - return headers.find(key) != headers.end(); -} - -inline std::string Response::get_header_value(const char* key, size_t id) const -{ - return detail::get_header_value(headers, key, id, ""); -} - -inline size_t Response::get_header_value_count(const char* key) const -{ - auto r = headers.equal_range(key); - return std::distance(r.first, r.second); -} - -inline void Response::set_header(const char* key, const char* val) -{ - headers.emplace(key, val); -} - -inline void Response::set_redirect(const char* url) -{ - set_header("Location", url); - status = 302; -} - -inline void Response::set_content(const char* s, size_t n, const char* content_type) -{ - body.assign(s, n); - set_header("Content-Type", content_type); -} - -inline void Response::set_content(const std::string& s, const char* content_type) -{ - body = s; - set_header("Content-Type", content_type); -} - -// Rstream implementation -template -inline void Stream::write_format(const char* fmt, const Args& ...args) -{ - const auto bufsiz = 2048; - char buf[bufsiz]; - -#if defined(_MSC_VER) && _MSC_VER < 1900 - auto n = _snprintf_s(buf, bufsiz, bufsiz - 1, fmt, args...); -#else - auto n = snprintf(buf, bufsiz - 1, fmt, args...); -#endif - if (n > 0) { - if (n >= bufsiz - 1) { - std::vector glowable_buf(bufsiz); - - while (n >= static_cast(glowable_buf.size() - 1)) { - glowable_buf.resize(glowable_buf.size() * 2); -#if defined(_MSC_VER) && _MSC_VER < 1900 - n = _snprintf_s(&glowable_buf[0], glowable_buf.size(), glowable_buf.size() - 1, fmt, args...); -#else - n = snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...); -#endif - } - write(&glowable_buf[0], n); - } else { - write(buf, n); - } - } -} - -// Socket stream implementation -inline SocketStream::SocketStream(socket_t sock): sock_(sock) -{ -} - -inline SocketStream::~SocketStream() -{ -} - -inline int SocketStream::read(char* ptr, size_t size) -{ - return recv(sock_, ptr, size, 0); -} - -inline int SocketStream::write(const char* ptr, size_t size) -{ - return send(sock_, ptr, size, 0); -} - -inline int SocketStream::write(const char* ptr) -{ - return write(ptr, strlen(ptr)); -} - -inline std::string SocketStream::get_remote_addr() const { - return detail::get_remote_addr(sock_); -} - -// Buffer stream implementation -inline int BufferStream::read(char* ptr, size_t size) -{ -#if defined(_MSC_VER) && _MSC_VER < 1900 - return static_cast(buffer._Copy_s(ptr, size, size)); -#else - return static_cast(buffer.copy(ptr, size)); -#endif -} - -inline int BufferStream::write(const char* ptr, size_t size) -{ - buffer.append(ptr, size); - return static_cast(size); -} - -inline int BufferStream::write(const char* ptr) -{ - size_t size = strlen(ptr); - buffer.append(ptr, size); - return static_cast(size); -} - -inline std::string BufferStream::get_remote_addr() const { - return ""; -} - -inline const std::string& BufferStream::get_buffer() const { - return buffer; -} - - -// HTTP server implementation -inline Server::Server() - : keep_alive_max_count_(5) - , is_running_(false) - , svr_sock_(INVALID_SOCKET) - , running_threads_(0) -{ -#ifndef _WIN32 - signal(SIGPIPE, SIG_IGN); -#endif -} - -inline Server::~Server() -{ -} - -inline Server& Server::Get(const char* pattern, Handler handler) -{ - get_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); - return *this; -} - -inline Server& Server::Post(const char* pattern, Handler handler) -{ - post_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); - return *this; -} - -inline Server& Server::Put(const char* pattern, Handler handler) -{ - put_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); - return *this; -} - -inline Server& Server::Delete(const char* pattern, Handler handler) -{ - delete_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); - return *this; -} - -inline Server& Server::Options(const char* pattern, Handler handler) -{ - options_handlers_.push_back(std::make_pair(std::regex(pattern), handler)); - return *this; -} - -inline bool Server::set_base_dir(const char* path) -{ - if (detail::is_dir(path)) { - base_dir_ = path; - return true; - } - return false; -} - -inline void Server::set_error_handler(Handler handler) -{ - error_handler_ = handler; -} - -inline void Server::set_logger(Logger logger) -{ - logger_ = logger; -} - -inline void Server::set_keep_alive_max_count(size_t count) -{ - keep_alive_max_count_ = count; -} - -inline int Server::bind_to_any_port(const char* host, int socket_flags) -{ - return bind_internal(host, 0, socket_flags); -} - -inline bool Server::listen_after_bind() { - return listen_internal(); -} - -inline bool Server::listen(const char* host, int port, int socket_flags) -{ - if (bind_internal(host, port, socket_flags) < 0) - return false; - return listen_internal(); -} - -inline bool Server::is_running() const -{ - return is_running_; -} - -inline void Server::stop() -{ - if (is_running_) { - assert(svr_sock_ != INVALID_SOCKET); - auto sock = svr_sock_; - svr_sock_ = INVALID_SOCKET; - detail::shutdown_socket(sock); - detail::close_socket(sock); - } -} - -inline bool Server::parse_request_line(const char* s, Request& req) -{ - static std::regex re("(GET|HEAD|POST|PUT|DELETE|OPTIONS) (([^?]+)(?:\\?(.+?))?) (HTTP/1\\.[01])\r\n"); - - std::cmatch m; - if (std::regex_match(s, m, re)) { - req.version = std::string(m[5]); - req.method = std::string(m[1]); - req.target = std::string(m[2]); - req.path = detail::decode_url(m[3]); - - // Parse query text - auto len = std::distance(m[4].first, m[4].second); - if (len > 0) { - detail::parse_query_text(m[4], req.params); - } - - return true; - } - - return false; -} - -inline void Server::write_response(Stream& strm, bool last_connection, const Request& req, Response& res) -{ - assert(res.status != -1); - - if (400 <= res.status && error_handler_) { - error_handler_(req, res); - } - - // Response line - strm.write_format("HTTP/1.1 %d %s\r\n", - res.status, - detail::status_message(res.status)); - - // Headers - if (last_connection || - req.get_header_value("Connection") == "close") { - res.set_header("Connection", "close"); - } - - if (!last_connection && - req.get_header_value("Connection") == "Keep-Alive") { - res.set_header("Connection", "Keep-Alive"); - } - - if (res.body.empty()) { - if (!res.has_header("Content-Length")) { - if (res.streamcb) { - // Streamed response - res.set_header("Transfer-Encoding", "chunked"); - } else { - res.set_header("Content-Length", "0"); - } - } - } else { -#ifdef CPPHTTPLIB_ZLIB_SUPPORT - // TODO: 'Accpet-Encoding' has gzip, not gzip;q=0 - const auto& encodings = req.get_header_value("Accept-Encoding"); - if (encodings.find("gzip") != std::string::npos && - detail::can_compress(res.get_header_value("Content-Type"))) { - detail::compress(res.body); - res.set_header("Content-Encoding", "gzip"); - } -#endif - - if (!res.has_header("Content-Type")) { - res.set_header("Content-Type", "text/plain"); - } - - auto length = std::to_string(res.body.size()); - res.set_header("Content-Length", length.c_str()); - } - - detail::write_headers(strm, res); - - // Body - if (req.method != "HEAD") { - if (!res.body.empty()) { - strm.write(res.body.c_str(), res.body.size()); - } else if (res.streamcb) { - bool chunked_response = !res.has_header("Content-Length"); - uint64_t offset = 0; - bool data_available = true; - while (data_available) { - std::string chunk = res.streamcb(offset); - offset += chunk.size(); - data_available = !chunk.empty(); - // Emit chunked response header and footer for each chunk - if (chunked_response) - chunk = detail::from_i_to_hex(chunk.size()) + "\r\n" + chunk + "\r\n"; - if (strm.write(chunk.c_str(), chunk.size()) < 0) - break; // Stop on error - } - } - } - - // Log - if (logger_) { - logger_(req, res); - } -} - -inline bool Server::handle_file_request(Request& req, Response& res) -{ - if (!base_dir_.empty() && detail::is_valid_path(req.path)) { - std::string path = base_dir_ + req.path; - - if (!path.empty() && path.back() == '/') { - path += "index.html"; - } - - if (detail::is_file(path)) { - detail::read_file(path, res.body); - auto type = detail::find_content_type(path); - if (type) { - res.set_header("Content-Type", type); - } - res.status = 200; - return true; - } - } - - return false; -} - -inline socket_t Server::create_server_socket(const char* host, int port, int socket_flags) const -{ - return detail::create_socket(host, port, - [](socket_t sock, struct addrinfo& ai) -> bool { - if (::bind(sock, ai.ai_addr, ai.ai_addrlen)) { - return false; - } - if (::listen(sock, 5)) { // Listen through 5 channels - return false; - } - return true; - }, socket_flags); -} - -inline int Server::bind_internal(const char* host, int port, int socket_flags) -{ - if (!is_valid()) { - return -1; - } - - svr_sock_ = create_server_socket(host, port, socket_flags); - if (svr_sock_ == INVALID_SOCKET) { - return -1; - } - - if (port == 0) { - struct sockaddr_storage address; - socklen_t len = sizeof(address); - if (getsockname(svr_sock_, reinterpret_cast(&address), &len) == -1) { - return -1; - } - if (address.ss_family == AF_INET) { - return ntohs(reinterpret_cast(&address)->sin_port); - } else if (address.ss_family == AF_INET6) { - return ntohs(reinterpret_cast(&address)->sin6_port); - } else { - return -1; - } - } else { - return port; - } -} - -inline bool Server::listen_internal() -{ - auto ret = true; - - is_running_ = true; - - for (;;) { - auto val = detail::select_read(svr_sock_, 0, 100000); - - if (val == 0) { // Timeout - if (svr_sock_ == INVALID_SOCKET) { - // The server socket was closed by 'stop' method. - break; - } - continue; - } - - socket_t sock = accept(svr_sock_, NULL, NULL); - - if (sock == INVALID_SOCKET) { - if (svr_sock_ != INVALID_SOCKET) { - detail::close_socket(svr_sock_); - ret = false; - } else { - ; // The server socket was closed by user. - } - break; - } - - // TODO: Use thread pool... - std::thread([=]() { - { - std::lock_guard guard(running_threads_mutex_); - running_threads_++; - } - - read_and_close_socket(sock); - - { - std::lock_guard guard(running_threads_mutex_); - running_threads_--; - } - }).detach(); - } - - // TODO: Use thread pool... - for (;;) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - std::lock_guard guard(running_threads_mutex_); - if (!running_threads_) { - break; - } - } - - is_running_ = false; - - return ret; -} - -inline bool Server::routing(Request& req, Response& res) -{ - if (req.method == "GET" && handle_file_request(req, res)) { - return true; - } - - if (req.method == "GET" || req.method == "HEAD") { - return dispatch_request(req, res, get_handlers_); - } else if (req.method == "POST") { - return dispatch_request(req, res, post_handlers_); - } else if (req.method == "PUT") { - return dispatch_request(req, res, put_handlers_); - } else if (req.method == "DELETE") { - return dispatch_request(req, res, delete_handlers_); - } else if (req.method == "OPTIONS") { - return dispatch_request(req, res, options_handlers_); - } - return false; -} - -inline bool Server::dispatch_request(Request& req, Response& res, Handlers& handlers) -{ - for (const auto& x: handlers) { - const auto& pattern = x.first; - const auto& handler = x.second; - - if (std::regex_match(req.path, req.matches, pattern)) { - handler(req, res); - return true; - } - } - return false; -} - -inline bool Server::process_request(Stream& strm, bool last_connection, bool& connection_close) -{ - const auto bufsiz = 2048; - char buf[bufsiz]; - - detail::stream_line_reader reader(strm, buf, bufsiz); - - // Connection has been closed on client - if (!reader.getline()) { - return false; - } - - Request req; - Response res; - - res.version = "HTTP/1.1"; - - // Request line and headers - if (!parse_request_line(reader.ptr(), req) || !detail::read_headers(strm, req.headers)) { - res.status = 400; - write_response(strm, last_connection, req, res); - return true; - } - - if (req.get_header_value("Connection") == "close") { - connection_close = true; - } - - req.set_header("REMOTE_ADDR", strm.get_remote_addr().c_str()); - - // Body - if (req.method == "POST" || req.method == "PUT") { - if (!detail::read_content(strm, req)) { - res.status = 400; - write_response(strm, last_connection, req, res); - return true; - } - - const auto& content_type = req.get_header_value("Content-Type"); - - if (req.get_header_value("Content-Encoding") == "gzip") { -#ifdef CPPHTTPLIB_ZLIB_SUPPORT - detail::decompress(req.body); -#else - res.status = 415; - write_response(strm, last_connection, req, res); - return true; -#endif - } - - if (!content_type.find("application/x-www-form-urlencoded")) { - detail::parse_query_text(req.body, req.params); - } else if(!content_type.find("multipart/form-data")) { - std::string boundary; - if (!detail::parse_multipart_boundary(content_type, boundary) || - !detail::parse_multipart_formdata(boundary, req.body, req.files)) { - res.status = 400; - write_response(strm, last_connection, req, res); - return true; - } - } - } - - if (routing(req, res)) { - if (res.status == -1) { - res.status = 200; - } - } else { - res.status = 404; - } - - write_response(strm, last_connection, req, res); - return true; -} - -inline bool Server::is_valid() const -{ - return true; -} - -inline bool Server::read_and_close_socket(socket_t sock) -{ - return detail::read_and_close_socket( - sock, - keep_alive_max_count_, - [this](Stream& strm, bool last_connection, bool& connection_close) { - return process_request(strm, last_connection, connection_close); - }); -} - -// HTTP client implementation -inline Client::Client( - const char* host, int port, time_t timeout_sec) - : host_(host) - , port_(port) - , timeout_sec_(timeout_sec) - , host_and_port_(host_ + ":" + std::to_string(port_)) -{ -} - -inline Client::~Client() -{ -} - -inline bool Client::is_valid() const -{ - return true; -} - -inline socket_t Client::create_client_socket() const -{ - return detail::create_socket(host_.c_str(), port_, - [=](socket_t sock, struct addrinfo& ai) -> bool { - detail::set_nonblocking(sock, true); - - auto ret = connect(sock, ai.ai_addr, ai.ai_addrlen); - if (ret < 0) { - if (detail::is_connection_error() || - !detail::wait_until_socket_is_ready(sock, timeout_sec_, 0)) { - detail::close_socket(sock); - return false; - } - } - - detail::set_nonblocking(sock, false); - return true; - }); -} - -inline bool Client::read_response_line(Stream& strm, Response& res) -{ - const auto bufsiz = 2048; - char buf[bufsiz]; - - detail::stream_line_reader reader(strm, buf, bufsiz); - - if (!reader.getline()) { - return false; - } - - const static std::regex re("(HTTP/1\\.[01]) (\\d+?) .*\r\n"); - - std::cmatch m; - if (std::regex_match(reader.ptr(), m, re)) { - res.version = std::string(m[1]); - res.status = std::stoi(std::string(m[2])); - } - - return true; -} - -inline bool Client::send(Request& req, Response& res) -{ - if (req.path.empty()) { - return false; - } - - auto sock = create_client_socket(); - if (sock == INVALID_SOCKET) { - return false; - } - - return read_and_close_socket(sock, req, res); -} - -inline void Client::write_request(Stream& strm, Request& req) -{ - BufferStream bstrm; - - // Request line - auto path = detail::encode_url(req.path); - - bstrm.write_format("%s %s HTTP/1.1\r\n", - req.method.c_str(), - path.c_str()); - - // Headers - if (is_ssl()) { - if (port_ == 443) { - req.set_header("Host", host_.c_str()); - } else { - req.set_header("Host", host_and_port_.c_str()); - } - } else { - if (port_ == 80) { - req.set_header("Host", host_.c_str()); - } else { - req.set_header("Host", host_and_port_.c_str()); - } - } - - if (!req.has_header("Accept")) { - req.set_header("Accept", "*/*"); - } - - if (!req.has_header("User-Agent")) { - req.set_header("User-Agent", "cpp-httplib/0.2"); - } - - // TODO: Support KeepAlive connection - // if (!req.has_header("Connection")) { - req.set_header("Connection", "close"); - // } - - if (req.body.empty()) { - if (req.method == "POST" || req.method == "PUT") { - req.set_header("Content-Length", "0"); - } - } else { - if (!req.has_header("Content-Type")) { - req.set_header("Content-Type", "text/plain"); - } - - auto length = std::to_string(req.body.size()); - req.set_header("Content-Length", length.c_str()); - } - - detail::write_headers(bstrm, req); - - // Body - if (!req.body.empty()) { - bstrm.write(req.body.c_str(), req.body.size()); - } - - // Flush buffer - auto& data = bstrm.get_buffer(); - strm.write(data.data(), data.size()); -} - -inline bool Client::process_request(Stream& strm, Request& req, Response& res, bool& connection_close) -{ - // Send request - write_request(strm, req); - - // Receive response and headers - if (!read_response_line(strm, res) || !detail::read_headers(strm, res.headers)) { - return false; - } - - if (res.get_header_value("Connection") == "close" || res.version == "HTTP/1.0") { - connection_close = true; - } - - // Body - if (req.method != "HEAD") { - if (!detail::read_content(strm, res, req.progress)) { - return false; - } - - if (res.get_header_value("Content-Encoding") == "gzip") { -#ifdef CPPHTTPLIB_ZLIB_SUPPORT - detail::decompress(res.body); -#else - return false; -#endif - } - } - - return true; -} - -inline bool Client::read_and_close_socket(socket_t sock, Request& req, Response& res) -{ - return detail::read_and_close_socket( - sock, - 0, - [&](Stream& strm, bool /*last_connection*/, bool& connection_close) { - return process_request(strm, req, res, connection_close); - }); -} - -inline bool Client::is_ssl() const -{ - return false; -} - -inline std::shared_ptr Client::Get(const char* path, Progress progress) -{ - return Get(path, Headers(), progress); -} - -inline std::shared_ptr Client::Get(const char* path, const Headers& headers, Progress progress) -{ - Request req; - req.method = "GET"; - req.path = path; - req.headers = headers; - req.progress = progress; - - auto res = std::make_shared(); - - return send(req, *res) ? res : nullptr; -} - -inline std::shared_ptr Client::Head(const char* path) -{ - return Head(path, Headers()); -} - -inline std::shared_ptr Client::Head(const char* path, const Headers& headers) -{ - Request req; - req.method = "HEAD"; - req.headers = headers; - req.path = path; - - auto res = std::make_shared(); - - return send(req, *res) ? res : nullptr; -} - -inline std::shared_ptr Client::Post( - const char* path, const std::string& body, const char* content_type) -{ - return Post(path, Headers(), body, content_type); -} - -inline std::shared_ptr Client::Post( - const char* path, const Headers& headers, const std::string& body, const char* content_type) -{ - Request req; - req.method = "POST"; - req.headers = headers; - req.path = path; - - req.headers.emplace("Content-Type", content_type); - req.body = body; - - auto res = std::make_shared(); - - return send(req, *res) ? res : nullptr; -} - -inline std::shared_ptr Client::Post(const char* path, const Params& params) -{ - return Post(path, Headers(), params); -} - -inline std::shared_ptr Client::Post(const char* path, const Headers& headers, const Params& params) -{ - std::string query; - for (auto it = params.begin(); it != params.end(); ++it) { - if (it != params.begin()) { - query += "&"; - } - query += it->first; - query += "="; - query += it->second; - } - - return Post(path, headers, query, "application/x-www-form-urlencoded"); -} - -inline std::shared_ptr Client::Put( - const char* path, const std::string& body, const char* content_type) -{ - return Put(path, Headers(), body, content_type); -} - -inline std::shared_ptr Client::Put( - const char* path, const Headers& headers, const std::string& body, const char* content_type) -{ - Request req; - req.method = "PUT"; - req.headers = headers; - req.path = path; - - req.headers.emplace("Content-Type", content_type); - req.body = body; - - auto res = std::make_shared(); - - return send(req, *res) ? res : nullptr; -} - -inline std::shared_ptr Client::Delete(const char* path) -{ - return Delete(path, Headers()); -} - -inline std::shared_ptr Client::Delete(const char* path, const Headers& headers) -{ - Request req; - req.method = "DELETE"; - req.path = path; - req.headers = headers; - - auto res = std::make_shared(); - - return send(req, *res) ? res : nullptr; -} - -inline std::shared_ptr Client::Options(const char* path) -{ - return Options(path, Headers()); -} - -inline std::shared_ptr Client::Options(const char* path, const Headers& headers) -{ - Request req; - req.method = "OPTIONS"; - req.path = path; - req.headers = headers; - - auto res = std::make_shared(); - - return send(req, *res) ? res : nullptr; -} - -/* - * SSL Implementation - */ -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT -namespace detail { - -template -inline bool read_and_close_socket_ssl( - socket_t sock, size_t keep_alive_max_count, - // TODO: OpenSSL 1.0.2 occasionally crashes... - // The upcoming 1.1.0 is going to be thread safe. - SSL_CTX* ctx, std::mutex& ctx_mutex, - U SSL_connect_or_accept, V setup, - T callback) -{ - SSL* ssl = nullptr; - { - std::lock_guard guard(ctx_mutex); - - ssl = SSL_new(ctx); - if (!ssl) { - return false; - } - } - - auto bio = BIO_new_socket(sock, BIO_NOCLOSE); - SSL_set_bio(ssl, bio, bio); - - setup(ssl); - - SSL_connect_or_accept(ssl); - - bool ret = false; - - if (keep_alive_max_count > 0) { - auto count = keep_alive_max_count; - while (count > 0 && - detail::select_read(sock, - CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND, - CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0) { - SSLSocketStream strm(sock, ssl); - auto last_connection = count == 1; - auto connection_close = false; - - ret = callback(strm, last_connection, connection_close); - if (!ret || connection_close) { - break; - } - - count--; - } - } else { - SSLSocketStream strm(sock, ssl); - auto dummy_connection_close = false; - ret = callback(strm, true, dummy_connection_close); - } - - SSL_shutdown(ssl); - - { - std::lock_guard guard(ctx_mutex); - SSL_free(ssl); - } - - close_socket(sock); - - return ret; -} - -class SSLInit { -public: - SSLInit() { - SSL_load_error_strings(); - SSL_library_init(); - } -}; - -static SSLInit sslinit_; - -} // namespace detail - -// SSL socket stream implementation -inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL* ssl) - : sock_(sock), ssl_(ssl) -{ -} - -inline SSLSocketStream::~SSLSocketStream() -{ -} - -inline int SSLSocketStream::read(char* ptr, size_t size) -{ - return SSL_read(ssl_, ptr, size); -} - -inline int SSLSocketStream::write(const char* ptr, size_t size) -{ - return SSL_write(ssl_, ptr, size); -} - -inline int SSLSocketStream::write(const char* ptr) -{ - return write(ptr, strlen(ptr)); -} - -inline std::string SSLSocketStream::get_remote_addr() const { - return detail::get_remote_addr(sock_); -} - -// SSL HTTP server implementation -inline SSLServer::SSLServer(const char* cert_path, const char* private_key_path) -{ - ctx_ = SSL_CTX_new(SSLv23_server_method()); - - if (ctx_) { - SSL_CTX_set_options(ctx_, - SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | - SSL_OP_NO_COMPRESSION | - SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); - - // auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); - // SSL_CTX_set_tmp_ecdh(ctx_, ecdh); - // EC_KEY_free(ecdh); - - if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || - SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != 1) { - SSL_CTX_free(ctx_); - ctx_ = nullptr; - } - } -} - -inline SSLServer::~SSLServer() -{ - if (ctx_) { - SSL_CTX_free(ctx_); - } -} - -inline bool SSLServer::is_valid() const -{ - return ctx_; -} - -inline bool SSLServer::read_and_close_socket(socket_t sock) -{ - return detail::read_and_close_socket_ssl( - sock, - keep_alive_max_count_, - ctx_, ctx_mutex_, - SSL_accept, - [](SSL* /*ssl*/) {}, - [this](Stream& strm, bool last_connection, bool& connection_close) { - return process_request(strm, last_connection, connection_close); - }); -} - -// SSL HTTP client implementation -inline SSLClient::SSLClient(const char* host, int port, time_t timeout_sec) - : Client(host, port, timeout_sec) -{ - ctx_ = SSL_CTX_new(SSLv23_client_method()); -} - -inline SSLClient::~SSLClient() -{ - if (ctx_) { - SSL_CTX_free(ctx_); - } -} - -inline bool SSLClient::is_valid() const -{ - return ctx_; -} - -inline bool SSLClient::read_and_close_socket(socket_t sock, Request& req, Response& res) -{ - return is_valid() && detail::read_and_close_socket_ssl( - sock, 0, - ctx_, ctx_mutex_, - SSL_connect, - [&](SSL* ssl) { - SSL_set_tlsext_host_name(ssl, host_.c_str()); - }, - [&](Stream& strm, bool /*last_connection*/, bool& connection_close) { - return process_request(strm, req, res, connection_close); - }); -} - -inline bool SSLClient::is_ssl() const -{ - return true; -} -#endif - -} // namespace httplib - -#endif //CPPHTTPLIB_HTTPLIB_H - -// vim: et ts=4 sw=4 cin cino={1s ff=unix diff --git a/lint/peglint.cc b/lint/peglint.cc index fc5ea2f..e3cd3a7 100644 --- a/lint/peglint.cc +++ b/lint/peglint.cc @@ -10,8 +10,6 @@ using namespace std; -int run_server(int port, const vector& syntax, const vector& source); - bool read_file(const char* path, vector& buff) { ifstream ifs(path, ios::in | ios::binary); @@ -33,8 +31,6 @@ int main(int argc, const char** argv) auto opt_help = false; auto opt_source = false; vector source; - auto opt_server = false; - int port = 1234; auto opt_trace = false; vector path_list; @@ -53,11 +49,6 @@ int main(int argc, const char** argv) std::string text = argv[argi++]; source.assign(text.begin(), text.end()); } - } else if (string("--server") == arg) { - opt_server = true; - if (argi < argc) { - port = std::stoi(argv[argi++]); - } } else if (string("--trace") == arg) { opt_trace = true; } else { @@ -65,28 +56,11 @@ int main(int argc, const char** argv) } } - if ((path_list.empty() && !opt_server) || opt_help) { - cerr << "usage: peglint [--ast] [--optimize_ast_nodes|--opt] [--source text] [--server [PORT]] [--trace] [grammar file path] [source file path]" << endl; + if (path_list.empty() || opt_help) { + cerr << "usage: peglint [--ast] [--optimize_ast_nodes|--opt] [--source text] [--trace] [grammar file path] [source file path]" << endl; return 1; } - // Sever mode - if (opt_server) { - vector syntax; - - if (path_list.size() >= 1 && !read_file(path_list[0], syntax)) { - cerr << "can't open the grammar file." << endl; - return -1; - } - - if (path_list.size() >= 2 && !read_file(path_list[1], source)) { - cerr << "can't open the code file." << endl; - return -1; - } - - return run_server(port, syntax, source); - } - // Check PEG grammar auto syntax_path = path_list[0]; diff --git a/lint/server.cc b/lint/server.cc deleted file mode 100644 index 9aa2444..0000000 --- a/lint/server.cc +++ /dev/null @@ -1,371 +0,0 @@ -#include "httplib.h" -#include "peglib.h" -#include -#include -#include -#include - -using namespace httplib; -using namespace std; - -static string indexHTML = R"( - - - -PEG Playground - - - - -
-
-
    -
  • Grammar:
  • -
  • Valid
  • -
-
{{syntax}}
-
-
-
-
    -
  • Code:
  • -
  • Valid
  • -
-
{{source}}
-

-        

-        
-
-
- - - - - - -)"; - -// https://stackoverflow.com/questions/7724448/simple-json-string-escape-for-c/33799784#33799784 -std::string escape_json(const std::string &s) { - std::ostringstream o; - for (auto c : s) { - if (c == '"' || c == '\\' || ('\x00' <= c && c <= '\x1f')) { - o << "\\u" - << std::hex << std::setw(4) << std::setfill('0') << (int)c; - } else { - o << c; - } - } - return o.str(); -} - -function makeJSONFormatter(string& json) -{ - auto init = make_shared(true); - - return [&json, init](size_t ln, size_t col, const string& msg) mutable { - if (!init) { - json += ","; - } - json += "{"; - json += R"("ln":)" + to_string(ln) + ","; - json += R"("col":)" + to_string(col) + ","; - json += R"("msg":")" + escape_json(msg) + R"(")"; - json += "}"; - *init = false; - }; -} - -bool parse_grammar(const string& text, peg::parser& peg, string& json) -{ - peg.log = makeJSONFormatter(json); - json += "["; - auto ret = peg.load_grammar(text.data(), text.size()); - json += "]"; - return ret; -} - -bool parse_code(const string& text, peg::parser& peg, string& json, shared_ptr& ast) -{ - peg.enable_ast(); - peg.log = makeJSONFormatter(json); - json += "["; - auto ret = peg.parse_n(text.data(), text.size(), ast); - json += "]"; - return ret; -} - -string replace_all(const string& str, const char* from, const char* to) -{ - string ret; - ret.reserve(str.length()); - - size_t from_len = 0; - while (from[from_len]) { - from_len++; - } - - size_t start_pos = 0, pos; - while ((pos = str.find(from, start_pos)) != string::npos) { - ret += str.substr(start_pos, pos - start_pos); - ret += to; - pos += from_len; - start_pos = pos; - } - ret += str.substr(start_pos); - return ret; -} - -int run_server(int port, const vector& syntax, const vector& source) -{ - Server svr; - - svr.Get("/", [&](const Request& /*req*/, Response& res) { - indexHTML = replace_all(indexHTML, "{{syntax}}", string(syntax.data(), syntax.size()).c_str()); - indexHTML = replace_all(indexHTML, "{{source}}", string(source.data(), source.size()).c_str()); - - res.set_content(indexHTML, "text/html"); - }); - - svr.Post("/parse", [](const Request& req, Response& res) { - const auto& grammarText = req.get_param_value("grammar"); - - string grammarResult; - string codeResult; - string astResult; - string astResultOptimized; - - peg::parser peg; - auto ret = parse_grammar(grammarText, peg, grammarResult); - - if (ret && peg) { - const auto& codeText = req.get_param_value("code"); - shared_ptr ast; - if (parse_code(codeText, peg, codeResult, ast)) { - astResult = escape_json(peg::ast_to_s(ast)); - astResultOptimized = escape_json(peg::ast_to_s(peg::AstOptimizer(true).optimize(ast))); - } - } - - string json; - json += "{"; - json += "\"grammar\":" + grammarResult; - if (!codeResult.empty()) { - json += ",\"code\":" + codeResult; - json += ",\"ast\":\"" + astResult + "\""; - json += ",\"astOptimized\":\"" + astResultOptimized + "\""; - } - json += "}"; - - res.set_content(json, "application/json"); - }); - - svr.set_error_handler([](const Request& /*req*/, Response& res) { - const char* fmt = "

Error Status: %d

"; - char buf[BUFSIZ]; - snprintf(buf, sizeof(buf), fmt, res.status); - res.set_content(buf, "text/html"); - }); - - cerr << "Server running at http://localhost:" << port << "/" << endl; - svr.listen("localhost", port); - - return 0; -} - -// vim: et ts=4 sw=4 cin cino={1s ff=unix