Browse Source

Initial commit

Bart van Strien 6 years ago
commit
8beae2e629

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+.*.swp
+*.o
+*.dll
+*.exe
+*.so
+build

+ 5 - 0
CMakeLists.txt

@@ -0,0 +1,5 @@
+cmake_minimum_required (VERSION 2.6)
+
+project (Https)
+
+add_subdirectory (src)

+ 17 - 0
example/example.lua

@@ -0,0 +1,17 @@
+package.cpath="./?.dll"
+
+local https = require "https"
+
+do
+	local code, body = https.request("https://example.com")
+	assert(code == 200, body)
+end
+
+do
+	local code, body, headers = https.request("http://example.com", {method = "post", headers = {}, body = "cake"})
+	assert(code == 200 and headers, body)
+
+	for i, v in pairs(headers) do
+		print(i, v)
+	end
+end

+ 86 - 0
src/CMakeLists.txt

@@ -0,0 +1,86 @@
+cmake_minimum_required (VERSION 3.0)
+
+### Basic compilation settings
+set (CMAKE_POSITION_INDEPENDENT_CODE TRUE)
+
+configure_file (
+	common/config.h.in
+	common/config.h
+)
+
+include_directories (
+	${CMAKE_CURRENT_SOURCE_DIR}
+	${CMAKE_CURRENT_BINARY_DIR}
+)
+
+### "Libraries"
+add_library (https MODULE
+	lua/main.cpp
+)
+
+add_library (https-common STATIC
+	common/HTTPRequest.cpp
+	common/HTTPSClient.cpp
+	common/PlaintextConnection.cpp
+)
+
+add_library (https-curl STATIC EXCLUDE_FROM_ALL
+	generic/CurlClient.cpp
+)
+
+add_library (https-openssl STATIC EXCLUDE_FROM_ALL
+	generic/OpenSSLConnection.cpp
+)
+
+add_library (https-schannel STATIC EXCLUDE_FROM_ALL
+	windows/SChannelConnection.cpp
+)
+
+add_library (https-nsurl STATIC EXCLUDE_FROM_ALL
+	macos/NSURLClient.mm
+)
+
+### Flags
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+	option (USE_CURL_BACKEND "Use the libcurl backend" ON)
+	option (USE_OPENSSL_BACKEND "Use the openssl backend" ON)
+	option (USE_SCHANNEL_BACKEND "Use the schannel backend (windows-only)" OFF)
+	option (USE_NSURL_BACKEND "Use the NSUrl backend (macos-only)" OFF)
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+	option (USE_CURL_BACKEND "Use the libcurl backend" OFF)
+	option (USE_OPENSSL_BACKEND "Use the openssl backend" OFF)
+	option (USE_SCHANNEL_BACKEND "Use the schannel backend (windows-only)" ON)
+	option (USE_NSURL_BACKEND "Use the NSUrl backend (macos-only)" OFF)
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+	option (USE_CURL_BACKEND "Use the libcurl backend" OFF)
+	option (USE_OPENSSL_BACKEND "Use the openssl backend" OFF)
+	option (USE_SCHANNEL_BACKEND "Use the schannel backend (windows-only)" OFF)
+	option (USE_NSURL_BACKEND "Use the NSUrl backend (macos-only)" ON)
+endif ()
+
+### Dependencies
+target_link_libraries (https https-common)
+
+find_package (Lua 5.1 REQUIRED)
+include_directories (${LUA_INCLUDE_DIR})
+target_link_libraries (https ${LUA_LIBRARIES})
+
+if (USE_CURL_BACKEND)
+	find_package (CURL REQUIRED)
+	include_directories (${CURL_INCLUDE_DIR})
+	target_link_libraries (https https-curl ${CURL_LIBRARIES})
+endif ()
+
+if (USE_OPENSSL_BACKEND)
+	find_package (OpenSSL REQUIRED)
+	include_directories (${OPENSSL_INCLUDE_DIR})
+	target_link_libraries (https https-openssl ${OPENSSL_LIBRARIES})
+endif ()
+
+if (USE_SCHANNEL_BACKEND)
+	target_link_libraries (https https-schannel ws2_32 secur32)
+endif ()
+
+if (USE_NSURL_BACKEND)
+	target_link_libraries (https https-nsurl)
+endif ()

+ 14 - 0
src/common/Connection.h

@@ -0,0 +1,14 @@
+#pragma once
+
+#include <cstdint>
+#include <string>
+
+class Connection
+{
+public:
+	virtual bool connect(const std::string &hostname, uint16_t port) = 0;
+	virtual size_t read(char *buffer, size_t size) = 0;
+	virtual size_t write(const char *buffer, size_t size) = 0;
+	virtual void close() = 0;
+	virtual ~Connection() {};
+};

+ 35 - 0
src/common/ConnectionClient.h

@@ -0,0 +1,35 @@
+#pragma once
+
+#include "HTTPSClient.h"
+#include "HTTPRequest.h"
+#include "Connection.h"
+
+template<typename Connection>
+class ConnectionClient : public HTTPSClient
+{
+public:
+	virtual bool valid() const override;
+	virtual HTTPSClient::Reply request(const HTTPSClient::Request &req) override;
+
+private:
+	static Connection *factory();
+};
+
+template<typename Connection>
+bool ConnectionClient<Connection>::valid() const
+{
+	return Connection::valid();
+}
+
+template<typename Connection>
+Connection *ConnectionClient<Connection>::factory()
+{
+	return new Connection();
+}
+
+template<typename Connection>
+HTTPSClient::Reply ConnectionClient<Connection>::request(const HTTPSClient::Request &req)
+{
+	HTTPRequest request(factory);
+	return request.request(req);
+}

+ 146 - 0
src/common/HTTPRequest.cpp

