| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| #pragma once |
| |
| #include <algorithm> |
| #include <string> |
| #include <string_view> |
| #include <unordered_map> |
| #include <vector> |
| #include <concepts> |
| |
| #include "ts/ts.h" |
| #include "ts/remap.h" |
| |
| #include "cripts/Headers.hpp" |
| |
| namespace cripts |
| { |
| class Context; |
| |
| class Url |
| { |
| using self_type = Url; |
| |
| class Component |
| { |
| using self_type = Component; |
| |
| public: |
| Component() = default; |
| Component(Url *owner) : _owner(owner) {} |
| |
| virtual cripts::string_view GetSV() = 0; |
| |
| std::vector<cripts::string_view> Split(char delim); |
| |
| operator cripts::string_view() { return GetSV(); } // Should not be explicit |
| |
| bool |
| operator==(cripts::string_view const &rhs) |
| { |
| return GetSV() == rhs; |
| } |
| |
| bool |
| operator!=(cripts::string_view const &rhs) |
| { |
| return GetSV() != rhs; |
| } |
| |
| virtual void |
| Reset() |
| { |
| _data.clear(); |
| } |
| |
| cripts::string_view::const_pointer |
| data() |
| { |
| return GetSV().data(); |
| } |
| |
| cripts::string_view::size_type |
| size() |
| { |
| return GetSV().size(); |
| } |
| |
| cripts::string_view::size_type |
| length() |
| { |
| return GetSV().size(); |
| } |
| |
| cripts::string_view::const_pointer |
| Data() |
| { |
| return GetSV().data(); |
| } |
| |
| cripts::string_view::size_type |
| Size() |
| { |
| return GetSV().size(); |
| } |
| |
| cripts::string_view::size_type |
| Length() |
| { |
| return GetSV().size(); |
| } |
| |
| // This is not ideal, but best way I can think of for now to mixin the cripts::string_view mixin class |
| // Remember to add things here when added to the Lulu.hpp file for the mixin class... :/ |
| [[nodiscard]] constexpr cripts::string_view |
| substr(cripts::string_view::size_type pos = 0, cripts::string_view::size_type count = cripts::string_view::npos) const |
| { |
| return _data.substr(pos, count); |
| } |
| |
| void |
| remove_prefix(cripts::string_view::size_type n) |
| { |
| _data.remove_prefix(n); |
| } |
| |
| void |
| remove_suffix(cripts::string_view::size_type n) |
| { |
| _data.remove_suffix(n); |
| } |
| |
| cripts::string_view & |
| ltrim(char c) |
| { |
| return _data.ltrim(c); |
| } |
| |
| cripts::string_view & |
| rtrim(char c) |
| { |
| return _data.rtrim(c); |
| } |
| |
| cripts::string_view & |
| trim(char c) |
| { |
| return _data.trim(c); |
| } |
| |
| cripts::string_view & |
| ltrim(const char *chars = " \t\r\n") |
| { |
| return _data.ltrim(chars); |
| } |
| |
| cripts::string_view & |
| rtrim(const char *chars = " \t\r\n") |
| { |
| return _data.rtrim(chars); |
| } |
| |
| cripts::string_view & |
| trim(const char *chars = " \t") |
| { |
| return _data.trim(chars); |
| } |
| |
| [[nodiscard]] constexpr char const * |
| data_end() const noexcept |
| { |
| return _data.data_end(); |
| } |
| |
| [[nodiscard]] bool |
| ends_with(cripts::string_view suffix) const |
| { |
| return _data.ends_with(suffix); |
| } |
| |
| [[nodiscard]] bool |
| starts_with(cripts::string_view const prefix) const |
| { |
| return _data.starts_with(prefix); |
| } |
| |
| [[nodiscard]] constexpr cripts::string_view::size_type |
| find(cripts::string_view const substr, cripts::string_view::size_type pos = 0) const |
| { |
| return _data.find(substr, pos); |
| } |
| |
| [[nodiscard]] constexpr cripts::string_view::size_type |
| rfind(cripts::string_view const substr, cripts::string_view::size_type pos = 0) const |
| { |
| return _data.rfind(substr, pos); |
| } |
| |
| [[nodiscard]] constexpr bool |
| contains(cripts::string_view const substr) const |
| { |
| return (_data.find(substr) != _data.npos); |
| } |
| |
| protected: |
| mutable cripts::string_view _data; |
| Url *_owner = nullptr; |
| bool _loaded = false; |
| |
| }; // End class Url::Component |
| |
| public: |
| // Each of the members of a URL has its own class, very convenient. Is this |
| // great C++? Dunno. |
| class Scheme : public Component |
| { |
| using super_type = Component; |
| using self_type = Scheme; |
| |
| public: |
| using Component::Component; |
| |
| cripts::string_view GetSV() override; |
| self_type operator=(cripts::string_view scheme); |
| |
| }; // End class Url::Scheme |
| |
| class Host : public Component |
| { |
| using super_type = Component; |
| using self_type = Host; |
| |
| public: |
| using Component::Component; |
| |
| cripts::string_view GetSV() override; |
| self_type operator=(cripts::string_view host); |
| |
| }; // End class Url::Host |
| |
| class Port |
| { |
| using self_type = Port; |
| |
| public: |
| Port(Url *owner) : _owner(owner){}; |
| |
| void |
| Reset() |
| { |
| _port = -1; |
| } |
| |
| operator integer(); // This should not be explicit, and should not be const |
| self_type operator=(int port); |
| |
| self_type |
| operator=(cripts::string_view str) |
| { |
| uint32_t port = 80; |
| auto result = std::from_chars(str.data(), str.data() + str.size(), port); |
| |
| if (result.ec != std::errc::invalid_argument) { |
| return *this; |
| } else { |
| return operator=(port); |
| } |
| } |
| |
| private: |
| Url *_owner = nullptr; |
| integer _port = -1; |
| }; // End class Url::Port |
| |
| class Path : public Component |
| { |
| public: |
| using Segments = std::vector<cripts::string_view>; |
| |
| private: |
| using super_type = Component; |
| using self_type = Path; |
| |
| class String : public cripts::StringViewMixin<String> |
| { |
| using super_type = cripts::StringViewMixin<String>; |
| using self_type = String; |
| |
| public: |
| String() = default; |
| |
| // Implemented in the Urls.cc file, bigger function |
| self_type &operator=(const cripts::string_view str) override; |
| |
| // These specialized assignment operators all use the above |
| template <size_t N> |
| self_type & |
| operator=(const char (&str)[N]) |
| { |
| return operator=(cripts::string_view(str, str[N - 1] ? N : N - 1)); |
| } |
| |
| self_type & |
| operator=(char *&str) |
| { |
| return operator=(cripts::string_view(str, strlen(str))); |
| } |
| |
| self_type & |
| operator=(char const *&str) |
| { |
| return operator=(cripts::string_view(str, strlen(str))); |
| } |
| |
| self_type & |
| operator=(const std::string &str) |
| { |
| return operator=(cripts::string_view(str)); |
| } |
| |
| private: |
| friend class Path; |
| |
| void |
| _initialize(cripts::string_view source, Path *owner, Segments::size_type ix) |
| { |
| _setSV(source); |
| _owner = owner; |
| _ix = ix; |
| } |
| |
| Path *_owner = nullptr; |
| Segments::size_type _ix = 0; |
| |
| }; // End class Url::Path::String |
| |
| public: |
| friend struct fmt::formatter<Path::String>; |
| |
| using Component::Component; |
| |
| void Reset() override; |
| |
| cripts::string_view GetSV() override; |
| cripts::string operator+=(cripts::string_view add); |
| self_type operator=(cripts::string_view path); |
| String operator[](Segments::size_type ix); |
| |
| void |
| Erase(Segments::size_type ix) |
| { |
| auto p = operator[](ix); |
| |
| _size -= p.size(); |
| p.operator=(""); |
| } |
| |
| void |
| Erase() |
| { |
| operator=(""); |
| } |
| |
| void |
| Clear() |
| { |
| Erase(); |
| } |
| |
| void Push(cripts::string_view val); |
| void Insert(Segments::size_type ix, cripts::string_view val); |
| |
| void |
| Flush() |
| { |
| if (_modified) { |
| operator=(GetSV()); |
| } |
| } |
| |
| private: |
| void _parser(); |
| |
| bool _modified = false; |
| Segments _segments; // Lazy loading on this |
| cripts::string _storage; // Used when recombining the segments into a full path |
| cripts::string::size_type _size = 0; // Mostly a guestimate for managing _storage |
| |
| }; // End class Url::Path |
| |
| class Query : public Component |
| { |
| using super_type = Component; |
| using self_type = Query; |
| |
| using OrderedParams = std::vector<cripts::string_view>; // Ordered parameter nmes |
| using HashParams = std::unordered_map<cripts::string_view, cripts::string_view>; // Hash lookups |
| |
| class Parameter : public cripts::StringViewMixin<Parameter> |
| { |
| using super_type = cripts::StringViewMixin<Parameter>; |
| using self_type = Parameter; |
| |
| public: |
| Parameter() = default; |
| |
| [[nodiscard]] cripts::string_view |
| Name() const |
| { |
| return _name; |
| } |
| |
| void |
| Erase() |
| { |
| _owner->Erase(_name); |
| } |
| |
| // Implemented in the Urls.cc file, bigger function |
| self_type &operator=(const cripts::string_view str) override; |
| |
| // These specialized assignment operators all use the above |
| template <size_t N> |
| self_type & |
| operator=(const char (&str)[N]) |
| { |
| return operator=(cripts::string_view(str, str[N - 1] ? N : N - 1)); |
| } |
| |
| self_type & |
| operator=(char *&str) |
| { |
| return operator=(cripts::string_view(str, strlen(str))); |
| } |
| |
| self_type & |
| operator=(char const *&str) |
| { |
| return operator=(cripts::string_view(str, strlen(str))); |
| } |
| |
| self_type & |
| operator=(const std::string &str) |
| { |
| return operator=(cripts::string_view(str)); |
| } |
| |
| private: |
| friend class Query; |
| |
| void |
| _initialize(cripts::string_view name, cripts::string_view source, Query *owner) |
| { |
| _setSV(source); |
| _name = name; |
| _owner = owner; |
| } |
| |
| Query *_owner = nullptr; |
| cripts::string_view _name; |
| |
| }; // End class Url::Query::Parameter |
| |
| public: |
| friend struct fmt::formatter<Query::Parameter>; |
| |
| using Component::Component; |
| |
| Query(cripts::string_view load) |
| { |
| _data = load; |
| _size = load.size(); |
| _loaded = true; |
| _standalone = true; |
| } |
| |
| void Reset() override; |
| |
| cripts::string_view GetSV() override; |
| self_type operator=(cripts::string_view query); |
| cripts::string operator+=(cripts::string_view add); |
| Parameter operator[](cripts::string_view param); |
| void Erase(cripts::string_view param); |
| void Erase(std::initializer_list<cripts::string_view> list, bool keep = false); |
| |
| void |
| Erase() |
| { |
| operator=(""); |
| _size = 0; |
| } |
| |
| void |
| Keep(std::initializer_list<cripts::string_view> list) |
| { |
| Erase(list, true); |
| } |
| |
| void |
| Clear() |
| { |
| return Erase(); |
| } |
| |
| void |
| Sort() |
| { |
| // Make sure the hash and vector are populated |
| _parser(); |
| |
| std::ranges::sort(_ordered); |
| _modified = true; |
| } |
| |
| void |
| Flush() |
| { |
| if (_modified) { |
| operator=(GetSV()); |
| } |
| } |
| |
| private: |
| void _parser(); |
| |
| bool _modified = false; |
| bool _standalone = false; // This component is used outside of a URL owner, not common |
| OrderedParams _ordered; // Ordered vector of all parameters, can be sorted etc. |
| HashParams _hashed; // Unordered map to go from "name" to the query parameter |
| cripts::string _storage; // Used when recombining the query params into a |
| // full query string |
| cripts::string::size_type _size = 0; // Mostly a guesttimate |
| |
| }; // End class Url::Query |
| |
| public: |
| Url() : scheme(this), host(this), port(this), path(this), query(this) {} |
| |
| // Clear anything "cached" in the Url, this is rather draconian, but it's safe... |
| virtual void |
| Reset() |
| { |
| if (_bufp && _urlp) { |
| TSHandleMLocRelease(_bufp, TS_NULL_MLOC, _urlp); |
| _urlp = nullptr; |
| _bufp = nullptr; |
| |
| query.Reset(); |
| path.Reset(); |
| } |
| _initialized = false; |
| _modified = false; |
| } |
| |
| [[nodiscard]] bool |
| Initialized() const |
| { |
| return _initialized; |
| } |
| |
| [[nodiscard]] bool |
| Modified() const |
| { |
| return _modified; |
| } |
| |
| [[nodiscard]] TSMLoc |
| UrlP() const |
| { |
| return _urlp; |
| } |
| |
| [[nodiscard]] virtual bool |
| ReadOnly() const |
| { |
| return false; |
| } |
| |
| // Some of these URL objects needs the full Context, making life miserable. |
| void |
| set_context(cripts::Context *context) |
| { |
| _context = context; |
| } |
| |
| // This is the full string for a URL, which needs allocations. |
| [[nodiscard]] cripts::string String(); |
| |
| Scheme scheme; |
| Host host; |
| Port port; |
| Path path; |
| Query query; |
| |
| protected: |
| static void |
| _ensure_initialized(self_type *ptr) |
| { |
| if (!ptr->Initialized()) [[unlikely]] { |
| ptr->_initialize(); |
| } |
| } |
| |
| virtual void |
| _initialize() |
| { |
| _initialized = true; |
| _modified = false; |
| } |
| |
| TSMBuffer _bufp = nullptr; // These two gets setup via initializing, to appropriate headers |
| TSMLoc _hdr_loc = nullptr; // Do not release any of this within the URL classes! |
| TSMLoc _urlp = nullptr; // This is owned by us. |
| cripts::Transaction *_state = nullptr; // Pointer into the owning Context's State |
| cripts::Context *_context = nullptr; // Pointer to the owning Context |
| bool _modified = false; // We have pending changes on the path/query components |
| bool _initialized = false; // Have we been initialized ? |
| |
| }; // End class Url |
| |
| // The Pristine URL is immutable, we should "delete" the operator= methods |
| namespace Pristine |
| { |
| class URL : public cripts::Url |
| { |
| using super_type = cripts::Url; |
| using self_type = URL; |
| |
| public: |
| URL() = default; |
| URL(const self_type &) = delete; |
| void operator=(const self_type &) = delete; |
| |
| static self_type &_get(cripts::Context *context); |
| |
| [[nodiscard]] bool |
| ReadOnly() const override |
| { |
| return true; |
| } |
| |
| protected: |
| void _initialize() override; |
| |
| }; // End class Pristine::URL |
| |
| } // namespace Pristine |
| |
| namespace Client |
| { |
| class URL : public cripts::Url |
| { |
| using super_type = cripts::Url; |
| using self_type = URL; |
| |
| public: |
| URL() = default; |
| URL(const self_type &) = delete; |
| void operator=(const self_type &) = delete; |
| |
| // We must not release the bufp etc. since it comes from the RRI structure |
| // However, we still need to clear cached data in query and path components |
| void |
| Reset() override |
| { |
| query.Reset(); |
| path.Reset(); |
| _initialized = false; |
| _modified = false; |
| } |
| |
| static self_type &_get(cripts::Context *context); |
| bool _update(); |
| |
| protected: |
| void _initialize() override; |
| |
| }; // End class Client::URL |
| |
| } // namespace Client |
| |
| namespace Remap |
| { |
| namespace From |
| { |
| class URL : public cripts::Url |
| { |
| using super_type = cripts::Url; |
| using self_type = URL; |
| |
| public: |
| URL() = default; |
| URL(const self_type &) = delete; |
| void operator=(const self_type &) = delete; |
| |
| // We must not release the bufp etc. since it comes from the RRI structure |
| void |
| Reset() override |
| { |
| } |
| |
| [[nodiscard]] bool |
| ReadOnly() const override |
| { |
| return true; |
| } |
| |
| static self_type &_get(cripts::Context *context); |
| bool Update(); |
| |
| private: |
| void _initialize() override; |
| |
| }; // End class Client::URL |
| |
| } // namespace From |
| |
| namespace To |
| { |
| class URL : public cripts::Url |
| { |
| using super_type = cripts::Url; |
| using self_type = URL; |
| |
| public: |
| URL() = default; |
| URL(const self_type &) = delete; |
| void operator=(const self_type &) = delete; |
| |
| // We must not release the bufp etc. since it comes from the RRI structure |
| void |
| Reset() override |
| { |
| } |
| |
| [[nodiscard]] bool |
| ReadOnly() const override |
| { |
| return true; |
| } |
| |
| static self_type &_get(cripts::Context *context); |
| bool Update(); |
| |
| private: |
| void _initialize() override; |
| |
| }; // End class Client::URL |
| |
| } // namespace To |
| |
| } // namespace Remap |
| |
| namespace Cache |
| { |
| class URL : public cripts::Url // ToDo: This can maybe be a subclass of Client::URL ? |
| { |
| using super_type = cripts::Url; |
| using self_type = URL; |
| |
| public: |
| URL() = default; |
| URL(const self_type &) = delete; |
| void operator=(const self_type &) = delete; |
| |
| static self_type &_get(cripts::Context *context); |
| bool _update(); |
| |
| protected: |
| void _initialize() override; |
| |
| }; // End class Cache::URL |
| |
| } // namespace Cache |
| |
| namespace Parent |
| { |
| class URL : public cripts::Url |
| { |
| using super_type = cripts::Url; |
| using self_type = URL; |
| |
| public: |
| URL() = default; |
| URL(const self_type &) = delete; |
| void operator=(const self_type &) = delete; |
| |
| static self_type &_get(cripts::Context *context); |
| bool Update(); |
| |
| protected: |
| void _initialize() override; |
| |
| }; // End class Cache::URL |
| |
| } // namespace Parent |
| |
| } // namespace cripts |
| |
| // Formatters for {fmt} |
| namespace fmt |
| { |
| template <std::derived_from<cripts::Url> T> struct formatter<T> { |
| constexpr auto |
| parse(format_parse_context &ctx) -> decltype(ctx.begin()) |
| { |
| return ctx.begin(); |
| } |
| |
| template <typename FormatContext> |
| auto |
| format(T &url, FormatContext &ctx) const -> decltype(ctx.out()) |
| { |
| return fmt::format_to(ctx.out(), "{}", url.String()); |
| } |
| }; |
| |
| template <> struct formatter<cripts::Url::Scheme> { |
| constexpr auto |
| parse(format_parse_context &ctx) -> decltype(ctx.begin()) |
| { |
| return ctx.begin(); |
| } |
| |
| template <typename FormatContext> |
| auto |
| format(cripts::Url::Scheme &scheme, FormatContext &ctx) const -> decltype(ctx.out()) |
| { |
| return fmt::format_to(ctx.out(), "{}", scheme.GetSV()); |
| } |
| }; |
| |
| template <> struct formatter<cripts::Url::Host> { |
| constexpr auto |
| parse(format_parse_context &ctx) -> decltype(ctx.begin()) |
| { |
| return ctx.begin(); |
| } |
| |
| template <typename FormatContext> |
| auto |
| format(cripts::Url::Host &host, FormatContext &ctx) const -> decltype(ctx.out()) |
| { |
| return fmt::format_to(ctx.out(), "{}", host.GetSV()); |
| } |
| }; |
| |
| template <> struct formatter<cripts::Url::Port> { |
| constexpr auto |
| parse(format_parse_context &ctx) -> decltype(ctx.begin()) |
| { |
| return ctx.begin(); |
| } |
| |
| template <typename FormatContext> |
| auto |
| format(cripts::Url::Port &port, FormatContext &ctx) const -> decltype(ctx.out()) |
| { |
| return fmt::format_to(ctx.out(), "{}", integer(port)); |
| } |
| }; |
| |
| template <> struct formatter<cripts::Url::Path::String> { |
| constexpr auto |
| parse(format_parse_context &ctx) -> decltype(ctx.begin()) |
| { |
| return ctx.begin(); |
| } |
| |
| template <typename FormatContext> |
| auto |
| format(cripts::Url::Path::String &path, FormatContext &ctx) const -> decltype(ctx.out()) |
| { |
| return fmt::format_to(ctx.out(), "{}", path.GetSV()); |
| } |
| }; |
| |
| template <> struct formatter<cripts::Url::Path> { |
| constexpr auto |
| parse(format_parse_context &ctx) -> decltype(ctx.begin()) |
| { |
| return ctx.begin(); |
| } |
| |
| template <typename FormatContext> |
| auto |
| format(cripts::Url::Path &path, FormatContext &ctx) const -> decltype(ctx.out()) |
| { |
| return fmt::format_to(ctx.out(), "{}", path.GetSV()); |
| } |
| }; |
| |
| template <> struct formatter<cripts::Url::Query::Parameter> { |
| constexpr auto |
| parse(format_parse_context &ctx) -> decltype(ctx.begin()) |
| { |
| return ctx.begin(); |
| } |
| |
| template <typename FormatContext> |
| auto |
| format(cripts::Url::Query::Parameter ¶m, FormatContext &ctx) const -> decltype(ctx.out()) |
| { |
| return fmt::format_to(ctx.out(), "{}", param.GetSV()); |
| } |
| }; |
| |
| template <> struct formatter<cripts::Url::Query> { |
| constexpr auto |
| parse(format_parse_context &ctx) -> decltype(ctx.begin()) |
| { |
| return ctx.begin(); |
| } |
| |
| template <typename FormatContext> |
| auto |
| format(cripts::Url::Query &query, FormatContext &ctx) const -> decltype(ctx.out()) |
| { |
| return fmt::format_to(ctx.out(), "{}", query.GetSV()); |
| } |
| }; |
| |
| } // namespace fmt |