// SPDX-License-Identifier: BSD-3-Clause AND ISC AND MIT /*BSD 3-Clause License Copyright (c) 2014-2017, ipkn 2020-2026, CrowCpp All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The Crow logo and other graphic material (excluding third party logos) used are under exclusive Copyright (c) 2021-2022, Farook Al-Sammarraie (The-EDev), All rights reserved. */ #pragma once #ifdef CROW_ENABLE_COMPRESSION #include #include // http://zlib.net/manual.html namespace crow // NOTE: Already documented in "crow/app.h" { namespace compression { // Values used in the 'windowBits' parameter for deflateInit2. enum algorithm { // 15 is the default value for deflate DEFLATE = 15, // windowBits can also be greater than 15 for optional gzip encoding. // Add 16 to windowBits to write a simple gzip header and trailer around the // compressed data instead of a zlib wrapper. GZIP = 15 | 16, }; inline std::string compress_string(std::string const &str, algorithm algo) { std::string compressed_str; z_stream stream{}; // Initialize with the default values if (::deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, algo, 8, Z_DEFAULT_STRATEGY) == Z_OK) { char buffer[8192]; stream.avail_in = str.size(); // zlib does not take a const pointer. The data is not altered. stream.next_in = const_cast(reinterpret_cast(str.c_str())); int code = Z_OK; do { stream.avail_out = sizeof(buffer); stream.next_out = reinterpret_cast(&buffer[0]); code = ::deflate(&stream, Z_FINISH); // Successful and non-fatal error code returned by deflate when used with // Z_FINISH flush if (code == Z_OK || code == Z_STREAM_END) { std::copy(&buffer[0], &buffer[sizeof(buffer) - stream.avail_out], std::back_inserter(compressed_str)); } } while (code == Z_OK); if (code != Z_STREAM_END) compressed_str.clear(); ::deflateEnd(&stream); } return compressed_str; } inline std::string decompress_string(std::string const &deflated_string) { std::string inflated_string; Bytef tmp[8192]; z_stream zstream{}; zstream.avail_in = deflated_string.size(); // Nasty const_cast but zlib won't alter its contents zstream.next_in = const_cast( reinterpret_cast(deflated_string.c_str())); // Initialize with automatic header detection, for gzip support if (::inflateInit2(&zstream, MAX_WBITS | 32) == Z_OK) { do { zstream.avail_out = sizeof(tmp); zstream.next_out = &tmp[0]; auto ret = ::inflate(&zstream, Z_NO_FLUSH); if (ret == Z_OK || ret == Z_STREAM_END) { std::copy(&tmp[0], &tmp[sizeof(tmp) - zstream.avail_out], std::back_inserter(inflated_string)); } else { // Something went wrong with inflate; make sure we return an empty // string inflated_string.clear(); break; } } while (zstream.avail_out == 0); // Free zlib's internal memory ::inflateEnd(&zstream); } return inflated_string; } } // namespace compression } // namespace crow #endif namespace crow { constexpr const char VERSION[] = "master"; } /* * SHA1 Wikipedia Page: http://en.wikipedia.org/wiki/SHA-1 * * Copyright (c) 2012-22 SAURAV MOHAPATRA * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /** * \file TinySHA1.hpp * \author SAURAV MOHAPATRA * \date 2012-22 * \brief TinySHA1 - a header only implementation of the SHA1 algorithm in C++. * Based on the implementation in boost::uuid::details. * * In this file are defined: * - sha1::SHA1 */ #ifndef _TINY_SHA1_HPP_ #define _TINY_SHA1_HPP_ #include #include #include #include /** * \namespace sha1 * \brief Here is defined the SHA1 class */ namespace sha1 { /** * \class SHA1 * \brief A tiny SHA1 algorithm implementation used internally in the * Crow server (specifically in crow/websocket.h). */ class SHA1 { public: typedef uint32_t digest32_t[5]; typedef uint8_t digest8_t[20]; inline static uint32_t LeftRotate(uint32_t value, size_t count) { return (value << count) ^ (value >> (32 - count)); } SHA1() { reset(); } virtual ~SHA1() {} SHA1(const SHA1 &s) { *this = s; } const SHA1 &operator=(const SHA1 &s) { memcpy(m_digest, s.m_digest, 5 * sizeof(uint32_t)); memcpy(m_block, s.m_block, 64); m_blockByteIndex = s.m_blockByteIndex; m_byteCount = s.m_byteCount; return *this; } SHA1 &reset() { m_digest[0] = 0x67452301; m_digest[1] = 0xEFCDAB89; m_digest[2] = 0x98BADCFE; m_digest[3] = 0x10325476; m_digest[4] = 0xC3D2E1F0; m_blockByteIndex = 0; m_byteCount = 0; return *this; } SHA1 &processByte(uint8_t octet) { this->m_block[this->m_blockByteIndex++] = octet; ++this->m_byteCount; if (m_blockByteIndex == 64) { this->m_blockByteIndex = 0; processBlock(); } return *this; } SHA1 &processBlock(const void *const start, const void *const end) { const uint8_t *begin = static_cast(start); const uint8_t *finish = static_cast(end); while (begin != finish) { processByte(*begin); begin++; } return *this; } SHA1 &processBytes(const void *const data, size_t len) { const uint8_t *block = static_cast(data); processBlock(block, block + len); return *this; } const uint32_t *getDigest(digest32_t digest) { size_t bitCount = this->m_byteCount * 8; processByte(0x80); if (this->m_blockByteIndex > 56) { while (m_blockByteIndex != 0) { processByte(0); } while (m_blockByteIndex < 56) { processByte(0); } } else { while (m_blockByteIndex < 56) { processByte(0); } } processByte(0); processByte(0); processByte(0); processByte(0); processByte(static_cast((bitCount >> 24) & 0xFF)); processByte(static_cast((bitCount >> 16) & 0xFF)); processByte(static_cast((bitCount >> 8) & 0xFF)); processByte(static_cast((bitCount) & 0xFF)); memcpy(digest, m_digest, 5 * sizeof(uint32_t)); return digest; } const uint8_t *getDigestBytes(digest8_t digest) { digest32_t d32; getDigest(d32); size_t di = 0; digest[di++] = ((d32[0] >> 24) & 0xFF); digest[di++] = ((d32[0] >> 16) & 0xFF); digest[di++] = ((d32[0] >> 8) & 0xFF); digest[di++] = ((d32[0]) & 0xFF); digest[di++] = ((d32[1] >> 24) & 0xFF); digest[di++] = ((d32[1] >> 16) & 0xFF); digest[di++] = ((d32[1] >> 8) & 0xFF); digest[di++] = ((d32[1]) & 0xFF); digest[di++] = ((d32[2] >> 24) & 0xFF); digest[di++] = ((d32[2] >> 16) & 0xFF); digest[di++] = ((d32[2] >> 8) & 0xFF); digest[di++] = ((d32[2]) & 0xFF); digest[di++] = ((d32[3] >> 24) & 0xFF); digest[di++] = ((d32[3] >> 16) & 0xFF); digest[di++] = ((d32[3] >> 8) & 0xFF); digest[di++] = ((d32[3]) & 0xFF); digest[di++] = ((d32[4] >> 24) & 0xFF); digest[di++] = ((d32[4] >> 16) & 0xFF); digest[di++] = ((d32[4] >> 8) & 0xFF); digest[di++] = ((d32[4]) & 0xFF); return digest; } protected: void processBlock() { uint32_t w[80]; for (size_t i = 0; i < 16; i++) { w[i] = (m_block[i * 4 + 0] << 24); w[i] |= (m_block[i * 4 + 1] << 16); w[i] |= (m_block[i * 4 + 2] << 8); w[i] |= (m_block[i * 4 + 3]); } for (size_t i = 16; i < 80; i++) { w[i] = LeftRotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1); } uint32_t a = m_digest[0]; uint32_t b = m_digest[1]; uint32_t c = m_digest[2]; uint32_t d = m_digest[3]; uint32_t e = m_digest[4]; for (std::size_t i = 0; i < 80; ++i) { uint32_t f = 0; uint32_t k = 0; if (i < 20) { f = (b & c) | (~b & d); k = 0x5A827999; } else if (i < 40) { f = b ^ c ^ d; k = 0x6ED9EBA1; } else if (i < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8F1BBCDC; } else { f = b ^ c ^ d; k = 0xCA62C1D6; } uint32_t temp = LeftRotate(a, 5) + f + e + k + w[i]; e = d; d = c; c = LeftRotate(b, 30); b = a; a = temp; } m_digest[0] += a; m_digest[1] += b; m_digest[2] += c; m_digest[3] += d; m_digest[4] += e; } private: digest32_t m_digest; uint8_t m_block[64]; size_t m_blockByteIndex; size_t m_byteCount; }; } // namespace sha1 #endif #include #include #include #include #include #include #include namespace crow { // ---------------------------------------------------------------------------- // 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. */ size_t qs_parse(char *qs, char *qs_kv[], size_t qs_kv_size, bool parse_url); /* 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, size_t 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 , 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) { unsigned char u1, u2, unyb, lnyb; while (n-- > 0) { u1 = static_cast(*s++); u2 = static_cast(*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 = static_cast(*s++); lnyb = static_cast(*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 = static_cast(*qs++); lnyb = static_cast(*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; } if (CROW_QS_ISQSCHR(*qs)) return -1; else return 0; } inline size_t qs_parse(char *qs, char *qs_kv[], size_t qs_kv_size, bool parse_url = true) { size_t 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 = parse_url ? qs + strcspn(qs, "?#") : qs; if (parse_url) { 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') { i++; break; } // x &'s -> means x iterations of this loop -> means *x+1* k/v pairs substr_ptr += j + 1; i++; } // 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, size_t qs_kv_size, int nth = 0) { size_t 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 nullptr; } inline std::unique_ptr> qs_dict_name2kv(const char *dict_name, char *const *qs_kv, size_t qs_kv_size, int nth = 0) { size_t i; size_t name_len, skip_to_eq, skip_to_brace_open, skip_to_brace_close; name_len = strlen(dict_name); #ifdef _qsSORTING // TODO: binary search for key in the sorted qs_kv #else // _qsSORTING for (i = 0; i < qs_kv_size; i++) { if (strncmp(dict_name, qs_kv[i], name_len) == 0) { skip_to_eq = strcspn(qs_kv[i], "="); if (qs_kv[i][skip_to_eq] == '=') skip_to_eq++; skip_to_brace_open = strcspn(qs_kv[i], "["); if (qs_kv[i][skip_to_brace_open] == '[') skip_to_brace_open++; skip_to_brace_close = strcspn(qs_kv[i], "]"); if (skip_to_brace_open <= skip_to_brace_close && skip_to_brace_open > 0 && skip_to_brace_close > 0 && nth == 0) { auto key = std::string(qs_kv[i] + skip_to_brace_open, skip_to_brace_close - skip_to_brace_open); auto value = std::string(qs_kv[i] + skip_to_eq); return std::unique_ptr>( new std::pair(key, value)); } else { --nth; } } } #endif // _qsSORTING return nullptr; } inline char *qs_scanvalue(const char *key, const char *qs, char *val, size_t val_len) { const char *tmp = strchr(qs, '?'); // find the beginning of the k/v substrings if (tmp != nullptr) qs = tmp + 1; const size_t key_len = strlen(key); while (*qs != '#' && *qs != '\0') { if (qs_strncmp(key, qs, key_len) == 0) break; qs += strcspn(qs, "&"); if (*qs == '&') qs++; } if (qs[0] == '\0') return nullptr; qs += strcspn(qs, "=&#"); if (qs[0] == '=') { qs++; size_t i = strcspn(qs, "&=#"); #ifdef _MSC_VER strncpy_s(val, val_len, qs, (val_len - 1) < (i + 1) ? (val_len - 1) : (i + 1)); #else strncpy(val, qs, (val_len - 1) < (i + 1) ? (val_len - 1) : (i + 1)); #endif qs_decode(val); } else { if (val_len > 0) val[0] = '\0'; } return val; } } // namespace crow // ---------------------------------------------------------------------------- namespace crow { struct request; /// A class to represent any data coming after the `?` in the request URL into /// key-value pairs. class query_string { public: static const int MAX_KEY_VALUE_PAIRS_COUNT = 256; query_string() = default; 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) noexcept { 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 params, bool url = true) : url_(std::move(params)) { if (url_.empty()) return; key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT); size_t count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT, url); key_value_pairs_.resize(count); key_value_pairs_.shrink_to_fit(); } 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; } /// Get a value from a name, used for `?name=value`. /// /// Note: this method returns the value of the first occurrence of the key /// only, to return all occurrences, see \ref get_list(). char *get(const std::string &name) const { char *ret = qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size()); return ret; } /// Works similar to \ref get() except it removes the item from the query /// string. char *pop(const std::string &name) { char *ret = get(name); if (ret != nullptr) { const std::string key_name = name + '='; for (unsigned int i = 0; i < key_value_pairs_.size(); i++) { std::string str_item(key_value_pairs_[i]); if (str_item.find(key_name) == 0) { key_value_pairs_.erase(key_value_pairs_.begin() + i); break; } } } return ret; } /// Returns a list of values, passed as /// `?name[]=value1&name[]=value2&...name[]=valuen` with n being the size of /// the list. /// /// Note: Square brackets in the above example are controlled by /// `use_brackets` boolean (true by default). If set to false, the example /// becomes `?name=value1,name=value2...name=valuen` std::vector get_list(const std::string &name, bool use_brackets = true) const { std::vector ret; std::string plus = name + (use_brackets ? "[]" : ""); 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; } /// Similar to \ref get_list() but it removes the std::vector pop_list(const std::string &name, bool use_brackets = true) { std::vector ret = get_list(name, use_brackets); const size_t name_len = name.length(); if (!ret.empty()) { for (unsigned int i = 0; i < key_value_pairs_.size(); i++) { std::string str_item(key_value_pairs_[i]); if (str_item.find(name) == 0) { if (use_brackets && str_item.find("[]=", name_len) == name_len) { key_value_pairs_.erase(key_value_pairs_.begin() + i--); } else if (!use_brackets && str_item.find('=', name_len) == name_len) { key_value_pairs_.erase(key_value_pairs_.begin() + i--); } } } } return ret; } /// Works similar to \ref get_list() except the brackets are mandatory must /// not be empty. /// /// For example calling `get_dict(yourname)` on /// `?yourname[sub1]=42&yourname[sub2]=84` would give a map containing `{sub1 /// : 42, sub2 : 84}`. /// /// if your query string has both empty brackets and ones with a key inside, /// use pop_list() to get all the values without a key before running this /// method. std::unordered_map get_dict(const std::string &name) const { std::unordered_map ret; int count = 0; while (1) { if (auto element = qs_dict_name2kv(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++)) ret.insert(*element); else break; } return ret; } /// Works the same as \ref get_dict() but removes the values from the query /// string. std::unordered_map pop_dict(const std::string &name) { const std::string name_value = name + '['; std::unordered_map ret = get_dict(name); if (!ret.empty()) { for (unsigned int i = 0; i < key_value_pairs_.size(); i++) { std::string str_item(key_value_pairs_[i]); if (str_item.find(name_value) == 0) { key_value_pairs_.erase(key_value_pairs_.begin() + i--); } } } return ret; } std::vector keys() const { std::vector keys; keys.reserve(key_value_pairs_.size()); for (const char *const element : key_value_pairs_) { const char *delimiter = strchr(element, '='); if (delimiter) keys.emplace_back(element, delimiter); else keys.emplace_back(element); } return keys; } private: std::string url_; std::vector key_value_pairs_; }; } // namespace crow // This file is generated from nginx/conf/mime.types using nginx_mime2cpp.py on // 2021-12-03. #include #include namespace crow { const std::unordered_map mime_types{ {"gz", "application/gzip"}, {"shtml", "text/html"}, {"htm", "text/html"}, {"html", "text/html"}, {"css", "text/css"}, {"xml", "text/xml"}, {"gif", "image/gif"}, {"jpg", "image/jpeg"}, {"jpeg", "image/jpeg"}, {"js", "application/javascript"}, {"atom", "application/atom+xml"}, {"rss", "application/rss+xml"}, {"mml", "text/mathml"}, {"txt", "text/plain"}, {"jad", "text/vnd.sun.j2me.app-descriptor"}, {"wml", "text/vnd.wap.wml"}, {"htc", "text/x-component"}, {"avif", "image/avif"}, {"png", "image/png"}, {"svgz", "image/svg+xml"}, {"svg", "image/svg+xml"}, {"tiff", "image/tiff"}, {"tif", "image/tiff"}, {"wbmp", "image/vnd.wap.wbmp"}, {"webp", "image/webp"}, {"ico", "image/x-icon"}, {"jng", "image/x-jng"}, {"bmp", "image/x-ms-bmp"}, {"woff", "font/woff"}, {"woff2", "font/woff2"}, {"ear", "application/java-archive"}, {"war", "application/java-archive"}, {"jar", "application/java-archive"}, {"json", "application/json"}, {"hqx", "application/mac-binhex40"}, {"doc", "application/msword"}, {"pdf", "application/pdf"}, {"ai", "application/postscript"}, {"eps", "application/postscript"}, {"ps", "application/postscript"}, {"rtf", "application/rtf"}, {"m3u8", "application/vnd.apple.mpegurl"}, {"kml", "application/vnd.google-earth.kml+xml"}, {"kmz", "application/vnd.google-earth.kmz"}, {"xls", "application/vnd.ms-excel"}, {"eot", "application/vnd.ms-fontobject"}, {"ppt", "application/vnd.ms-powerpoint"}, {"odg", "application/vnd.oasis.opendocument.graphics"}, {"odp", "application/vnd.oasis.opendocument.presentation"}, {"ods", "application/vnd.oasis.opendocument.spreadsheet"}, {"odt", "application/vnd.oasis.opendocument.text"}, {"pptx", "application/" "vnd.openxmlformats-officedocument.presentationml.presentation"}, {"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, {"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, {"wmlc", "application/vnd.wap.wmlc"}, {"wasm", "application/wasm"}, {"7z", "application/x-7z-compressed"}, {"cco", "application/x-cocoa"}, {"jardiff", "application/x-java-archive-diff"}, {"jnlp", "application/x-java-jnlp-file"}, {"run", "application/x-makeself"}, {"pm", "application/x-perl"}, {"pl", "application/x-perl"}, {"pdb", "application/x-pilot"}, {"prc", "application/x-pilot"}, {"rar", "application/x-rar-compressed"}, {"rpm", "application/x-redhat-package-manager"}, {"sea", "application/x-sea"}, {"swf", "application/x-shockwave-flash"}, {"sit", "application/x-stuffit"}, {"tk", "application/x-tcl"}, {"tcl", "application/x-tcl"}, {"crt", "application/x-x509-ca-cert"}, {"pem", "application/x-x509-ca-cert"}, {"der", "application/x-x509-ca-cert"}, {"xpi", "application/x-xpinstall"}, {"xhtml", "application/xhtml+xml"}, {"xspf", "application/xspf+xml"}, {"zip", "application/zip"}, {"dll", "application/octet-stream"}, {"exe", "application/octet-stream"}, {"bin", "application/octet-stream"}, {"deb", "application/octet-stream"}, {"dmg", "application/octet-stream"}, {"img", "application/octet-stream"}, {"iso", "application/octet-stream"}, {"msm", "application/octet-stream"}, {"msp", "application/octet-stream"}, {"msi", "application/octet-stream"}, {"kar", "audio/midi"}, {"midi", "audio/midi"}, {"mid", "audio/midi"}, {"mp3", "audio/mpeg"}, {"ogg", "audio/ogg"}, {"m4a", "audio/x-m4a"}, {"ra", "audio/x-realaudio"}, {"3gp", "video/3gpp"}, {"3gpp", "video/3gpp"}, {"ts", "video/mp2t"}, {"mp4", "video/mp4"}, {"mpg", "video/mpeg"}, {"mpeg", "video/mpeg"}, {"mov", "video/quicktime"}, {"webm", "video/webm"}, {"flv", "video/x-flv"}, {"m4v", "video/x-m4v"}, {"mng", "video/x-mng"}, {"asf", "video/x-ms-asf"}, {"asx", "video/x-ms-asf"}, {"wmv", "video/x-ms-wmv"}, {"avi", "video/x-msvideo"}}; } // settings for crow // TODO(ipkn) replace with runtime config. libucl? /* #ifdef - enables debug mode */ // #define CROW_ENABLE_DEBUG /* #ifdef - enables logging */ #define CROW_ENABLE_LOGGING /* #ifdef - enforces section 5.2 and 6.1 of RFC6455 (only accepting masked * messages from clients) */ // #define CROW_ENFORCE_WS_SPEC /* #define - specifies log level */ /* Debug = 0 Info = 1 Warning = 2 Error = 3 Critical = 4 default to INFO */ #ifndef CROW_LOG_LEVEL #define CROW_LOG_LEVEL 1 #endif #ifndef CROW_STATIC_DIRECTORY #define CROW_STATIC_DIRECTORY "static/" #endif #ifndef CROW_STATIC_ENDPOINT #define CROW_STATIC_ENDPOINT "/static/" #endif // compiler flags #if defined(_MSC_VER) #if _MSC_VER < 1900 #define CROW_MSVC_WORKAROUND #define constexpr const #define noexcept throw() #endif #endif #ifdef CROW_USE_BOOST #include #include #ifdef CROW_ENABLE_SSL #include #endif #else #ifndef ASIO_STANDALONE #define ASIO_STANDALONE #endif #include #include #ifdef CROW_ENABLE_SSL #include #endif #endif #if (defined(CROW_USE_BOOST) && BOOST_VERSION >= 107000) || \ (ASIO_VERSION >= 101008) #define GET_IO_CONTEXT(s) ((asio::io_context &)(s).get_executor().context()) #else #define GET_IO_CONTEXT(s) ((s).get_io_service()) #endif namespace crow { #ifdef CROW_USE_BOOST namespace asio = boost::asio; using error_code = boost::system::error_code; #else using error_code = asio::error_code; #endif using tcp = asio::ip::tcp; using stream_protocol = asio::local::stream_protocol; /// A wrapper for the asio::ip::tcp::socket and asio::ssl::stream struct SocketAdaptor { using context = void; SocketAdaptor(asio::io_context &io_context, context *) : socket_(io_context) {} asio::io_context &get_io_context() { return GET_IO_CONTEXT(socket_); } /// Get the TCP socket handling data transfers, regardless of what layer is /// handling transfers on top of the socket. tcp::socket &raw_socket() { return socket_; } /// Get the object handling data transfers, this can be either a TCP socket or /// an SSL stream (if SSL is enabled). tcp::socket &socket() { return socket_; } tcp::endpoint remote_endpoint() const { return socket_.remote_endpoint(); } std::string address() const { return socket_.remote_endpoint().address().to_string(); } bool is_open() const { return socket_.is_open(); } void close() { error_code ec; socket_.close(ec); } void shutdown_readwrite() { error_code ec; socket_.shutdown(asio::socket_base::shutdown_type::shutdown_both, ec); } void shutdown_write() { error_code ec; socket_.shutdown(asio::socket_base::shutdown_type::shutdown_send, ec); } void shutdown_read() { error_code ec; socket_.shutdown(asio::socket_base::shutdown_type::shutdown_receive, ec); } template void start(F f) { f(error_code()); } tcp::socket socket_; }; struct UnixSocketAdaptor { using context = void; UnixSocketAdaptor(asio::io_context &io_context, context *) : socket_(io_context) {} asio::io_context &get_io_context() { return GET_IO_CONTEXT(socket_); } stream_protocol::socket &raw_socket() { return socket_; } stream_protocol::socket &socket() { return socket_; } stream_protocol::endpoint remote_endpoint() { return socket_.local_endpoint(); } std::string address() const { return ""; } bool is_open() { return socket_.is_open(); } void close() { error_code ec; socket_.close(ec); } void shutdown_readwrite() { error_code ec; socket_.shutdown(asio::socket_base::shutdown_type::shutdown_both, ec); } void shutdown_write() { error_code ec; socket_.shutdown(asio::socket_base::shutdown_type::shutdown_send, ec); } void shutdown_read() { error_code ec; socket_.shutdown(asio::socket_base::shutdown_type::shutdown_receive, ec); } template void start(F f) { f(error_code()); } stream_protocol::socket socket_; }; #ifdef CROW_ENABLE_SSL struct SSLAdaptor { using context = asio::ssl::context; using ssl_socket_t = asio::ssl::stream; SSLAdaptor(asio::io_context &io_context, context *ctx) : ssl_socket_(new ssl_socket_t(io_context, *ctx)) {} asio::ssl::stream &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(); } std::string address() const { return ssl_socket_->lowest_layer().remote_endpoint().address().to_string(); } bool is_open() { return ssl_socket_ ? raw_socket().is_open() : false; } void close() { if (is_open()) { error_code ec; raw_socket().close(ec); } } void shutdown_readwrite() { if (is_open()) { error_code ec; raw_socket().shutdown(asio::socket_base::shutdown_type::shutdown_both, ec); } } void shutdown_write() { if (is_open()) { error_code ec; raw_socket().shutdown(asio::socket_base::shutdown_type::shutdown_send, ec); } } void shutdown_read() { if (is_open()) { error_code ec; raw_socket().shutdown(asio::socket_base::shutdown_type::shutdown_receive, ec); } } asio::io_context &get_io_context() { return GET_IO_CONTEXT(raw_socket()); } template void start(F f) { ssl_socket_->async_handshake(asio::ssl::stream_base::server, [f](const error_code &ec) { f(ec); }); } std::unique_ptr> ssl_socket_; }; #endif } // namespace crow #include #include #include #include #include #include namespace crow { enum class LogLevel { #ifndef ERROR #ifndef DEBUG DEBUG = 0, INFO, WARNING, ERROR, CRITICAL, #endif #endif Debug = 0, Info, Warning, Error, Critical, }; class ILogHandler { public: virtual ~ILogHandler() = default; virtual void log(const std::string &message, LogLevel level) = 0; }; class CerrLogHandler : public ILogHandler { public: void log(const std::string &message, LogLevel level) override { std::string log_msg; log_msg.reserve(message.length() + 1 + 32 + 3 + 8 + 2); log_msg.append("(").append(timestamp()).append(") ["); switch (level) { case LogLevel::Debug: log_msg.append("DEBUG "); break; case LogLevel::Info: log_msg.append("INFO "); break; case LogLevel::Warning: log_msg.append("WARNING "); break; case LogLevel::Error: log_msg.append("ERROR "); break; case LogLevel::Critical: log_msg.append("CRITICAL"); break; } log_msg.append("] ").append(message); std::cerr << log_msg << std::endl; } private: static std::string timestamp() { char date[32]; time_t t = time(0); tm my_tm; #if defined(_MSC_VER) || defined(__MINGW32__) #ifdef CROW_USE_LOCALTIMEZONE localtime_s(&my_tm, &t); #else gmtime_s(&my_tm, &t); #endif #else #ifdef CROW_USE_LOCALTIMEZONE localtime_r(&t, &my_tm); #else gmtime_r(&t, &my_tm); #endif #endif size_t sz = strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", &my_tm); return std::string(date, date + sz); } }; class logger { public: logger(LogLevel level) : level_(level) {} ~logger() { #ifdef CROW_ENABLE_LOGGING if (level_ >= get_current_log_level()) { get_handler_ref()->log(stringstream_.str(), level_); } #endif } // template 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 = static_cast(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_; }; } // namespace crow #define CROW_LOG_CRITICAL \ if (crow::logger::get_current_log_level() <= crow::LogLevel::Critical) \ crow::logger(crow::LogLevel::Critical) #define CROW_LOG_ERROR \ if (crow::logger::get_current_log_level() <= crow::LogLevel::Error) \ crow::logger(crow::LogLevel::Error) #define CROW_LOG_WARNING \ if (crow::logger::get_current_log_level() <= crow::LogLevel::Warning) \ crow::logger(crow::LogLevel::Warning) #define CROW_LOG_INFO \ if (crow::logger::get_current_log_level() <= crow::LogLevel::Info) \ crow::logger(crow::LogLevel::Info) #define CROW_LOG_DEBUG \ if (crow::logger::get_current_log_level() <= crow::LogLevel::Debug) \ crow::logger(crow::LogLevel::Debug) #include namespace crow { /// An abstract class that allows any other class to be returned by a handler. struct returnable { std::string content_type; virtual std::string dump() const = 0; returnable(std::string ctype) : content_type{ctype} {} virtual ~returnable() {} }; } // namespace crow #ifdef CROW_USE_BOOST #include #ifdef CROW_ENABLE_SSL #include #endif #else #ifndef ASIO_STANDALONE #define ASIO_STANDALONE #endif #include #ifdef CROW_ENABLE_SSL #include #endif #endif namespace crow { #ifdef CROW_USE_BOOST namespace asio = boost::asio; using error_code = boost::system::error_code; #else using error_code = asio::error_code; #endif using tcp = asio::ip::tcp; using stream_protocol = asio::local::stream_protocol; struct TCPAcceptor { using endpoint = tcp::endpoint; tcp::acceptor acceptor_; TCPAcceptor(asio::io_context &io_context) : acceptor_(io_context) {} int16_t port() const { return acceptor_.local_endpoint().port(); } std::string address() const { return acceptor_.local_endpoint().address().to_string(); } std::string url_display(bool ssl_used) const { auto address = acceptor_.local_endpoint().address(); return (ssl_used ? "https://" : "http://") + (address.is_v4() ? address.to_string() : "[" + address.to_string() + "]") + ":" + std::to_string(acceptor_.local_endpoint().port()); } tcp::acceptor &raw_acceptor() { return acceptor_; } endpoint local_endpoint() const { return acceptor_.local_endpoint(); } inline static tcp::acceptor::reuse_address reuse_address_option() { return tcp::acceptor::reuse_address(true); } }; struct UnixSocketAcceptor { using endpoint = stream_protocol::endpoint; stream_protocol::acceptor acceptor_; UnixSocketAcceptor(asio::io_context &io_context) : acceptor_(io_context) {} int16_t port() const { return 0; } std::string address() const { return acceptor_.local_endpoint().path(); } std::string url_display(bool) const { return acceptor_.local_endpoint().path(); } stream_protocol::acceptor &raw_acceptor() { return acceptor_; } endpoint local_endpoint() const { return acceptor_.local_endpoint(); } inline static stream_protocol::acceptor::reuse_address reuse_address_option() { // reuse addr must be false // (https://github.com/chriskohlhoff/asio/issues/622) return stream_protocol::acceptor::reuse_address(false); } }; } // namespace crow #include #include #include #include #include #include #include #include #include #include #include #include #include #include // TODO(EDev): Adding C++20's [[likely]] and [[unlikely]] attributes might be // useful #if defined(__GNUG__) || defined(__clang__) #define CROW_LIKELY(X) __builtin_expect(!!(X), 1) #define CROW_UNLIKELY(X) __builtin_expect(!!(X), 0) #else #define CROW_LIKELY(X) (X) #define CROW_UNLIKELY(X) (X) #endif namespace crow { /// @cond SKIP namespace black_magic { #ifndef CROW_MSVC_WORKAROUND /// Out of Range Exception for const_str struct OutOfRange { OutOfRange(unsigned /*pos*/, unsigned /*length*/) {} }; /// Helper function to throw an exception if i is larger than len constexpr unsigned requires_in_range(unsigned i, unsigned len) { return i >= len ? throw OutOfRange(i, len) : i; } /// A constant string implementation. class const_str { const char *const begin_; unsigned size_; public: template 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); } /// Check that the CROW_ROUTE string is valid 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, "", 0, 5); } constexpr bool is_uint(const_str s, unsigned i) { return is_equ_n(s, i, "", 0, 6); } constexpr bool is_float(const_str s, unsigned i) { return is_equ_n(s, i, "", 0, 7) || is_equ_n(s, i, "", 0, 8); } constexpr bool is_str(const_str s, unsigned i) { return is_equ_n(s, i, "", 0, 5) || is_equ_n(s, i, "", 0, 8); } constexpr bool is_path(const_str s, unsigned i) { return is_equ_n(s, i, "", 0, 6); } #endif template struct parameter_tag { static const int value = 0; }; #define CROW_INTERNAL_PARAMETER_TAG(t, i) \ template <> struct parameter_tag { \ 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 struct compute_parameter_tag_from_args_list; template <> struct compute_parameter_tag_from_args_list<> { static const int value = 0; }; template struct compute_parameter_tag_from_args_list { static const int sub_value = compute_parameter_tag_from_args_list::value; static const int value = parameter_tag::type>::value ? sub_value * 6 + parameter_tag::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, "", 5) == 0 ? get_parameter_tag_runtime( s, find_closing_tag_runtime(s, p)) * 6 + 1 : std::strncmp(s + p, "", 6) == 0 ? get_parameter_tag_runtime( s, find_closing_tag_runtime(s, p)) * 6 + 2 : (std::strncmp(s + p, "", 7) == 0 || std::strncmp(s + p, "", 8) == 0) ? get_parameter_tag_runtime( s, find_closing_tag_runtime(s, p)) * 6 + 3 : (std::strncmp(s + p, "", 5) == 0 || std::strncmp(s + p, "", 8) == 0) ? get_parameter_tag_runtime( s, find_closing_tag_runtime(s, p)) * 6 + 4 : std::strncmp(s + p, "", 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 struct S { template using push = S; template using push_back = S; template