@@ -0,0 +1,146 @@
+#include <sstream>
+#include <string>
+#include <memory>
+
+#include "HTTPRequest.h"
+#include "PlaintextConnection.h"
+
+HTTPRequest::HTTPRequest(ConnectionFactory factory)
+	: factory(factory)
+{
+}
+
+HTTPSClient::Reply HTTPRequest::request(const HTTPSClient::Request &req)
+{
+	HTTPSClient::Reply reply;
+	reply.responseCode = 400;
+
+	auto info = parseUrl(req.url);
+	if (!info.valid)
+		return reply;
+
+	std::unique_ptr<Connection> conn;
+	if (info.schema == "http")
+		conn.reset(new PlaintextConnection());
+	else if (info.schema == "https")
+		conn.reset(factory());
+	else
+		throw std::runtime_error("Unknown url schema");
+
+	if (!conn->connect(info.hostname, info.port))
+		return reply;
+
+	// Build the request
+	{
+		std::stringstream request;
+		request << (req.method == HTTPSClient::Request::GET ? "GET " : "POST ") << info.query << " HTTP/1.1\r\n";
+
+		for (auto &header : req.headers)
+			request << header.first << ": " << header.second << "\r\n";
+
+		request << "Connection: Close\r\n";
+
+		request << "Host: " << info.hostname << "\r\n";
+
+		if (req.method == HTTPSClient::Request::POST && req.headers.count("Content-Type") == 0)
+			request << "Content-Type: application/x-www-form-urlencoded\r\n";
+
+		if (req.method == HTTPSClient::Request::POST)
+			request << "Content-Length: " << req.postdata.size() << "\r\n";
+
+		request << "\r\n";
+
+		if (req.method == HTTPSClient::Request::POST)
+			request << req.postdata;
+
+		// Send it
+		std::string requestData = request.str();
+		conn->write(requestData.c_str(), requestData.size());
+	}
+
+	// Now receive the reply
+	std::stringstream response;
+	{
+		char buffer[8192];
+
+		while (true)
+		{
+			size_t read = conn->read(buffer, sizeof(buffer));
+			response.write(buffer, read);
+			if (read == 0)
+				break;
+		}
+
+		conn->close();
+	}
+
+	reply.responseCode = 500;
+	// And parse it
+	{
+		std::string protocol;
+		response >> protocol;
+		if (protocol != "HTTP/1.1")
+			return reply;
+
+		response >> reply.responseCode;
+		response.ignore(1, '\n');
+
+		for (std::string line; getline(response, line, '\n') && line != "\r"; )
+		{
+			auto sep = line.find(':');
+			reply.headers[line.substr(0, sep)] = line.substr(sep+1, line.size()-sep-1);
+		}
+
+		auto begin = std::istreambuf_iterator<char>(response);
+		auto end = std::istreambuf_iterator<char>();
+		reply.body = std::string(begin, end);
+	}
+
+	return reply;
+}
+
+HTTPRequest::DissectedURL HTTPRequest::parseUrl(const std::string &url)
+{
+	DissectedURL dis;
+	dis.valid = false;
+
+	// Schema
+	auto schemaStart = 0;
+	auto schemaEnd = url.find("://");
+	dis.schema = url.substr(schemaStart, schemaEnd-schemaStart);
+
+	// Auth+Hostname+Port
+	auto connStart = schemaEnd+3;
+	auto connEnd = url.find('/', connStart);
+	if (connEnd == std::string::npos)
+		connEnd = url.size();
+
+	// TODO: Auth
+	if (url.find("@", connStart, connEnd-connStart) != std::string::npos)
+		return dis;
+
+	// Port
+	auto portStart = url.find(':', connStart);
+	auto portEnd = connEnd;
+	if (portStart == std::string::npos || portStart > portEnd)
+	{
+		dis.port = dis.schema == "http" ? 80 : 443;
+		portStart = portEnd;
+	}
+	else
+		dis.port = std::stoi(url.substr(portStart+1, portEnd-portStart-1));
+
+	// Hostname
+	auto hostnameStart = connStart;
+	auto hostnameEnd = portStart;
+	dis.hostname = url.substr(hostnameStart, hostnameEnd-hostnameStart);
+
+	// And the query
+	dis.query = url.substr(connEnd);
+	if (dis.query.size() == 0)
+		dis.query = "/";
+
+	dis.valid = true;
+	
+	return dis;
+}

+ 30 - 0
src/common/HTTPRequest.h

@@ -0,0 +1,30 @@
+#pragma once
+
+#include <functional>
+
+#include "HTTPSClient.h"
+#include "Connection.h"
+
+class HTTPRequest
+{
+public:
+	typedef std::function<Connection *()> ConnectionFactory;
+	HTTPRequest(ConnectionFactory factory);
+
+	HTTPSClient::Reply request(const HTTPSClient::Request &req);
+
+private:
+	ConnectionFactory factory;
+
+	struct DissectedURL
+	{
+		bool valid;
+		std::string schema;
+		std::string hostname;
+		uint16_t port;
+		std::string query;
+		// TODO: Auth?
+	};
+
+	DissectedURL parseUrl(const std::string &url);
+};

+ 36 - 0
src/common/HTTPSClient.cpp

@@ -0,0 +1,36 @@
+#include <algorithm>
+
+#include "HTTPSClient.h"
+
+// This may not be the order you expect, as shorter strings always compare less,
+// but it's sufficient for our map
+bool HTTPSClient::ci_string_less::operator()(const std::string &lhs, const std::string &rhs) const
+{
+	const size_t lhs_size = lhs.size();
+	const size_t rhs_size = rhs.size();
+	const size_t steps = std::min(lhs_size, rhs_size);
+
+	if (lhs_size < rhs_size)
+		return true;
+	else if (lhs_size > rhs_size)
+		return false;
+
+	for (size_t i = 0; i < steps; ++i)
+	{
+		char l = std::tolower(lhs[i]);
+		char r = std::tolower(rhs[i]);
+		if (l < r)
+			return true;
+		else if (l > r)
+			return false;
+	}
+
+	return false;
+}
+
+HTTPSClient::Request::Request(const std::string &url)
+	: url(url)
+	, method(GET)
+{
+}
+

+ 40 - 0
src/common/HTTPSClient.h

@@ -0,0 +1,40 @@
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <map>
+
+class HTTPSClient
+{
+public:
+	struct ci_string_less
+	{
+		bool operator()(const std::string &lhs, const std::string &rhs) const;
+	};
+	using header_map = std::map<std::string, std::string, ci_string_less>;
+
+	struct Request
+	{
+		Request(const std::string &url);
+
+		header_map headers;
+		std::string url;
+		std::string postdata;
+
+		enum Method
+		{
+			GET,
+			POST,
+		} method;
+	};
+
+	struct Reply
+	{
+		header_map headers;
+		std::string body;
+		int responseCode;
+	};
+
+	virtual bool valid() const = 0;
+	virtual Reply request(const Request &req) = 0;
+};

+ 100 - 0
src/common/PlaintextConnection.cpp

