732 lines
21 KiB
C++
732 lines
21 KiB
C++
#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<std::string> 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<std::string> 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<v.size();i++)
|
|
{
|
|
if(v[i] == '+')
|
|
s.push_back(' ');
|
|
else if(v[i] == '%')
|
|
{
|
|
i++;
|
|
uint8_t n = HexToNibble(v[i])<<4;
|
|
i++;
|
|
n |= HexToNibble(v[i]);
|
|
s.push_back((char)n);
|
|
}
|
|
else
|
|
s.push_back(v[i]);
|
|
|
|
}
|
|
return s;
|
|
}
|
|
std::string HttpUtils::UrlPathEncode(std::string v,bool ignoreSpace)
|
|
{
|
|
std::string s = {};
|
|
|
|
for(auto item : v)
|
|
{
|
|
if(item >= '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<v.size();i++)
|
|
{
|
|
if(v[i] == '%')
|
|
{
|
|
i++;
|
|
uint8_t n = HexToNibble(v[i])<<4;
|
|
i++;
|
|
n |= HexToNibble(v[i]);
|
|
s.push_back((char)n);
|
|
}
|
|
else
|
|
s.push_back(v[i]);
|
|
|
|
}
|
|
return s;
|
|
}
|
|
|
|
std::string HttpUtils::UrlEncode(std::string v)
|
|
{
|
|
std::string s = {};
|
|
|
|
for(auto item : v)
|
|
{
|
|
if(item == ' ')
|
|
s.push_back('+');
|
|
else if(item >= '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<std::string> HttpUtils::SplitString(std::string text, std::string delimiter,std::size_t maxCnt)
|
|
{
|
|
std::vector<std::string> 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<std::string> 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<std::string> 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";
|
|
}
|
|
|
|
} |