From 3a5f7e0aac71759fd07ba9c73e00a35653fbc0a8 Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Sun, 17 Jul 2022 21:39:10 -0700 Subject: [PATCH] ircd::resource: Implement more complex path routing. --- include/ircd/m/resource.h | 20 ++++- include/ircd/resource/resource.h | 6 +- ircd/resource.cc | 139 ++++++++++++++++--------------- matrix/resource.cc | 124 ++++++++++++++++++++++++++- 4 files changed, 217 insertions(+), 72 deletions(-) diff --git a/include/ircd/m/resource.h b/include/ircd/m/resource.h index 4d4400795..69ffeb69d 100644 --- a/include/ircd/m/resource.h +++ b/include/ircd/m/resource.h @@ -25,7 +25,19 @@ struct ircd::m::resource static log::log log; - using ircd::resource::resource; + static string_view path_version(const string_view &); + static string_view path_canonize(const mutable_buffer &, const string_view &); + + private: + char path_buf[512]; + + ircd::resource &route(const string_view &path) const override; + string_view params(const string_view &path) const override; + + public: + resource(const string_view &path, struct opts); + resource(const string_view &path); + ~resource() noexcept override; }; struct ircd::m::resource::method @@ -46,6 +58,7 @@ struct ircd::m::resource::request { template struct object; + string_view version; // api version pair authorization; // proffering any string_view access_token; // proffering user m::request::x_matrix x_matrix; // proferring server @@ -66,6 +79,11 @@ struct ircd::m::resource::request::object { const m::resource::request &request; + const decltype(request.version) &version + { + request.version + }; + const decltype(request.access_token) &access_token { request.access_token diff --git a/include/ircd/resource/resource.h b/include/ircd/resource/resource.h index 1c8eb075a..7d9e79449 100644 --- a/include/ircd/resource/resource.h +++ b/include/ircd/resource/resource.h @@ -39,21 +39,23 @@ struct ircd::resource std::unique_ptr default_method_head; std::unique_ptr default_method_options; + private: using method_closure = std::function; string_view method_list(const mutable_buffer &buf, const method_closure &) const; string_view method_list(const mutable_buffer &buf) const; - response handle_options(client &, const request &) const; response handle_head(client &, const request &) const; public: method &operator[](const string_view &name) const; + virtual resource &route(const string_view &path) const; + virtual string_view params(const string_view &path) const; resource(const string_view &path, struct opts); resource(const string_view &path); resource(resource &&) = delete; resource(const resource &) = delete; - ~resource() noexcept; + virtual ~resource() noexcept; static resource &find(const string_view &path); }; diff --git a/ircd/resource.cc b/ircd/resource.cc index 6801a902f..88c403d3f 100644 --- a/ircd/resource.cc +++ b/ircd/resource.cc @@ -22,78 +22,31 @@ ircd::util::instance_map::map ircd::resource & ircd::resource::find(const string_view &path_) { + const auto &resources{instance_map::map}; + auto it + { + resources.begin() + }; + + if(unlikely(it == resources.end())) + throw http::error + { + http::code::NOT_FOUND + }; + + auto &resource + { + it->second + }; + const string_view path { rstrip(path_, '/') }; - auto &resources(instance_map::map); - auto it - { - resources.lower_bound(path) - }; - - if(it == end(resources)) try - { - --it; - if(it == begin(resources) || !startswith(path, it->first)) - return *resources.at("/"); - } - catch(const std::out_of_range &e) - { - throw http::error - { - http::code::NOT_FOUND - }; - } - - auto rpath{it->first}; - //assert(!endswith(rpath, '/')); - - // Exact file or directory match - if(path == rpath) - return *it->second; - - // Directories handle all paths under them. - while(!startswith(path, rpath)) - { - // Walk the iterator back to find if there is a directory prefixing this path. - if(it == begin(resources)) - throw http::error - { - http::code::NOT_FOUND - }; - - --it; - rpath = it->first; - if(~it->second->opts->flags & it->second->DIRECTORY) - continue; - - // If the closest directory still doesn't match hand this off to the - // webroot which can then service or 404 this itself. - if(!startswith(path, rpath)) - return *resources.at("/"); - } - - // Check if the resource is a directory; if not, it can only - // handle exact path matches. - if(~it->second->opts->flags & it->second->DIRECTORY && path != rpath) - throw http::error - { - http::code::NOT_FOUND - }; - - if(it->second->opts->flags & it->second->DIRECTORY) - { - const auto rem(lstrip(path, rpath)); - if(!empty(rem) && !startswith(rem, '/')) - throw http::error - { - http::code::NOT_FOUND - }; - } - - return *it->second; + return path && path != "/"? + resource->route(path): + *resource; } // @@ -168,6 +121,54 @@ noexcept }; } +ircd::string_view +ircd::resource::params(const string_view &path) +const +{ + const auto prefix_tokens + { + token_count(this->path, '/') + }; + + const auto params_after + { + prefix_tokens? prefix_tokens - 1: 0 + }; + + const auto ret + { + tokens_after(path, '/', params_after) + }; + + return ret; +} + +ircd::resource & +ircd::resource::route(const string_view &path) +const +{ + if(this->path != "/" && startswith(path, this->path)) + if(path == this->path || opts->flags & flag::DIRECTORY) + return mutable_cast(*this); + + const auto &resources{instance_map::map}; + assert(!resources.empty()); + auto it + { + resources.lower_bound(path) + }; + + if(it == end(resources) || it->first > path) + --it; + + if(it->second != this) + return it->second->route(path); + + it = begin(resources); + assert(it != end(resources)); + return mutable_cast(*it->second); +} + ircd::resource::method & ircd::resource::operator[](const string_view &name) const try @@ -580,7 +581,9 @@ try } }; - client.request.params = lstrip(head.path, resource->path); + // The path components after the resource->path become the parameter + // vector (or parv[]) passed to the resource as its arguments. + client.request.params = resource->params(head.path); client.request.params = strip(client.request.params, '/'); client.request.parv = { diff --git a/matrix/resource.cc b/matrix/resource.cc index 4201ceafd..d87b3167a 100644 --- a/matrix/resource.cc +++ b/matrix/resource.cc @@ -38,6 +38,123 @@ ircd::m::x_matrix_verify_destination { "default", true }, }; +// +// m::resource +// + +ircd::m::resource::resource(const string_view &path) +:m::resource +{ + path, {}, +} +{ +} + +ircd::m::resource::resource(const string_view &path, + struct opts opts) +:ircd::resource +{ + path_canonize(path_buf, path), opts +} +{ +} + +ircd::m::resource::~resource() +noexcept +{ +} + +ircd::string_view +ircd::m::resource::params(const string_view &path) +const +{ + const auto prefix_tokens + { + token_count(this->path, '/') + }; + + const auto version + { + path_version(path) + }; + + const auto params_after + { + prefix_tokens? prefix_tokens - 1 + bool(version): 0 + }; + + const auto ret + { + tokens_after(path, '/', params_after) + }; + + return ret; +} + +ircd::resource & +ircd::m::resource::route(const string_view &path) +const +{ + thread_local char canon_buf[1024]; + const string_view canon_path + { + path_canonize(canon_buf, path) + }; + + return mutable_cast(ircd::resource::route(canon_path)); +} + +ircd::string_view +ircd::m::resource::path_canonize(const mutable_buffer &buf, + const string_view &path) +{ + const auto version + { + path_version(path) + }; + + if(!version) + return path; + + const auto &[before, after] + { + tokens_split(path, '/', 2, 1) + }; + + mutable_buffer out{buf}; + consume(out, copy(out, '/')); + consume(out, copy(out, before)); + if(likely(after)) + { + consume(out, copy(out, '/')); + consume(out, copy(out, after)); + } + + return string_view + { + data(buf), data(out) + }; +} + +ircd::string_view +ircd::m::resource::path_version(const string_view &path) +{ + const auto version + { + token(path, '/', 2, {}) + }; + + const bool pass + { + true + && version.size() >= 2 + && (version[0] == 'v' || version[0] == 'r') + && (version[1] >= '0' && version[1] <= '9') + }; + + return pass? version: string_view{}; +} + // // m::resource::method // @@ -86,10 +203,11 @@ try if(ident) log::debug { - log, "%s %s %s `%s'", + log, "%s %s %s %s `%s'", client.loghead(), ident, request.head.method, + request.version?: "??"_sv, request.head.path, }; @@ -160,6 +278,10 @@ ircd::m::resource::request::request(const method &method, { r } +,version +{ + path_version(head.path) +} ,authorization { split(head.authorization, ' ')