@@ -0,0 +1,100 @@
+#include <cstring>
+#ifndef PLATFORM_WINDOWS
+#	include <netdb.h>
+#	include <unistd.h>
+#	include <sys/types.h>
+#	include <sys/socket.h>
+#else
+#	include <winsock2.h>
+#	include <ws2tcpip.h>
+#	undef min
+#	undef max
+#endif // PLATFORM_WINDOWS
+
+#include "PlaintextConnection.h"
+
+#ifdef PLATFORM_WINDOWS
+	static void close(int fd)
+	{
+		closesocket(fd);
+	}
+#endif // PLATFORM_WINDOWS
+
+PlaintextConnection::PlaintextConnection()
+	: fd(-1)
+{
+#ifdef PLATFORM_WINDOWS
+	static bool wsaInit = false;
+	if (!wsaInit)
+	{
+		WSADATA data;
+		WSAStartup(MAKEWORD(2, 2), &data);
+	}
+#endif
+}
+
+PlaintextConnection::~PlaintextConnection()
+{
+	if (fd != -1)
+		::close(fd);
+}
+
+bool PlaintextConnection::connect(const std::string &hostname, uint16_t port)
+{
+	addrinfo hints;
+	std::memset(&hints, 0, sizeof(hints));
+	hints.ai_flags = hints.ai_protocol = 0;
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+
+	addrinfo *addrs = nullptr;
+	std::string portString = std::to_string(port);
+	getaddrinfo(hostname.c_str(), portString.c_str(), &hints, &addrs);
+
+	// Try all addresses returned
+	bool connected = false;
+	for (addrinfo *addr = addrs; !connected && addr; addr = addr->ai_next)
+	{
+		fd = socket(addr->ai_family, SOCK_STREAM, 0);
+		connected = ::connect(fd, addr->ai_addr, addr->ai_addrlen) == 0;
+		if (!connected)
+			::close(fd);
+	}
+
+	freeaddrinfo(addrs);
+
+	if (!connected)
+	{
+		fd = -1;
+		return false;
+	}
+
+	return true;
+}
+
+size_t PlaintextConnection::read(char *buffer, size_t size)
+{
+	ssize_t read = ::recv(fd, buffer, size, 0);
+	if (read < 0)
+		read = 0;
+	return read;
+}
+
+size_t PlaintextConnection::write(const char *buffer, size_t size)
+{
+	ssize_t written = ::send(fd, buffer, size, 0);
+	if (written < 0)
+		written = 0;
+	return written;
+}
+
+void PlaintextConnection::close()
+{
+	::close(fd);
+	fd = -1;
+}
+
+int PlaintextConnection::getFd() const
+{
+	return fd;
+}

+ 19 - 0
src/common/PlaintextConnection.h

@@ -0,0 +1,19 @@
+#pragma once
+
+#include "Connection.h"
+
+class PlaintextConnection : public Connection
+{
+public:
+	PlaintextConnection();
+	virtual bool connect(const std::string &hostname, uint16_t port);
+	virtual size_t read(char *buffer, size_t size);
+	virtual size_t write(const char *buffer, size_t size);
+	virtual void close();
+	virtual ~PlaintextConnection();
+
+	int getFd() const;
+
+private:
+	int fd;
+};

+ 4 - 0
src/common/config.h.in

@@ -0,0 +1,4 @@
+#cmakedefine USE_CURL_BACKEND
+#cmakedefine USE_OPENSSL_BACKEND
+#cmakedefine USE_SCHANNEL_BACKEND
+#cmakedefine USE_NSURL_BACKEND

+ 122 - 0
src/generic/CurlClient.cpp

@@ -0,0 +1,122 @@
+#include <dlfcn.h>
+#include <stdexcept>
+#include <sstream>
+#include <vector>
+
+#include "CurlClient.h"
+
+CurlClient::Curl::Curl()
+{
+	void *handle = dlopen("libcurl.so", RTLD_LAZY);
+	if (!handle)
+	{
+		loaded = false;
+		return;
+	}
+
+	void (*global_init)() = (void(*)()) dlsym(handle, "curl_global_init");
+	easy_init = (CURL*(*)()) dlsym(handle, "curl_easy_init");
+	easy_cleanup = (void(*)(CURL*)) dlsym(handle, "curl_easy_cleanup");
+	easy_setopt = (CURLcode(*)(CURL*,CURLoption,...)) dlsym(handle, "curl_easy_setopt");
+	easy_perform = (CURLcode(*)(CURL*)) dlsym(handle, "curl_easy_perform");
+	easy_getinfo = (CURLcode(*)(CURL*,CURLINFO,...)) dlsym(handle, "curl_easy_getinfo");
+	slist_append = (curl_slist*(*)(curl_slist*,const char*)) dlsym(handle, "curl_slist_append");
+	slist_free_all = (void(*)(curl_slist*)) dlsym(handle, "curl_slist_free_all");
+
+	loaded = (global_init && easy_init && easy_cleanup && easy_setopt && easy_perform && easy_getinfo && slist_append && slist_free_all);
+
+	if (!loaded)
+		return;
+
+	global_init();
+}
+
+static size_t stringstreamWriter(char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+	std::stringstream *ss = (std::stringstream*) userdata;
+	size_t count = size*nmemb;
+	ss->write(ptr, count);
+	return count;
+}
+
+static size_t headerWriter(char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+	std::map<std::string, std::string> &headers = *((std::map<std::string,std::string>*) userdata);
+	size_t count = size*nmemb;
+	std::string line(ptr, count);
+	size_t split = line.find(':');
+	size_t newline = line.find('\r');
+	if (newline == std::string::npos)
+		newline = line.size();
+
+	if (split != std::string::npos)
+		headers[line.substr(0, split)] = line.substr(split+1, newline-split-1);
+	return count;
+}
+
+bool CurlClient::valid() const
+{
+	return curl.loaded;
+}
+
+HTTPSClient::Reply CurlClient::request(const HTTPSClient::Request &req)
+{
+	Reply reply;
+	reply.responseCode = 400;
+
+	CURL *handle = curl.easy_init();
+	if (!handle)
+		throw std::runtime_error("Could not create curl request");
+
+	curl.easy_setopt(handle, CURLOPT_URL, req.url.c_str());
+	curl.easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L);
+
+	if (req.method == Request::POST)
+	{
+		curl.easy_setopt(handle, CURLOPT_POST, 1L);
+		curl.easy_setopt(handle, CURLOPT_POSTFIELDS, req.postdata.c_str());
+		curl.easy_setopt(handle, CURLOPT_POSTFIELDSIZE, req.postdata.size());
+	}
+
+	// Curl doesn't copy memory, keep the strings around
+	std::vector<std::string> lines;
+	for (auto &header : req.headers)
+	{
+		std::stringstream line;
+		line << header.first << ": " << header.second;
+		lines.push_back(line.str());
+	}
+
+	curl_slist *sendHeaders = nullptr;
+	for (auto &line : lines)
+		sendHeaders = curl.slist_append(sendHeaders, line.c_str());
+
+	if (sendHeaders)
+		curl.easy_setopt(handle, CURLOPT_HTTPHEADER, sendHeaders);
+
+	std::stringstream body;
+
+	curl.easy_setopt(handle, CURLOPT_WRITEFUNCTION, stringstreamWriter);
+	curl.easy_setopt(handle, CURLOPT_WRITEDATA, &body);
+
+	curl.easy_setopt(handle, CURLOPT_HEADERFUNCTION, headerWriter);
+	curl.easy_setopt(handle, CURLOPT_HEADERDATA, &reply.headers);
+
+	curl.easy_perform(handle);
+
+	if (sendHeaders)
+		curl.slist_free_all(sendHeaders);
+
+	{
+		long responseCode;
+		curl.easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &responseCode);
+		reply.responseCode = (int) responseCode;
+	}
+
+	reply.body = body.str();
+
+	curl.easy_cleanup(handle);
+	return reply;
+}
+
+CurlClient::Curl CurlClient::curl;

