From a175bf5af9ebc9a31959dfabd11a7bc9d4bdb651 Mon Sep 17 00:00:00 2001 From: JDierkse Date: Mon, 10 Feb 2020 20:33:22 +0100 Subject: [PATCH] Initial commit --- .gitignore | 8 + .gitmodules | 3 + Http/HttpClient.cpp | 169 ++++++++ Http/HttpClientHelpers.h | 45 ++ Http/HttpClientImpl.cpp | 792 ++++++++++++++++++++++++++++++++++++ Http/HttpClientImpl.h | 94 +++++ Http/HttpConnection.cpp | 92 +++++ Http/HttpConnection.h | 49 +++ Http/HttpHeader.h | 23 ++ Http/HttpHeaders.cpp | 127 ++++++ Http/HttpHeaders.h | 55 +++ Http/HttpMimeTypes.cpp | 31 ++ Http/HttpMimeTypes.h | 15 + Http/HttpReply.cpp | 244 +++++++++++ Http/HttpReply.h | 46 +++ Http/HttpRequest.h | 21 + Http/HttpRequestHandler.cpp | 138 +++++++ Http/HttpRequestHandler.h | 35 ++ Http/HttpRequestParser.cpp | 304 ++++++++++++++ Http/HttpRequestParser.h | 75 ++++ Http/HttpServer.cpp | 21 + Http/HttpServerImpl.cpp | 79 ++++ Http/HttpServerImpl.h | 40 ++ Http/Makefile | 1 + Makefile | 1 + Makefile.conf | 6 + Makefile.target | 12 + Makefiles | 1 + include/HttpClient.h | 69 ++++ include/HttpPostData.h | 22 + include/HttpServer.h | 28 ++ 31 files changed, 2646 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Http/HttpClient.cpp create mode 100644 Http/HttpClientHelpers.h create mode 100644 Http/HttpClientImpl.cpp create mode 100644 Http/HttpClientImpl.h create mode 100644 Http/HttpConnection.cpp create mode 100644 Http/HttpConnection.h create mode 100644 Http/HttpHeader.h create mode 100644 Http/HttpHeaders.cpp create mode 100644 Http/HttpHeaders.h create mode 100644 Http/HttpMimeTypes.cpp create mode 100644 Http/HttpMimeTypes.h create mode 100644 Http/HttpReply.cpp create mode 100644 Http/HttpReply.h create mode 100644 Http/HttpRequest.h create mode 100644 Http/HttpRequestHandler.cpp create mode 100644 Http/HttpRequestHandler.h create mode 100644 Http/HttpRequestParser.cpp create mode 100644 Http/HttpRequestParser.h create mode 100644 Http/HttpServer.cpp create mode 100644 Http/HttpServerImpl.cpp create mode 100644 Http/HttpServerImpl.h create mode 120000 Http/Makefile create mode 120000 Makefile create mode 100644 Makefile.conf create mode 100644 Makefile.target create mode 160000 Makefiles create mode 100644 include/HttpClient.h create mode 100644 include/HttpPostData.h create mode 100644 include/HttpServer.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c150de --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.o.* +*.d.* +*.a.* +.*.swp +.AppleDouble +lib +Libraries +fixPermissions.sh diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7da2786 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Makefiles"] + path = Makefiles + url = https://gogs.dierkse.nl/JDierkse/Makefiles.git diff --git a/Http/HttpClient.cpp b/Http/HttpClient.cpp new file mode 100644 index 0000000..bbe2466 --- /dev/null +++ b/Http/HttpClient.cpp @@ -0,0 +1,169 @@ +#include "HttpClient.h" +#include "HttpClientImpl.h" + + +namespace Http { + +HttpClient::HttpClient() : + m_pHttpClientImpl(new HttpClientImpl()) +{ +} + +HttpClient::~HttpClient() = default; + +int HttpClient::GetUrlReturnCode(const std::string& url) const +{ + return m_pHttpClientImpl->GetUrlReturnCode(url); +} + +std::string HttpClient::GetUrlContents(const std::string& url) const +{ + return m_pHttpClientImpl->GetUrlContents(url); +} + +void HttpClient::GetUrlSilent(const std::string& url) const +{ + m_pHttpClientImpl->GetUrlSilent(url); +} + +std::string HttpClient::GetUrlRedirect(const std::string& url) const +{ + return m_pHttpClientImpl->GetUrlRedirect(url); +} + +std::string HttpClient::GetUrlContents(const std::string& url, const std::vector& httpHeaders) const +{ + return m_pHttpClientImpl->GetUrlContents(url, httpHeaders); +} + +void HttpClient::GetUrlSilent(const std::string& url, const std::vector& httpHeaders) const +{ + m_pHttpClientImpl->GetUrlSilent(url, httpHeaders); +} + +std::string HttpClient::GetUrlRedirect(const std::string& url, const std::vector& httpHeaders) const +{ + return m_pHttpClientImpl->GetUrlRedirect(url, httpHeaders); +} + +std::string HttpClient::GetUrlContents(const std::string& url, const std::string& cookieFile) const +{ + return m_pHttpClientImpl->GetUrlContents(url, cookieFile); +} + +void HttpClient::GetUrlSilent(const std::string& url, const std::string& cookieFile) const +{ + m_pHttpClientImpl->GetUrlSilent(url, cookieFile); +} + +std::string HttpClient::GetUrlRedirect(const std::string& url, const std::string& cookieFile) const +{ + return m_pHttpClientImpl->GetUrlRedirect(url, cookieFile); +} + +std::string HttpClient::GetUrlContents(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile) const +{ + return m_pHttpClientImpl->GetUrlContents(url, httpHeaders, cookieFile); +} + +void HttpClient::GetUrlSilent(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile) const +{ + m_pHttpClientImpl->GetUrlSilent(url, httpHeaders, cookieFile); +} + +std::string HttpClient::GetUrlRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile) const +{ + return m_pHttpClientImpl->GetUrlRedirect(url, httpHeaders, cookieFile); +} + +std::string HttpClient::PutUrlContents(const std::string& url, const std::vector& httpHeaders, const std::string& data) const +{ + return m_pHttpClientImpl->PutUrlContents(url, httpHeaders, data); +} + +void HttpClient::PutUrlSilent(const std::string& url, const std::vector& httpHeaders, const std::string& data) const +{ + m_pHttpClientImpl->PutUrlSilent(url, httpHeaders, data); +} + +std::string HttpClient::PutUrlRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& data) const +{ + return m_pHttpClientImpl->PutUrlRedirect(url, httpHeaders, data); +} + +std::string HttpClient::PutUrlContents(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const +{ + return m_pHttpClientImpl->PutUrlContents(url, httpHeaders, cookieFile, data); +} + +void HttpClient::PutUrlSilent(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const +{ + m_pHttpClientImpl->PutUrlSilent(url, httpHeaders, cookieFile, data); +} + +std::string HttpClient::PutUrlRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const +{ + return m_pHttpClientImpl->PutUrlRedirect(url, httpHeaders, cookieFile, data); +} + +std::string HttpClient::GetUrlPostContents(const std::string& url, const std::vector& httpHeaders, const std::string& data) const +{ + return m_pHttpClientImpl->GetUrlPostContents(url, httpHeaders, data); +} + +void HttpClient::GetUrlPostSilent(const std::string& url, const std::vector& httpHeaders, const std::string& data) const +{ + m_pHttpClientImpl->GetUrlPostSilent(url, httpHeaders, data); +} + +std::string HttpClient::GetUrlPostRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& data) const +{ + return m_pHttpClientImpl->GetUrlPostRedirect(url, httpHeaders, data); +} + +std::string HttpClient::GetUrlPostContents(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const +{ + return m_pHttpClientImpl->GetUrlPostContents(url, httpHeaders, cookieFile, data); +} + +void HttpClient::GetUrlPostSilent(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const +{ + m_pHttpClientImpl->GetUrlPostSilent(url, httpHeaders, cookieFile, data); +} + +std::string HttpClient::GetUrlPostRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const +{ + return m_pHttpClientImpl->GetUrlPostRedirect(url, httpHeaders, cookieFile, data); +} + +std::string HttpClient::GetUrlPostAttachmentContents(const std::string& url, const std::string& data, const std::string& filename, const std::string& fileFieldname) const +{ + return m_pHttpClientImpl->GetUrlPostAttachmentContents(url, data, filename, fileFieldname); +} + +void HttpClient::GetUrlPostAttachmentSilent(const std::string& url, const std::string& data, const std::string& filename, const std::string& fileFieldname) const +{ + m_pHttpClientImpl->GetUrlPostAttachmentSilent(url, data, filename, fileFieldname); +} + +std::string HttpClient::GetUrlPostAttachmentRedirect(const std::string& url, const std::string& data, const std::string& filename, const std::string& fileFieldname) const +{ + return m_pHttpClientImpl->GetUrlPostAttachmentRedirect(url, data, filename, fileFieldname); +} + +std::string HttpClient::GetUrlPostAttachmentContents(const std::string& url, const std::string& cookieFile, const std::string& data, const std::string& filename, const std::string& fileFieldname) const +{ + return m_pHttpClientImpl->GetUrlPostAttachmentContents(url, cookieFile, data, filename, fileFieldname); +} + +void HttpClient::GetUrlPostAttachmentSilent(const std::string& url, const std::string& cookieFile, const std::string& data, const std::string& filename, const std::string& fileFieldname) const +{ + m_pHttpClientImpl->GetUrlPostAttachmentSilent(url, cookieFile, data, filename, fileFieldname); +} + +std::string HttpClient::GetUrlPostAttachmentRedirect(const std::string& url, const std::string& cookieFile, const std::string& data, const std::string& filename, const std::string& fileFieldname) const +{ + return m_pHttpClientImpl->GetUrlPostAttachmentRedirect(url, cookieFile, data, filename, fileFieldname); +} + +} // namespace Http diff --git a/Http/HttpClientHelpers.h b/Http/HttpClientHelpers.h new file mode 100644 index 0000000..7c1615f --- /dev/null +++ b/Http/HttpClientHelpers.h @@ -0,0 +1,45 @@ +#ifndef HTTP_HTTPCLIENTHELPERS_H +#define HTTP_HTTPCLIENTHELPERS_H + +#include +#include +#include +#include + + +namespace Http { + +static std::vector > g_mutexes; + +static void LockCallback(int mode, int type, const char* /*file*/, int /*line*/) +{ + if (mode & CRYPTO_LOCK) + g_mutexes[type]->lock(); + else + g_mutexes[type]->unlock(); +} + +static unsigned long ThreadId(void) +{ + return static_cast(std::hash{}(std::this_thread::get_id())); +} + +static void InitializeLocks(void) +{ + g_mutexes.resize(CRYPTO_num_locks()); + for (int i = 0; i < CRYPTO_num_locks(); ++i) + g_mutexes[i] = std::shared_ptr(new std::mutex()); + + CRYPTO_set_locking_callback(LockCallback); + CRYPTO_set_id_callback(ThreadId); +} + +static void FreeLocks(void) +{ + CRYPTO_set_locking_callback(NULL); + CRYPTO_set_id_callback(NULL); +} + +} // namespace Http + +#endif // HTTP_HTTPCLIENTHELPERS_H diff --git a/Http/HttpClientImpl.cpp b/Http/HttpClientImpl.cpp new file mode 100644 index 0000000..29e97a8 --- /dev/null +++ b/Http/HttpClientImpl.cpp @@ -0,0 +1,792 @@ +#include "HttpClientImpl.h" +#include "HttpClientHelpers.h" +#include "Logging.h" +#include "StringAlgorithm.h" +#include +#include +#include +#include + + +namespace Http { + +HttpClientImpl::HttpClientImpl() +{ + curl_global_init(CURL_GLOBAL_ALL); + + m_userAgents.push_back("Opera/9.80 (X11; Linux i686; U; en) Presto/2.7.62 Version/11.00"); + + InitializeLocks(); +} + +HttpClientImpl::~HttpClientImpl() +{ + FreeLocks(); + curl_global_cleanup(); +} + +int HttpClientImpl::GetUrlReturnCode(const std::string& url) const +{ + return std::stoi(PerformOperation(HttpClientImpl::Operation::Silent, HttpClientImpl::Method::GET, url, std::vector(), std::string(), std::string(), true)); +} + +std::string HttpClientImpl::GetUrlContents(const std::string& url) const +{ + return PerformOperation(HttpClientImpl::Operation::GetContent, HttpClientImpl::Method::GET, url); +} + +void HttpClientImpl::GetUrlSilent(const std::string& url) const +{ + PerformOperation(HttpClientImpl::Operation::Silent, HttpClientImpl::Method::GET, url); +} + +std::string HttpClientImpl::GetUrlRedirect(const std::string& url) const +{ + return PerformOperation(HttpClientImpl::Operation::GetRedirect, HttpClientImpl::Method::GET, url); +} + +std::string HttpClientImpl::GetUrlContents(const std::string& url, const std::vector& httpHeaders) const +{ + return PerformOperation(HttpClientImpl::Operation::GetContent, HttpClientImpl::Method::GET, url, httpHeaders); +} + +void HttpClientImpl::GetUrlSilent(const std::string& url, const std::vector& httpHeaders) const +{ + PerformOperation(HttpClientImpl::Operation::Silent, HttpClientImpl::Method::GET, url, httpHeaders); +} + +std::string HttpClientImpl::GetUrlRedirect(const std::string& url, const std::vector& httpHeaders) const +{ + return PerformOperation(HttpClientImpl::Operation::GetRedirect, HttpClientImpl::Method::GET, url, httpHeaders); +} + +std::string HttpClientImpl::GetUrlContents(const std::string& url, const std::string& cookieFile) const +{ + return PerformOperation(HttpClientImpl::Operation::GetContent, HttpClientImpl::Method::GET, url, std::vector(), cookieFile); +} + +void HttpClientImpl::GetUrlSilent(const std::string& url, const std::string& cookieFile) const +{ + PerformOperation(HttpClientImpl::Operation::Silent, HttpClientImpl::Method::GET, url, std::vector(), cookieFile); +} + +std::string HttpClientImpl::GetUrlRedirect(const std::string& url, const std::string& cookieFile) const +{ + return PerformOperation(HttpClientImpl::Operation::GetRedirect, HttpClientImpl::Method::GET, url, std::vector(), cookieFile); +} + +std::string HttpClientImpl::GetUrlContents(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile) const +{ + return PerformOperation(HttpClientImpl::Operation::GetContent, HttpClientImpl::Method::GET, url, httpHeaders, cookieFile); +} + +void HttpClientImpl::GetUrlSilent(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile) const +{ + PerformOperation(HttpClientImpl::Operation::Silent, HttpClientImpl::Method::GET, url, httpHeaders, cookieFile); +} + +std::string HttpClientImpl::GetUrlRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile) const +{ + return PerformOperation(HttpClientImpl::Operation::GetRedirect, HttpClientImpl::Method::GET, url, httpHeaders, cookieFile); +} + +std::string HttpClientImpl::PutUrlContents(const std::string& url, const std::vector& httpHeaders, const std::string& data) const +{ + return PerformOperation(HttpClientImpl::Operation::GetContent, HttpClientImpl::Method::PUT, url, httpHeaders, std::string(), data); +} + +void HttpClientImpl::PutUrlSilent(const std::string& url, const std::vector& httpHeaders, const std::string& data) const +{ + PerformOperation(HttpClientImpl::Operation::Silent, HttpClientImpl::Method::PUT, url, httpHeaders, std::string(), data); +} + +std::string HttpClientImpl::PutUrlRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& data) const +{ + return PerformOperation(HttpClientImpl::Operation::GetRedirect, HttpClientImpl::Method::PUT, url, httpHeaders, std::string(), data); +} + +std::string HttpClientImpl::PutUrlContents(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const +{ + return PerformOperation(HttpClientImpl::Operation::GetContent, HttpClientImpl::Method::PUT, url, httpHeaders, cookieFile, data); +} + +void HttpClientImpl::PutUrlSilent(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const +{ + PerformOperation(HttpClientImpl::Operation::Silent, HttpClientImpl::Method::PUT, url, httpHeaders, cookieFile, data); +} + +std::string HttpClientImpl::PutUrlRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const +{ + return PerformOperation(HttpClientImpl::Operation::GetRedirect, HttpClientImpl::Method::PUT, url, httpHeaders, cookieFile, data); +} + +std::string HttpClientImpl::GetUrlPostContents(const std::string& url, const std::vector& httpHeaders, const std::string& data) const +{ + return PerformOperation(HttpClientImpl::Operation::GetContent, HttpClientImpl::Method::POST, url, httpHeaders, std::string(), data); +} + +void HttpClientImpl::GetUrlPostSilent(const std::string& url, const std::vector& httpHeaders, const std::string& data) const +{ + PerformOperation(HttpClientImpl::Operation::Silent, HttpClientImpl::Method::POST, url, httpHeaders, std::string(), data); +} + +std::string HttpClientImpl::GetUrlPostRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& data) const +{ + return PerformOperation(HttpClientImpl::Operation::GetRedirect, HttpClientImpl::Method::POST, url, httpHeaders, std::string(), data); +} + +std::string HttpClientImpl::GetUrlPostContents(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const +{ + return PerformOperation(HttpClientImpl::Operation::GetContent, HttpClientImpl::Method::POST, url, httpHeaders, cookieFile, data); +} + +void HttpClientImpl::GetUrlPostSilent(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const +{ + PerformOperation(HttpClientImpl::Operation::Silent, HttpClientImpl::Method::POST, url, httpHeaders, cookieFile, data); +} + +std::string HttpClientImpl::GetUrlPostRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const +{ + return PerformOperation(HttpClientImpl::Operation::GetRedirect, HttpClientImpl::Method::POST, url, httpHeaders, cookieFile, data); +} + +std::string HttpClientImpl::GetUrlPostAttachmentContents(const std::string& url, const std::string& data, const std::string& filename, const std::string& fileFieldname) const +{ + std::string buffer; + std::string contents; + std::ifstream fileStream(filename, std::ios::in | std::ios::binary); + + if (fileStream) + { + fileStream.seekg(0, std::ios::end); + contents.resize(fileStream.tellg()); + fileStream.seekg(0, std::ios::beg); + fileStream.read(&contents[0], contents.size()); + fileStream.close(); + } + + CURL* curl = curl_easy_init(); + CURLcode result; + + struct curl_httppost *formpost = nullptr; + struct curl_httppost *lastptr = nullptr; + struct curl_slist *headerlist = nullptr; + static const char headerBuffer[] = "Expect:"; + + curl_global_init(CURL_GLOBAL_ALL); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "cache-control:", + CURLFORM_COPYCONTENTS, "no-cache", + CURLFORM_END); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "content-type:", + CURLFORM_COPYCONTENTS, "multipart/form-data", + CURLFORM_END); + + std::vector postTokens = Utilities::split(data, '&'); + + for (std::vector::iterator it = postTokens.begin(); it != postTokens.end(); ++it) + { + std::vector tokens = Utilities::split(*it, '='); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, tokens[0].c_str(), + CURLFORM_COPYCONTENTS, tokens[1].c_str(), + CURLFORM_END); + } + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, fileFieldname.c_str(), + CURLFORM_BUFFER, "data", + CURLFORM_BUFFERPTR, contents.data(), + CURLFORM_BUFFERLENGTH, contents.size(), + CURLFORM_END); + + headerlist = curl_slist_append(headerlist, headerBuffer); + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + curl_formfree(formpost); + curl_slist_free_all(headerlist); + + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + error << "Data: " << buffer; + throw std::runtime_error(error.str()); + } + + return buffer; +} + +void HttpClientImpl::GetUrlPostAttachmentSilent(const std::string& url, const std::string& data, const std::string& filename, const std::string& fileFieldname) const +{ + std::string contents; + std::ifstream fileStream(filename, std::ios::in | std::ios::binary); + + if (fileStream) + { + fileStream.seekg(0, std::ios::end); + contents.resize(fileStream.tellg()); + fileStream.seekg(0, std::ios::beg); + fileStream.read(&contents[0], contents.size()); + fileStream.close(); + } + + CURL* curl = curl_easy_init(); + CURLcode result; + + struct curl_httppost *formpost = nullptr; + struct curl_httppost *lastptr = nullptr; + struct curl_slist *headerlist = nullptr; + static const char headerBuffer[] = "Expect:"; + + curl_global_init(CURL_GLOBAL_ALL); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "cache-control:", + CURLFORM_COPYCONTENTS, "no-cache", + CURLFORM_END); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "content-type:", + CURLFORM_COPYCONTENTS, "multipart/form-data", + CURLFORM_END); + + std::vector postTokens = Utilities::split(data, '&'); + + for (std::vector::iterator it = postTokens.begin(); it != postTokens.end(); ++it) + { + std::vector tokens = Utilities::split(*it, '='); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, tokens[0].c_str(), + CURLFORM_COPYCONTENTS, tokens[1].c_str(), + CURLFORM_END); + } + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, fileFieldname.c_str(), + CURLFORM_BUFFER, "data", + CURLFORM_BUFFERPTR, contents.data(), + CURLFORM_BUFFERLENGTH, contents.size(), + CURLFORM_END); + + headerlist = curl_slist_append(headerlist, headerBuffer); + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + curl_formfree(formpost); + curl_slist_free_all(headerlist); + + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + throw std::runtime_error(error.str()); + } +} + +std::string HttpClientImpl::GetUrlPostAttachmentRedirect(const std::string& url, const std::string& data, const std::string& filename, const std::string& fileFieldname) const +{ + std::string contents; + std::ifstream fileStream(filename, std::ios::in | std::ios::binary); + + if (fileStream) + { + fileStream.seekg(0, std::ios::end); + contents.resize(fileStream.tellg()); + fileStream.seekg(0, std::ios::beg); + fileStream.read(&contents[0], contents.size()); + fileStream.close(); + } + + CURL* curl = curl_easy_init(); + CURLcode result; + + struct curl_httppost *formpost = nullptr; + struct curl_httppost *lastptr = nullptr; + struct curl_slist *headerlist = nullptr; + static const char headerBuffer[] = "Expect:"; + + curl_global_init(CURL_GLOBAL_ALL); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "cache-control:", + CURLFORM_COPYCONTENTS, "no-cache", + CURLFORM_END); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "content-type:", + CURLFORM_COPYCONTENTS, "multipart/form-data", + CURLFORM_END); + + std::vector postTokens = Utilities::split(data, '&'); + + for (std::vector::iterator it = postTokens.begin(); it != postTokens.end(); ++it) + { + std::vector tokens = Utilities::split(*it, '='); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, tokens[0].c_str(), + CURLFORM_COPYCONTENTS, tokens[1].c_str(), + CURLFORM_END); + } + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, fileFieldname.c_str(), + CURLFORM_BUFFER, "data", + CURLFORM_BUFFERPTR, contents.data(), + CURLFORM_BUFFERLENGTH, contents.size(), + CURLFORM_END); + + headerlist = curl_slist_append(headerlist, headerBuffer); + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + + if (code < 300 || code >= 400) + { + curl_easy_cleanup(curl); + curl_formfree(formpost); + curl_slist_free_all(headerlist); + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + throw std::runtime_error(error.str()); + } + + return std::string(); + } + + char* pRedirectUrl; + curl_easy_getinfo(curl, CURLINFO_REDIRECT_URL, &pRedirectUrl); + std::string redirectUrl(pRedirectUrl); + + curl_easy_cleanup(curl); + curl_formfree(formpost); + curl_slist_free_all(headerlist); + + return redirectUrl; +} + +std::string HttpClientImpl::GetUrlPostAttachmentContents(const std::string& url, const std::string& cookieFile, const std::string& data, const std::string& filename, const std::string& fileFieldname) const +{ + std::string buffer; + std::string contents; + std::ifstream fileStream(filename, std::ios::in | std::ios::binary); + + if (fileStream) + { + fileStream.seekg(0, std::ios::end); + contents.resize(fileStream.tellg()); + fileStream.seekg(0, std::ios::beg); + fileStream.read(&contents[0], contents.size()); + fileStream.close(); + } + + CURL* curl = curl_easy_init(); + CURLcode result; + + struct curl_httppost *formpost = nullptr; + struct curl_httppost *lastptr = nullptr; + struct curl_slist *headerlist = nullptr; + static const char headerBuffer[] = "Expect:"; + + curl_global_init(CURL_GLOBAL_ALL); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "cache-control:", + CURLFORM_COPYCONTENTS, "no-cache", + CURLFORM_END); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "content-type:", + CURLFORM_COPYCONTENTS, "multipart/form-data", + CURLFORM_END); + + std::vector postTokens = Utilities::split(data, '&'); + + for (std::vector::iterator it = postTokens.begin(); it != postTokens.end(); ++it) + { + std::vector tokens = Utilities::split(*it, '='); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, tokens[0].c_str(), + CURLFORM_COPYCONTENTS, tokens[1].c_str(), + CURLFORM_END); + } + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, fileFieldname.c_str(), + CURLFORM_BUFFER, "data", + CURLFORM_BUFFERPTR, contents.data(), + CURLFORM_BUFFERLENGTH, contents.size(), + CURLFORM_END); + + headerlist = curl_slist_append(headerlist, headerBuffer); + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + curl_easy_setopt(curl, CURLOPT_COOKIEFILE, cookieFile.c_str()); + curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookieFile.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + curl_formfree(formpost); + curl_slist_free_all(headerlist); + + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + error << "Data: " << buffer; + throw std::runtime_error(error.str()); + } + + return buffer; +} + +void HttpClientImpl::GetUrlPostAttachmentSilent(const std::string& url, const std::string& cookieFile, const std::string& data, const std::string& filename, const std::string& fileFieldname) const +{ + std::string contents; + std::ifstream fileStream(filename, std::ios::in | std::ios::binary); + + if (fileStream) + { + fileStream.seekg(0, std::ios::end); + contents.resize(fileStream.tellg()); + fileStream.seekg(0, std::ios::beg); + fileStream.read(&contents[0], contents.size()); + fileStream.close(); + } + + CURL* curl = curl_easy_init(); + CURLcode result; + + struct curl_httppost *formpost = nullptr; + struct curl_httppost *lastptr = nullptr; + struct curl_slist *headerlist = nullptr; + static const char headerBuffer[] = "Expect:"; + + curl_global_init(CURL_GLOBAL_ALL); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "cache-control:", + CURLFORM_COPYCONTENTS, "no-cache", + CURLFORM_END); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "content-type:", + CURLFORM_COPYCONTENTS, "multipart/form-data", + CURLFORM_END); + + std::vector postTokens = Utilities::split(data, '&'); + + for (std::vector::iterator it = postTokens.begin(); it != postTokens.end(); ++it) + { + std::vector tokens = Utilities::split(*it, '='); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, tokens[0].c_str(), + CURLFORM_COPYCONTENTS, tokens[1].c_str(), + CURLFORM_END); + } + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, fileFieldname.c_str(), + CURLFORM_BUFFER, "data", + CURLFORM_BUFFERPTR, contents.data(), + CURLFORM_BUFFERLENGTH, contents.size(), + CURLFORM_END); + + headerlist = curl_slist_append(headerlist, headerBuffer); + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + curl_easy_setopt(curl, CURLOPT_COOKIEFILE, cookieFile.c_str()); + curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookieFile.c_str()); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + curl_formfree(formpost); + curl_slist_free_all(headerlist); + + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + throw std::runtime_error(error.str()); + } +} + +std::string HttpClientImpl::GetUrlPostAttachmentRedirect(const std::string& url, const std::string& cookieFile, const std::string& data, const std::string& filename, const std::string& fileFieldname) const +{ + std::string contents; + std::ifstream fileStream(filename, std::ios::in | std::ios::binary); + + if (fileStream) + { + fileStream.seekg(0, std::ios::end); + contents.resize(fileStream.tellg()); + fileStream.seekg(0, std::ios::beg); + fileStream.read(&contents[0], contents.size()); + fileStream.close(); + } + + CURL* curl = curl_easy_init(); + CURLcode result; + + struct curl_httppost *formpost = nullptr; + struct curl_httppost *lastptr = nullptr; + struct curl_slist *headerlist = nullptr; + static const char headerBuffer[] = "Expect:"; + + curl_global_init(CURL_GLOBAL_ALL); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "cache-control:", + CURLFORM_COPYCONTENTS, "no-cache", + CURLFORM_END); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "content-type:", + CURLFORM_COPYCONTENTS, "multipart/form-data", + CURLFORM_END); + + std::vector postTokens = Utilities::split(data, '&'); + + for (std::vector::iterator it = postTokens.begin(); it != postTokens.end(); ++it) + { + std::vector tokens = Utilities::split(*it, '='); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, tokens[0].c_str(), + CURLFORM_COPYCONTENTS, tokens[1].c_str(), + CURLFORM_END); + } + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, fileFieldname.c_str(), + CURLFORM_BUFFER, "data", + CURLFORM_BUFFERPTR, contents.data(), + CURLFORM_BUFFERLENGTH, contents.size(), + CURLFORM_END); + + headerlist = curl_slist_append(headerlist, headerBuffer); + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + curl_easy_setopt(curl, CURLOPT_COOKIEFILE, cookieFile.c_str()); + curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookieFile.c_str()); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + + if (code < 300 || code >= 400) + { + curl_easy_cleanup(curl); + curl_formfree(formpost); + curl_slist_free_all(headerlist); + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + throw std::runtime_error(error.str()); + } + + return std::string(); + } + + char* pRedirectUrl; + curl_easy_getinfo(curl, CURLINFO_REDIRECT_URL, &pRedirectUrl); + std::string redirectUrl(pRedirectUrl); + + curl_easy_cleanup(curl); + curl_formfree(formpost); + curl_slist_free_all(headerlist); + + return redirectUrl; +} + +std::string HttpClientImpl::PerformOperation(HttpClientImpl::Operation::type type, HttpClientImpl::Method::type method, const std::string& url, const std::vector httpHeaders, const std::string& cookieFile, const std::string& data, bool returnReturnCode) const +{ + std::string buffer; + std::string error; + CURL* curl = curl_easy_init(); + + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + curl_easy_setopt(curl, CURLOPT_USERAGENT, m_userAgents[rand() % m_userAgents.size()].c_str()); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + + if (type == HttpClientImpl::Operation::Silent) + { + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, nullptr); + } + else if (type == HttpClientImpl::Operation::GetContent) + { + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + } + else if (type == HttpClientImpl::Operation::GetRedirect) + { + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, nullptr); + } + + //if (method == HttpClientImpl::Method::GET) + if (method == HttpClientImpl::Method::PUT) + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); + else if (method == HttpClientImpl::Method::POST) + curl_easy_setopt(curl, CURLOPT_POST, 1); + + if (method != HttpClientImpl::Method::GET && !data.empty()) + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); + + if (!httpHeaders.empty()) + { + struct curl_slist *list = nullptr; + for (auto it = httpHeaders.begin(); it != httpHeaders.end(); ++it) + list = curl_slist_append(list, it->c_str()); + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + } + + if (!cookieFile.empty()) + { + curl_easy_setopt(curl, CURLOPT_COOKIEFILE, cookieFile.c_str()); + curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookieFile.c_str()); + } + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + + if (!returnReturnCode) + { + if (code < 100) + { + // Non-Existent + std::stringstream err; + err << "Error (" << code << ") encountered while retrieving " << url << "\n"; + error = err.str(); + } + else if (code < 200) + { + // Informational + std::stringstream info; + info << "Informational (" << code << ") encountered while retrieving " << url << "\n"; + Logging::Log(Logging::Severity::Info, info.str()); + } + else if (code < 300) + { + // Success + if (code == 202) + buffer = PerformOperation(type, method, url, httpHeaders, cookieFile, data); + } + else if (code < 400) + { + // Redirection + if (type == HttpClientImpl::Operation::GetRedirect) + { + char* pRedirectUrl; + curl_easy_getinfo(curl, CURLINFO_REDIRECT_URL, &pRedirectUrl); + buffer = std::string(pRedirectUrl); + } + else + { + std::stringstream redirect; + redirect << "Redirect (" << code << ") encountered while retrieving " << url << "\n"; + Logging::Log(Logging::Severity::Info, redirect.str()); + } + } + else if (code < 500) + { + // Client Error + std::stringstream err; + err << "Client Error (" << code << ") encountered while retrieving " << url << "\n"; + err << "Response: " << buffer << "\n"; +/* + if (code == 429) + { + // Too Many Requests + curl_off_t wait = 0; + curl_easy_getinfo(curl, CURLINFO_RETRY_AFTER, &wait); + err << "Retry after " << wait << " seconds\n"; + } +*/ + error = err.str(); + } + else if (code < 600) + { + // Server Error + std::stringstream err; + err << "Server Error (" << code << ") encountered while retrieving " << url << "\n"; + err << "Response: " << buffer << "\n"; + error = err.str(); + } + else + { + // Non-Existent + std::stringstream err; + err << "Error (" << code << ") encountered while retrieving " << url << "\n"; + err << "Response: " << buffer << "\n"; + error = err.str(); + } + } + else + { + buffer = std::to_string(code); + } + + curl_easy_cleanup(curl); + + if (!error.empty()) + throw std::runtime_error(error); + + return buffer; +} + +size_t HttpClientImpl::WriteCallback(char* data, size_t size, size_t nmemb, std::string* writerData) +{ + if (writerData == nullptr) + return size * nmemb; + + writerData->append(data, size * nmemb); + return size * nmemb; +} + +} // namespace Http diff --git a/Http/HttpClientImpl.h b/Http/HttpClientImpl.h new file mode 100644 index 0000000..ce81f41 --- /dev/null +++ b/Http/HttpClientImpl.h @@ -0,0 +1,94 @@ +#ifndef HTTP_HTTPCLIENTIMPL_H +#define HTTP_HTTPCLIENTIMPL_H + +#include +#include + + +typedef void CURL; + +namespace Http { + +class HttpClientImpl +{ +public: + HttpClientImpl(); + ~HttpClientImpl(); + + HttpClientImpl(const HttpClientImpl&) = delete; + + int GetUrlReturnCode(const std::string& url) const; + + std::string GetUrlContents(const std::string& url) const; + void GetUrlSilent(const std::string& url) const; + std::string GetUrlRedirect(const std::string& url) const; + + std::string GetUrlContents(const std::string& url, const std::vector& httpHeaders) const; + void GetUrlSilent(const std::string& url, const std::vector& httpHeaders) const; + std::string GetUrlRedirect(const std::string& url, const std::vector& httpHeaders) const; + + std::string GetUrlContents(const std::string& url, const std::string& cookieFile) const; + void GetUrlSilent(const std::string& url, const std::string& cookieFile) const; + std::string GetUrlRedirect(const std::string& url, const std::string& cookieFile) const; + + std::string GetUrlContents(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile) const; + void GetUrlSilent(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile) const; + std::string GetUrlRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile) const; + + std::string PutUrlContents(const std::string& url, const std::vector& httpHeaders, const std::string& data) const; + void PutUrlSilent(const std::string& url, const std::vector& httpHeaders, const std::string& data) const; + std::string PutUrlRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& data) const; + + std::string PutUrlContents(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const; + void PutUrlSilent(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const; + std::string PutUrlRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const; + + std::string GetUrlPostContents(const std::string& url, const std::vector& httpHeaders, const std::string& data) const; + void GetUrlPostSilent(const std::string& url, const std::vector& httpHeaders, const std::string& data) const; + std::string GetUrlPostRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& data) const; + + std::string GetUrlPostContents(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const; + void GetUrlPostSilent(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const; + std::string GetUrlPostRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const; + + std::string GetUrlPostAttachmentContents(const std::string& url, const std::string& data, const std::string& filename, const std::string& fileFieldname) const; + void GetUrlPostAttachmentSilent(const std::string& url, const std::string& data, const std::string& filename, const std::string& fileFieldname) const; + std::string GetUrlPostAttachmentRedirect(const std::string& url, const std::string& data, const std::string& filename, const std::string& fileFieldname) const; + + std::string GetUrlPostAttachmentContents(const std::string& url, const std::string& cookieFile, const std::string& data, const std::string& filename, const std::string& fileFieldname) const; + void GetUrlPostAttachmentSilent(const std::string& url, const std::string& cookieFile, const std::string& data, const std::string& filename, const std::string& fileFieldname) const; + std::string GetUrlPostAttachmentRedirect(const std::string& url, const std::string& cookieFile, const std::string& data, const std::string& filename, const std::string& fileFieldname) const; + +private: + struct Method + { + enum type + { + GET, + PUT, + POST + }; + }; + + struct Operation + { + enum type + { + Silent, + GetContent, + GetRedirect + }; + }; + +private: + std::string PerformOperation(HttpClientImpl::Operation::type type, HttpClientImpl::Method::type method, const std::string& url, const std::vector httpHeaders = std::vector(), const std::string& cookieFile = std::string(), const std::string& data = std::string(), bool returnCode = false) const; + void ThrowException(int code); + static size_t WriteCallback(char* data, size_t size, size_t nmemb, std::string* writerData); + +private: + std::vector m_userAgents; +}; + +} // namespace Http + +#endif // HTTP_HTTPCLIENTIMPL_H diff --git a/Http/HttpConnection.cpp b/Http/HttpConnection.cpp new file mode 100644 index 0000000..7ba097b --- /dev/null +++ b/Http/HttpConnection.cpp @@ -0,0 +1,92 @@ +#include "HttpConnection.h" +#include + + +namespace Http { + +HttpConnection::HttpConnection(asio::io_service& ioService, HttpRequestHandler& handler) : + m_strand(ioService), + m_socket(ioService), + m_requestHandler(handler), + m_dataState(false, 0), + m_dataIncomplete(false) +{ +} + +HttpConnection::~HttpConnection() +{ +} + +asio::ip::tcp::socket& HttpConnection::Socket() +{ + return m_socket; +} + +void HttpConnection::Start() +{ + m_socket.async_read_some(asio::buffer(m_data), m_strand.wrap(std::bind(&HttpConnection::HandleRead, shared_from_this(), std::placeholders::_1, std::placeholders::_2))); +} + +void HttpConnection::HandleRead(const asio::error_code& ec, std::size_t length) +{ + if (!ec) + { + size_t dataLocation; + Utilities::Tribool result; + Buffer::iterator dataIterator; + + if (!m_dataIncomplete) + { + m_dataState = m_requestParser.Parse(m_request, m_data.data(), m_data.data() + length); + dataLocation = std::get<1>(m_dataState) - m_data.data(); + } + else + { + std::get<1>(m_dataState) = m_data.data(); + dataLocation = 0; + } + + std::tie(result, dataIterator) = m_dataState; + + if (result && length < m_request.headers.GetContentLength() + dataLocation) + { + m_dataIncomplete = true; + result = Utilities::Tribool::Indeterminate; + } + else + { + m_dataIncomplete = false; + } + + if (result) + { + std::string data; + + while (dataIterator != m_data.data() + length) + data.push_back(*dataIterator++); + + HttpReply reply = m_requestHandler.HandleRequest(m_request, data); + asio::async_write(m_socket, reply.ToBuffers(), m_strand.wrap(std::bind(&HttpConnection::HandleWrite, shared_from_this(), std::placeholders::_1))); + } + else if (!result) + { + HttpReply reply = HttpReply::StockReply(HttpReply::Status::BadRequest); + asio::async_write(m_socket, reply.ToBuffers(), m_strand.wrap(std::bind(&HttpConnection::HandleWrite, shared_from_this(), std::placeholders::_1))); + } + else + { + m_socket.async_read_some(asio::buffer(m_data), m_strand.wrap(std::bind(&HttpConnection::HandleRead, shared_from_this(), std::placeholders::_1, std::placeholders::_2))); + } + } +} + +void HttpConnection::HandleWrite(const asio::error_code& ec) +{ + if (!ec) + { + asio::error_code ignored_ec; + m_socket.shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec); + } +} + +} // namespace Http diff --git a/Http/HttpConnection.h b/Http/HttpConnection.h new file mode 100644 index 0000000..cd0fdc2 --- /dev/null +++ b/Http/HttpConnection.h @@ -0,0 +1,49 @@ +#ifndef HTTP_HTTPCONNECTION_H +#define HTTP_HTTPCONNECTION_H + +#include "HttpReply.h" +#include "HttpRequest.h" +#include "HttpRequestHandler.h" +#include "HttpRequestParser.h" +#include "asio.hpp" +#include "Tribool.h" +#include +#include +#include +#include + + +namespace Http { + +class HttpConnection : + public std::enable_shared_from_this +{ +public: + HttpConnection(asio::io_service& ioService, HttpRequestHandler& handler); + ~HttpConnection(); + + HttpConnection(const HttpConnection&) = delete; + + asio::ip::tcp::socket& Socket(); + void Start(); + +private: + void HandleRead(const asio::error_code& ec, std::size_t length); + void HandleWrite(const asio::error_code& ec); + + typedef std::array Buffer; + +private: + asio::io_service::strand m_strand; + asio::ip::tcp::socket m_socket; + HttpRequestHandler& m_requestHandler; + Buffer m_data; + std::tuple m_dataState; + bool m_dataIncomplete; + HttpRequest m_request; + HttpRequestParser m_requestParser; +}; + +} // namespace Http + +#endif // HTTP_HTTPCONNECTION_H diff --git a/Http/HttpHeader.h b/Http/HttpHeader.h new file mode 100644 index 0000000..bdbcaff --- /dev/null +++ b/Http/HttpHeader.h @@ -0,0 +1,23 @@ +#ifndef HTTP_HTTPHEADER_H +#define HTTP_HTTPHEADER_H + +#include + + +namespace Http { + +struct HttpHeader +{ +public: + bool operator==(const std::string& other) const + { + return name == other; + } + + std::string name; + std::string value; +}; + +} // namespace Http + +#endif // HTTP_HTTPHEADER_H diff --git a/Http/HttpHeaders.cpp b/Http/HttpHeaders.cpp new file mode 100644 index 0000000..3b758be --- /dev/null +++ b/Http/HttpHeaders.cpp @@ -0,0 +1,127 @@ +#include "HttpHeaders.h" +#include + + +namespace Http { + +HttpHeaders::HttpHeaders() +{ +} + +HttpHeaders::~HttpHeaders() +{ +} + +HttpHeaders::iterator HttpHeaders::begin() +{ + return m_headers.begin(); +} + +HttpHeaders::const_iterator HttpHeaders::begin() const +{ + return m_headers.begin(); +} + +HttpHeaders::iterator HttpHeaders::end() +{ + return m_headers.end(); +} + +HttpHeaders::const_iterator HttpHeaders::end() const +{ + return m_headers.end(); +} + +HttpHeaders::reference HttpHeaders::front() +{ + return m_headers.front(); +} + +HttpHeaders::const_reference HttpHeaders::front() const +{ + return m_headers.front(); +} + +HttpHeaders::reference HttpHeaders::back() +{ + return m_headers.back(); +} + +HttpHeaders::const_reference HttpHeaders::back() const +{ + return m_headers.back(); +} + +HttpHeaders::reference HttpHeaders::at(size_t n) +{ + return m_headers.at(n); +} + +HttpHeaders::const_reference HttpHeaders::at(size_t n) const +{ + return m_headers.at(n); +} + +HttpHeaders::reference HttpHeaders::operator[](size_t n) +{ + return m_headers[n]; +} + +HttpHeaders::const_reference HttpHeaders::operator[](size_t n) const +{ + return m_headers[n]; +} + +size_t HttpHeaders::size() const +{ + return m_headers.size(); +} + +bool HttpHeaders::empty() const +{ + return m_headers.empty(); +} + +void HttpHeaders::resize(size_t n, HttpHeader h) +{ + m_headers.resize(n, h); +} + +void HttpHeaders::push_back(const_reference h) +{ + m_headers.push_back(h); +} + +void HttpHeaders::pop_back() +{ + m_headers.pop_back(); +} + +int HttpHeaders::GetContentLength() const +{ + std::vector::const_iterator iterator = std::find(begin(), end(), "Content-Length"); + if (iterator != end()) + return std::stoi(iterator->value); + + return 0; +} + +std::string HttpHeaders::GetBoundary() const +{ + std::vector::const_iterator iterator = std::find(begin(), end(), "Content-Type"); + if (iterator != end()) + { + std::string boundaryHeader = "boundary"; + std::string value = iterator->value; + size_t location = value.find(boundaryHeader); + if (location != std::string::npos) + { + location = value.find("=", location) + 1; + return value.substr(location); + } + } + + return std::string(); +} + +} // namespace Http diff --git a/Http/HttpHeaders.h b/Http/HttpHeaders.h new file mode 100644 index 0000000..789fbec --- /dev/null +++ b/Http/HttpHeaders.h @@ -0,0 +1,55 @@ +#ifndef HTTP_HTTPHEADERS_H +#define HTTP_HTTPHEADERS_H + +#include "HttpHeader.h" +#include +#include + + +namespace Http { + +class HttpHeaders +{ +public: + HttpHeaders(); + ~HttpHeaders(); + +public: + typedef std::vector::iterator iterator; + typedef std::vector::const_iterator const_iterator; + typedef HttpHeader& reference; + typedef const HttpHeader& const_reference; + + iterator begin(); + const_iterator begin() const; + iterator end(); + const_iterator end() const; + + reference front(); + const_reference front() const; + reference back(); + const_reference back() const; + reference at(size_t n); + const_reference at(size_t n) const; + reference operator[](size_t n); + const_reference operator[](size_t n) const; + + size_t size() const; + bool empty() const; + + void resize(size_t n, HttpHeader h = HttpHeader()); + + void push_back(const_reference h); + void pop_back(); + +public: + int GetContentLength() const; + std::string GetBoundary() const; + +private: + std::vector m_headers; +}; + +} // namespace Http + +#endif // HTTP_HTTPHEADERS_H diff --git a/Http/HttpMimeTypes.cpp b/Http/HttpMimeTypes.cpp new file mode 100644 index 0000000..52f1b77 --- /dev/null +++ b/Http/HttpMimeTypes.cpp @@ -0,0 +1,31 @@ +#include "HttpMimeTypes.h" + + +namespace Http { +namespace MimeTypes { + +struct Mapping +{ + const char* extension; + const char* mime_type; +} mappings[] = +{ + { "gif", "image/gif" }, + { "htm", "text/html" }, + { "html", "text/html" }, + { "jpg", "image/jpeg" }, + { "png", "image/png" }, + { 0, 0 } +}; + +std::string ExtensionToType(const std::string& extension) +{ + for (Mapping* m = mappings; m->extension; ++m) + if (m->extension == extension) + return m->mime_type; + + return "text/plain"; +} + +} // namespace MimeTypes +} // namespace Http diff --git a/Http/HttpMimeTypes.h b/Http/HttpMimeTypes.h new file mode 100644 index 0000000..9bc4c35 --- /dev/null +++ b/Http/HttpMimeTypes.h @@ -0,0 +1,15 @@ +#ifndef HTTP_HTTPMIMETYPES_H +#define HTTP_HTTPMIMETYPES_H + +#include + + +namespace Http { +namespace MimeTypes { + +std::string ExtensionToType(const std::string& extension); + +} // namespace MimeTypes +} // namespace Http + +#endif // HTTP_HTTPMIMETYPES_H diff --git a/Http/HttpReply.cpp b/Http/HttpReply.cpp new file mode 100644 index 0000000..413e795 --- /dev/null +++ b/Http/HttpReply.cpp @@ -0,0 +1,244 @@ +#include "HttpReply.h" + + +namespace Http { + +namespace StatusStrings { + +const std::string Ok = + "HTTP/1.0 200 OK\r\n"; +const std::string Created = + "HTTP/1.0 201 Created\r\n"; +const std::string Accepted = + "HTTP/1.0 202 Accepted\r\n"; +const std::string NoContent = + "HTTP/1.0 204 No Content\r\n"; +const std::string MultipleChoices = + "HTTP/1.0 300 Multiple Choices\r\n"; +const std::string MovedPermanently = + "HTTP/1.0 301 Moved Permanently\r\n"; +const std::string MovedTemporarily = + "HTTP/1.0 302 Moved Temporarily\r\n"; +const std::string NotModified = + "HTTP/1.0 304 Not Modified\r\n"; +const std::string BadRequest = + "HTTP/1.0 400 Bad Request\r\n"; +const std::string Unauthorized = + "HTTP/1.0 401 Unauthorized\r\n"; +const std::string Forbidden = + "HTTP/1.0 403 Forbidden\r\n"; +const std::string NotFound = + "HTTP/1.0 404 Not Found\r\n"; +const std::string InternalServerError = + "HTTP/1.0 500 Internal Server Error\r\n"; +const std::string NotImplemented = + "HTTP/1.0 501 Not Implemented\r\n"; +const std::string BadGateway = + "HTTP/1.0 502 Bad Gateway\r\n"; +const std::string ServiceUnavailable = + "HTTP/1.0 503 Service Unavailable\r\n"; + +asio::const_buffer ToBuffer(HttpReply::Status::type status) +{ + switch (status) + { + case HttpReply::Status::Ok: + return asio::buffer(Ok); + case HttpReply::Status::Created: + return asio::buffer(Created); + case HttpReply::Status::Accepted: + return asio::buffer(Accepted); + case HttpReply::Status::NoContent: + return asio::buffer(NoContent); + case HttpReply::Status::MultipleChoices: + return asio::buffer(MultipleChoices); + case HttpReply::Status::MovedPermanently: + return asio::buffer(MovedPermanently); + case HttpReply::Status::MovedTemporarily: + return asio::buffer(MovedTemporarily); + case HttpReply::Status::NotModified: + return asio::buffer(NotModified); + case HttpReply::Status::BadRequest: + return asio::buffer(BadRequest); + case HttpReply::Status::Unauthorized: + return asio::buffer(Unauthorized); + case HttpReply::Status::Forbidden: + return asio::buffer(Forbidden); + case HttpReply::Status::NotFound: + return asio::buffer(NotFound); + case HttpReply::Status::InternalServerError: + return asio::buffer(InternalServerError); + case HttpReply::Status::NotImplemented: + return asio::buffer(NotImplemented); + case HttpReply::Status::BadGateway: + return asio::buffer(BadGateway); + case HttpReply::Status::ServiceUnavailable: + return asio::buffer(ServiceUnavailable); + default: + return asio::buffer(InternalServerError); + } +} + +} // namespace StatusStrings + +namespace miscellaneousStrings { + +const char nameValueSeparator[] = { ':', ' ' }; +const char crlf[] = { '\r', '\n' }; + +} // namespace miscellaneousStrings + +std::vector HttpReply::ToBuffers() +{ + std::vector buffers; + buffers.push_back(StatusStrings::ToBuffer(status)); + for (std::size_t i = 0; i < headers.size(); ++i) + { + HttpHeader& h = headers[i]; + buffers.push_back(asio::buffer(h.name)); + buffers.push_back(asio::buffer(miscellaneousStrings::nameValueSeparator)); + buffers.push_back(asio::buffer(h.value)); + buffers.push_back(asio::buffer(miscellaneousStrings::crlf)); + } + buffers.push_back(asio::buffer(miscellaneousStrings::crlf)); + buffers.push_back(asio::buffer(content)); + + return buffers; +} + +namespace StockReplies { + +const char Ok[] = ""; +const char Created[] = + "" + "Created" + "

