// Copyright (c) 2022 Robin Davies // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "pch.h" #include "Uri.hpp" #include "HtmlHelper.hpp" #include "PiPedalException.hpp" using namespace pipedal; std::string uri::segment(int index) const { const char *p = path_start; int segment = 0; if (p != path_end && *p == '/') ++p; while (p != path_end && *p != '?' && *p != '#') { if (segment == index) { const char*segmentStart = p; while(p != path_end && *p != '/' && *p != '?' && *p != '#') { ++p; } return HtmlHelper::decode_url_segment(segmentStart,p); } else { while(p != path_end && *p != '/' && *p != '?' && *p != '#') { ++p; } } if (p != path_end && *p == '/') ++p; ++segment; } throw std::invalid_argument("Invalid segement number."); }; int uri::segment_count() const { const char *p = path_start; int segment = 0; if (p != path_end && *p == '/') ++p; while (p != path_end && *p != '?' && *p != '#') { while(p != path_end && *p != '/' && *p != '?' && *p != '#') { ++p; } if (p != path_end && *p == '/') ++p; ++segment; } return segment; } void uri::set(const char*start, const char*end) { this->text = std::string(start,end); set_(); } void uri::set(const char*text) { this->text = text; set_(); } void uri::set_() { const char*start = this->text.c_str(); const char*end = start+this->text.size(); const char* p = start; this->isRelative = true; bool hasScheme = false; while (p != end) { char c = *p; if (c == ':') { hasScheme = true; break; } if (c == '/' || c == '?' || c == '#') { break; } ++p; } if (hasScheme) { scheme_start = start; scheme_end = p; ++p; } else { scheme_start = start; scheme_end = start; p = start; } if (p[0] == '/' && p[1] == '/') { this->isRelative = false; // authority. p += 2; user_start = p; user_end = p; authority_start = p; authority_end = nullptr; const char*port_start = nullptr; while (p != end) { char c = *p; if (c == '@') { user_end = p; authority_start = p+1; port_start = nullptr; } else if (c == ':') { authority_end = p; port_start = p+1; } else if (c == ']') // ignore colon inside ipv6 address. { port_start = nullptr; } if (c == '/' || c == '?' || c == '#') { break; } ++p; } if (authority_end == nullptr) authority_end = p; if (port_start != nullptr) { char*intEnd; port_ = (int)std::strtol(port_start,&intEnd,10); if (intEnd != p) throwInvalid(); } else { port_ = -1; } } else { user_start = p; user_end = p; authority_start = p; authority_end = p; port_ = -1; } path_start = p; if (p != end && *p == '/') this->isRelative = false; while (p != end && *p != '?' && *p != '#') { ++p; } path_end = p; if (p != end && *p == '?') { ++p; query_start = p; while (p != end && *p != '#') { ++p; } query_end = p; } else { query_start = query_end = p; } if (p != end && *p == '#') { ++p; fragment_start = p; fragment_end = end; } else { fragment_start = fragment_end = p; } } void uri::throwInvalid() { throw std::invalid_argument("Invalid uri."); } static bool compare_name(const char*start, const char*end, const char*szName) { while (start != end) { if (*start != *szName) return false; ++start; ++szName; } return *szName == 0; } int uri::query_count() const { int count = 0; const char*p = query_start; while (p != query_end && *p != '#') { const char*nameStart = p; while (p != query_end && *p != '&' && *p != '#') { ++p; } if (p != query_end && *p == '&') ++p; ++count; if (p == query_end || *p == '#') { break; } } return count; } bool uri::has_query(const char*name) const { const char*p = query_start; while (p != query_end && *p != '#') { const char*nameStart = p; while (p != query_end && *p != '&' && *p != '=' && *p != '#') { ++p; } // compare names. if (compare_name(nameStart,p,name)) { return true; } while (p != query_end && *p != '&' && *p != '#') { ++p; } if (p != query_end && *p == '&') ++p; } return false; } query_segment uri::query(int index) const { const char*p = query_start; int ix = 0; while (p != query_end && *p != '#') { const char*nameStart = p; while (p != query_end && *p != '&' && *p != '=' && *p != '#') { ++p; } // compare names. //if (compare_name(nameStart,p,name)) if (ix == index) { const char *nameEnd = p; if (p != query_end && *p == '=') { ++p; const char*value_start = p; while(p != query_end && *p != '&' && *p != '#') { ++p; } return query_segment( std::string(nameStart,nameEnd), std::string(value_start,p) ); } else { return query_segment( std::string(nameStart,nameEnd), "" ); } } while (p != query_end && *p != '&' && *p != '#') { ++p; } if (p != query_end && *p == '&') { ++p; } ++ix; } throw PiPedalArgumentException("Index out of range."); } std::string uri::query(const char*name) const { const char*p = query_start; while (p != query_end && *p != '#') { const char*nameStart = p; while (p != query_end && *p != '&' && *p != '=' && *p != '#') { ++p; } // compare names. if (compare_name(nameStart,p,name)) { if (p != query_end && *p == '=') { ++p; const char*value_start = p; while(p != query_end && *p != '&' && *p != '#') { ++p; } return HtmlHelper::decode_url_segment(value_start,p); } else { return ""; } } while (p != query_end && *p != '&' && *p != '#') { ++p; } if (p != query_end && *p == '&') { ++p; } } return ""; } query_segment uri::query(int index) { const char*p = query_start; int count = 0; while (p != query_end && *p != '#') { if (count == index) { const char*nameStart = p; while (p != query_end && *p != '&' && *p != '=' && *p != '#') { ++p; } const char *nameEnd = p; const char *valueStart, *valueEnd; if(p == query_end || *p != '=') { valueStart = valueEnd = p; } else { ++p; valueStart = p; while(p != query_end && *p != '&' && *p != '#') { ++p; } valueEnd = p; } return query_segment(std::string(nameStart,nameEnd), HtmlHelper::decode_url_segment(valueStart,valueEnd)); } else { while (p != query_end && *p != '&' && *p != '#') { ++p; } } if (p != query_end && *p == '&') ++p; ++count; } throw std::invalid_argument("Argument out of range."); } std::vector uri::segments() { std::vector result; for (int i = 0; i < segment_count(); ++i) { result.push_back(segment(i)); } return result; } std::string uri::get_extension() const { const char*p = this->path_end; while (p != this->path_start) { char c = p[-1]; if (c == '/') return ""; if (c == '.') { --p; return std::string(p,this->path_end-p); } --p; } return ""; } std::string uri::to_canonical_form() const { uri_builder builder(*this); return builder.str(); } std::string uri_builder::str() const { std::stringstream s; if (scheme_.length() != 0) { s << scheme_ << ':'; } if (authority_.length() != 0) { s << "//"; if (user_.length() != 0) { s << user_ << '@'; } s << authority_; if (port_ != -1) { s << ':' << (uint16_t)port_; } } if ( authority_.length() != 0 || !isRelative_) { // canonical root form is http://xyz/ not httP://xyz s << '/'; } for (size_t i = 0; i < segments_.size(); ++i) { if (i != 0) s << '/'; HtmlHelper::encode_url_segment(s,segments_[i],false); } for (size_t i = 0; i < queries_.size(); ++i) { if (i == 0) { s << '?'; } else { s << '&'; } s << queries_[i].key << "="; HtmlHelper::encode_url_segment(s,queries_[i].value,true); } if (fragment_.length() != 0) { s << "#" << fragment_; } return s.str(); }