| /* | |
| 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. | |
| */ | |
| #include <algorithm> | |
| #include <sstream> | |
| #include "cripts/Lulu.hpp" | |
| #include "cripts/Preamble.hpp" | |
| namespace cripts | |
| { | |
| std::vector<cripts::string_view> | |
| Url::Component::Split(const char delim) | |
| { | |
| return cripts::Splitter(GetSV(), delim); | |
| } | |
| cripts::string_view | |
| Url::Scheme::GetSV() | |
| { | |
| if (_owner && _data.empty()) { | |
| const char *value = nullptr; | |
| int len = 0; | |
| _ensure_initialized(_owner); | |
| value = TSUrlSchemeGet(_owner->_bufp, _owner->_urlp, &len); | |
| _data = cripts::string_view(value, len); | |
| } | |
| return _data; | |
| } | |
| Url::Scheme | |
| Url::Scheme::operator=(cripts::string_view scheme) | |
| { | |
| CAssert(!_owner->ReadOnly()); // This can not be a read-only URL | |
| _ensure_initialized(_owner); | |
| TSUrlSchemeSet(_owner->_bufp, _owner->_urlp, scheme.data(), scheme.size()); | |
| _owner->_modified = true; | |
| Reset(); | |
| _loaded = false; | |
| return *this; | |
| } | |
| cripts::string_view | |
| Url::Host::GetSV() | |
| { | |
| if (_owner && _data.empty()) { | |
| const char *value = nullptr; | |
| int len = 0; | |
| _ensure_initialized(_owner); | |
| value = TSUrlHostGet(_owner->_bufp, _owner->_urlp, &len); | |
| _data = cripts::string_view(value, len); | |
| _loaded = true; | |
| } | |
| return _data; | |
| } | |
| Url::Host | |
| Url::Host::operator=(cripts::string_view host) | |
| { | |
| _ensure_initialized(_owner); | |
| CAssert(!_owner->ReadOnly()); // This can not be a read-only URL | |
| TSUrlHostSet(_owner->_bufp, _owner->_urlp, host.data(), host.size()); | |
| _owner->_modified = true; | |
| Reset(); | |
| _loaded = false; | |
| return *this; | |
| } | |
| Url::Port::operator integer() // This should not be explicit | |
| { | |
| _ensure_initialized(_owner); | |
| if (_owner && _port < 0) { | |
| _port = TSUrlPortGet(_owner->_bufp, _owner->_urlp); | |
| } | |
| return _port; | |
| } | |
| Url::Port | |
| Url::Port::operator=(int port) | |
| { | |
| _ensure_initialized(_owner); | |
| CAssert(!_owner->ReadOnly()); // This can not be a read-only URL | |
| TSUrlPortSet(_owner->_bufp, _owner->_urlp, port); | |
| _owner->_modified = true; | |
| Reset(); | |
| return *this; | |
| } | |
| cripts::string_view | |
| Url::Path::GetSV() | |
| { | |
| if (_state && _state->segments.size() > 0) { | |
| std::ostringstream path; | |
| std::ranges::copy(_state->segments, std::ostream_iterator<cripts::string_view>(path, "/")); | |
| _state->storage.reserve(_state->size); | |
| _state->storage = std::string_view(path.str()); | |
| if (_state->storage.size() > 0) { | |
| _state->storage.pop_back(); // Removes the trailing / | |
| } | |
| return {_state->storage}; | |
| } else if (_owner && _data.empty()) { | |
| const char *value = nullptr; | |
| int len = 0; | |
| _ensure_initialized(_owner); | |
| value = TSUrlPathGet(_owner->_bufp, _owner->_urlp, &len); | |
| _data = cripts::string_view(value, len); | |
| if (_state) { | |
| _state->size = len; | |
| } | |
| _loaded = true; | |
| } | |
| return _data; | |
| } | |
| Url::Path::String | |
| Url::Path::operator[](Segments::size_type ix) | |
| { | |
| Url::Path::String ret; | |
| _ensure_initialized(_owner); | |
| _parser(); // Make sure the segments are loaded | |
| if (_state && ix < _state->segments.size()) { | |
| ret._initialize(_state->segments[ix], this, ix); | |
| } | |
| return ret; // RVO | |
| } | |
| Url::Path & | |
| Url::Path::operator=(cripts::string_view path) | |
| { | |
| _ensure_initialized(_owner); | |
| CAssert(!_owner->ReadOnly()); // This can not be a read-only URL | |
| TSUrlPathSet(_owner->_bufp, _owner->_urlp, path.data(), path.size()); | |
| _owner->_modified = true; | |
| Reset(); | |
| _loaded = false; | |
| return *this; | |
| } | |
| cripts::string | |
| Url::Path::operator+=(cripts::string_view add) | |
| { | |
| cripts::string str; | |
| if (add.size() > 0) { | |
| str.assign(GetSV()); | |
| str += add; | |
| operator=(str); | |
| } | |
| return str; // RVO | |
| } | |
| Url::Path::String & | |
| Url::Path::String::operator=(const cripts::string_view str) | |
| { | |
| _ensure_initialized(_owner->_owner); | |
| CAssert(!_owner->_owner->ReadOnly()); // This can not be a read-only URL | |
| CAssert(_owner->_state); // Should have been allocated by operator[]/_parser() | |
| _owner->_state->size -= _owner->_state->segments[_ix].size(); | |
| _owner->_state->segments[_ix] = str; | |
| _owner->_state->size += str.size(); | |
| _owner->_state->modified = true; | |
| return *this; | |
| } | |
| void | |
| Url::Path::Reset() | |
| { | |
| Component::Reset(); | |
| _state.reset(); | |
| } | |
| void | |
| Url::Path::Push(cripts::string_view val) | |
| { | |
| _parser(); | |
| auto &s = _ensure_state(); | |
| s.modified = true; | |
| s.segments.push_back(val); | |
| } | |
| void | |
| Url::Path::Insert(Segments::size_type ix, cripts::string_view val) | |
| { | |
| _parser(); | |
| auto &s = _ensure_state(); | |
| s.modified = true; | |
| s.segments.insert(s.segments.begin() + ix, val); | |
| } | |
| void | |
| Url::Path::_parser() | |
| { | |
| auto &s = _ensure_state(); | |
| if (s.segments.size() == 0) { | |
| s.segments = Split('/'); | |
| } | |
| } | |
| Url::Query::Parameter & | |
| Url::Query::Parameter::operator=(const cripts::string_view str) | |
| { | |
| CAssert(!_owner->_state || !_owner->_state->standalone); | |
| _ensure_initialized(_owner->_owner); | |
| CAssert(!_owner->_owner->ReadOnly()); // This can not be a read-only URL | |
| auto &s = _owner->_ensure_state(); | |
| auto iter = s.hashed.find(_name); | |
| if (iter != s.hashed.end()) { | |
| iter->second = str; // Can be an empty string here! | |
| } else { | |
| s.ordered.push_back(_name); | |
| s.hashed[_name] = str; | |
| } | |
| s.modified = true; | |
| return *this; | |
| } | |
| cripts::string_view | |
| Url::Query::GetSV() | |
| { | |
| if (!_state || !_state->standalone) { | |
| _ensure_initialized(_owner); | |
| } | |
| if (_state && _state->ordered.size() > 0) { | |
| _state->storage.clear(); | |
| _state->storage.reserve(_state->size); | |
| // ToDo: This is wonky, has to be a better std:: iteration to do here | |
| for (const auto key : _state->ordered) { | |
| auto iter = _state->hashed.find(key); | |
| if (_state->storage.size() > 0) { | |
| _state->storage += "&"; | |
| } | |
| if (iter != _state->hashed.end()) { | |
| _state->storage += iter->first; | |
| if (iter->second.size() > 0) { | |
| _state->storage += '='; | |
| _state->storage += iter->second; | |
| } | |
| } | |
| } | |
| return {_state->storage}; | |
| } | |
| // This gets weird when we modify the query parameter components, and can possibly empty | |
| // the entire query parameter. At which point, we don't want to reload the string_view | |
| // from the URL object inside of ATS... | |
| if (_owner && !_loaded) { | |
| const char *value = nullptr; | |
| int len = 0; | |
| value = TSUrlHttpQueryGet(_owner->_bufp, _owner->_urlp, &len); | |
| _data = cripts::string_view(value, len); | |
| if (_state) { | |
| _state->size = len; | |
| } | |
| _loaded = true; | |
| } | |
| return _data; | |
| } | |
| Url::Query & | |
| Url::Query::operator=(cripts::string_view query) | |
| { | |
| CAssert(!_state || !_state->standalone); | |
| _ensure_initialized(_owner); | |
| CAssert(!_owner->ReadOnly()); // This can not be a read-only URL | |
| TSUrlHttpQuerySet(_owner->_bufp, _owner->_urlp, query.data(), query.size()); | |
| _owner->_modified = true; | |
| Reset(); | |
| _loaded = false; | |
| return *this; | |
| } | |
| cripts::string | |
| Url::Query::operator+=(cripts::string_view add) | |
| { | |
| cripts::string str; | |
| if (add.size() > 0) { | |
| str.assign(GetSV()); | |
| str += add; | |
| operator=(str); | |
| } | |
| return str; // RVO | |
| } | |
| Url::Query::Parameter | |
| Url::Query::operator[](cripts::string_view param) | |
| { | |
| // Make sure the hash and vector are populated, but only if we have an owner | |
| if (!_state || !_state->standalone) { | |
| _ensure_initialized(_owner); | |
| } | |
| _parser(); | |
| Parameter ret; | |
| auto iter = _state->hashed.find(param); | |
| if (iter != _state->hashed.end()) { | |
| ret._initialize(iter->first, iter->second, this); | |
| } else { | |
| ret._initialize(param, "", this); | |
| } | |
| return ret; | |
| } | |
| void | |
| Url::Query::Erase(cripts::string_view param) | |
| { | |
| // Make sure the hash and vector are populated | |
| _parser(); | |
| auto &s = *_state; | |
| auto iter = s.hashed.find(param); | |
| auto viter = std::ranges::find(s.ordered, param); | |
| if (iter != s.hashed.end()) { | |
| s.size -= iter->second.size(); // Size of the erased value | |
| s.hashed.erase(iter); | |
| CAssert(viter != s.ordered.end()); | |
| s.size -= viter->size(); // Length of the erased key | |
| s.ordered.erase(viter); | |
| if (s.ordered.size() == 0) { | |
| Reset(); | |
| // After Reset() the state is gone; re-create it just so _modified can be tracked. | |
| _ensure_state().modified = true; | |
| } else { | |
| s.modified = true; | |
| } | |
| } | |
| } | |
| void | |
| Url::Query::Erase(std::initializer_list<cripts::string_view> list, bool keep) | |
| { | |
| if (keep) { | |
| // Make sure the hash and vector are populated | |
| _parser(); | |
| auto &s = *_state; | |
| for (auto viter = s.ordered.begin(); viter != s.ordered.end();) { | |
| if (list.end() == std::ranges::find(list, *viter)) { | |
| // Duplicate keys (?a=1&a=2) share one hashed entry, so a later occurrence may | |
| // already be gone -- guard the lookup; attacker input must not abort. | |
| if (auto iter = s.hashed.find(*viter); iter != s.hashed.end()) { | |
| s.size -= iter->second.size(); // Size of the erased value | |
| s.hashed.erase(iter); | |
| } | |
| s.size -= viter->size(); // Length of the erased key | |
| viter = s.ordered.erase(viter); | |
| s.modified = true; | |
| } else { | |
| ++viter; | |
| } | |
| } | |
| if (s.ordered.size() == 0) { | |
| Reset(); | |
| } | |
| } else { | |
| for (auto &it : list) { | |
| Erase(it); | |
| } | |
| } | |
| } | |
| void | |
| Url::Query::Reset() | |
| { | |
| Component::Reset(); | |
| _state.reset(); | |
| } | |
| void | |
| Url::Query::_parser() | |
| { | |
| auto &s = _ensure_state(); | |
| if (s.ordered.size() == 0) { | |
| for (const auto sv : Split('&')) { | |
| const auto eq = sv.find_first_of('='); | |
| cripts::string_view key = sv.substr(0, eq); | |
| cripts::string_view val; | |
| if (eq != cripts::string_view::npos) { | |
| val = sv.substr(eq + 1); | |
| } | |
| s.ordered.push_back(key); // Keep the order | |
| s.hashed[key] = val; | |
| } | |
| } | |
| } | |
| cripts::string | |
| Url::String() | |
| { | |
| cripts::string ret; | |
| CAssert(_context); | |
| _ensure_initialized(this); | |
| int full_len = 0; | |
| char *full_str = TSUrlStringGet(_bufp, _urlp, &full_len); | |
| if (full_str) { | |
| ret.assign(full_str, full_len); | |
| TSfree(static_cast<void *>(full_str)); | |
| } | |
| return ret; | |
| } | |
| Pristine::URL & | |
| Pristine::URL::_get(cripts::Context *context) | |
| { | |
| auto &slot = context->_urls.pristine; | |
| if (!slot) { | |
| slot = std::make_unique<Pristine::URL>(); | |
| slot->set_context(context); | |
| } | |
| _ensure_initialized(slot.get()); | |
| return *slot; | |
| } | |
| void | |
| Pristine::URL::_initialize() | |
| { | |
| TSAssert(_context->state.txnp); | |
| if (TSHttpTxnPristineUrlGet(_context->state.txnp, &_bufp, &_urlp) != TS_SUCCESS) { | |
| _context->state.error.Fail(); | |
| } else { | |
| super_type::_initialize(); // Only if successful | |
| } | |
| } | |
| void | |
| Client::URL::_initialize() | |
| { | |
| if (_context->rriValid()) { | |
| super_type::_initialize(); | |
| _bufp = _context->rri->requestBufp; | |
| _hdr_loc = _context->rri->requestHdrp; | |
| _urlp = _context->rri->requestUrl; | |
| _owns_urlp = false; | |
| } else { | |
| Client::Request &req = Client::Request::_get(_context); // Repurpose / create the shared request object | |
| _bufp = req.BufP(); | |
| _hdr_loc = req.MLoc(); | |
| if (TSHttpHdrUrlGet(_bufp, _hdr_loc, &_urlp) != TS_SUCCESS) { | |
| _context->state.error.Fail(); | |
| } else { | |
| super_type::_initialize(); | |
| _owns_urlp = true; | |
| } | |
| } | |
| } | |
| Client::URL & | |
| Client::URL::_get(cripts::Context *context) | |
| { | |
| _ensure_initialized(&context->_urls.request); | |
| return context->_urls.request; | |
| } | |
| bool | |
| Client::URL::_update() | |
| { | |
| _ensure_initialized(this); | |
| path.Flush(); | |
| query.Flush(); | |
| return _modified; | |
| } | |
| void | |
| Remap::From::URL::_initialize() | |
| { | |
| if (_context->rriValid()) { | |
| super_type::_initialize(); | |
| _bufp = _context->rri->requestBufp; | |
| _hdr_loc = _context->rri->requestHdrp; | |
| _urlp = _context->rri->mapFromUrl; | |
| } else { | |
| _context->state.error.Fail(); | |
| } | |
| } | |
| Remap::From::URL & | |
| Remap::From::URL::_get(cripts::Context *context) | |
| { | |
| auto &slot = context->_urls.remap.from; | |
| if (!slot) { | |
| slot = std::make_unique<Remap::From::URL>(); | |
| slot->set_context(context); | |
| } | |
| _ensure_initialized(slot.get()); | |
| return *slot; | |
| } | |
| void | |
| Remap::To::URL::_initialize() | |
| { | |
| if (_context->rriValid()) { | |
| super_type::_initialize(); | |
| _bufp = _context->rri->requestBufp; | |
| _hdr_loc = _context->rri->requestHdrp; | |
| _urlp = _context->rri->mapToUrl; | |
| } else { | |
| _context->state.error.Fail(); | |
| } | |
| } | |
| Remap::To::URL & | |
| Remap::To::URL::_get(cripts::Context *context) | |
| { | |
| auto &slot = context->_urls.remap.to; | |
| if (!slot) { | |
| slot = std::make_unique<Remap::To::URL>(); | |
| slot->set_context(context); | |
| } | |
| _ensure_initialized(slot.get()); | |
| return *slot; | |
| } | |
| Cache::URL & | |
| Cache::URL::_get(cripts::Context *context) | |
| { | |
| _ensure_initialized(&context->_cache.url); | |
| return context->_cache.url; | |
| } | |
| void | |
| Cache::URL::_initialize() | |
| { | |
| Cache::URL *url = &_context->_cache.url; | |
| Client::Request &req = Client::Request::_get(_context); // Repurpose / create the shared request object | |
| switch (_context->state.hook) { | |
| // In these hooks, the internal cache-url has been properly set | |
| case TS_HTTP_SEND_RESPONSE_HDR_HOOK: | |
| case TS_HTTP_READ_RESPONSE_HDR_HOOK: | |
| case TS_HTTP_SEND_REQUEST_HDR_HOOK: | |
| case TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK: | |
| case TS_HTTP_TXN_CLOSE_HOOK: | |
| if (TSUrlCreate(req.BufP(), &url->_urlp) == TS_SUCCESS) { | |
| TSAssert(_context->state.txnp); | |
| if (TSHttpTxnCacheLookupUrlGet(_context->state.txnp, req.BufP(), url->_urlp) != TS_SUCCESS) { | |
| _context->state.error.Fail(); | |
| return; | |
| } | |
| } else { | |
| _context->state.error.Fail(); | |
| return; | |
| } | |
| break; | |
| default: { // This means we have to clone. ToDo: For now, this is implicitly using Client::URL | |
| Client::URL &src = Client::URL::_get(_context); | |
| if (TSUrlClone(req.BufP(), req.BufP(), src.UrlP(), &url->_urlp) != TS_SUCCESS) { | |
| _context->state.error.Fail(); | |
| return; | |
| } | |
| } break; | |
| } | |
| // Only if we succeeded above | |
| super_type::_initialize(); | |
| _bufp = req.BufP(); | |
| _hdr_loc = req.MLoc(); | |
| } | |
| // This has to be implemented here, since the cripts::Context is not complete yet | |
| bool | |
| Cache::URL::_update() | |
| { | |
| // For correctness, we will also make sure the Path and Query objects are flushed | |
| path.Flush(); | |
| query.Flush(); | |
| if (_modified) { | |
| _ensure_initialized(&_context->_cache.url); | |
| TSAssert(_context->state.txnp); | |
| _modified = false; | |
| if (TS_SUCCESS == TSHttpTxnCacheLookupUrlSet(_context->state.txnp, _bufp, _urlp)) { | |
| if (_context->p_instance.DebugOn()) { | |
| _context->p_instance.debug("Successfully setting cache-key to {}", String()); | |
| } | |
| return true; | |
| } else { | |
| if (_context->p_instance.DebugOn()) { | |
| _context->p_instance.debug("Could not set the cache key to {}", String()); | |
| } | |
| _context->state.error.Fail(); | |
| return false; | |
| } | |
| } | |
| return false; | |
| } | |
| // ToDo: This may need more work, to understand which hooks the parent URL is actually available | |
| Parent::URL & | |
| Parent::URL::_get(cripts::Context *context) | |
| { | |
| auto &slot = context->_urls.parent; | |
| if (!slot) { | |
| slot = std::make_unique<Parent::URL>(); | |
| slot->set_context(context); | |
| } | |
| _ensure_initialized(slot.get()); | |
| return *slot; | |
| } | |
| void | |
| Parent::URL::_initialize() | |
| { | |
| Client::Request &req = Client::Request::_get(_context); // Repurpose / create the shared request object | |
| if (TSUrlCreate(req.BufP(), &_urlp) == TS_SUCCESS) { | |
| TSAssert(_context->state.txnp); | |
| if (TSHttpTxnParentSelectionUrlGet(_context->state.txnp, req.BufP(), _urlp) != TS_SUCCESS) { | |
| _context->state.error.Fail(); | |
| return; | |
| } | |
| } else { | |
| _context->state.error.Fail(); | |
| return; | |
| } | |
| // Only if successful above | |
| super_type::_initialize(); | |
| _bufp = req.BufP(); | |
| _hdr_loc = req.MLoc(); | |
| } | |
| } // namespace cripts |