first commit
This commit is contained in:
77
src/Http/ContentDisposition.cpp
Normal file
77
src/Http/ContentDisposition.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
#include "TessesFramework/Http/ContentDisposition.hpp"
|
||||
#include "TessesFramework/Http/HttpUtils.hpp"
|
||||
#include <iostream>
|
||||
namespace Tesses::Framework::Http
|
||||
{
|
||||
bool ContentDisposition::TryParse(std::string str, ContentDisposition& cd)
|
||||
{
|
||||
auto res = HttpUtils::SplitString(str,"; ");
|
||||
|
||||
|
||||
if(res.empty()) return false;
|
||||
cd.type = res[0];
|
||||
bool hasFileNameStar = false;
|
||||
for(size_t i = 1; i < res.size(); i++)
|
||||
{
|
||||
auto res2 = HttpUtils::SplitString(res[i],"=",2);
|
||||
if(res2.size() == 2)
|
||||
{
|
||||
if(res2[0] == "filename*")
|
||||
{
|
||||
//cd.filename = res2[1];
|
||||
//UTF-8''
|
||||
std::string p = res2[1];
|
||||
if(p.find("UTF-8''") == 0)
|
||||
{
|
||||
hasFileNameStar = true;
|
||||
p = HttpUtils::UrlPathDecode(p.substr(7));
|
||||
cd.filename = p;
|
||||
}
|
||||
}
|
||||
else if(res2[0] == "filename" && !hasFileNameStar)
|
||||
{
|
||||
std::string p = res2[1];
|
||||
if(!p.empty() && p[0] == '\"')
|
||||
{
|
||||
p = p.substr(1, p.size()-2);
|
||||
}
|
||||
|
||||
p = HttpUtils::UrlPathDecode(p);
|
||||
cd.filename = p;
|
||||
}
|
||||
else if(res2[0] == "name")
|
||||
{
|
||||
std::string p = res2[1];
|
||||
if(!p.empty() && p[0] == '\"')
|
||||
{
|
||||
p = p.substr(1, p.size()-2);
|
||||
}
|
||||
|
||||
cd.fieldName = HttpUtils::UrlPathDecode(p);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
std::string ContentDisposition::ToString()
|
||||
{
|
||||
std::vector<std::string> parts;
|
||||
|
||||
parts.push_back(this->type);
|
||||
|
||||
if(!this->fieldName.empty())
|
||||
{
|
||||
parts.push_back("name=\"" + HttpUtils::UrlPathEncode(this->fieldName,true) + "\"");
|
||||
}
|
||||
|
||||
if(!this->filename.empty())
|
||||
{
|
||||
parts.push_back("filename=\"" + HttpUtils::UrlPathEncode(this->filename,true) + "\"");
|
||||
}
|
||||
|
||||
return HttpUtils::Join("; ", parts);
|
||||
}
|
||||
}
|
||||
131
src/Http/FileServer.cpp
Normal file
131
src/Http/FileServer.cpp
Normal file
@ -0,0 +1,131 @@
|
||||
#include "TessesFramework/Http/FileServer.hpp"
|
||||
#include "TessesFramework/Filesystem/LocalFS.hpp"
|
||||
#include "TessesFramework/Filesystem/SubdirFilesystem.hpp"
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
using LocalFilesystem = Tesses::Framework::Filesystem::LocalFilesystem;
|
||||
using SubdirFilesystem = Tesses::Framework::Filesystem::SubdirFilesystem;
|
||||
using VFSPath = Tesses::Framework::Filesystem::VFSPath;
|
||||
using VFS = Tesses::Framework::Filesystem::VFS;
|
||||
namespace Tesses::Framework::Http
|
||||
{
|
||||
FileServer::FileServer(std::filesystem::path path,bool allowListing,bool spa) : FileServer(path,allowListing,spa,{"index.html","default.html","index.htm","default.htm"})
|
||||
{
|
||||
|
||||
}
|
||||
FileServer::FileServer(std::filesystem::path path,bool allowListing, bool spa, std::vector<std::string> defaultNames)
|
||||
{
|
||||
LocalFilesystem* lfs=new LocalFilesystem;
|
||||
SubdirFilesystem* sdfs=new SubdirFilesystem(lfs,lfs->SystemToVFSPath(path),true);
|
||||
this->vfs = sdfs;
|
||||
this->spa = spa;
|
||||
|
||||
this->ownsVFS=true;
|
||||
this->allowListing = allowListing;
|
||||
this->defaultNames = defaultNames;
|
||||
}
|
||||
FileServer::FileServer(Tesses::Framework::Filesystem::VFS* fs, bool owns, bool allowListing,bool spa) : FileServer(fs,owns,allowListing,spa,{"index.html","default.html","index.htm","default.htm"})
|
||||
{
|
||||
|
||||
}
|
||||
FileServer::FileServer(Tesses::Framework::Filesystem::VFS* fs, bool owns, bool allowListing, bool spa, std::vector<std::string> defaultNames)
|
||||
{
|
||||
this->vfs = fs;
|
||||
this->ownsVFS = owns;
|
||||
this->allowListing = allowListing;
|
||||
this->defaultNames = defaultNames;
|
||||
this->spa = spa;
|
||||
}
|
||||
bool FileServer::SendFile(ServerContext& ctx,VFSPath path)
|
||||
{
|
||||
|
||||
auto strm = this->vfs->OpenFile(path,"rb");
|
||||
bool retVal = false;
|
||||
if(strm != nullptr)
|
||||
{
|
||||
ctx.WithMimeType(HttpUtils::MimeType(path.GetFileName())).SendStream(strm);
|
||||
retVal = true;
|
||||
|
||||
}
|
||||
|
||||
delete strm;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
bool FileServer::Handle(ServerContext& ctx)
|
||||
{
|
||||
auto path = HttpUtils::UrlPathDecode(ctx.path);
|
||||
|
||||
if(this->vfs->DirectoryExists(path))
|
||||
{
|
||||
for(auto f : defaultNames)
|
||||
{
|
||||
VFSPath p(path,f);
|
||||
|
||||
|
||||
if(this->vfs->FileExists(p))
|
||||
return SendFile(ctx,p);
|
||||
}
|
||||
if(this->allowListing)
|
||||
{
|
||||
std::string p = HttpUtils::HtmlEncode(ctx.originalPath);
|
||||
std::string html = "<!DOCTYPE html><html><head><title>Index of ";
|
||||
html.append(p);
|
||||
html.append("</title></head><body><h1>Index of ");
|
||||
html.append(p);
|
||||
html.append("</h1><hr><pre><a href=\"../\">../</a>\r\n");
|
||||
std::vector<VFSPath> ents;
|
||||
vfs->GetPaths(path, ents);
|
||||
|
||||
for(auto item : ents)
|
||||
{
|
||||
if(vfs->DirectoryExists(item))
|
||||
{
|
||||
//is dir
|
||||
std::string path = item.GetFileName();
|
||||
html.append("<a href=\"");
|
||||
html.append(HttpUtils::UrlPathEncode(path) + "/");
|
||||
html.append("\">");
|
||||
html.append(HttpUtils::HtmlEncode(path) + "/");
|
||||
html.append("</a>\r\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
//is file
|
||||
std::string path = item.GetFileName();
|
||||
html.append("<a href=\"");
|
||||
html.append(HttpUtils::UrlPathEncode(path));
|
||||
html.append("\">");
|
||||
html.append(HttpUtils::HtmlEncode(path));
|
||||
html.append("</a>\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
html.append("</pre><hr></body></html>");
|
||||
|
||||
ctx.WithMimeType("text/html").SendText(html);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(this->vfs->FileExists(path))
|
||||
{
|
||||
return SendFile(ctx,path);
|
||||
}
|
||||
else if(this->spa)
|
||||
{
|
||||
for(auto f : defaultNames)
|
||||
{
|
||||
VFSPath p(f);
|
||||
p.relative=false;
|
||||
if(this->vfs->FileExists(p))
|
||||
return SendFile(ctx,p);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
FileServer::~FileServer()
|
||||
{
|
||||
if(this->ownsVFS)
|
||||
delete this->vfs;
|
||||
}
|
||||
}
|
||||
226
src/Http/HttpClient.cpp
Normal file
226
src/Http/HttpClient.cpp
Normal file
@ -0,0 +1,226 @@
|
||||
#include "TessesFramework/Http/HttpClient.hpp"
|
||||
#include "TessesFramework/Crypto/ClientTLSStream.hpp"
|
||||
#include "TessesFramework/Streams/NetworkStream.hpp"
|
||||
#include "TessesFramework/TextStreams/StreamWriter.hpp"
|
||||
#include "TessesFramework/TextStreams/StreamReader.hpp"
|
||||
#include "TessesFramework/Http/HttpStream.hpp"
|
||||
#include "TessesFramework/Streams/BufferedStream.hpp"
|
||||
#include <iostream>
|
||||
using Stream = Tesses::Framework::Streams::Stream;
|
||||
using NetworkStream = Tesses::Framework::Streams::NetworkStream;
|
||||
using ClientTLSStream = Tesses::Framework::Crypto::ClientTLSStream;
|
||||
using StreamReader = Tesses::Framework::TextStreams::StreamReader;
|
||||
using StreamWriter = Tesses::Framework::TextStreams::StreamWriter;
|
||||
using BufferedStream = Tesses::Framework::Streams::BufferedStream;
|
||||
using HttpStream = Tesses::Framework::Http::HttpStream;
|
||||
namespace Tesses::Framework::Http
|
||||
{
|
||||
HttpRequest::HttpRequest()
|
||||
{
|
||||
this->body=nullptr;
|
||||
this->followRedirects=true;
|
||||
this->ignoreSSLErrors=false;
|
||||
this->trusted_root_cert_bundle="";
|
||||
this->method = "GET";
|
||||
this->requestHeaders.SetValue("Connection","close");
|
||||
}
|
||||
|
||||
void HttpRequest::SendRequest(Tesses::Framework::Streams::Stream* strm)
|
||||
{
|
||||
Uri uri;
|
||||
if(!Uri::TryParse(this->url, uri)) return;
|
||||
|
||||
|
||||
if(body != nullptr)
|
||||
{
|
||||
body->HandleHeaders(requestHeaders);
|
||||
}
|
||||
|
||||
std::string request = method + " " + uri.GetPathAndQuery() + " HTTP/1.1\r\nHost: " + uri.HostPort() + "\r\n";
|
||||
|
||||
for(auto headers : requestHeaders.kvp)
|
||||
{
|
||||
for(auto item : headers.second)
|
||||
{
|
||||
request.append(headers.first);
|
||||
request.append(": ");
|
||||
request.append(item);
|
||||
request.append("\r\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
request.append("\r\n");
|
||||
|
||||
|
||||
StreamWriter writer(strm, false);
|
||||
writer.Write(request);
|
||||
|
||||
if(body != nullptr)
|
||||
{
|
||||
body->Write(strm);
|
||||
}
|
||||
}
|
||||
|
||||
Stream* HttpRequest::EstablishConnection(Uri uri, bool ignoreSSLErrors, std::string trusted_root_cert_bundle)
|
||||
{
|
||||
if(uri.scheme == "http:")
|
||||
{
|
||||
return new NetworkStream(uri.host,uri.GetPort(),false,false,false);
|
||||
}
|
||||
else if(uri.scheme == "https:")
|
||||
{
|
||||
auto netStrm = new NetworkStream(uri.host,uri.GetPort(),false,false,false);
|
||||
if(netStrm == nullptr) return netStrm;
|
||||
return new ClientTLSStream(netStrm, true, ignoreSSLErrors,uri.host, trusted_root_cert_bundle);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HttpResponse::~HttpResponse()
|
||||
{
|
||||
if(this->owns)
|
||||
delete this->handleStrm;
|
||||
}
|
||||
|
||||
HttpResponse::HttpResponse(Stream* strm, bool owns)
|
||||
{
|
||||
this->handleStrm=nullptr;
|
||||
this->owns=owns;
|
||||
StreamReader reader(strm, false);
|
||||
std::string statusLine;
|
||||
if(!reader.ReadLine(statusLine)) return;
|
||||
auto statusLinesPart = HttpUtils::SplitString(statusLine," ",3);
|
||||
if(statusLinesPart.size() >= 2)
|
||||
{
|
||||
this->version = statusLinesPart[0];
|
||||
this->statusCode = (StatusCode)std::stoi(statusLinesPart[1]);
|
||||
}
|
||||
std::string line;
|
||||
while(reader.ReadLine(line))
|
||||
{
|
||||
if(line.empty()) break;
|
||||
auto v = HttpUtils::SplitString(line,": ", 2);
|
||||
if(v.size() != 2)
|
||||
{
|
||||
delete strm;
|
||||
return;
|
||||
}
|
||||
this->responseHeaders.AddValue(v[0],v[1]);
|
||||
line.clear();
|
||||
}
|
||||
this->handleStrm = strm;
|
||||
}
|
||||
|
||||
HttpResponse::HttpResponse(HttpRequest& req)
|
||||
{
|
||||
this->owns=true;
|
||||
this->handleStrm = nullptr;
|
||||
|
||||
std::string url = req.url;
|
||||
Uri uri;
|
||||
while(Uri::TryParse(url, uri))
|
||||
{
|
||||
auto strm = HttpRequest::EstablishConnection(uri, req.ignoreSSLErrors,req.trusted_root_cert_bundle);
|
||||
if(strm == nullptr) return;
|
||||
auto reqHeaders = req.requestHeaders;
|
||||
|
||||
if(req.body != nullptr)
|
||||
{
|
||||
req.body->HandleHeaders(reqHeaders);
|
||||
}
|
||||
|
||||
std::string request = req.method + " " + uri.GetPathAndQuery() + " HTTP/1.1\r\nHost: " + uri.HostPort() + "\r\n";
|
||||
|
||||
for(auto headers : reqHeaders.kvp)
|
||||
{
|
||||
for(auto item : headers.second)
|
||||
{
|
||||
request.append(headers.first);
|
||||
request.append(": ");
|
||||
request.append(item);
|
||||
request.append("\r\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
request.append("\r\n");
|
||||
|
||||
|
||||
StreamWriter writer(strm, false);
|
||||
writer.Write(request);
|
||||
|
||||
if(req.body != nullptr)
|
||||
{
|
||||
req.body->Write(strm);
|
||||
}
|
||||
|
||||
StreamReader reader(strm, false);
|
||||
std::string statusLine;
|
||||
if(!reader.ReadLine(statusLine)) break;
|
||||
auto statusLinesPart = HttpUtils::SplitString(statusLine," ",3);
|
||||
if(statusLinesPart.size() >= 2)
|
||||
{
|
||||
this->version = statusLinesPart[0];
|
||||
this->statusCode = (StatusCode)std::stoi(statusLinesPart[1]);
|
||||
}
|
||||
std::string line;
|
||||
while(reader.ReadLine(line))
|
||||
{
|
||||
if(line.empty()) break;
|
||||
auto v = HttpUtils::SplitString(line,": ", 2);
|
||||
if(v.size() != 2)
|
||||
{
|
||||
delete strm;
|
||||
return;
|
||||
}
|
||||
this->responseHeaders.AddValue(v[0],v[1]);
|
||||
line.clear();
|
||||
}
|
||||
|
||||
std::string location;
|
||||
Uri uri2;
|
||||
|
||||
|
||||
if(req.followRedirects && (this->statusCode == MovedPermanently || this->statusCode == PermanentRedirect || this->statusCode == TemporaryRedirect) && this->responseHeaders.TryGetFirst("Location",location) && uri.Relative(location, uri2))
|
||||
{
|
||||
this->responseHeaders.Clear();
|
||||
|
||||
url = uri2.ToString();
|
||||
|
||||
|
||||
delete strm;
|
||||
continue;
|
||||
}
|
||||
this->handleStrm = strm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::string HttpResponse::ReadAsString()
|
||||
{
|
||||
auto strm = ReadAsStream();
|
||||
if(strm == nullptr) return {};
|
||||
StreamReader r(strm, true);
|
||||
return r.ReadToEnd();
|
||||
}
|
||||
void HttpResponse::CopyToStream(Stream* strm)
|
||||
{
|
||||
if(strm == nullptr) return;
|
||||
auto src = ReadAsStream();
|
||||
if(src == nullptr) return;
|
||||
src->CopyTo(*strm);
|
||||
delete src;
|
||||
}
|
||||
Stream* HttpResponse::ReadAsStream()
|
||||
{
|
||||
if(this->handleStrm == nullptr) return nullptr;
|
||||
int64_t length = -1;
|
||||
if(!this->responseHeaders.TryGetFirstInt("Content-Length",length))
|
||||
{
|
||||
length = -1;
|
||||
}
|
||||
|
||||
return new HttpStream(this->handleStrm,false,length,true,version=="HTTP/1.1");
|
||||
}
|
||||
}
|
||||
651
src/Http/HttpServer.cpp
Normal file
651
src/Http/HttpServer.cpp
Normal file
@ -0,0 +1,651 @@
|
||||
#include "TessesFramework/Http/HttpServer.hpp"
|
||||
#include "TessesFramework/Streams/FileStream.hpp"
|
||||
#include "TessesFramework/Streams/MemoryStream.hpp"
|
||||
#include "TessesFramework/TextStreams/StreamWriter.hpp"
|
||||
#include "TessesFramework/TextStreams/StreamReader.hpp"
|
||||
#include "TessesFramework/Http/ContentDisposition.hpp"
|
||||
#include "TessesFramework/Streams/BufferedStream.hpp"
|
||||
#include "TessesFramework/Http/HttpStream.hpp"
|
||||
|
||||
#include <iostream>
|
||||
using FileStream = Tesses::Framework::Streams::FileStream;
|
||||
using Stream = Tesses::Framework::Streams::Stream;
|
||||
using SeekOrigin = Tesses::Framework::Streams::SeekOrigin;
|
||||
using MemoryStream = Tesses::Framework::Streams::MemoryStream;
|
||||
using StreamReader = Tesses::Framework::TextStreams::StreamReader;
|
||||
using StreamWriter = Tesses::Framework::TextStreams::StreamWriter;
|
||||
using TcpServer = Tesses::Framework::Streams::TcpServer;
|
||||
using NetworkStream = Tesses::Framework::Streams::NetworkStream;
|
||||
using BufferedStream = Tesses::Framework::Streams::BufferedStream;
|
||||
|
||||
|
||||
|
||||
namespace Tesses::Framework::Http
|
||||
{
|
||||
|
||||
/*
|
||||
static int _header_field(multipart_parser* p, const char *at, size_t length)
|
||||
{
|
||||
UploadData* data = static_cast<UploadData*>(multipart_parser_get_data(p));
|
||||
data->currentHeaderKey = std::string(at,length);
|
||||
return 0;
|
||||
}
|
||||
static int _header_value(multipart_parser* p, const char *at, size_t length)
|
||||
{
|
||||
UploadData* data = static_cast<UploadData*>(multipart_parser_get_data(p));
|
||||
data->currentHeaders.AddValue(data->currentHeaderKey, std::string(at,length));
|
||||
std::cout << data->currentHeaderKey << ": " << std::string(at,length) << std::endl;
|
||||
return 0;
|
||||
}
|
||||
static int _part_begin(multipart_parser* p)
|
||||
{
|
||||
UploadData* data = static_cast<UploadData*>(multipart_parser_get_data(p));
|
||||
std::string cd0;
|
||||
ContentDisposition cd1;
|
||||
std::string ct;
|
||||
if(!data->currentHeaders.TryGetFirst("Content-Type",ct))
|
||||
ct = "application/octet-stream";
|
||||
if(data->currentHeaders.TryGetFirst("Content-Disposition", cd0) && ContentDisposition::TryParse(cd0,cd1))
|
||||
{
|
||||
if(cd1.filename.empty())
|
||||
{
|
||||
data->isFile=false;
|
||||
data->key = cd1.fieldName;
|
||||
data->currentBody = new MemoryStream(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
data->isFile = true;
|
||||
data->currentBody = data->cb(ct, cd1.filename, cd1.fieldName);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static int _part_end(multipart_parser* p)
|
||||
{
|
||||
UploadData* data = static_cast<UploadData*>(multipart_parser_get_data(p));
|
||||
if(data->currentBody == nullptr) return 0;
|
||||
if(data->isFile)
|
||||
{
|
||||
delete data->currentBody;
|
||||
data->currentBody = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
MemoryStream* ms = dynamic_cast<MemoryStream*>(data->currentBody);
|
||||
if(ms != nullptr)
|
||||
{
|
||||
ms->Seek(0, SeekOrigin::Begin);
|
||||
auto& buff = ms->GetBuffer();
|
||||
data->ctx->queryParams.AddValue(data->key, std::string(buff.begin(),buff.end()));
|
||||
}
|
||||
delete data->currentBody;
|
||||
data->currentBody=nullptr;
|
||||
}
|
||||
data->currentHeaders.Clear();
|
||||
return 0;
|
||||
}*/
|
||||
bool ServerContext::NeedToParseFormData()
|
||||
{
|
||||
std::string ct;
|
||||
if(this->requestHeaders.TryGetFirst("Content-Type",ct))
|
||||
{
|
||||
if(ct.find("multipart/form-data") == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool parseUntillBoundaryEnd(Tesses::Framework::Streams::Stream* src, Tesses::Framework::Streams::Stream* dest, std::string boundary)
|
||||
{
|
||||
bool hasMore=true;
|
||||
uint8_t checkBuffer[boundary.size()];
|
||||
int b;
|
||||
size_t i = 0;
|
||||
size_t i2 = 0;
|
||||
|
||||
uint8_t buffer[1024];
|
||||
size_t offsetInMem = 0;
|
||||
|
||||
while((b = src->ReadByte()) != -1)
|
||||
{
|
||||
if(i == boundary.size())
|
||||
{
|
||||
if (b == '-')
|
||||
{
|
||||
i2++;
|
||||
if(i2 == 2) hasMore=false;
|
||||
}
|
||||
if (b == '\n') break;
|
||||
else if(b != '-')
|
||||
i2 = 0;
|
||||
continue;
|
||||
}
|
||||
i2=0;
|
||||
|
||||
if (b == boundary[i]) //start filling the check buffer
|
||||
{
|
||||
checkBuffer[i] = (uint8_t)b;
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t idx = 0;
|
||||
while (idx < i) //write the buffer data to stream
|
||||
{
|
||||
|
||||
if(offsetInMem >= sizeof(buffer))
|
||||
{
|
||||
dest->Write(buffer, sizeof(buffer));
|
||||
offsetInMem=0;
|
||||
|
||||
}
|
||||
|
||||
buffer[offsetInMem++] = checkBuffer[idx];
|
||||
|
||||
idx++;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
|
||||
if(offsetInMem >= sizeof(buffer))
|
||||
{
|
||||
dest->Write(buffer, sizeof(buffer));
|
||||
offsetInMem=0;
|
||||
|
||||
}
|
||||
|
||||
buffer[offsetInMem++] = (uint8_t)b;
|
||||
}
|
||||
}
|
||||
|
||||
if(offsetInMem > 0)
|
||||
{
|
||||
dest->Write(buffer,offsetInMem);
|
||||
}
|
||||
return hasMore;
|
||||
}
|
||||
|
||||
static bool parseSection(ServerContext* ctx, std::string boundary, std::function<Tesses::Framework::Streams::Stream*(std::string mime, std::string filename, std::string name)> cb)
|
||||
{
|
||||
HttpDictionary req;
|
||||
StreamReader reader(ctx->GetStream());
|
||||
std::string line;
|
||||
while(reader.ReadLine(line))
|
||||
{
|
||||
auto v = HttpUtils::SplitString(line,": ", 2);
|
||||
if(v.size() == 2)
|
||||
req.AddValue(v[0],v[1]);
|
||||
line.clear();
|
||||
}
|
||||
|
||||
std::string cd0;
|
||||
ContentDisposition cd1;
|
||||
std::string ct;
|
||||
|
||||
if(!req.TryGetFirst("Content-Type",ct))
|
||||
ct = "application/octet-stream";
|
||||
if(req.TryGetFirst("Content-Disposition", cd0) && ContentDisposition::TryParse(cd0,cd1))
|
||||
{
|
||||
if(cd1.filename.empty())
|
||||
{
|
||||
MemoryStream ms(true);
|
||||
|
||||
bool retVal = parseUntillBoundaryEnd(&ctx->GetStream(),&ms,boundary);
|
||||
auto& buff = ms.GetBuffer();
|
||||
|
||||
ctx->queryParams.AddValue(cd1.fieldName, std::string(buff.begin(),buff.end()));
|
||||
|
||||
return retVal;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
auto strm = cb(ct, cd1.filename, cd1.fieldName);
|
||||
bool retVal = parseUntillBoundaryEnd(&ctx->GetStream(),strm,boundary);
|
||||
delete strm;
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ServerContext::ParseFormData(std::function<Tesses::Framework::Streams::Stream*(std::string mime, std::string filename, std::string name)> cb)
|
||||
{
|
||||
std::string ct;
|
||||
if(this->requestHeaders.TryGetFirst("Content-Type",ct))
|
||||
{
|
||||
if(ct.find("multipart/form-data") != 0)
|
||||
{
|
||||
std::cout << "Not form data" << std::endl;
|
||||
return;
|
||||
}
|
||||
auto res = ct.find("boundary=");
|
||||
if(res == std::string::npos) return;
|
||||
ct = "--" + ct.substr(res+9);
|
||||
}
|
||||
Stream nullStrm;
|
||||
parseUntillBoundaryEnd(this->strm,&nullStrm, ct);
|
||||
|
||||
while(parseSection(this, ct, cb));
|
||||
}
|
||||
|
||||
HttpServer::HttpServer(uint16_t port, IHttpServer* http, bool owns)
|
||||
{
|
||||
this->server = new TcpServer(port, 10);
|
||||
this->http = http;
|
||||
this->owns = owns;
|
||||
this->thrd=nullptr;
|
||||
this->port = port;
|
||||
}
|
||||
HttpServer::HttpServer(uint16_t port, IHttpServer& http) : HttpServer(port,&http,false)
|
||||
{
|
||||
|
||||
}
|
||||
Stream* ServerContext::OpenResponseStream()
|
||||
{
|
||||
if(sent) return nullptr;
|
||||
int64_t length = -1;
|
||||
if(!this->responseHeaders.TryGetFirstInt("Content-Length",length))
|
||||
length = -1;
|
||||
|
||||
if(this->version == "HTTP/1.1" && length == -1)
|
||||
this->responseHeaders.SetValue("Transfer-Encoding","chunked");
|
||||
|
||||
this->WriteHeaders();
|
||||
return new HttpStream(this->strm,false,length,false,version == "HTTP/1.1");
|
||||
}
|
||||
Stream* ServerContext::OpenRequestStream()
|
||||
{
|
||||
int64_t length = -1;
|
||||
if(!this->requestHeaders.TryGetFirstInt("Content-Length",length))
|
||||
length = -1;
|
||||
return new HttpStream(this->strm,false,length,true,version == "HTTP/1.1");
|
||||
}
|
||||
void HttpServer::StartAccepting()
|
||||
{
|
||||
fflush(stdout);
|
||||
if(http == nullptr) return;
|
||||
auto svr=this->server;
|
||||
auto http = this->http;
|
||||
thrd = new Threading::Thread([svr,http]()->void {
|
||||
while(TF_IsRunning())
|
||||
{
|
||||
std::string ip;
|
||||
uint16_t port;
|
||||
auto sock =svr->GetStream(ip,port);
|
||||
if(sock == nullptr) return;
|
||||
Threading::Thread thrd2([sock,http,ip,port]()->void {
|
||||
HttpServer::Process(*sock,*http,ip,port,false);
|
||||
});
|
||||
thrd2.Detach();
|
||||
}
|
||||
});
|
||||
std::cout << "\e[34mInterfaces:\n";
|
||||
for(auto _ip : NetworkStream::GetIPs())
|
||||
{
|
||||
std::cout << "\e[32m";
|
||||
std::cout << _ip.first << ": ";
|
||||
std::cout << "\e[35mhttp://";
|
||||
std::cout << _ip.second << ":" << std::to_string(this->port) << "/\n";
|
||||
}
|
||||
std::cout << "\e[31mAlmost Ready to Listen\e[39m\n";
|
||||
}
|
||||
HttpServer::~HttpServer()
|
||||
{
|
||||
this->server->Close();
|
||||
TF_ConnectToSelf(this->port);
|
||||
if(this->thrd != nullptr)
|
||||
{
|
||||
this->thrd->Join();
|
||||
delete this->thrd;
|
||||
}
|
||||
if(this->owns)
|
||||
delete http;
|
||||
delete this->server;
|
||||
}
|
||||
IHttpServer::~IHttpServer()
|
||||
{
|
||||
|
||||
}
|
||||
ServerContext::ServerContext(Stream* strm)
|
||||
{
|
||||
this->statusCode = OK;
|
||||
this->strm = strm;
|
||||
this->sent = false;
|
||||
this->responseHeaders.AddValue("Server","TessesFrameworkWebServer");
|
||||
}
|
||||
Stream& ServerContext::GetStream()
|
||||
{
|
||||
return *this->strm;
|
||||
}
|
||||
void ServerContext::SendBytes(std::vector<uint8_t> buff)
|
||||
{
|
||||
MemoryStream strm(false);
|
||||
strm.GetBuffer() = buff;
|
||||
SendStream(strm);
|
||||
}
|
||||
void ServerContext::SendText(std::string text)
|
||||
{
|
||||
MemoryStream strm(false);
|
||||
auto& buff= strm.GetBuffer();
|
||||
buff.insert(buff.end(),text.begin(),text.end());
|
||||
SendStream(strm);
|
||||
}
|
||||
void ServerContext::SendErrorPage(bool showPath)
|
||||
{
|
||||
if(sent) return;
|
||||
std::string errorHtml = showPath ? ("<html><head><title>File " + HttpUtils::HtmlEncode(this->originalPath) + " " + HttpUtils::StatusCodeString(this->statusCode) + "</title></head><body><h1>" + std::to_string((int)this->statusCode) + " " + HttpUtils::StatusCodeString(this->statusCode) + "</h1><h4>" + HttpUtils::HtmlEncode(this->originalPath) + "</h4></body></html>") : "";
|
||||
|
||||
WithMimeType("text/html").SendText(errorHtml);
|
||||
}
|
||||
void ServerContext::SendStream(Stream* strm)
|
||||
{
|
||||
if(strm == nullptr) return;
|
||||
SendStream(*strm);
|
||||
}
|
||||
void ServerContext::SendStream(Stream& strm)
|
||||
{
|
||||
if(sent) return;
|
||||
if(strm.CanSeek())
|
||||
{
|
||||
int64_t len=strm.GetLength();
|
||||
std::string range={};
|
||||
if(this->requestHeaders.TryGetFirst("Range",range))
|
||||
{
|
||||
auto res = HttpUtils::SplitString(range,"=",2);
|
||||
if(res.size() == 2)
|
||||
{
|
||||
if(res[0] != "bytes")
|
||||
{
|
||||
this->statusCode = BadRequest;
|
||||
this->WriteHeaders();
|
||||
return;
|
||||
}
|
||||
res = HttpUtils::SplitString(res[1],", ",2);
|
||||
if(res.size() != 1)
|
||||
{
|
||||
this->statusCode = BadRequest;
|
||||
this->WriteHeaders();
|
||||
return;
|
||||
}
|
||||
auto dash=HttpUtils::SplitString(res[0],"-",2);
|
||||
int64_t begin = 0;
|
||||
int64_t end = -1;
|
||||
|
||||
if(dash.size() == 1 && res[0].find_first_of('-') != std::string::npos)
|
||||
{
|
||||
//NUMBER-
|
||||
begin = std::stoll(dash[0]);
|
||||
}
|
||||
else if(dash.size() == 2)
|
||||
{
|
||||
//NUMBER-NUMBER
|
||||
//or
|
||||
//-NUMBER
|
||||
|
||||
if(dash[0].size() > 0)
|
||||
{
|
||||
//NUMBER-NUMBER
|
||||
begin = std::stoll(dash[0]);
|
||||
end = std::stoll(dash[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
//-NUMBER
|
||||
end = std::stoll(dash[1]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this->statusCode = BadRequest;
|
||||
this->WriteHeaders();
|
||||
return;
|
||||
}
|
||||
|
||||
if(end == -1)
|
||||
{
|
||||
end = len-1;
|
||||
}
|
||||
|
||||
if(end > len-1)
|
||||
{
|
||||
this->statusCode = RangeNotSatisfiable;
|
||||
this->WithSingleHeader("Content-Range","bytes */" + std::to_string(len));
|
||||
this->WriteHeaders();
|
||||
return;
|
||||
}
|
||||
|
||||
if(begin >= end) {
|
||||
this->statusCode = RangeNotSatisfiable;
|
||||
this->WithSingleHeader("Content-Range","bytes */" + std::to_string(len));
|
||||
this->WriteHeaders();
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t myLen = (end - begin)+1;
|
||||
|
||||
this->WithSingleHeader("Accept-Ranges","bytes");
|
||||
this->WithSingleHeader("Content-Length",std::to_string(myLen));
|
||||
this->WithSingleHeader("Content-Range","bytes " + std::to_string(begin) + "-" + std::to_string(end) + "/" + std::to_string(len));
|
||||
this->statusCode = PartialContent;
|
||||
this->WriteHeaders();
|
||||
strm.Seek(begin,SeekOrigin::Begin);
|
||||
|
||||
uint8_t buffer[1024];
|
||||
|
||||
size_t read=0;
|
||||
do {
|
||||
read = sizeof(buffer);
|
||||
myLen = (end - begin)+1;
|
||||
if(myLen < read) read = (size_t)myLen;
|
||||
if(read == 0) break;
|
||||
read = strm.Read(buffer,read);
|
||||
if(read == 0) break;
|
||||
this->strm->WriteBlock(buffer,read);
|
||||
|
||||
begin += read;
|
||||
} while(read > 0);
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
this->statusCode = BadRequest;
|
||||
this->SendErrorPage(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(len > -1)
|
||||
{
|
||||
this->WithSingleHeader("Accept-Range","bytes");
|
||||
this->WithSingleHeader("Content-Length",std::to_string(len));
|
||||
this->WriteHeaders();
|
||||
strm.CopyTo(*this->strm);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
auto chunkedStream = this->OpenResponseStream();
|
||||
this->strm->CopyTo(chunkedStream);
|
||||
delete chunkedStream;
|
||||
|
||||
}
|
||||
}
|
||||
ServerContext& ServerContext::WithHeader(std::string key, std::string value)
|
||||
{
|
||||
this->responseHeaders.AddValue(key, value);
|
||||
return *this;
|
||||
}
|
||||
ServerContext& ServerContext::WithSingleHeader(std::string key, std::string value)
|
||||
{
|
||||
this->responseHeaders.SetValue(key, value);
|
||||
return *this;
|
||||
}
|
||||
ServerContext& ServerContext::WithMimeType(std::string mime)
|
||||
{
|
||||
this->responseHeaders.SetValue("Content-Type",mime);
|
||||
return *this;
|
||||
}
|
||||
ServerContext& ServerContext::WithContentDisposition(std::string filename, bool isInline)
|
||||
{
|
||||
ContentDisposition cd;
|
||||
cd.type = isInline ? "inline" : "attachment";
|
||||
cd.filename = filename;
|
||||
|
||||
//std::string cd;
|
||||
//cd = (isInline ? "inline; filename*=UTF-8''" : "attachment; filename*=UTF-8''") + HttpUtils::UrlPathEncode(filename);
|
||||
this->responseHeaders.SetValue("Content-Disposition",cd.ToString());
|
||||
return *this;
|
||||
}
|
||||
void ServerContext::SendNotFound()
|
||||
{
|
||||
if(sent) return;
|
||||
statusCode = StatusCode::NotFound;
|
||||
SendErrorPage(true);
|
||||
}
|
||||
void ServerContext::SendBadRequest()
|
||||
{
|
||||
if(sent) return;
|
||||
statusCode = StatusCode::BadRequest;
|
||||
SendErrorPage(false);
|
||||
}
|
||||
void ServerContext::SendException(std::exception& ex)
|
||||
{
|
||||
/*<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Internal Server Error at /</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Internal Server Error at /</h1>
|
||||
<p>what(): std::exception</p>
|
||||
</body>
|
||||
</html>*/
|
||||
this->WithMimeType("text/html").SendText("<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Internal Server Error at " + HttpUtils::HtmlEncode(this->originalPath) + "</title></head><body><h1>Internal Server Error at " + HttpUtils::HtmlEncode(this->originalPath) + "</h1><p>what(): " + HttpUtils::HtmlEncode(ex.what()) + "</p></body></html>");
|
||||
}
|
||||
ServerContext& ServerContext::WriteHeaders()
|
||||
{
|
||||
if(this->sent) return *this;
|
||||
this->sent = true;
|
||||
|
||||
StreamWriter writer(this->strm,false);
|
||||
writer.newline = "\r\n";
|
||||
writer.WriteLine("HTTP/1.1 " + std::to_string((int)statusCode) + " " + HttpUtils::StatusCodeString(statusCode));
|
||||
for(auto& hdr : responseHeaders.kvp)
|
||||
{
|
||||
auto& key = hdr.first;
|
||||
for(auto& val : hdr.second)
|
||||
{
|
||||
writer.WriteLine(key + ": " + val);
|
||||
}
|
||||
}
|
||||
writer.WriteLine();
|
||||
|
||||
return *this;
|
||||
}
|
||||
void HttpServer::Process(Stream& strm, IHttpServer& server, std::string ip, uint16_t port, bool encrypted)
|
||||
{
|
||||
|
||||
while(true)
|
||||
{
|
||||
BufferedStream bStrm(strm);
|
||||
StreamReader reader(bStrm);
|
||||
ServerContext ctx(&bStrm);
|
||||
ctx.ip = ip;
|
||||
ctx.port = port;
|
||||
ctx.encrypted = encrypted;
|
||||
try{
|
||||
bool firstLine = true;
|
||||
std::string line;
|
||||
while(reader.ReadLine(line))
|
||||
{
|
||||
if(firstLine)
|
||||
{
|
||||
auto v = HttpUtils::SplitString(line, " ", 3);
|
||||
if(v.size() != 3) {
|
||||
ctx.statusCode = BadRequest;
|
||||
ctx.WithMimeType("text/plain").SendText("First line is not 3 elements");
|
||||
return;
|
||||
}
|
||||
ctx.method = v[0];
|
||||
auto pp = HttpUtils::SplitString(v[1],"?", 2);
|
||||
pp.resize(2);
|
||||
|
||||
ctx.originalPath = pp[0];
|
||||
ctx.path = ctx.originalPath;
|
||||
|
||||
auto queryPart = pp[1];
|
||||
if(!queryPart.empty())
|
||||
{
|
||||
HttpUtils::QueryParamsDecode(ctx.queryParams, queryPart);
|
||||
}
|
||||
|
||||
ctx.version = v[2];
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
auto v = HttpUtils::SplitString(line,": ", 2);
|
||||
if(v.size() != 2) {
|
||||
ctx.statusCode = BadRequest;
|
||||
ctx.WithMimeType("text/plain").SendText("Header line is not 2 elements");
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.requestHeaders.AddValue(v[0],v[1]);
|
||||
|
||||
}
|
||||
line.clear();
|
||||
firstLine=false;
|
||||
}
|
||||
|
||||
if(firstLine) return;
|
||||
|
||||
std::string type;
|
||||
int64_t length;
|
||||
|
||||
if(ctx.requestHeaders.TryGetFirst("Content-Type",type) && type == "application/x-www-form-urlencoded" && ctx.requestHeaders.TryGetFirstInt("Content-Length",length))
|
||||
{
|
||||
size_t len = (size_t)length;
|
||||
uint8_t* buffer = new uint8_t[len];
|
||||
len = bStrm.ReadBlock(buffer,len);
|
||||
std::string query((const char*)buffer,len);
|
||||
delete buffer;
|
||||
HttpUtils::QueryParamsDecode(ctx.queryParams, query);
|
||||
}
|
||||
|
||||
if(!server.Handle(ctx))
|
||||
{
|
||||
ctx.SendNotFound();
|
||||
}
|
||||
}
|
||||
catch(std::exception& ex)
|
||||
{
|
||||
ctx.SendException(ex);
|
||||
}
|
||||
|
||||
if(ctx.version != "HTTP/1.1" ) return;
|
||||
|
||||
std::string connection;
|
||||
if(ctx.requestHeaders.TryGetFirst("Connection", connection))
|
||||
{
|
||||
if(connection != "keep-alive") return;
|
||||
}
|
||||
|
||||
if(ctx.responseHeaders.TryGetFirst("Connection", connection))
|
||||
{
|
||||
if(connection != "keep-alive") return;
|
||||
}
|
||||
|
||||
if(bStrm.EndOfStream()) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
166
src/Http/HttpStream.cpp
Normal file
166
src/Http/HttpStream.cpp
Normal file
@ -0,0 +1,166 @@
|
||||
#include "TessesFramework/Http/HttpStream.hpp"
|
||||
#include "TessesFramework/TextStreams/StreamWriter.hpp"
|
||||
#include "TessesFramework/TextStreams/StreamReader.hpp"
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
using StreamWriter = Tesses::Framework::TextStreams::StreamWriter;
|
||||
using StreamReader = Tesses::Framework::TextStreams::StreamReader;
|
||||
namespace Tesses::Framework::Http
|
||||
{
|
||||
HttpStream::HttpStream(Tesses::Framework::Streams::Stream* strm, bool owns, int64_t length, bool recv, bool http1_1)
|
||||
{
|
||||
this->strm = strm;
|
||||
this->owns = owns;
|
||||
this->length = length;
|
||||
this->recv = recv;
|
||||
this->http1_1 = http1_1;
|
||||
this->offset = 0;
|
||||
this->read = 0;
|
||||
this->position = 0;
|
||||
this->done=false;
|
||||
}
|
||||
HttpStream::HttpStream(Tesses::Framework::Streams::Stream& strm, int64_t length, bool recv,bool http1_1) : HttpStream(&strm,false,length,recv,http1_1)
|
||||
{
|
||||
|
||||
}
|
||||
bool HttpStream::CanRead()
|
||||
{
|
||||
if(this->done) return false;
|
||||
if(!this->recv) return false;
|
||||
if(this->offset < this->read) return true;
|
||||
return this->strm->CanRead();
|
||||
}
|
||||
bool HttpStream::CanWrite()
|
||||
{
|
||||
if(this->recv) return false;
|
||||
return this->strm->CanWrite();
|
||||
}
|
||||
bool HttpStream::EndOfStream()
|
||||
{
|
||||
if(this->done) return true;
|
||||
if(!this->recv) return true;
|
||||
if(this->offset < this->read) return false;
|
||||
return this->strm->EndOfStream();
|
||||
}
|
||||
int64_t HttpStream::GetLength()
|
||||
{
|
||||
return this->length;
|
||||
}
|
||||
int64_t HttpStream::GetPosition()
|
||||
{
|
||||
return this->position;
|
||||
}
|
||||
size_t HttpStream::Read(uint8_t* buff, size_t len)
|
||||
{
|
||||
if(this->done) return 0;
|
||||
if(!this->recv) return 0;
|
||||
if(this->length == 0) return 0;
|
||||
if(this->length > 0)
|
||||
{
|
||||
len = std::min((size_t)(this->length - this->position), len);
|
||||
if(len > 0)
|
||||
len = this->strm->Read(buff,len);
|
||||
this->position += len;
|
||||
return len;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(this->http1_1)
|
||||
{
|
||||
if(this->offset < this->read)
|
||||
{
|
||||
len = std::min((size_t)(this->read - this->offset), len);
|
||||
if(len > 0)
|
||||
len = this->strm->Read(buff,len);
|
||||
this->offset += len;
|
||||
this->position += len;
|
||||
if(this->offset >= this->read)
|
||||
{
|
||||
StreamReader reader(this->strm, false);
|
||||
reader.ReadLine();
|
||||
}
|
||||
return len;
|
||||
}
|
||||
else
|
||||
{
|
||||
StreamReader reader(this->strm, false);
|
||||
std::string line = reader.ReadLine();
|
||||
if(!line.empty())
|
||||
{
|
||||
this->read = std::stoull(line, NULL, 16);
|
||||
|
||||
|
||||
if(this->read == 0)
|
||||
{
|
||||
reader.ReadLine();
|
||||
this->done=true;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->offset=0;
|
||||
|
||||
len = std::min((size_t)(this->read - this->offset), len);
|
||||
if(len > 0)
|
||||
len = this->strm->Read(buff,len);
|
||||
this->offset += len;
|
||||
this->position += len;
|
||||
return len;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return this->strm->Read(buff,len);
|
||||
}
|
||||
}
|
||||
}
|
||||
size_t HttpStream::Write(const uint8_t* buff, size_t len)
|
||||
{
|
||||
if(this->recv) return 0;
|
||||
if(this->length == 0) return 0;
|
||||
if(this->length > 0)
|
||||
{
|
||||
len = std::min((size_t)(this->length - this->position), len);
|
||||
if(len > 0)
|
||||
len = this->strm->Write(buff,len);
|
||||
this->position += len;
|
||||
return len;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(this->http1_1)
|
||||
{
|
||||
std::stringstream strm;
|
||||
strm << std::hex << len;
|
||||
|
||||
StreamWriter writer(this->strm,false);
|
||||
writer.newline = "\r\n";
|
||||
writer.WriteLine(strm.str());
|
||||
|
||||
this->strm->WriteBlock(buff, len);
|
||||
|
||||
writer.WriteLine();
|
||||
return len;
|
||||
}
|
||||
else
|
||||
{
|
||||
return this->strm->Write(buff,len);
|
||||
}
|
||||
}
|
||||
}
|
||||
HttpStream::~HttpStream()
|
||||
{
|
||||
if(this->length == -1 && this->http1_1)
|
||||
{
|
||||
StreamWriter writer(this->strm,false);
|
||||
writer.newline = "\r\n";
|
||||
writer.WriteLine("0");
|
||||
writer.WriteLine();
|
||||
}
|
||||
if(this->owns) delete this->strm;
|
||||
}
|
||||
}
|
||||
732
src/Http/HttpUtils.cpp
Normal file
732
src/Http/HttpUtils.cpp
Normal file
@ -0,0 +1,732 @@
|
||||
#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";
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user