JDierkse пре 5 година
комит
a175bf5af9

+ 8 - 0
.gitignore

@@ -0,0 +1,8 @@
+*.o.*
+*.d.*
+*.a.*
+.*.swp
+.AppleDouble
+lib
+Libraries
+fixPermissions.sh

+ 3 - 0
.gitmodules

@@ -0,0 +1,3 @@
+[submodule "Makefiles"]
+	path = Makefiles
+	url = https://gogs.dierkse.nl/JDierkse/Makefiles.git

+ 169 - 0
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<std::string>& httpHeaders) const
+{
+	return m_pHttpClientImpl->GetUrlContents(url, httpHeaders);
+}
+
+void HttpClient::GetUrlSilent(const std::string& url, const std::vector<std::string>& httpHeaders) const
+{
+	m_pHttpClientImpl->GetUrlSilent(url, httpHeaders);
+}
+
+std::string HttpClient::GetUrlRedirect(const std::string& url, const std::vector<std::string>& 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<std::string>& httpHeaders, const std::string& cookieFile) const
+{
+	return m_pHttpClientImpl->GetUrlContents(url, httpHeaders, cookieFile);
+}
+
+void HttpClient::GetUrlSilent(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& cookieFile) const
+{
+	m_pHttpClientImpl->GetUrlSilent(url, httpHeaders, cookieFile);
+}
+
+std::string HttpClient::GetUrlRedirect(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& cookieFile) const
+{
+	return m_pHttpClientImpl->GetUrlRedirect(url, httpHeaders, cookieFile);
+}
+
+std::string HttpClient::PutUrlContents(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& data) const
+{
+	return m_pHttpClientImpl->PutUrlContents(url, httpHeaders, data);
+}
+
+void HttpClient::PutUrlSilent(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& data) const
+{
+	m_pHttpClientImpl->PutUrlSilent(url, httpHeaders, data);
+}
+
+std::string HttpClient::PutUrlRedirect(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& data) const
+{
+	return m_pHttpClientImpl->PutUrlRedirect(url, httpHeaders, data);
+}
+
+std::string HttpClient::PutUrlContents(const std::string& url, const std::vector<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& httpHeaders, const std::string& data) const
+{
+	return m_pHttpClientImpl->GetUrlPostContents(url, httpHeaders, data);
+}
+
+void HttpClient::GetUrlPostSilent(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& data) const
+{
+	m_pHttpClientImpl->GetUrlPostSilent(url, httpHeaders, data);
+}
+
+std::string HttpClient::GetUrlPostRedirect(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& data) const
+{
+	return m_pHttpClientImpl->GetUrlPostRedirect(url, httpHeaders, data);
+}
+
+std::string HttpClient::GetUrlPostContents(const std::string& url, const std::vector<std::string>& 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<std::string>& 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<std::string>& 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

+ 45 - 0
Http/HttpClientHelpers.h

@@ -0,0 +1,45 @@
+#ifndef HTTP_HTTPCLIENTHELPERS_H
+#define HTTP_HTTPCLIENTHELPERS_H
+
+#include <openssl/crypto.h>
+#include <memory>
+#include <mutex>
+#include <thread>
+
+
+namespace Http {
+
+static std::vector<std::shared_ptr<std::mutex> > 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<unsigned long>(std::hash<std::thread::id>{}(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<std::mutex>(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

+ 792 - 0
Http/HttpClientImpl.cpp

@@ -0,0 +1,792 @@
+#include "HttpClientImpl.h"
+#include "HttpClientHelpers.h"
+#include "Logging.h"
+#include "StringAlgorithm.h"
+#include <curl/curl.h>
+#include <openssl/crypto.h>
+#include <fstream>
+#include <sstream>
+
+
+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(), 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<std::string>& httpHeaders) const
+{
+	return PerformOperation(HttpClientImpl::Operation::GetContent, HttpClientImpl::Method::GET, url, httpHeaders);
+}
+
+void HttpClientImpl::GetUrlSilent(const std::string& url, const std::vector<std::string>& httpHeaders) const
+{
+	PerformOperation(HttpClientImpl::Operation::Silent, HttpClientImpl::Method::GET, url, httpHeaders);
+}
+
+std::string HttpClientImpl::GetUrlRedirect(const std::string& url, const std::vector<std::string>& 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<std::string>(), cookieFile);
+}
+
+void HttpClientImpl::GetUrlSilent(const std::string& url, const std::string& cookieFile) const
+{
+	PerformOperation(HttpClientImpl::Operation::Silent, HttpClientImpl::Method::GET, url, std::vector<std::string>(), 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<std::string>(), cookieFile);
+}
+
+std::string HttpClientImpl::GetUrlContents(const std::string& url, const std::vector<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string> postTokens = Utilities::split(data, '&');
+
+	for (std::vector<std::string>::iterator it = postTokens.begin(); it != postTokens.end(); ++it)
+	{
+		std::vector<std::string> 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<std::string> postTokens = Utilities::split(data, '&');
+
+	for (std::vector<std::string>::iterator it = postTokens.begin(); it != postTokens.end(); ++it)
+	{
+		std::vector<std::string> 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<std::string> postTokens = Utilities::split(data, '&');
+
+	for (std::vector<std::string>::iterator it = postTokens.begin(); it != postTokens.end(); ++it)
+	{
+		std::vector<std::string> 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<std::string> postTokens = Utilities::split(data, '&');
+
+	for (std::vector<std::string>::iterator it = postTokens.begin(); it != postTokens.end(); ++it)
+	{
+		std::vector<std::string> 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<std::string> postTokens = Utilities::split(data, '&');
+
+	for (std::vector<std::string>::iterator it = postTokens.begin(); it != postTokens.end(); ++it)
+	{
+		std::vector<std::string> 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<std::string> postTokens = Utilities::split(data, '&');
+
+	for (std::vector<std::string>::iterator it = postTokens.begin(); it != postTokens.end(); ++it)
+	{
+		std::vector<std::string> 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<std::string> 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

+ 94 - 0
Http/HttpClientImpl.h

@@ -0,0 +1,94 @@
+#ifndef HTTP_HTTPCLIENTIMPL_H
+#define HTTP_HTTPCLIENTIMPL_H
+
+#include <string>
+#include <vector>
+
+
+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<std::string>& httpHeaders) const;
+	void GetUrlSilent(const std::string& url, const std::vector<std::string>& httpHeaders) const;
+	std::string GetUrlRedirect(const std::string& url, const std::vector<std::string>& 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<std::string>& httpHeaders,  const std::string& cookieFile) const;
+	void GetUrlSilent(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& cookieFile) const;
+	std::string GetUrlRedirect(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& cookieFile) const;
+
+	std::string PutUrlContents(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& data) const;
+	void PutUrlSilent(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& data) const;
+	std::string PutUrlRedirect(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& data) const;
+
+	std::string PutUrlContents(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& cookieFile, const std::string& data) const;
+	void PutUrlSilent(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& cookieFile, const std::string& data) const;
+	std::string PutUrlRedirect(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& cookieFile, const std::string& data) const;
+
+	std::string GetUrlPostContents(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& data) const;
+	void GetUrlPostSilent(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& data) const;
+	std::string GetUrlPostRedirect(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& data) const;
+
+	std::string GetUrlPostContents(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& cookieFile, const std::string& data) const;
+	void GetUrlPostSilent(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& cookieFile, const std::string& data) const;
+	std::string GetUrlPostRedirect(const std::string& url, const std::vector<std::string>& 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<std::string> httpHeaders = std::vector<std::string>(), 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<std::string> m_userAgents;
+};
+
+} // namespace Http
+
+#endif // HTTP_HTTPCLIENTIMPL_H

+ 92 - 0
Http/HttpConnection.cpp

@@ -0,0 +1,92 @@
+#include "HttpConnection.h"
+#include <functional>
+
+
+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

+ 49 - 0
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 <array>
+#include <memory>
+#include <string>
+#include <tuple>
+
+
+namespace Http {
+
+class HttpConnection :
+	public std::enable_shared_from_this<HttpConnection>
+{
+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<char, 8192> Buffer;
+
+private:
+	asio::io_service::strand m_strand;
+	asio::ip::tcp::socket m_socket;
+	HttpRequestHandler& m_requestHandler;
+	Buffer m_data;
+	std::tuple<Utilities::Tribool, Buffer::iterator> m_dataState;
+	bool m_dataIncomplete;
+	HttpRequest m_request;
+	HttpRequestParser m_requestParser;
+};
+
+} // namespace Http
+
+#endif // HTTP_HTTPCONNECTION_H

+ 23 - 0
Http/HttpHeader.h

@@ -0,0 +1,23 @@
+#ifndef HTTP_HTTPHEADER_H
+#define HTTP_HTTPHEADER_H
+
+#include <string>
+
+
+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

+ 127 - 0
Http/HttpHeaders.cpp

@@ -0,0 +1,127 @@
+#include "HttpHeaders.h"
+#include <algorithm>
+
+
+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<HttpHeader>::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<HttpHeader>::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

+ 55 - 0
Http/HttpHeaders.h

@@ -0,0 +1,55 @@
+#ifndef HTTP_HTTPHEADERS_H
+#define HTTP_HTTPHEADERS_H
+
+#include "HttpHeader.h"
+#include <string>
+#include <vector>
+
+
+namespace Http {
+
+class HttpHeaders
+{
+public:
+	HttpHeaders();
+	~HttpHeaders();
+
+public:
+	typedef std::vector<HttpHeader>::iterator iterator;
+	typedef std::vector<HttpHeader>::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<HttpHeader> m_headers;
+};
+
+} // namespace Http
+
+#endif // HTTP_HTTPHEADERS_H

+ 31 - 0
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

+ 15 - 0
Http/HttpMimeTypes.h

@@ -0,0 +1,15 @@
+#ifndef HTTP_HTTPMIMETYPES_H
+#define HTTP_HTTPMIMETYPES_H
+
+#include <string>
+
+
+namespace Http {
+namespace MimeTypes {
+
+std::string ExtensionToType(const std::string& extension);
+
+} // namespace MimeTypes
+} // namespace Http
+
+#endif // HTTP_HTTPMIMETYPES_H

+ 244 - 0
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<asio::const_buffer> HttpReply::ToBuffers()
+{
+	std::vector<asio::const_buffer> 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[] =
+	"<html>"
+	"<head><title>Created</title></head>"
+	"<body><h1>201 Created</h1></body>"
+	"</html>";
+const char Accepted[] =
+	"<html>"
+	"<head><title>Accepted</title></head>"
+	"<body><h1>202 Accepted</h1></body>"
+	"</html>";
+const char NoContent[] =
+	"<html>"
+	"<head><title>No Content</title></head>"
+	"<body><h1>204 Content</h1></body>"
+	"</html>";
+const char MultipleChoices[] =
+	"<html>"
+	"<head><title>Multiple Choices</title></head>"
+	"<body><h1>300 Multiple Choices</h1></body>"
+	"</html>";
+const char MovedPermanently[] =
+	"<html>"
+	"<head><title>Moved Permanently</title></head>"
+	"<body><h1>301 Moved Permanently</h1></body>"
+	"</html>";
+const char MovedTemporarily[] =
+	"<html>"
+	"<head><title>Moved Temporarily</title></head>"
+	"<body><h1>302 Moved Temporarily</h1></body>"
+	"</html>";
+const char NotModified[] =
+	"<html>"
+	"<head><title>Not Modified</title></head>"
+	"<body><h1>304 Not Modified</h1></body>"
+	"</html>";
+const char BadRequest[] =
+	"<html>"
+	"<head><title>Bad Request</title></head>"
+	"<body><h1>400 Bad Request</h1></body>"
+	"</html>";
+const char Unauthorized[] =
+	"<html>"
+	"<head><title>Unauthorized</title></head>"
+	"<body><h1>401 Unauthorized</h1></body>"
+	"</html>";
+const char Forbidden[] =
+	"<html>"
+	"<head><title>Forbidden</title></head>"
+	"<body><h1>403 Forbidden</h1></body>"
+	"</html>";
+const char NotFound[] =
+	"<html>"
+	"<head><title>Not Found</title></head>"
+	"<body><h1>404 Not Found</h1></body>"
+	"</html>";
+const char InternalServerError[] =
+	"<html>"
+	"<head><title>Internal Server Error</title></head>"
+	"<body><h1>500 Internal Server Error</h1></body>"
+	"</html>";
+const char NotImplemented[] =
+	"<html>"
+	"<head><title>Not Implemented</title></head>"
+	"<body><h1>501 Not Implemented</h1></body>"
+	"</html>";
+const char BadGateway[] =
+	"<html>"
+	"<head><title>Bad Gateway</title></head>"
+	"<body><h1>502 Bad Gateway</h1></body>"
+	"</html>";
+const char ServiceUnavailable[] =
+	"<html>"
+	"<head><title>Service Unavailable</title></head>"
+	"<body><h1>503 Service Unavailable</h1></body>"
+	"</html>";
+
+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

+ 46 - 0
Http/HttpReply.h

@@ -0,0 +1,46 @@
+#ifndef HTTP_HTTPREPLY_H
+#define HTTP_HTTPREPLY_H
+
+#include "HttpHeaders.h"
+#include "asio.hpp"
+#include <string>
+#include <vector>
+
+
+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<asio::const_buffer> ToBuffers();
+	static HttpReply StockReply(Status::type status);
+};
+
+} // namespace Http
+
+#endif // HTTP_HTTPREPLY_H

+ 21 - 0
Http/HttpRequest.h

@@ -0,0 +1,21 @@
+#ifndef HTTP_HTTPREQUEST_H
+#define HTTP_HTTPREQUEST_H
+
+#include "HttpHeaders.h"
+#include <string>
+
+
+namespace Http {
+
+struct HttpRequest
+{
+	std::string method;
+	std::string uri;
+	int httpVersionMajor;
+	int httpVersionMinor;
+	HttpHeaders headers;
+};
+
+} // namespace Http
+
+#endif // HTTP_HTTPREQUEST_H

+ 138 - 0
Http/HttpRequestHandler.cpp

@@ -0,0 +1,138 @@
+#include "HttpRequestHandler.h"
+#include "HttpMimeTypes.h"
+#include "StringAlgorithm.h"
+#include <fstream>
+#include <sstream>
+
+
+namespace Http {
+
+HttpRequestHandler::HttpRequestHandler(std::function<std::string(const std::string&, const std::vector<HttpPostData>&)> 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<HttpPostData> 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<HttpPostData> 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<HttpPostData>();
+
+	std::vector<HttpPostData> 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<Http::HttpPostData>& 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<char>(value);
+					i += 2;
+				}
+				else
+				{
+					return false;
+				}
+			}
+			else
+			{
+				return false;
+			}
+		}
+		else if (in[i] == '+')
+		{
+			out += ' ';
+		}
+		else
+		{
+			out += in[i];
+		}
+	}
+	return true;
+}
+
+} // namespace Http

+ 35 - 0
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 <functional>
+#include <string>
+#include <vector>
+
+
+namespace Http {
+
+class HttpRequestHandler
+{
+public:
+	explicit HttpRequestHandler(std::function<std::string(const std::string&, const std::vector<HttpPostData>&)> callback);
+
+	HttpRequestHandler(const HttpRequestHandler&) = delete;
+
+	HttpReply HandleRequest(const HttpRequest& request, const std::string& data);
+
+private:
+	std::vector<HttpPostData> GetPostData(const std::string& boundary, const std::string& data) const;
+	std::string HandlePostData(const std::string& uri, const std::vector<HttpPostData>& postData);
+
+	static bool URLDecode(const std::string& in, std::string& out);
+
+private:
+	std::function<std::string(const std::string&, const std::vector<HttpPostData>&)> m_callback;
+};
+
+} // namespace Http
+
+#endif // HTTP_HTTPREQUESTHANDLER_H

+ 304 - 0
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

+ 75 - 0
Http/HttpRequestParser.h

@@ -0,0 +1,75 @@
+#ifndef HTTP_HTTPREQUESTPARSER_H
+#define HTTP_HTTPREQUESTPARSER_H
+
+#include "HttpRequest.h"
+#include "Tribool.h"
+#include <tuple>
+
+
+namespace Http {
+
+struct HttpRequest;
+
+class HttpRequestParser
+{
+public:
+	HttpRequestParser();
+
+	void Reset();
+
+	template <typename InputIterator>
+	std::tuple<Utilities::Tribool, InputIterator> 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

+ 21 - 0
Http/HttpServer.cpp

@@ -0,0 +1,21 @@
+#include "HttpServer.h"
+#include "HttpServerImpl.h"
+
+
+namespace Http {
+
+HttpServer::HttpServer(unsigned short port, std::function<std::string(const std::string&, const std::vector<HttpPostData>&)> callback) :
+	m_pHttpServerImpl(new HttpServerImpl(port, callback))
+{
+}
+
+HttpServer::~HttpServer()
+{
+}
+
+void HttpServer::Wait()
+{
+	m_pHttpServerImpl->Wait();
+}
+
+} // namespace Http

+ 79 - 0
Http/HttpServerImpl.cpp

@@ -0,0 +1,79 @@
+#include "HttpServerImpl.h"
+#include "Logging.h"
+#include <sys/wait.h>
+#include <functional>
+#include <sstream>
+
+
+namespace Http {
+
+HttpServerImpl::HttpServerImpl(unsigned short port, std::function<std::string(const std::string&, const std::vector<HttpPostData>&)> 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<std::thread>([&] { 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

+ 40 - 0
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 <functional>
+#include <memory>
+#include <thread>
+#include <vector>
+
+
+namespace Http {
+
+class HttpServerImpl
+{
+public:
+	HttpServerImpl(unsigned short port, std::function<std::string(const std::string&, const std::vector<HttpPostData>&)> callback);
+	~HttpServerImpl();
+
+	void Wait();
+
+private:
+	void StartAccept();
+	void HandleAccept(const asio::error_code& ec);
+	void HandleStop();
+
+private:
+	std::size_t m_threadPoolSize;
+	std::vector<std::shared_ptr<std::thread> > m_threads;
+	asio::io_service m_ioService;
+	asio::signal_set m_signals;
+	asio::ip::tcp::acceptor m_acceptor;
+	std::shared_ptr<HttpConnection> m_connection;
+	HttpRequestHandler m_requestHandler;
+};
+
+} // namespace Http
+
+#endif // HTTP_HTTPSERVERIMPL_H

+ 1 - 0
Http/Makefile

@@ -0,0 +1 @@
+../Makefile

+ 1 - 0
Makefile

@@ -0,0 +1 @@
+Makefiles/Makefile

+ 6 - 0
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

+ 12 - 0
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

+ 1 - 0
Makefiles

@@ -0,0 +1 @@
+Subproject commit d82c8a204714538d0e9414808388e8c79f20db26

+ 69 - 0
include/HttpClient.h

@@ -0,0 +1,69 @@
+#ifndef HTTPCLIENT_H
+#define HTTPCLIENT_H
+
+#include <memory>
+#include <string>
+#include <vector>
+
+
+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<std::string>& httpHeaders) const;
+	void GetUrlSilent(const std::string& url, const std::vector<std::string>& httpHeaders) const;
+	std::string GetUrlRedirect(const std::string& url, const std::vector<std::string>& 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<std::string>& httpHeaders,  const std::string& cookieFile) const;
+	void GetUrlSilent(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& cookieFile) const;
+	std::string GetUrlRedirect(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& cookieFile) const;
+
+	std::string PutUrlContents(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& data) const;
+	void PutUrlSilent(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& data) const;
+	std::string PutUrlRedirect(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& data) const;
+
+	std::string PutUrlContents(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& cookieFile, const std::string& data) const;
+	void PutUrlSilent(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& cookieFile, const std::string& data) const;
+	std::string PutUrlRedirect(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& cookieFile, const std::string& data) const;
+
+	std::string GetUrlPostContents(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& data) const;
+	void GetUrlPostSilent(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& data) const;
+	std::string GetUrlPostRedirect(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& data) const;
+
+	std::string GetUrlPostContents(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& cookieFile, const std::string& data) const;
+	void GetUrlPostSilent(const std::string& url, const std::vector<std::string>& httpHeaders, const std::string& cookieFile, const std::string& data) const;
+	std::string GetUrlPostRedirect(const std::string& url, const std::vector<std::string>& 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<HttpClientImpl> m_pHttpClientImpl;
+};
+
+} // namespace Http
+
+#endif // HTTPCLIENT_H

+ 22 - 0
include/HttpPostData.h

@@ -0,0 +1,22 @@
+#ifndef HTTP_HTTPPOSTDATA_H
+#define HTTP_HTTPPOSTDATA_H
+
+#include <string>
+
+
+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

+ 28 - 0
include/HttpServer.h

@@ -0,0 +1,28 @@
+#ifndef HTTPSERVER_H
+#define HTTPSERVER_H
+
+#include "HttpPostData.h"
+#include <functional>
+#include <memory>
+#include <vector>
+
+
+namespace Http {
+
+class HttpServerImpl;
+
+class HttpServer
+{
+public:
+	HttpServer(unsigned short port, std::function<std::string(const std::string&, const std::vector<HttpPostData>&)> callback);
+	~HttpServer();
+
+	void Wait();
+
+private:
+	std::unique_ptr<HttpServerImpl> m_pHttpServerImpl;
+};
+
+} // namespace Http
+
+#endif // HTTPSERVER_H