+ 28 - 0
src/generic/CurlClient.h

@@ -0,0 +1,28 @@
+#pragma once
+
+#include <curl/curl.h>
+
+#include "common/HTTPSClient.h"
+
+class CurlClient : public HTTPSClient
+{
+public:
+	virtual bool valid() const override;
+	virtual HTTPSClient::Reply request(const HTTPSClient::Request &req) override;
+
+private:
+	static struct Curl
+	{
+		Curl();
+		bool loaded;
+
+		CURL *(*easy_init)();
+		void (*easy_cleanup)(CURL *handle);
+		CURLcode (*easy_setopt)(CURL *handle, CURLoption option, ...);
+		CURLcode (*easy_perform)(CURL *easy_handle);
+		CURLcode (*easy_getinfo)(CURL *curl, CURLINFO info, ...);
+
+		curl_slist *(*slist_append)(curl_slist *list, const char *string);
+		void (*slist_free_all)(curl_slist *list);
+	} curl;
+};

+ 144 - 0
src/generic/OpenSSLConnection.cpp

@@ -0,0 +1,144 @@
+#include <dlfcn.h>
+
+#include "OpenSSLConnection.h"
+
+// Not present in openssl 1.1 headers
+#define SSL_CTRL_OPTIONS 32
+
+template <class T>
+static inline bool loadSymbol(T &var, void *handle, const char *name)
+{
+	var = reinterpret_cast<T>(dlsym(handle, name));
+	return var != nullptr;
+}
+
+OpenSSLConnection::SSLFuncs::SSLFuncs()
+{
+	valid = false;
+
+	// Try OpenSSL 1.1
+	void *sslhandle = dlopen("libssl.so.1.1", RTLD_LAZY);
+	void *cryptohandle = dlopen("libcrypto.so.1.1", RTLD_LAZY);
+	// Try OpenSSL 1.0
+	if (!sslhandle || !cryptohandle)
+	{
+		sslhandle = dlopen("libssl.so.1.0.0", RTLD_LAZY);
+		cryptohandle = dlopen("libcrypto.so.1.0.0", RTLD_LAZY);
+	}
+	// Try OpenSSL without version
+	if (!sslhandle || !cryptohandle)
+	{
+		sslhandle = dlopen("libssl.so", RTLD_LAZY);
+		cryptohandle = dlopen("libcrypto.so", RTLD_LAZY);
+	}
+	// Give up
+	if (!sslhandle || !cryptohandle)
+		return;
+
+	valid = true;
+	valid = valid && (loadSymbol(library_init, sslhandle, "SSL_library_init") ||
+			loadSymbol(init_ssl, sslhandle, "OPENSSL_init_ssl"));
+
+	valid = valid && loadSymbol(CTX_new, sslhandle, "SSL_CTX_new");
+	valid = valid && loadSymbol(CTX_ctrl, sslhandle, "SSL_CTX_ctrl");
+	valid = valid && loadSymbol(CTX_set_verify, sslhandle, "SSL_CTX_set_verify");
+	valid = valid && loadSymbol(CTX_set_default_verify_paths, sslhandle, "SSL_CTX_set_default_verify_paths");
+	valid = valid && loadSymbol(CTX_free, sslhandle, "SSL_CTX_free");
+
+	valid = valid && loadSymbol(SSL_new, sslhandle, "SSL_new");
+	valid = valid && loadSymbol(SSL_free, sslhandle, "SSL_free");
+	valid = valid && loadSymbol(set_fd, sslhandle, "SSL_set_fd");
+	valid = valid && loadSymbol(connect, sslhandle, "SSL_connect");
+	valid = valid && loadSymbol(read, sslhandle, "SSL_read");
+	valid = valid && loadSymbol(write, sslhandle, "SSL_write");
+	valid = valid && loadSymbol(shutdown, sslhandle, "SSL_shutdown");
+	valid = valid && loadSymbol(get_verify_result, sslhandle, "SSL_get_verify_result");
+	valid = valid && loadSymbol(get_peer_certificate, sslhandle, "SSL_get_peer_certificate");
+
+	valid = valid && (loadSymbol(SSLv23_method, sslhandle, "SSLv23_method") ||
+			loadSymbol(SSLv23_method, sslhandle, "TLS_method"));
+
+	valid = valid && loadSymbol(check_host, cryptohandle, "X509_check_host");
+
+	if (library_init)
+		library_init();
+	else if(init_ssl)
+		init_ssl(0, nullptr);
+	// else not valid
+}
+
+bool OpenSSLConnection::valid()
+{
+	return ssl.valid;
+}
+
+OpenSSLConnection::OpenSSLConnection()
+	: conn(nullptr)
+{
+	context = ssl.CTX_new(ssl.SSLv23_method());
+	if (!context)
+		return;
+
+	ssl.CTX_ctrl(context, SSL_CTRL_OPTIONS, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3, nullptr);
+	ssl.CTX_set_verify(context, SSL_VERIFY_PEER, nullptr);
+	ssl.CTX_set_default_verify_paths(context);
+}
+
+OpenSSLConnection::~OpenSSLConnection()
+{
+	if (conn)
+		ssl.SSL_free(conn);
+
+	if (context)
+		ssl.CTX_free(context);
+}
+
+bool OpenSSLConnection::connect(const std::string &hostname, uint16_t port)
+{
+	if (!context)
+		return false;
+
+	if (!socket.connect(hostname, port))
+		return false;
+
+	conn = ssl.SSL_new(context);
+	if (!conn)
+	{
+		socket.close();
+		return false;
+	}
+
+	ssl.set_fd(conn, socket.getFd());
+	if (ssl.connect(conn) != 1 || ssl.get_verify_result(conn) != X509_V_OK)
+	{
+		socket.close();
+		return false;
+	}
+
+	X509 *cert = ssl.get_peer_certificate(conn);
+	if (ssl.check_host(cert, hostname.c_str(), hostname.size(), 0, nullptr) != 1)
+	{
+		close();
+		return false;
+	}
+
+	return true;
+}
+
+size_t OpenSSLConnection::read(char *buffer, size_t size)
+{
+	return ssl.read(conn, buffer, (int) size);
+}
+
+size_t OpenSSLConnection::write(const char *buffer, size_t size)
+{
+	return ssl.write(conn, buffer, (int) size);
+}
+
+void OpenSSLConnection::close()
+{
+	ssl.shutdown(conn);
+	socket.close();
+}
+
+OpenSSLConnection::SSLFuncs OpenSSLConnection::ssl;

