| #pragma once | |
| #include <array> | |
| #include <chrono> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <ctime> | |
| #include <functional> | |
| #include <iomanip> | |
| #include <limits> | |
| #include <stdexcept> | |
| #include <string> | |
| #include <string_view> | |
| #include <tuple> | |
| #include <type_traits> | |
| #include <utility> | |
| #include <variant> | |
| #include "boost/callable_traits.hpp" // NOLINT | |
| #include "boost/url/decode_view.hpp" // NOLINT | |
| #include "boost/url/parse.hpp" // NOLINT | |
| #include "boost/url/url.hpp" // NOLINT | |
| #include "boost/url/url_view.hpp" // NOLINT | |
| #include <nlohmann/json.hpp> | |
| namespace crow { | |
| namespace black_magic { | |
| enum class TypeCode : std::uint8_t { | |
| Unspecified = 0, | |
| Integer = 1, | |
| UnsignedInteger = 2, | |
| Float = 3, | |
| String = 4, | |
| Path = 5, | |
| Max = 6, | |
| }; | |
| // Remove when we have c++23 | |
| template <typename E> | |
| constexpr typename std::underlying_type<E>::type toUnderlying(E e) noexcept { | |
| return static_cast<typename std::underlying_type<E>::type>(e); | |
| } | |
| template <typename T> | |
| constexpr TypeCode getParameterTag() { | |
| if constexpr (std::is_same_v<int, T>) { | |
| return TypeCode::Integer; | |
| } | |
| if constexpr (std::is_same_v<char, T>) { | |
| return TypeCode::Integer; | |
| } | |
| if constexpr (std::is_same_v<int16_t, T>) { | |
| return TypeCode::Integer; | |
| } | |
| if constexpr (std::is_same_v<int32_t, T>) { | |
| return TypeCode::Integer; | |
| } | |
| if constexpr (std::is_same_v<int64_t, T>) { | |
| return TypeCode::Integer; | |
| } | |
| if constexpr (std::is_same_v<unsigned int, T>) { | |
| return TypeCode::UnsignedInteger; | |
| } | |
| if constexpr (std::is_same_v<unsigned char, T>) { | |
| return TypeCode::UnsignedInteger; | |
| } | |
| if constexpr (std::is_same_v<uint16_t, T>) { | |
| return TypeCode::UnsignedInteger; | |
| } | |
| if constexpr (std::is_same_v<uint32_t, T>) { | |
| return TypeCode::UnsignedInteger; | |
| } | |
| if constexpr (std::is_same_v<uint64_t, T>) { | |
| return TypeCode::UnsignedInteger; | |
| } | |
| if constexpr (std::is_same_v<double, T>) { | |
| return TypeCode::Float; | |
| } | |
| if constexpr (std::is_same_v<std::string, T>) { | |
| return TypeCode::String; | |
| } | |
| return TypeCode::Unspecified; | |
| } | |
| template <typename... Args> | |
| struct computeParameterTagFromArgsList; | |
| template <> | |
| struct computeParameterTagFromArgsList<> { | |
| static constexpr int value = 0; | |
| }; | |
| constexpr uint64_t getParameterTag(std::string_view url) { | |
| uint64_t tagValue = 0; | |
| size_t urlSegmentIndex = std::string_view::npos; | |
| size_t paramIndex = 0; | |
| for (size_t urlIndex = 0; urlIndex < url.size(); urlIndex++) { | |
| char character = url[urlIndex]; | |
| if (character == '<') { | |
| if (urlSegmentIndex != std::string_view::npos) { | |
| return 0; | |
| } | |
| urlSegmentIndex = urlIndex; | |
| } | |
| if (character == '>') { | |
| if (urlSegmentIndex == std::string_view::npos) { | |
| return 0; | |
| } | |
| std::string_view tag = | |
| url.substr(urlSegmentIndex, urlIndex + 1 - urlSegmentIndex); | |
| // Note, this is a really lame way to do std::pow(6, paramIndex) | |
| // std::pow doesn't work in constexpr in clang. | |
| // Ideally in the future we'd move this to use a power of 2 packing | |
| // (probably 8 instead of 6) so that these just become bit shifts | |
| uint64_t insertIndex = 1; | |
| for (size_t unused = 0; unused < paramIndex; unused++) { | |
| insertIndex *= 6; | |
| } | |
| if (tag == "<int>") { | |
| tagValue += insertIndex * toUnderlying(TypeCode::Integer); | |
| } | |
| if (tag == "<uint>") { | |
| tagValue += insertIndex * toUnderlying(TypeCode::UnsignedInteger); | |
| } | |
| if (tag == "<float>" || tag == "<double>") { | |
| tagValue += insertIndex * toUnderlying(TypeCode::Float); | |
| } | |
| if (tag == "<str>" || tag == "<string>") { | |
| tagValue += insertIndex * toUnderlying(TypeCode::String); | |
| } | |
| if (tag == "<path>") { | |
| tagValue += insertIndex * toUnderlying(TypeCode::Path); | |
| } | |
| paramIndex++; | |
| urlSegmentIndex = std::string_view::npos; | |
| } | |
| } | |
| if (urlSegmentIndex != std::string_view::npos) { | |
| return 0; | |
| } | |
| return tagValue; | |
| } | |
| template <typename Arg, typename... Args> | |
| struct computeParameterTagFromArgsList<Arg, Args...> { | |
| static constexpr int subValue = | |
| computeParameterTagFromArgsList<Args...>::value; | |
| static constexpr int value = | |
| getParameterTag<typename std::decay<Arg>::type>() != TypeCode::Unspecified | |
| ? static_cast<uint64_t>(subValue * toUnderlying(TypeCode::Max)) + | |
| static_cast<uint64_t>( | |
| getParameterTag<typename std::decay<Arg>::type>()) | |
| : subValue; | |
| }; | |
| bool isParameterTagCompatible(uint64_t a, uint64_t b); | |
| template <typename... T> | |
| struct S { | |
| template <typename U> | |
| using push = S<U, T...>; | |
| template <typename U> | |
| using push_back = S<T..., U>; | |
| template <template <typename... Args> class U> | |
| using rebind = U<T...>; | |
| }; | |
| template <typename F, typename Set> | |
| struct CallHelper; | |
| template <typename F, typename... Args> | |
| struct CallHelper<F, S<Args...>> { | |
| template <typename F1, typename... Args1, | |
| typename = decltype(std::declval<F1>()(std::declval<Args1>()...))> | |
| static char test(int); | |
| template <typename...> | |
| static int test(...); | |
| static constexpr bool value = sizeof(test<F, Args...>(0)) == sizeof(char); | |
| }; | |
| template <uint64_t N> | |
| struct SingleTagToType {}; | |
| template <> | |
| struct SingleTagToType<1> { | |
| using type = int64_t; | |
| }; | |
| template <> | |
| struct SingleTagToType<2> { | |
| using type = uint64_t; | |
| }; | |
| template <> | |
| struct SingleTagToType<3> { | |
| using type = double; | |
| }; | |
| template <> | |
| struct SingleTagToType<4> { | |
| using type = std::string; | |
| }; | |
| template <> | |
| struct SingleTagToType<5> { | |
| using type = std::string; | |
| }; | |
| template <uint64_t Tag> | |
| struct Arguments { | |
| using subarguments = typename Arguments<Tag / 6>::type; | |
| using type = typename subarguments::template push< | |
| typename SingleTagToType<Tag % 6>::type>; | |
| }; | |
| template <> | |
| struct Arguments<0> { | |
| using type = S<>; | |
| }; | |
| template <typename T> | |
| struct Promote { | |
| using type = T; | |
| }; | |
| template <typename T> | |
| using PromoteT = typename Promote<T>::type; | |
| template <> | |
| struct Promote<char> { | |
| using type = int64_t; | |
| }; | |
| template <> | |
| struct Promote<int16_t> { | |
| using type = int64_t; | |
| }; | |
| template <> | |
| struct Promote<int> { | |
| using type = int64_t; | |
| }; | |
| template <> | |
| struct Promote<int64_t> { | |
| using type = int64_t; | |
| }; | |
| template <> | |
| struct Promote<unsigned char> { | |
| using type = uint64_t; | |
| }; | |
| template <> | |
| struct Promote<uint16_t> { | |
| using type = uint64_t; | |
| }; | |
| template <> | |
| struct Promote<unsigned int> { | |
| using type = uint64_t; | |
| }; | |
| template <> | |
| struct Promote<uint64_t> { | |
| using type = uint64_t; | |
| }; | |
| } // namespace black_magic | |
| namespace utility { | |
| template <typename T> | |
| struct FunctionTraits { | |
| template <size_t i> | |
| using arg = std::tuple_element_t<i, boost::callable_traits::args_t<T>>; | |
| }; | |
| std::string base64encode(std::string_view data); | |
| // TODO this is temporary and should be deleted once base64 is refactored out of | |
| // crow | |
| bool base64Decode(std::string_view input, std::string& output); | |
| bool constantTimeStringCompare(std::string_view a, std::string_view b); | |
| struct ConstantTimeCompare { | |
| bool operator()(std::string_view a, std::string_view b) const { | |
| return constantTimeStringCompare(a, b); | |
| } | |
| }; | |
| namespace details { | |
| inline boost::urls::url appendUrlPieces( | |
| boost::urls::url& url, const std::initializer_list<std::string_view> args) { | |
| for (std::string_view arg : args) { | |
| url.segments().push_back(arg); | |
| } | |
| return url; | |
| } | |
| inline boost::urls::url urlFromPiecesDetail( | |
| const std::initializer_list<std::string_view> args) { | |
| boost::urls::url url("/"); | |
| appendUrlPieces(url, args); | |
| return url; | |
| } | |
| } // namespace details | |
| class OrMorePaths {}; | |
| template <typename... AV> | |
| inline boost::urls::url urlFromPieces(const AV... args) { | |
| return details::urlFromPiecesDetail({args...}); | |
| } | |
| template <typename... AV> | |
| inline void appendUrlPieces(boost::urls::url& url, const AV... args) { | |
| details::appendUrlPieces(url, {args...}); | |
| } | |
| namespace details { | |
| // std::reference_wrapper<std::string> - extracts segment to variable | |
| // std::string_view - checks if segment is equal to variable | |
| using UrlSegment = std::variant<std::reference_wrapper<std::string>, | |
| std::string_view, OrMorePaths>; | |
| enum class UrlParseResult : uint8_t { | |
| Continue, | |
| Fail, | |
| Done, | |
| }; | |
| class UrlSegmentMatcherVisitor { | |
| public: | |
| UrlParseResult operator()(std::string& output) { | |
| output = segment; | |
| return UrlParseResult::Continue; | |
| } | |
| UrlParseResult operator()(std::string_view expected) { | |
| if (segment == expected) { | |
| return UrlParseResult::Continue; | |
| } | |
| return UrlParseResult::Fail; | |
| } | |
| UrlParseResult operator()(OrMorePaths /*unused*/) { | |
| return UrlParseResult::Done; | |
| } | |
| explicit UrlSegmentMatcherVisitor(std::string_view segmentIn) | |
| : segment(segmentIn) {} | |
| private: | |
| std::string_view segment; | |
| }; | |
| bool readUrlSegments(boost::urls::url_view url, // NOLINT | |
| std::initializer_list<UrlSegment>&& segments); | |
| } // namespace details | |
| template <typename... Args> | |
| inline bool readUrlSegments(boost::urls::url_view url, | |
| Args&&... args) // NOLINT | |
| { | |
| return details::readUrlSegments(url, {std::forward<Args>(args)...}); | |
| } | |
| boost::urls::url replaceUrlSegment(boost::urls::url_view urlView, // NOLINT | |
| uint replaceLoc, | |
| std::string_view newSegment); | |
| std::string setProtocolDefaults(boost::urls::url_view urlView); // NOLINT | |
| uint16_t setPortDefaults(boost::urls::url_view url); // NOLINT | |
| bool validateAndSplitUrl(std::string_view destUrl, std::string& urlProto, | |
| std::string& host, uint16_t& port, std::string& path); | |
| } // namespace utility | |
| } // namespace crow | |
| namespace nlohmann { | |
| template <> | |
| struct adl_serializer<boost::urls::url> { | |
| // nlohmann requires a specific casing to look these up in adl | |
| // NOLINTNEXTLINE(readability-identifier-naming) | |
| static void to_json(json& j, const boost::urls::url& url) { | |
| boost::urls::decode_view url_decoded{url}; | |
| j = std::string(url_decoded.begin(), url_decoded.end()); | |
| } | |
| }; | |
| template <> | |
| struct adl_serializer<boost::urls::url_view> { | |
| // NOLINTNEXTLINE(readability-identifier-naming, | |
| // performance-unnecessary-value-param) | |
| static void to_json(json& j, boost::urls::url_view url) { | |
| boost::urls::decode_view url_decoded{url}; | |
| j = std::string(url_decoded.begin(), url_decoded.end()); | |
| } | |
| }; | |
| } // namespace nlohmann |