201 Created

" + ""; +const char Accepted[] = + "" + "Accepted" + "

202 Accepted

" + ""; +const char NoContent[] = + "" + "No Content" + "

204 Content

" + ""; +const char MultipleChoices[] = + "" + "Multiple Choices" + "

300 Multiple Choices

" + ""; +const char MovedPermanently[] = + "" + "Moved Permanently" + "

301 Moved Permanently

" + ""; +const char MovedTemporarily[] = + "" + "Moved Temporarily" + "

302 Moved Temporarily

" + ""; +const char NotModified[] = + "" + "Not Modified" + "

304 Not Modified

" + ""; +const char BadRequest[] = + "" + "Bad Request" + "

400 Bad Request

" + ""; +const char Unauthorized[] = + "" + "Unauthorized" + "

401 Unauthorized

" + ""; +const char Forbidden[] = + "" + "Forbidden" + "

403 Forbidden

" + ""; +const char NotFound[] = + "" + "Not Found" + "

404 Not Found

" + ""; +const char InternalServerError[] = + "" + "Internal Server Error" + "

500 Internal Server Error

" + ""; +const char NotImplemented[] = + "" + "Not Implemented" + "

501 Not Implemented

" + ""; +const char BadGateway[] = + "" + "Bad Gateway" + "