+ 54 - 0
src/generic/OpenSSLConnection.h

@@ -0,0 +1,54 @@
+#pragma once
+
+#include <openssl/ssl.h>
+
+#include "common/Connection.h"
+#include "common/PlaintextConnection.h"
+
+class OpenSSLConnection : public Connection
+{
+public:
+	OpenSSLConnection();
+	virtual bool connect(const std::string &hostname, uint16_t port) override;
+	virtual size_t read(char *buffer, size_t size) override;
+	virtual size_t write(const char *buffer, size_t size) override;
+	virtual void close() override;
+	virtual ~OpenSSLConnection();
+
+	static bool valid();
+
+private:
+	PlaintextConnection socket;
+	SSL_CTX *context;
+	SSL *conn;
+
+	struct SSLFuncs
+	{
+		SSLFuncs();
+		bool valid;
+
+		int (*library_init)();
+		int (*init_ssl)(uint64_t opts, const void *settings);
+
+		SSL_CTX *(*CTX_new)(const SSL_METHOD *method);
+		long (*CTX_ctrl)(SSL_CTX *ctx, int cmd, long larg, void *parg);
+		void (*CTX_set_verify)(SSL_CTX *ctx, int mode, void *verify_callback);
+		int (*CTX_set_default_verify_paths)(SSL_CTX *ctx);
+		void (*CTX_free)(SSL_CTX *ctx);
+
+		SSL *(*SSL_new)(SSL_CTX *ctx);
+		void (*SSL_free)(SSL *ctx);
+		int (*set_fd)(SSL *ssl, int fd);
+		int (*connect)(SSL *ssl);
+		int (*read)(SSL *ssl, void *buf, int num);
+		int (*write)(SSL *ssl, const void *buf, int num);
+		int (*shutdown)(SSL *ssl);
+		long (*get_verify_result)(const SSL *ssl);
+		X509 *(*get_peer_certificate)(const SSL *ssl);
+
+		const SSL_METHOD *(*SSLv23_method)();
+
+		int (*check_host)(X509 *cert, const char *name, size_t namelen, unsigned int flags, char **peername);
+	};
+	static SSLFuncs ssl;
+};

+ 179 - 0
src/lua/main.cpp

@@ -0,0 +1,179 @@
+#include <lua.hpp>
+
+// Sorry for the ifdef soup ahead
+#include "common/config.h"
+#include "common/HTTPSClient.h"
+#include "common/ConnectionClient.h"
+#ifdef USE_CURL_BACKEND
+#	include "generic/CurlClient.h"
+#endif
+#ifdef USE_OPENSSL_BACKEND
+#	include "generic/OpenSSLConnection.h"
+#endif
+#ifdef USE_SCHANNEL_BACKEND
+#	include "windows/SChannelConnection.h"
+#endif
+#ifdef USE_NSURL_BACKEND
+#	import <Foundation/Foundation.h>
+#	include "macos/NSURLClient.h"
+#endif
+
+#ifdef USE_CURL_BACKEND
+	static CurlClient curlclient;
+#endif
+#ifdef USE_OPENSSL_BACKEND
+	static ConnectionClient<OpenSSLConnection> opensslclient;
+#endif
+#ifdef USE_SCHANNEL_BACKEND
+	static ConnectionClient<SChannelConnection> schannelclient;
+#endif
+#ifdef USE_NSURL_BACKEND
+	static NSURLClient nsurlclient;
+#endif
+
+static HTTPSClient *clients[] = {
+#ifdef USE_CURL_BACKEND
+	&curlclient,
+#endif
+#ifdef USE_OPENSSL_BACKEND
+	&opensslclient,
+#endif
+#ifdef USE_SCHANNEL_BACKEND
+	&schannelclient,
+#endif
+#ifdef USE_NSURL_BACKEND
+	&nsurlclient,
+#endif
+	nullptr,
+};
+
+static std::string w_checkstring(lua_State *L, int idx)
+{
+	size_t len;
+	const char *str = luaL_checklstring(L, idx, &len);
+	return std::string(str, len);
+}
+
+static void w_pushstring(lua_State *L, const std::string &str)
+{
+	lua_pushlstring(L, str.data(), str.size());
+}
+
+static void w_readheaders(lua_State *L, int idx, HTTPSClient::header_map &headers)
+{
+	if (idx < 0)
+		idx += lua_gettop(L) + 1;
+
+	lua_pushnil(L);
+	while (lua_next(L, idx))
+	{
+		auto header = w_checkstring(L, -2);
+		headers[header] = w_checkstring(L, -1);
+		lua_pop(L, 1);
+	}
+	lua_pop(L, 1);
+}
+
+static HTTPSClient::Request::Method w_optmethod(lua_State *L, int idx, HTTPSClient::Request::Method defaultMethod)
+{
+	if (lua_isnoneornil(L, idx))
+		return defaultMethod;
+
+	auto str = w_checkstring(L, idx);
+	if (str == "get")
+		return HTTPSClient::Request::GET;
+	else if (str == "post")
+		return HTTPSClient::Request::POST;
+	else
+		luaL_argerror(L, idx, "expected one of \"get\" or \"set\"");
+
+	return defaultMethod;
+}
+
+static int w_request(lua_State *L)
+{
+	auto url = w_checkstring(L, 1);
+	HTTPSClient::Request req(url);
+
+	bool foundClient = false;
+	bool advanced = false;
+
+	if (lua_istable(L, 2))
+	{
+		advanced = true;
+
+		HTTPSClient::Request::Method defaultMethod = HTTPSClient::Request::GET;
+
+		lua_getfield(L, 2, "data");
+		if (!lua_isnoneornil(L, -1))
+		{
+			req.postdata = w_checkstring(L, -1);
+			defaultMethod = HTTPSClient::Request::POST;
+		}
+		lua_pop(L, 1);
+
+		lua_getfield(L, 2, "method");
+		req.method = w_optmethod(L, -1, defaultMethod);
+		lua_pop(L, 1);
+
+		lua_getfield(L, 2, "headers");
+		if (!lua_isnoneornil(L, -1))
+			w_readheaders(L, -1, req.headers);
+		lua_pop(L, 1);
+	}
+
+#if defined(USE_NSURL_BACKEND) && defined(__APPLE__)
+	@autoreleasepool
+#elif defined(USE_NSURL_BACKEND) && defined(GNUSTEP)
+	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+#endif
+	{
+		for (size_t i = 0; clients[i]; ++i)
+		{
+			HTTPSClient &client = *clients[i];
+			if (!client.valid())
+				continue;
+
+			auto reply = client.request(req);
+			lua_pushinteger(L, reply.responseCode);
+			w_pushstring(L, reply.body);
+
+			if (advanced)
+			{
+				lua_newtable(L);
+				for (auto header : reply.headers)
+				{
+					w_pushstring(L, header.first);
+					w_pushstring(L, header.second);
+					lua_settable(L, -3);
+				}
+			}
+
+			foundClient = true;
+			break;
+		}
+
+		if (!foundClient)
+		{
+			lua_pushnil(L);
+			lua_pushstring(L, "No applicable implementation found");
+			if (advanced)
+				lua_pushnil(L);
+		}
+	}
+#if defined(USE_NSURL_BACKEND) && defined(GNUSTEP)
+	[pool release];
+#endif
+
+	return advanced ? 3 : 2;
+}
+
+extern "C" int luaopen_https(lua_State *L)
+{
+	lua_newtable(L);
+
+	lua_pushcfunction(L, w_request);
+	lua_setfield(L, -2, "request");
+
+	return 1;
+}

