#include "TessesFramework/Http/HttpUtils.hpp" #include "TessesFramework/Filesystem/VFS.hpp" using VFSPath = Tesses::Framework::Filesystem::VFSPath; namespace Tesses::Framework::Http { bool Uri::Relative(std::string url, Uri& uri) { auto index = url.find_first_of("//"); if(index != std::string::npos) { if(Uri::TryParse(url,uri)) { if(index == 0) uri.scheme = this->scheme; return true; } } else if(!url.empty()) { if(url[0] == '/') { auto thirdPart = HttpUtils::SplitString(url,"#",2); if(thirdPart.empty()) return false; if(thirdPart.size() == 2) { uri.hash=thirdPart[1]; } auto fourthPart = HttpUtils::SplitString(thirdPart[1],"?",2); VFSPath p = fourthPart[0]; uri.path = p.CollapseRelativeParents().ToString(); //this should be safe if(fourthPart.size() == 2) { HttpUtils::QueryParamsDecode(uri.query, fourthPart[1]); } } else { auto thirdPart = HttpUtils::SplitString(url,"#",2); if(thirdPart.empty()) return false; if(thirdPart.size() == 2) { uri.hash=thirdPart[1]; } auto fourthPart = HttpUtils::SplitString(thirdPart[1],"?",2); VFSPath p = VFSPath(this->path,fourthPart[0]); uri.path = p.CollapseRelativeParents().ToString(); //this should be safe if(fourthPart.size() == 2) { HttpUtils::QueryParamsDecode(uri.query, fourthPart[1]); } } uri.scheme = this->scheme; uri.host = this->host; uri.port = this->port; return true; } return false; } std::string Uri::HostPort() { if(this->port != 0) return this->host + ":" + std::to_string(this->port); return this->host; } uint16_t Uri::GetPort() { if(this->port != 0) return this->port; if(this->scheme == "http:") return 80; if(this->scheme == "https:") return 443; if(this->scheme == "sftp:") return 22; if(this->scheme == "ftp:") return 21; if(this->scheme == "tftp:") return 69; return 0; } bool Uri::TryParse(std::string url, Uri& uri) { uri.scheme = ""; uri.port=0; auto firstPart = HttpUtils::SplitString(url,"//",2); if(firstPart.size() == 2) uri.scheme=firstPart[0]; else if(firstPart.empty()) return false; auto secondPart = HttpUtils::SplitString(firstPart.size() == 2 ? firstPart[1] : firstPart[0] ,"/",2); if(secondPart.size() == 1) { uri.path = "/"; } else if(secondPart.size() == 2) { auto thirdPart = HttpUtils::SplitString(secondPart[1],"#",2); if(thirdPart.empty()) return false; if(thirdPart.size() == 2) { uri.hash=thirdPart[1]; } auto fourthPart = HttpUtils::SplitString(thirdPart[0],"?",2); uri.path = "/" + fourthPart[0]; //this should be safe if(fourthPart.size() == 2) { HttpUtils::QueryParamsDecode(uri.query, fourthPart[1]); } } else { return false; } auto hostPortPart = HttpUtils::SplitString(secondPart[0],":",2); if(hostPortPart.empty()) return false; if(hostPortPart.size() == 2) { uri.port = (uint16_t)std::stoul(hostPortPart[1]); } uri.host = hostPortPart[0]; return true; } std::string Uri::GetPathAndQuery() { return this->path + this->GetQuery(); } std::string Uri::GetQuery() { if(this->query.kvp.empty()) return ""; std::string queryStr = "?"; queryStr.append(HttpUtils::QueryParamsEncode(query)); return queryStr; } std::string Uri::ToString() { std::string uri = this->scheme; uri.append("//"); uri.append(this->host); if(this->port > 0) { uri.push_back(':'); uri.append(std::to_string(this->port)); } uri.append(this->GetPathAndQuery()); return uri; } char HttpUtils::NibbleToHex(uint8_t b) { b %= 16; if(b >= 0 && b <= 9) return b + '0'; if(b >= 10 && b <= 15) return b + ('a' - 10); return 0; } uint8_t HttpUtils::HexToNibble(char c) { if(c >= '0' && c <= '9') return (uint8_t)(c - '0'); if(c >= 'A' && c <= 'F') return (uint8_t)(c - 55); if(c >= 'a' && c <= 'f') return (uint8_t)(c - 87); return 0; } std::string HttpUtils::MimeType(std::filesystem::path p) { std::string ext = p.extension().string(); if(ext == ".html" || ext == ".htm") { return "text/html"; } if(ext == ".txt" || ext == ".log" || ext == ".twss") { return "text/plain"; } if(ext == ".woff") { return "application/x-font-woff"; } if(ext == ".vtt") { return "text/vtt"; } if(ext == ".svg") { return "image/svg+xml"; } if(ext == ".webp") { return "image/webp"; } if(ext == ".vcf") { return "text/v-card"; } if(ext == ".rss" || ext == ".xml" || ext == ".atom" || ext == ".rdf") { return "application/xml"; } if(ext == ".js") { return "text/javascript"; } if(ext == ".json") { return "application/json"; } if(ext == ".wasm") { return "application/wasm"; } if(ext == ".png") { return "image/png"; } if(ext == ".jpg" || ext == ".jpeg") { return "image/jpeg"; } if(ext == ".css") { return "text/css"; } if(ext == ".gif") { return "image/gif"; } if(ext == ".mp4") { return "video/mp4"; } if(ext == ".mov") { return "video/quicktime"; } if(ext == ".m4a") { return "audio/mp4"; } if(ext == ".webm") { return "video/webm"; } if(ext == ".webmanifest") { return "application/manifest+json"; } if(ext == ".ico") { return "image/x-icon"; } return "application/octet-stream"; } bool HttpUtils::Invalid(char c) { //just do windows because it is the strictist when it comes to windows, mac and linux if(c >= 0 && c < 32) return true; if(c == 127) return true; if(c == '\\') return true; if(c == '*') return true; if(c == '/') return true; if(c == '|') return true; if(c == ':') return true; if(c == '<') return true; if(c == '>') return true; if(c == '\"') return true; if(c == '?') return true; return false; } std::string HttpUtils::Sanitise(std::string text) { std::string myStr={}; for(auto item : text) { if(Invalid(item)) continue; myStr.push_back(item); } return myStr; } void HttpUtils::QueryParamsDecode(HttpDictionary& dict,std::string query) { for(auto item : SplitString(query,"&")) { std::vector ss=SplitString(item,"=",2); if(ss.size() >= 1) { std::string value = {}; if(ss.size() == 2) { value = UrlDecode(ss[1]); } dict.AddValue(UrlDecode(ss[0]),value); } } } std::string HttpUtils::Join(std::string joinStr, std::vector ents) { std::string str={}; bool first=true; for(auto item : ents) { if(!first) str.append(joinStr); str.append(item); first=false; } return str; } std::string HttpUtils::QueryParamsEncode(HttpDictionary& dict) { std::string s={}; bool first = true; for(auto item : dict.kvp) { for(auto item2 : item.second) { if(!first) { s.push_back('&'); } s.insert(s.size(),UrlEncode(item.first)); s.push_back('='); s.insert(s.size(),UrlEncode(item2)); first=false; } } return s; } std::string HttpUtils::UrlDecode(std::string v) { std::string s = {}; for(size_t i = 0;i= 'A' && item <= 'Z') s.push_back(item); else if(item >= 'a' && item <= 'z') s.push_back(item); else if(item >= '0' && item <= '9') s.push_back(item); else if(item == '-' || item == '_' || item == '.' || item == '~') s.push_back(item); else { if(item != ' ' || !ignoreSpace) { s.push_back('%'); s.push_back(NibbleToHex((item >> 4) & 0xF)); s.push_back(NibbleToHex((item) & 0xF)); } else { s.push_back(' '); } } } return s; } std::string HttpUtils::UrlPathDecode(std::string v) { std::string s = {}; for(size_t i = 0;i= 'A' && item <= 'Z') s.push_back(item); else if(item >= 'a' && item <= 'z') s.push_back(item); else if(item >= '0' && item <= '9') s.push_back(item); else if(item == '-' || item == '_' || item == '.' || item == '~') s.push_back(item); else { s.push_back('%'); s.push_back(NibbleToHex((item >> 4) & 0xF)); s.push_back(NibbleToHex((item) & 0xF)); } } return s; } std::vector HttpUtils::SplitString(std::string text, std::string delimiter,std::size_t maxCnt) { std::vector strs; std::size_t i = 1; while(text.length() > 0) { if(i == maxCnt) { strs.push_back(text); break; } std::size_t index= text.find(delimiter); if(index == std::string::npos) { strs.push_back(text); break; } else { std::string left = text.substr(0,index); text = text.substr(index+delimiter.size()); strs.push_back(left); } i++; } return strs; } std::string HttpUtils::HtmlEncode(std::string html) { std::string myHtml = {}; for(auto item : html) { if(item == '\"') { myHtml.append("""); } else if(item == '\'') { myHtml.append("'"); } else if(item == '&') { myHtml.append("&"); } else if(item == '<') { myHtml.append("<"); } else if(item == '>') { myHtml.append(">"); } else { myHtml.push_back(item); } } return myHtml; } std::string HttpUtils::StatusCodeString(StatusCode code) { switch(code) { case StatusCode::Continue: return "Continue"; case StatusCode::SwitchingProtocols: return "Switching Protocols"; case StatusCode::Processing: return "Processing"; case StatusCode::EarlyHints: return "Early Hints"; case StatusCode::OK: return "OK"; case StatusCode::Created: return "Created"; case StatusCode::Accepted: return "Accepted"; case StatusCode::NonAuthoritativeInformation: return "Non-Authoritative Information"; case StatusCode::NoContent: return "No Content"; case StatusCode::ResetContent: return "Reset Content"; case StatusCode::PartialContent: return "PartialContent"; case StatusCode::MultiStatus: return "Multi-Status"; case StatusCode::AlreadyReported: return "Already Reported"; case StatusCode::IMUsed: return "IM Used"; case StatusCode::MultipleChoices: return "Multiple Choices"; case StatusCode::MovedPermanently: return "Moved Permanently"; case StatusCode::Found: return "Found"; case StatusCode::SeeOther: return "See Other"; case StatusCode::NotModified: return "Not Modified"; case StatusCode::UseProxy: return "Use Proxy"; case StatusCode::TemporaryRedirect: return "Temporary Redirect"; case StatusCode::PermanentRedirect: return "Permanent Redirect"; case StatusCode::BadRequest: return "Bad Request"; case StatusCode::Unauthorized: return "Unauthorized"; case StatusCode::PaymentRequired: return "Payment Required"; case StatusCode::Forbidden: return "Forbidden"; case StatusCode::NotFound: return "Not Found"; case StatusCode::MethodNotAllowed: return "Method Not Allowed"; case StatusCode::NotAcceptable: return "Not Acceptable"; case StatusCode::ProxyAuthenticationRequired: return "Proxy Authentication Required"; case StatusCode::RequestTimeout: return "Request Timeout"; case StatusCode::Conflict: return "Conflict"; case StatusCode::Gone: return "Gone"; case StatusCode::LengthRequired: return "Length Required"; case StatusCode::PreconditionFailed: return "Precondition Failed"; case StatusCode::PayloadTooLarge: return "Payload Too Large"; case StatusCode::URITooLong: return "URI Too Long"; case StatusCode::UnsupportedMediaType: return "Unsupported Media Type"; case StatusCode::RangeNotSatisfiable: return "Range Not Satisfiable"; case StatusCode::ExpectationFailed: return "Expectation Failed"; case StatusCode::ImATeapot: return "I'm a teapot"; case StatusCode::MisdirectedRequest: return "Misdirected Request"; case StatusCode::UnprocessableContent: return "Unprocessable Content"; case StatusCode::Locked: return "Locked"; case StatusCode::FailedDependency: return "Failed Dependency"; case StatusCode::TooEarly: return "Too Early"; case StatusCode::UpgradeRequired: return "Upgrade Required"; case StatusCode::PreconditionRequired: return "Precondition Required"; case StatusCode::TooManyRequests: return "Too Many Requests"; case StatusCode::RequestHeaderFieldsTooLarge: return "Request Header Fields Too Large"; case StatusCode::UnavailableForLegalReasons: return "Unavailable For Legal Reasons"; case StatusCode::InternalServerError: return "Internal Server Error"; case StatusCode::NotImplemented: return "Not Implemented"; case StatusCode::ServiceUnavailable: return "Service Unavailable"; case StatusCode::GatewayTimeout: return "Gateway Timeout"; case StatusCode::HTTPVersionNotSupported: return "HTTP Version Not Supported"; case StatusCode::VariantAlsoNegotiates: return "Variant Also Negotiates"; case StatusCode::InsufficientStorage: return "Insufficient Storage"; case StatusCode::LoopDetected: return "Loop Detected"; case StatusCode::NotExtended: return "Not Extended"; case StatusCode::NetworkAuthenticationRequired: return "Network Authentication Required"; default: return ""; } } void HttpDictionary::Clear() { kvp.clear(); } void HttpDictionary::Clear(std::string key, bool kvpExistsAfter) { if(kvpExistsAfter) { kvp[key].clear(); } else { if(kvp.count(key) == 0) return; kvp[key].clear(); kvp.erase(key); } } void HttpDictionary::SetValue(std::string key, std::string value) { kvp[key] = {value}; } void HttpDictionary::SetValue(std::string key, std::vector value) { kvp[key] = value; } void HttpDictionary::AddValue(std::string key, std::string value) { kvp[key].push_back(value); } void HttpDictionary::AddValue(std::string key, std::vector value) { auto& ls = kvp[key]; ls.insert(ls.end(), value.begin(), value.end()); } bool HttpDictionary::TryGetFirst(std::string key, std::string& value) { if(kvp.count(key) == 0) return false; auto& ls = kvp[key]; if(ls.empty()) return false; value = ls.front(); return true; } bool HttpDictionary::TryGetFirstInt(std::string key, int64_t& value) { std::string val; if(!TryGetFirst(key,val)) return false; try{ size_t off = 0; auto v = std::stoll(val,&off); if(off != val.size()) return false; value = v; } catch(std::exception& ex) { return false; } return true; } bool HttpDictionary::TryGetFirstDouble(std::string key, double& value) { std::string val; if(!TryGetFirst(key,val)) return false; try{ size_t off = 0; auto v = std::stod(val,&off); if(off != val.size()) return false; value = v; } catch(std::exception& ex) { return false; } return true; } bool HttpDictionary::GetFirstBoolean(std::string key) { std::string val; if(!TryGetFirst(key,val)) return false; return val == "true" || val == "on"; } }