502 Bad Gateway

" + ""; +const char ServiceUnavailable[] = + "" + "Service Unavailable" + "

503 Service Unavailable

" + ""; + +std::string ToString(HttpReply::Status::type status) +{ + switch (status) + { + case HttpReply::Status::Ok: + return Ok; + case HttpReply::Status::Created: + return Created; + case HttpReply::Status::Accepted: + return Accepted; + case HttpReply::Status::NoContent: + return NoContent; + case HttpReply::Status::MultipleChoices: + return MultipleChoices; + case HttpReply::Status::MovedPermanently: + return MovedPermanently; + case HttpReply::Status::MovedTemporarily: + return MovedTemporarily; + case HttpReply::Status::NotModified: + return NotModified; + case HttpReply::Status::BadRequest: + return BadRequest; + case HttpReply::Status::Unauthorized: + return Unauthorized; + case HttpReply::Status::Forbidden: + return Forbidden; + case HttpReply::Status::NotFound: + return NotFound; + case HttpReply::Status::InternalServerError: + return InternalServerError; + case HttpReply::Status::NotImplemented: + return NotImplemented; + case HttpReply::Status::BadGateway: + return BadGateway; + case HttpReply::Status::ServiceUnavailable: + return ServiceUnavailable; + default: + return InternalServerError; + } +} + +} // namespace StockReplies + +HttpReply HttpReply::StockReply(HttpReply::Status::type status) +{ + HttpReply reply; + reply.status = status; + reply.content = StockReplies::ToString(status); + reply.headers.resize(2); + reply.headers[0].name = "Content-Length"; + reply.headers[0].value = std::to_string(reply.content.size()); + reply.headers[1].name = "Content-Type"; + reply.headers[1].value = "text/html"; + return reply; +} + +} // namespace Http diff --git a/Http/HttpReply.h b/Http/HttpReply.h new file mode 100644 index 0000000..2a02c4c --- /dev/null +++ b/Http/HttpReply.h @@ -0,0 +1,46 @@ +#ifndef HTTP_HTTPREPLY_H +#define HTTP_HTTPREPLY_H + +#include "HttpHeaders.h" +#include "asio.hpp" +#include +#include + + +namespace Http { + +struct HttpReply +{ + struct Status + { + enum type + { + Ok = 200, + Created = 201, + Accepted = 202, + NoContent = 204, + MultipleChoices = 300, + MovedPermanently = 301, + MovedTemporarily = 302, + NotModified = 304, + BadRequest = 400, + Unauthorized = 401, + Forbidden = 403, + NotFound = 404, + InternalServerError = 500, + NotImplemented = 501, + BadGateway = 502, + ServiceUnavailable = 503 + }; + }; + + Status::type status; + HttpHeaders headers; + std::string content; + std::vector ToBuffers(); + static HttpReply StockReply(Status::type status); +}; + +} // namespace Http + +#endif // HTTP_HTTPREPLY_H diff --git a/Http/HttpRequest.h b/Http/HttpRequest.h new file mode 100644 index 0000000..3de5e4c --- /dev/null +++ b/Http/HttpRequest.h @@ -0,0 +1,21 @@ +#ifndef HTTP_HTTPREQUEST_H +#define HTTP_HTTPREQUEST_H + +#include "HttpHeaders.h" +#include + + +namespace Http { + +struct HttpRequest +{ + std::string method; + std::string uri; + int httpVersionMajor; + int httpVersionMinor; + HttpHeaders headers; +}; + +} // namespace Http + +#endif // HTTP_HTTPREQUEST_H diff --git a/Http/HttpRequestHandler.cpp b/Http/HttpRequestHandler.cpp new file mode 100644 index 0000000..c6c34cc --- /dev/null +++ b/Http/HttpRequestHandler.cpp @@ -0,0 +1,138 @@ +#include "HttpRequestHandler.h" +#include "HttpMimeTypes.h" +#include "StringAlgorithm.h" +#include +#include + + +namespace Http { + +HttpRequestHandler::HttpRequestHandler(std::function&)> callback) : + m_callback(callback) +{ +} + +HttpReply HttpRequestHandler::HandleRequest(const HttpRequest& request, const std::string& data) +{ + std::string requestPath; + if (!URLDecode(request.uri, requestPath)) + return HttpReply::StockReply(HttpReply::Status::BadRequest); + + if (requestPath.empty() || requestPath[0] != '/' || requestPath.find("..") != std::string::npos) + return HttpReply::StockReply(HttpReply::Status::BadRequest); + + if (!Utilities::iequals(request.uri, "/api") && !Utilities::istarts_with(request.uri, "/api/")) + return HttpReply::StockReply(HttpReply::Status::NotFound); + + std::vector postData = GetPostData(request.headers.GetBoundary(), data); + std::string answer = HandlePostData(request.uri.substr(4), postData); + + HttpReply reply; + reply.status = HttpReply::Status::Ok; + reply.content = answer; + + reply.headers.resize(3); + reply.headers[0].name = "Content-Length"; + reply.headers[0].value = std::to_string(reply.content.size()); + reply.headers[1].name = "Content-Type"; + reply.headers[1].value = "application/json"; + reply.headers[2].name = "Access-Control-Allow-Origin"; + reply.headers[2].value = "*"; + return reply; +} + +std::vector HttpRequestHandler::GetPostData(const std::string& boundary, const std::string& data) const +{ + size_t boundarySize = boundary.size(); + size_t location = data.find(boundary); + size_t nextLocation, dataStart, dataSize; + + if (boundarySize == 0) + return std::vector(); + + std::vector postDataCollection; + while (location != std::string::npos) + { + dataStart = location + boundarySize; + nextLocation = data.find(boundary, dataStart); + if (nextLocation == std::string::npos) + break; + + dataSize = nextLocation - dataStart - 4; + std::string postItem = data.substr(dataStart, dataSize); + + std::string fieldName = ""; + size_t headerLocation = postItem.find("Content-Disposition"); + size_t headerEnd = 0; + if (headerLocation != std::string::npos) + { + headerEnd = postItem.find('\n', postItem.find('\n', headerLocation + 1) + 1 ) + 1; + + std::string nameString = "name"; + size_t nameLocation = postItem.find(nameString, headerLocation); + if (nameLocation != std::string::npos && nameLocation < headerEnd) + { + size_t nameStart = nameLocation + nameString.size() + 2; + size_t nameSize = postItem.find('"', nameStart) - nameStart; + fieldName = postItem.substr(nameStart, nameSize); + } + } + postItem = postItem.substr(headerEnd); + + HttpPostData postData; + postData.name = fieldName; + postData.value = postItem; + + postDataCollection.push_back(postData); + + location = nextLocation + boundarySize; + } + + return postDataCollection; +} + +std::string HttpRequestHandler::HandlePostData(const std::string& uri, const std::vector& postData) +{ + return m_callback(uri, postData); +} + +bool HttpRequestHandler::URLDecode(const std::string& in, std::string& out) +{ + out.clear(); + out.reserve(in.size()); + for (std::size_t i = 0; i < in.size(); ++i) + { + if (in[i] == '%') + { + if (i + 3 <= in.size()) + { + int value = 0; + std::istringstream is(in.substr(i + 1, 2)); + if (is >> std::hex >> value) + { + out += static_cast(value); + i += 2; + } + else + { + return false; + } + } + else + { + return false; + } + } + else if (in[i] == '+') + { + out += ' '; + } + else + { + out += in[i]; + } + } + return true; +} + +} // namespace Http diff --git a/Http/HttpRequestHandler.h b/Http/HttpRequestHandler.h new file mode 100644 index 0000000..9ad01cb --- /dev/null +++ b/Http/HttpRequestHandler.h @@ -0,0 +1,35 @@ +#ifndef HTTP_HTTPREQUESTHANDLER_H +#define HTTP_HTTPREQUESTHANDLER_H + +#include "HttpReply.h" +#include "HttpRequest.h" +#include "HttpPostData.h" +#include +#include +#include + + +namespace Http { + +class HttpRequestHandler +{ +public: + explicit HttpRequestHandler(std::function&)> callback); + + HttpRequestHandler(const HttpRequestHandler&) = delete; + + HttpReply HandleRequest(const HttpRequest& request, const std::string& data); + +private: + std::vector GetPostData(const std::string& boundary, const std::string& data) const; + std::string HandlePostData(const std::string& uri, const std::vector& postData); + + static bool URLDecode(const std::string& in, std::string& out); + +private: + std::function&)> m_callback; +}; + +} // namespace Http + +#endif // HTTP_HTTPREQUESTHANDLER_H diff --git a/Http/HttpRequestParser.cpp b/Http/HttpRequestParser.cpp new file mode 100644 index 0000000..114d229 --- /dev/null +++ b/Http/HttpRequestParser.cpp @@ -0,0 +1,304 @@ +#include "HttpRequestParser.h" +#include "HttpRequest.h" + + +namespace Http { + +HttpRequestParser::HttpRequestParser() : + m_state(State::MethodStart) +{ +} + +void HttpRequestParser::Reset() +{ + m_state = State::MethodStart; +} + +Utilities::Tribool HttpRequestParser::Consume(HttpRequest& request, char input) +{ + switch (m_state) + { + case State::MethodStart: + if (!IsChar(input) || IsCtl(input) || IsTSpecial(input)) + { + return false; + } + else + { + m_state = State::Method; + request.method.push_back(input); + return Utilities::Tribool::Indeterminate; + } + case State::Method: + if (input == ' ') + { + m_state = State::URI; + return Utilities::Tribool::Indeterminate; + } + else if (!IsChar(input) || IsCtl(input) || IsTSpecial(input)) + { + return false; + } + else + { + request.method.push_back(input); + return Utilities::Tribool::Indeterminate; + } + case State::URI: + if (input == ' ') + { + m_state = State::HttpVersionH; + return Utilities::Tribool::Indeterminate; + } + else if (IsCtl(input)) + { + return false; + } + else + { + request.uri.push_back(input); + return Utilities::Tribool::Indeterminate; + } + case State::HttpVersionH: + if (input == 'H') + { + m_state = State::HttpVersionT1; + return Utilities::Tribool::Indeterminate; + } + else + { + return false; + } + case State::HttpVersionT1: + if (input == 'T') + { + m_state = State::HttpVersionT2; + return Utilities::Tribool::Indeterminate; + } + else + { + return false; + } + case State::HttpVersionT2: + if (input == 'T') + { + m_state = State::HttpVersionP; + return Utilities::Tribool::Indeterminate; + } + else + { + return false; + } + case State::HttpVersionP: + if (input == 'P') + { + m_state = State::HttpVersionSlash; + return Utilities::Tribool::Indeterminate; + } + else + { + return false; + } + case State::HttpVersionSlash: + if (input == '/') + { + request.httpVersionMajor = 0; + request.httpVersionMinor = 0; + m_state = State::HttpVersionMajorStart; + return Utilities::Tribool::Indeterminate; + } + else + { + return false; + } + case State::HttpVersionMajorStart: + if (IsDigit(input)) + { + request.httpVersionMajor = request.httpVersionMajor * 10 + input - '0'; + m_state = State::HttpVersionMajor; + return Utilities::Tribool::Indeterminate; + } + else + { + return false; + } + case State::HttpVersionMajor: + if (input == '.') + { + m_state = State::HttpVersionMinorStart; + return Utilities::Tribool::Indeterminate; + } + else if (IsDigit(input)) + { + request.httpVersionMajor = request.httpVersionMajor * 10 + input - '0'; + return Utilities::Tribool::Indeterminate; + } + else + { + return false; + } + case State::HttpVersionMinorStart: + if (IsDigit(input)) + { + request.httpVersionMinor = request.httpVersionMinor * 10 + input - '0'; + m_state = State::HttpVersionMinor; + return Utilities::Tribool::Indeterminate; + } + else + { + return false; + } + case State::HttpVersionMinor: + if (input == '\r') + { + m_state = State::ExpectingNewline1; + return Utilities::Tribool::Indeterminate; + } + else if (IsDigit(input)) + { + request.httpVersionMinor = request.httpVersionMinor * 10 + input - '0'; + return Utilities::Tribool::Indeterminate; + } + else + { + return false; + } + case State::ExpectingNewline1: + if (input == '\n') + { + m_state = State::HeaderLineStart; + return Utilities::Tribool::Indeterminate; + } + else + { + return false; + } + case State::HeaderLineStart: + if (input == '\r') + { + m_state = State::ExpectingNewline3; + return Utilities::Tribool::Indeterminate; + } + else if (!request.headers.empty() && (input == ' ' || input == '\t')) + { + m_state = State::HeaderLws; + return Utilities::Tribool::Indeterminate; + } + else if (!IsChar(input) || IsCtl(input) || IsTSpecial(input)) + { + return false; + } + else + { + request.headers.push_back(HttpHeader()); + request.headers.back().name.push_back(input); + m_state = State::HeaderName; + return Utilities::Tribool::Indeterminate; + } + case State::HeaderLws: + if (input == '\r') + { + m_state = State::ExpectingNewline2; + return Utilities::Tribool::Indeterminate; + } + else if (input == ' ' || input == '\t') + { + return Utilities::Tribool::Indeterminate; + } + else if (IsCtl(input)) + { + return false; + } + else + { + m_state = State::HeaderValue; + request.headers.back().value.push_back(input); + return Utilities::Tribool::Indeterminate; + } + case State::HeaderName: + if (input == ':') + { + m_state = State::SpaceBeforeHeaderValue; + return Utilities::Tribool::Indeterminate; + } + else if (!IsChar(input) || IsCtl(input) || IsTSpecial(input)) + { + return false; + } + else + { + request.headers.back().name.push_back(input); + return Utilities::Tribool::Indeterminate; + } + case State::SpaceBeforeHeaderValue: + if (input == ' ') + { + m_state = State::HeaderValue; + return Utilities::Tribool::Indeterminate; + } + else + { + return false; + } + case State::HeaderValue: + if (input == '\r') + { + m_state = State::ExpectingNewline2; + return Utilities::Tribool::Indeterminate; + } + else if (IsCtl(input)) + { + return false; + } + else + { + request.headers.back().value.push_back(input); + return Utilities::Tribool::Indeterminate; + } + case State::ExpectingNewline2: + if (input == '\n') + { + m_state = State::HeaderLineStart; + return Utilities::Tribool::Indeterminate; + } + else + { + return false; + } + case State::ExpectingNewline3: + return (input == '\n'); + default: + return false; + } +} + +bool HttpRequestParser::IsChar(int c) +{ + return c >= 0 && c <= 127; +} + +bool HttpRequestParser::IsCtl(int c) +{ + return (c >= 0 && c <= 31) || (c == 127); +} + +bool HttpRequestParser::IsTSpecial(int c) +{ + switch (c) + { + case '(': case ')': case '<': case '>': case '@': + case ',': case ';': case ':': case '\\': case '"': + case '/': case '[': case ']': case '?': case '=': + case '{': case '}': case ' ': case '\t': + return true; + default: + return false; + } +} + +bool HttpRequestParser::IsDigit(int c) +{ + return c >= '0' && c <= '9'; +} + +} // namespace Http diff --git a/Http/HttpRequestParser.h b/Http/HttpRequestParser.h new file mode 100644 index 0000000..6aad541 --- /dev/null +++ b/Http/HttpRequestParser.h @@ -0,0 +1,75 @@ +#ifndef HTTP_HTTPREQUESTPARSER_H +#define HTTP_HTTPREQUESTPARSER_H + +#include "HttpRequest.h" +#include "Tribool.h" +#include + + +namespace Http { + +struct HttpRequest; + +class HttpRequestParser +{ +public: + HttpRequestParser(); + + void Reset(); + + template + std::tuple Parse(HttpRequest& request, InputIterator begin, InputIterator end) + { + Utilities::Tribool result = Utilities::Tribool::Indeterminate; + while (begin != end) + { + result = Consume(request, *begin++); + if (result || !result) + return std::make_tuple(result, begin); + } + + return std::make_tuple(result, begin); + } + +private: + Utilities::Tribool Consume(HttpRequest& request, char input); + static bool IsChar(int c); + static bool IsCtl(int c); + static bool IsTSpecial(int c); + static bool IsDigit(int c); + +private: + struct State + { + enum type + { + MethodStart, + Method, + URI, + HttpVersionH, + HttpVersionT1, + HttpVersionT2, + HttpVersionP, + HttpVersionSlash, + HttpVersionMajorStart, + HttpVersionMajor, + HttpVersionMinorStart, + HttpVersionMinor, + ExpectingNewline1, + HeaderLineStart, + HeaderLws, + HeaderName, + SpaceBeforeHeaderValue, + HeaderValue, + ExpectingNewline2, + ExpectingNewline3 + }; + }; + +private: + State::type m_state; +}; + +} // namespace Http + +#endif // HTTP_HTTPREQUESTPARSER_H diff --git a/Http/HttpServer.cpp b/Http/HttpServer.cpp new file mode 100644 index 0000000..ec72147 --- /dev/null +++ b/Http/HttpServer.cpp @@ -0,0 +1,21 @@ +#include "HttpServer.h" +#include "HttpServerImpl.h" + + +namespace Http { + +HttpServer::HttpServer(unsigned short port, std::function&)> callback) : + m_pHttpServerImpl(new HttpServerImpl(port, callback)) +{ +} + +HttpServer::~HttpServer() +{ +} + +void HttpServer::Wait() +{ + m_pHttpServerImpl->Wait(); +} + +} // namespace Http diff --git a/Http/HttpServerImpl.cpp b/Http/HttpServerImpl.cpp new file mode 100644 index 0000000..fdc1db8 --- /dev/null +++ b/Http/HttpServerImpl.cpp @@ -0,0 +1,79 @@ +#include "HttpServerImpl.h" +#include "Logging.h" +#include +#include +#include + + +namespace Http { + +HttpServerImpl::HttpServerImpl(unsigned short port, std::function&)> callback) : + m_threadPoolSize(3), + m_signals(m_ioService), + m_acceptor(m_ioService), + m_requestHandler(callback) +{ + m_signals.add(SIGINT); + m_signals.add(SIGTERM); + m_signals.add(SIGQUIT); + m_signals.async_wait(std::bind(&HttpServerImpl::HandleStop, this)); + + asio::ip::tcp::resolver resolver(m_ioService); + asio::ip::tcp::endpoint endpoint(asio::ip::tcp::v4(), port); + m_acceptor.open(endpoint.protocol()); + m_acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true)); + m_acceptor.bind(endpoint); + m_acceptor.listen(); + + for (std::size_t i = 0; i < m_threadPoolSize; ++i) + { + auto thread = std::make_shared([&] { m_ioService.run(); }); + +#ifdef __linux__ + pthread_setname_np(thread->native_handle(), "HttpServer"); +#endif + + m_threads.push_back(thread); + } + + StartAccept(); +} + +HttpServerImpl::~HttpServerImpl() +{ +} + +void HttpServerImpl::Wait() +{ + for (std::size_t i = 0; i < m_threads.size(); ++i) + m_threads[i]->join(); +} + +void HttpServerImpl::StartAccept() +{ + m_connection.reset(new HttpConnection(m_ioService, m_requestHandler)); + m_acceptor.async_accept(m_connection->Socket(), std::bind(&HttpServerImpl::HandleAccept, this, std::placeholders::_1)); +} + +void HttpServerImpl::HandleAccept(const std::error_code& ec) +{ + if (!ec) + { + m_connection->Start(); + } + else + { + std::stringstream ss; + ss << "Http Accept error: " << ec.message() << std::endl; + Logging::Log(Logging::Severity::Error, ss.str()); + } + + StartAccept(); +} + +void HttpServerImpl::HandleStop() +{ + m_ioService.stop(); +} + +} // namespace Http diff --git a/Http/HttpServerImpl.h b/Http/HttpServerImpl.h new file mode 100644 index 0000000..6536767 --- /dev/null +++ b/Http/HttpServerImpl.h @@ -0,0 +1,40 @@ +#ifndef HTTP_HTTPSERVERIMPL_H +#define HTTP_HTTPSERVERIMPL_H + +#include "HttpConnection.h" +#include "HttpRequestHandler.h" +#include "asio.hpp" +#include +#include +#include +#include + + +namespace Http { + +class HttpServerImpl +{ +public: + HttpServerImpl(unsigned short port, std::function&)> callback); + ~HttpServerImpl(); + + void Wait(); + +private: + void StartAccept(); + void HandleAccept(const asio::error_code& ec); + void HandleStop(); + +private: + std::size_t m_threadPoolSize; + std::vector > m_threads; + asio::io_service m_ioService; + asio::signal_set m_signals; + asio::ip::tcp::acceptor m_acceptor; + std::shared_ptr m_connection; + HttpRequestHandler m_requestHandler; +}; + +} // namespace Http + +#endif // HTTP_HTTPSERVERIMPL_H diff --git a/Http/Makefile b/Http/Makefile new file mode 120000 index 0000000..d0b0e8e --- /dev/null +++ b/Http/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 120000 index 0000000..6aa5187 --- /dev/null +++ b/Makefile @@ -0,0 +1 @@ +Makefiles/Makefile \ No newline at end of file diff --git a/Makefile.conf b/Makefile.conf new file mode 100644 index 0000000..5ebd4a7 --- /dev/null +++ b/Makefile.conf @@ -0,0 +1,6 @@ +# +# Makefile.conf +# + +CFLAGS += -I$(ROOTPATH) -I$(ROOTPATH)/include -I$(ROOTPATH)/Libraries/asio/include -I$(ROOTPATH)/Libraries/Logging/include -I$(ROOTPATH)/Libraries/Utilities/include +DEBUGDIR := .debug diff --git a/Makefile.target b/Makefile.target new file mode 100644 index 0000000..3400b77 --- /dev/null +++ b/Makefile.target @@ -0,0 +1,12 @@ +# +# Makefile.target +# + +Http.a.$(ARCH) : $(OBJECTS) + $(call build_target_library_arch,$@,$^) +Http.a: + $(call build_target,$@) + +.DEFAULT_GOAL := Http.a + +TARGETS += Http.a diff --git a/Makefiles b/Makefiles new file mode 160000 index 0000000..d82c8a2 --- /dev/null +++ b/Makefiles @@ -0,0 +1 @@ +Subproject commit d82c8a204714538d0e9414808388e8c79f20db26 diff --git a/include/HttpClient.h b/include/HttpClient.h new file mode 100644 index 0000000..2fa9b4e --- /dev/null +++ b/include/HttpClient.h @@ -0,0 +1,69 @@ +#ifndef HTTPCLIENT_H +#define HTTPCLIENT_H + +#include +#include +#include + + +namespace Http { + +class HttpClientImpl; + +class HttpClient +{ +public: + HttpClient(); + ~HttpClient(); + + HttpClient(const HttpClient&) = delete; + + int GetUrlReturnCode(const std::string& url) const; + + std::string GetUrlContents(const std::string& url) const; + void GetUrlSilent(const std::string& url) const; + std::string GetUrlRedirect(const std::string& url) const; + + std::string GetUrlContents(const std::string& url, const std::vector& httpHeaders) const; + void GetUrlSilent(const std::string& url, const std::vector& httpHeaders) const; + std::string GetUrlRedirect(const std::string& url, const std::vector& httpHeaders) const; + + std::string GetUrlContents(const std::string& url, const std::string& cookieFile) const; + void GetUrlSilent(const std::string& url, const std::string& cookieFile) const; + std::string GetUrlRedirect(const std::string& url, const std::string& cookieFile) const; + + std::string GetUrlContents(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile) const; + void GetUrlSilent(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile) const; + std::string GetUrlRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile) const; + + std::string PutUrlContents(const std::string& url, const std::vector& httpHeaders, const std::string& data) const; + void PutUrlSilent(const std::string& url, const std::vector& httpHeaders, const std::string& data) const; + std::string PutUrlRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& data) const; + + std::string PutUrlContents(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const; + void PutUrlSilent(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const; + std::string PutUrlRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const; + + std::string GetUrlPostContents(const std::string& url, const std::vector& httpHeaders, const std::string& data) const; + void GetUrlPostSilent(const std::string& url, const std::vector& httpHeaders, const std::string& data) const; + std::string GetUrlPostRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& data) const; + + std::string GetUrlPostContents(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const; + void GetUrlPostSilent(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const; + std::string GetUrlPostRedirect(const std::string& url, const std::vector& httpHeaders, const std::string& cookieFile, const std::string& data) const; + + std::string GetUrlPostAttachmentContents(const std::string& url, const std::string& data, const std::string& filename, const std::string& fileFieldname) const; + void GetUrlPostAttachmentSilent(const std::string& url, const std::string& data, const std::string& filename, const std::string& fileFieldname) const; + std::string GetUrlPostAttachmentRedirect(const std::string& url, const std::string& data, const std::string& filename, const std::string& fileFieldname) const; + + std::string GetUrlPostAttachmentContents(const std::string& url, const std::string& cookieFile, const std::string& data, const std::string& filename, const std::string& fileFieldname) const; + void GetUrlPostAttachmentSilent(const std::string& url, const std::string& cookieFile, const std::string& data, const std::string& filename, const std::string& fileFieldname) const; + std::string GetUrlPostAttachmentRedirect(const std::string& url, const std::string& cookieFile, const std::string& data, const std::string& filename, const std::string& fileFieldname) const; + +private: + std::unique_ptr m_pHttpClientImpl; +}; + +} // namespace Http + +#endif // HTTPCLIENT_H diff --git a/include/HttpPostData.h b/include/HttpPostData.h new file mode 100644 index 0000000..bd7ad2c --- /dev/null +++ b/include/HttpPostData.h @@ -0,0 +1,22 @@ +#ifndef HTTP_HTTPPOSTDATA_H +#define HTTP_HTTPPOSTDATA_H + +#include + + +namespace Http { + +struct HttpPostData +{ + bool operator==(const std::string& other) const + { + return name == other; + } + + std::string name; + std::string value; +}; + +} // namespace Http + +#endif // HTTP_HTTPPOSTDATA_H diff --git a/include/HttpServer.h b/include/HttpServer.h new file mode 100644 index 0000000..2a47ddf --- /dev/null +++ b/include/HttpServer.h @@ -0,0 +1,28 @@ +#ifndef HTTPSERVER_H +#define HTTPSERVER_H + +#include "HttpPostData.h" +#include +#include +#include + + +namespace Http { + +class HttpServerImpl; + +class HttpServer +{ +public: + HttpServer(unsigned short port, std::function&)> callback); + ~HttpServer(); + + void Wait(); + +private: + std::unique_ptr m_pHttpServerImpl; +}; + +} // namespace Http + +#endif // HTTPSERVER_H