+ 10 - 0
src/macos/NSURLClient.h

@@ -0,0 +1,10 @@
+#pragma once
+
+#include "common/HTTPSClient.h"
+
+class NSURLClient : public HTTPSClient
+{
+public:
+	virtual bool valid() const override;
+	virtual HTTPSClient::Reply request(const HTTPSClient::Request &req) override;
+};

+ 69 - 0
src/macos/NSURLClient.mm

@@ -0,0 +1,69 @@
+#import <Foundation/Foundation.h>
+
+#include "NSURLClient.h"
+
+bool NSURLClient::valid() const
+{
+	return true;
+}
+
+static NSString *toNSString(const std::string &str)
+{
+	return [NSString stringWithUTF8String: str.c_str()];
+}
+
+static std::string toCppString(NSData *data)
+{
+	return std::string((const char*) [data bytes], (size_t) [data length]);
+}
+
+static std::string toCppString(NSString *str)
+{
+	return std::string([str UTF8String]);
+}
+
+HTTPSClient::Reply NSURLClient::request(const HTTPSClient::Request &req)
+{
+	NSURL *url = [NSURL URLWithString: toNSString(req.url)];
+	NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL: url];
+
+	switch(req.method)
+	{
+	case Request::GET:
+		[request setHTTPMethod: @"GET"];
+		break;
+	case Request::POST:
+		[request setHTTPMethod: @"POST"];
+		[request setHTTPBody: [NSData dataWithBytesNoCopy: (void*) req.postdata.data() length: req.postdata.size()]];
+		break;
+	}
+
+	for (auto &header : req.headers)
+		[request setValue: toNSString(header.second) forHTTPHeaderField: toNSString(header.first)];
+
+	NSHTTPURLResponse *response = nil;
+	NSError *error = nil;
+	NSData *body = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error];
+
+	HTTPSClient::Reply reply;
+	reply.responseCode = 400;
+
+	if (body)
+	{
+		reply.body = toCppString(body);
+	}
+
+	if (response)
+	{
+		reply.responseCode = [response statusCode];
+
+		NSDictionary *headers = [response allHeaderFields];
+		for (NSString *key in headers)
+		{
+			NSString *value = [headers objectForKey: key];
+			reply.headers[toCppString(key)] = toCppString(value);
+		}
+	}
+
+	return reply;
+}

+ 398 - 0
src/windows/SChannelConnection.cpp

@@ -0,0 +1,398 @@
+#define SECURITY_WIN32
+#include <windows.h>
+#include <security.h>
+#include <schnlsp.h>
+#include <assert.h>
+
+#include "SChannelConnection.h"
+
+#ifndef SCH_USE_STRONG_CRYPTO
+#	define SCH_USE_STRONG_CRYPTO 0x00400000
+#endif
+#ifndef SP_PROT_TLS1_1_CLIENT
+#	define SP_PROT_TLS1_1_CLIENT 0x00000200
+#endif
+#ifndef SP_PROT_TLS1_2_CLIENT
+#	define SP_PROT_TLS1_2_CLIENT 0x00000800
+#endif
+
+#ifdef DEBUG_SCHANNEL
+#include <iostream>
+std::ostream &debug = std::cout;
+#else
+struct Debug
+{
+	template<typename T>
+	Debug &operator<<(const T&) { return *this; }
+} debug;
+#endif
+
+static void enqueue(std::vector<char> &buffer, char *data, size_t size)
+{
+	size_t oldSize = buffer.size();
+	buffer.resize(oldSize + size);
+	memcpy(&buffer[oldSize], data, size);
+}
+
+static void enqueue_prepend(std::vector<char> &buffer, char *data, size_t size)
+{
+	size_t oldSize = buffer.size();
+	buffer.resize(oldSize + size);
+	memmove(&buffer[size], &buffer[0], oldSize);
+	memcpy(&buffer[0], data, size);
+}
+
+static size_t dequeue(std::vector<char> &buffer, char *data, size_t size)
+{
+	size = std::min(size, buffer.size());
+	size_t remaining = buffer.size() - size;
+
+	memcpy(data, &buffer[0], size);
+	memmove(&buffer[0], &buffer[size], remaining);
+	buffer.resize(remaining);
+
+	return size;
+}
+
+SChannelConnection::SChannelConnection()
+	: context(nullptr)
+{
+}
+
+SChannelConnection::~SChannelConnection()
+{
+	// TODO?
+	if (CtxtHandle *context = static_cast<CtxtHandle*>(this->context))
+	{
+		DeleteSecurityContext(context);
+		delete context;
+	}
+}
+
+bool SChannelConnection::connect(const std::string &hostname, uint16_t port)
+{
+	debug << "Trying to connect to " << hostname << ":" << port << "\n";
+	if (!socket.connect(hostname, port))
+		return false;
+	debug << "Connected\n";
+
+	SCHANNEL_CRED cred;
+	memset(&cred, 0, sizeof(cred));
+
+	cred.dwVersion = SCHANNEL_CRED_VERSION;
+	cred.grbitEnabledProtocols = SP_PROT_TLS1_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT;
+	cred.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION | SCH_CRED_NO_DEFAULT_CREDS | SCH_USE_STRONG_CRYPTO | SCH_CRED_REVOCATION_CHECK_CHAIN;
+
+	CredHandle credHandle;
+	if (AcquireCredentialsHandle(nullptr, (char*) UNISP_NAME, SECPKG_CRED_OUTBOUND, nullptr, &cred, nullptr, nullptr, &credHandle, nullptr) != SEC_E_OK)
+	{
+		debug << "Failed to acquire handle\n";
+		socket.close();
+		return false;
+	}
+	debug << "Acquired handle\n";
+
+	CtxtHandle *context = new CtxtHandle;
+	CtxtHandle *inHandle = nullptr, *outHandle = context;
+
+	SecBufferDesc inputBuffer, outputBuffer;
+	inputBuffer.ulVersion = outputBuffer.ulVersion = SECBUFFER_VERSION;
+	inputBuffer.cBuffers = outputBuffer.cBuffers = 0;
+	inputBuffer.pBuffers = outputBuffer.pBuffers = nullptr;
+
+	ULONG contextAttr;
+
+	static constexpr size_t bufferSize = 8192;
+	bool done = false, success = false, contextCreated = false;
+	char *recvBuffer = nullptr;
+	char *sendBuffer = new char[2*bufferSize];
+
+	SecBuffer recvSecBuffer, sendSecBuffer;
+	recvSecBuffer.BufferType = sendSecBuffer.BufferType = SECBUFFER_TOKEN;
+	sendSecBuffer.cbBuffer = bufferSize;
+	sendSecBuffer.pvBuffer = sendBuffer;
+
+	outputBuffer.cBuffers = 1;
+	outputBuffer.pBuffers = &sendSecBuffer;
+
+	do
+	{
+		bool recvData = false;
+		auto ret = InitializeSecurityContext(&credHandle, inHandle, (char*) hostname.c_str(), ISC_REQ_STREAM, 0, 0, &inputBuffer, 0, outHandle, &outputBuffer, &contextAttr, nullptr);
+		switch (ret)
+		{
+		case SEC_I_COMPLETE_NEEDED:
+		case SEC_I_COMPLETE_AND_CONTINUE:
+			if (CompleteAuthToken(outHandle, &outputBuffer) != SEC_E_OK)
+				done = true;
+			else if (ret == SEC_I_COMPLETE_NEEDED)
+				success = done = true;
+			break;
+		case SEC_I_CONTINUE_NEEDED:
+			recvData = true;
+			break;
+		case SEC_E_INCOMPLETE_CREDENTIALS:
+			done = true;
+			break;
+		case SEC_E_INCOMPLETE_MESSAGE:
+			recvData = true;
+			break;
+		case SEC_E_OK:
+			success = done = true;
+			break;
+		default:
+			done = true;
+			// TODO: error
+			break;
+		}
+
+		if (!done)
+			contextCreated = true;
+
+		inHandle = context;
+		outHandle = nullptr;
+
+		debug << "Initialize done, with " << outputBuffer.cBuffers << " output buffers and status " << ret << "\n";
+		for (unsigned int i = 0; i < outputBuffer.cBuffers && !success; ++i)
+		{
+			auto &buffer = outputBuffer.pBuffers[i];
+			debug << "\tBuffer of size: " << buffer.cbBuffer << "\n";
+			if (buffer.cbBuffer > 0 && buffer.BufferType == SECBUFFER_TOKEN)
+			{
+				socket.write((const char*) buffer.pvBuffer, buffer.cbBuffer);
+			}
+			else
+				debug << "Got buffer with type " << buffer.BufferType << "\n";
+
+			if (buffer.pvBuffer == sendBuffer)
+			{
+				memset(sendBuffer, 0, bufferSize);
+				buffer.cbBuffer = bufferSize;
+			}
+			//FreeContextBuffer(&buffer);
+		}
+
+		if (recvData)
+		{
+			debug << "Receiving data\n";
+			if (!recvBuffer)
+				recvBuffer = new char[bufferSize];
+
+			recvSecBuffer.cbBuffer = socket.read(recvBuffer, bufferSize);
+			recvSecBuffer.pvBuffer = recvBuffer;
+
+			inputBuffer.cBuffers = 1;
+			inputBuffer.pBuffers = &recvSecBuffer;
+		}
+		else
+		{
+			inputBuffer.cBuffers = 0;
+			inputBuffer.pBuffers = nullptr;
+		}
+
+		// TODO: A bunch of frees?
+	} while (!done);
+
+	delete[] sendBuffer;
+	delete[] recvBuffer;
+
+	debug << "Done!\n";
+	// TODO: Check resulting context attributes
+	if (success)
+	{
+		this->context = static_cast<void*>(context);
+	}
+	else if (contextCreated)
+	{
+		DeleteSecurityContext(context);
+		delete context;
+	}
+
+	return success;
+}
+
+size_t SChannelConnection::read(char *buffer, size_t size)
+{
+	if (decRecvBuffer.size() > 0)
+	{
+		size = dequeue(decRecvBuffer, buffer, size);
+		debug << "Read " << size << " bytes of previously decoded data\n";
+		return size;
+	}
+	else if (encRecvBuffer.size() > 0)
+	{
+		size = dequeue(encRecvBuffer, buffer, size);
+		debug << "Read " << size << " bytes of extra data\n";
+	}
+	else
+	{
+		size = socket.read(buffer, size);
+		debug << "Received " << size << " bytes of data\n";
+	}
+
+	return decrypt(buffer, size);
+}
+
+size_t SChannelConnection::decrypt(char *buffer, size_t size, bool recurse)
+{
+	SecBuffer secBuffers[4];
+	secBuffers[0].cbBuffer = size;
+	secBuffers[0].BufferType = SECBUFFER_DATA;
+	secBuffers[0].pvBuffer = buffer;
+
+	for (size_t i = 1; i < 4; ++i)
+		secBuffers[i].BufferType = SECBUFFER_EMPTY;
+
+	SecBufferDesc secBufferDesc;
+	secBufferDesc.ulVersion = SECBUFFER_VERSION;
+	secBufferDesc.cBuffers = 4;
+	secBufferDesc.pBuffers = &secBuffers[0];
+
+	auto ret = DecryptMessage(static_cast<CtxtHandle*>(context), &secBufferDesc, 0, nullptr); // FIXME
+	debug << "DecryptMessage returns: " << ret << "\n";
+	switch (ret)
+	{
+	case SEC_E_OK:
+	{
+		void *actualDataStart = buffer;
+		for (size_t i = 0; i < 4; ++i)
+		{
+			auto &buffer = secBuffers[i];
+			if (buffer.BufferType == SECBUFFER_DATA)
+			{
+				actualDataStart = buffer.pvBuffer;
+				size = buffer.cbBuffer;
+			}
+			else if (buffer.BufferType == SECBUFFER_EXTRA)
+			{
+				debug << "\tExtra data in buffer " << i << " (" << buffer.cbBuffer << " bytes)\n";
+				enqueue(encRecvBuffer, static_cast<char*>(buffer.pvBuffer), buffer.cbBuffer);
+			}
+			else if (buffer.BufferType != SECBUFFER_EMPTY)
+				debug << "\tBuffer of type " << buffer.BufferType << "\n";
+		}
+
+		if (actualDataStart)
+			memmove(buffer, actualDataStart, size);
+
+		break;
+	}
+	case SEC_E_INCOMPLETE_MESSAGE:
+	{
+		// Move all our current data to encRecvBuffer
+		enqueue(encRecvBuffer, buffer, size);
+
+		// Now try to read some more data from the socket
+		size_t bufferSize = encRecvBuffer.size() + 8192;
+		char *recvBuffer = new char[bufferSize];
+		size_t recvd = socket.read(recvBuffer+encRecvBuffer.size(), 8192);
+		debug << recvd << " bytes of extra data read from socket\n";
+
+		if (recvd == 0 && !recurse)
+		{
+			debug << "Recursion prevented, bailing\n";
+			return 0;
+		}
+
+		// Fill our buffer with the queued data and the newly received data
+		size_t totalSize = encRecvBuffer.size() + recvd;
+		dequeue(encRecvBuffer, recvBuffer, encRecvBuffer.size());
+		debug << "Trying to decrypt with " << totalSize << " bytes of data\n";
+
+		// Now try to decrypt that
+		size_t decrypted = decrypt(recvBuffer, totalSize, false);
+		debug << "\tObtained " << decrypted << " bytes of decrypted data\n";
+
+		// Copy the first size bytes to the output buffer
+		size = std::min(size, decrypted);
+		memcpy(buffer, recvBuffer, size);
+
+		// And write the remainder to our queued decrypted data...
+		// Note: we prepend, since our recursive call may already have written
+		// something and we can be sure decrypt wasn't called if the buffer was
+		// non-empty in read
+		enqueue_prepend(decRecvBuffer, recvBuffer+size, decrypted-size);
+		debug << "\tStoring " << decrypted-size << " bytes of extra decrypted data\n";
+		return size;
+	}
+	// TODO: More?
+	default:
+		size = 0;
+		break;
+	}
+
+	debug << "\tDecrypted " << size << " bytes of data\n";
+
+	return size;
+}
+
+size_t SChannelConnection::write(const char *buffer, size_t size)
+{
+	static constexpr size_t bufferSize = 8192;
+	assert(size <= bufferSize);
+
+	SecPkgContext_StreamSizes Sizes;
+	QueryContextAttributes(
+            static_cast<CtxtHandle*>(context),
+            SECPKG_ATTR_STREAM_SIZES,
+            &Sizes);
+	debug << "stream sizes:\n\theader: " << Sizes.cbHeader << "\n\tfooter: " << Sizes.cbTrailer << "\n";
+
+	char *sendBuffer = new char[bufferSize + Sizes.cbHeader + Sizes.cbTrailer];
+	memcpy(sendBuffer+Sizes.cbHeader, buffer, size);
+
+	SecBuffer secBuffers[4];
+	secBuffers[0].cbBuffer = Sizes.cbHeader;
+	secBuffers[0].BufferType = SECBUFFER_STREAM_HEADER;
+	secBuffers[0].pvBuffer = sendBuffer;
+
+	secBuffers[1].cbBuffer = size;
+	secBuffers[1].BufferType = SECBUFFER_DATA;
+	secBuffers[1].pvBuffer = sendBuffer+Sizes.cbHeader;
+
+	secBuffers[2].cbBuffer = Sizes.cbTrailer;
+	secBuffers[2].pvBuffer = sendBuffer+Sizes.cbHeader+size;
+	secBuffers[2].BufferType = SECBUFFER_STREAM_TRAILER;
+
+	secBuffers[3].cbBuffer = 0;
+	secBuffers[3].BufferType = SECBUFFER_EMPTY;
+	secBuffers[3].pvBuffer = nullptr;
+
+	SecBufferDesc secBufferDesc;
+	secBufferDesc.ulVersion = SECBUFFER_VERSION;
+	secBufferDesc.cBuffers = 4;
+	secBufferDesc.pBuffers = secBuffers;
+
+	auto ret = EncryptMessage(static_cast<CtxtHandle*>(context), 0, &secBufferDesc, 0); // FIXME
+	debug << "Send:\n\tHeader size: " << secBuffers[0].cbBuffer << "\n\t\ttype: " << secBuffers[0].BufferType << "\n\tData size: " << secBuffers[1].cbBuffer << "\n\t\ttype: " << secBuffers[1].BufferType << "\n\tFooter size: " << secBuffers[2].cbBuffer << "\n\t\ttype: " << secBuffers[2].BufferType << "\n";
+
+	size_t sendSize = 0;
+	for (size_t i = 0; i < 4; ++i)
+		if (secBuffers[i].cbBuffer != bufferSize)
+			sendSize += secBuffers[i].cbBuffer;
+
+	debug << "\tReal length? " << sendSize << "\n";
+	switch (ret)
+	{
+	case SEC_E_OK:
+		socket.write(sendBuffer, sendSize);
+		break;
+	// TODO: More?
+	default:
+		size = 0;
+		break;
+	}
+
+	delete[] sendBuffer;
+	return size;
+}
+
+void SChannelConnection::close()
+{
+	// TODO
+}
+
+bool SChannelConnection::valid()
+{
+	return true;
+}

+ 27 - 0
src/windows/SChannelConnection.h

@@ -0,0 +1,27 @@
+#pragma once
+
+#include "common/Connection.h"
+#include "common/PlaintextConnection.h"
+
+#include <vector>
+
+class SChannelConnection : public Connection
+{
+public:
+	SChannelConnection();
+	virtual bool connect(const std::string &hostname, uint16_t port) override;
+	virtual size_t read(char *buffer, size_t size) override;
+	virtual size_t write(const char *buffer, size_t size) override;
+	virtual void close() override;
+	virtual ~SChannelConnection();
+
+	static bool valid();
+
+private:
+	PlaintextConnection socket;
+	void *context; // FIXME
+	std::vector<char> encRecvBuffer;
+	std::vector<char> decRecvBuffer;
+
+	size_t decrypt(char *buffer, size_t size, bool recurse = true);
+};