Skip to content

Commit 8beae2e

Browse files
committed
Initial commit
0 parents  commit 8beae2e

22 files changed

Lines changed: 1569 additions & 0 deletions

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.*.swp
2+
*.o
3+
*.dll
4+
*.exe
5+
*.so
6+
build

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
cmake_minimum_required (VERSION 2.6)
2+
3+
project (Https)
4+
5+
add_subdirectory (src)

example/example.lua

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package.cpath="./?.dll"
2+
3+
local https = require "https"
4+
5+
do
6+
local code, body = https.request("https://example.com")
7+
assert(code == 200, body)
8+
end
9+
10+
do
11+
local code, body, headers = https.request("http://example.com", {method = "post", headers = {}, body = "cake"})
12+
assert(code == 200 and headers, body)
13+
14+
for i, v in pairs(headers) do
15+
print(i, v)
16+
end
17+
end

src/CMakeLists.txt

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
cmake_minimum_required (VERSION 3.0)
2+
3+
### Basic compilation settings
4+
set (CMAKE_POSITION_INDEPENDENT_CODE TRUE)
5+
6+
configure_file (
7+
common/config.h.in
8+
common/config.h
9+
)
10+
11+
include_directories (
12+
${CMAKE_CURRENT_SOURCE_DIR}
13+
${CMAKE_CURRENT_BINARY_DIR}
14+
)
15+
16+
### "Libraries"
17+
add_library (https MODULE
18+
lua/main.cpp
19+
)
20+
21+
add_library (https-common STATIC
22+
common/HTTPRequest.cpp
23+
common/HTTPSClient.cpp
24+
common/PlaintextConnection.cpp
25+
)
26+
27+
add_library (https-curl STATIC EXCLUDE_FROM_ALL
28+
generic/CurlClient.cpp
29+
)
30+
31+
add_library (https-openssl STATIC EXCLUDE_FROM_ALL
32+
generic/OpenSSLConnection.cpp
33+
)
34+
35+
add_library (https-schannel STATIC EXCLUDE_FROM_ALL
36+
windows/SChannelConnection.cpp
37+
)
38+
39+
add_library (https-nsurl STATIC EXCLUDE_FROM_ALL
40+
macos/NSURLClient.mm
41+
)
42+
43+
### Flags
44+
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
45+
option (USE_CURL_BACKEND "Use the libcurl backend" ON)
46+
option (USE_OPENSSL_BACKEND "Use the openssl backend" ON)
47+
option (USE_SCHANNEL_BACKEND "Use the schannel backend (windows-only)" OFF)
48+
option (USE_NSURL_BACKEND "Use the NSUrl backend (macos-only)" OFF)
49+
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
50+
option (USE_CURL_BACKEND "Use the libcurl backend" OFF)
51+
option (USE_OPENSSL_BACKEND "Use the openssl backend" OFF)
52+
option (USE_SCHANNEL_BACKEND "Use the schannel backend (windows-only)" ON)
53+
option (USE_NSURL_BACKEND "Use the NSUrl backend (macos-only)" OFF)
54+
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
55+
option (USE_CURL_BACKEND "Use the libcurl backend" OFF)
56+
option (USE_OPENSSL_BACKEND "Use the openssl backend" OFF)
57+
option (USE_SCHANNEL_BACKEND "Use the schannel backend (windows-only)" OFF)
58+
option (USE_NSURL_BACKEND "Use the NSUrl backend (macos-only)" ON)
59+
endif ()
60+
61+
### Dependencies
62+
target_link_libraries (https https-common)
63+
64+
find_package (Lua 5.1 REQUIRED)
65+
include_directories (${LUA_INCLUDE_DIR})
66+
target_link_libraries (https ${LUA_LIBRARIES})
67+
68+
if (USE_CURL_BACKEND)
69+
find_package (CURL REQUIRED)
70+
include_directories (${CURL_INCLUDE_DIR})
71+
target_link_libraries (https https-curl ${CURL_LIBRARIES})
72+
endif ()
73+
74+
if (USE_OPENSSL_BACKEND)
75+
find_package (OpenSSL REQUIRED)
76+
include_directories (${OPENSSL_INCLUDE_DIR})
77+
target_link_libraries (https https-openssl ${OPENSSL_LIBRARIES})
78+
endif ()
79+
80+
if (USE_SCHANNEL_BACKEND)
81+
target_link_libraries (https https-schannel ws2_32 secur32)
82+
endif ()
83+
84+
if (USE_NSURL_BACKEND)
85+
target_link_libraries (https https-nsurl)
86+
endif ()

src/common/Connection.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
#include <string>
5+
6+
class Connection
7+
{
8+
public:
9+
virtual bool connect(const std::string &hostname, uint16_t port) = 0;
10+
virtual size_t read(char *buffer, size_t size) = 0;
11+
virtual size_t write(const char *buffer, size_t size) = 0;
12+
virtual void close() = 0;
13+
virtual ~Connection() {};
14+
};

src/common/ConnectionClient.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#pragma once
2+
3+
#include "HTTPSClient.h"
4+
#include "HTTPRequest.h"
5+
#include "Connection.h"
6+
7+
template<typename Connection>
8+
class ConnectionClient : public HTTPSClient
9+
{
10+
public:
11+
virtual bool valid() const override;
12+
virtual HTTPSClient::Reply request(const HTTPSClient::Request &req) override;
13+
14+
private:
15+
static Connection *factory();
16+
};
17+
18+
template<typename Connection>
19+
bool ConnectionClient<Connection>::valid() const
20+
{
21+
return Connection::valid();
22+
}
23+
24+
template<typename Connection>
25+
Connection *ConnectionClient<Connection>::factory()
26+
{
27+
return new Connection();
28+
}
29+
30+
template<typename Connection>
31+
HTTPSClient::Reply ConnectionClient<Connection>::request(const HTTPSClient::Request &req)
32+
{
33+
HTTPRequest request(factory);
34+
return request.request(req);
35+
}

src/common/HTTPRequest.cpp

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
#include <sstream>
2+
#include <string>
3+
#include <memory>
4+
5+
#include "HTTPRequest.h"
6+
#include "PlaintextConnection.h"
7+
8+
HTTPRequest::HTTPRequest(ConnectionFactory factory)
9+
: factory(factory)
10+
{
11+
}
12+
13+
HTTPSClient::Reply HTTPRequest::request(const HTTPSClient::Request &req)
14+
{
15+
HTTPSClient::Reply reply;
16+
reply.responseCode = 400;
17+
18+
auto info = parseUrl(req.url);
19+
if (!info.valid)
20+
return reply;
21+
22+
std::unique_ptr<Connection> conn;
23+
if (info.schema == "http")
24+
conn.reset(new PlaintextConnection());
25+
else if (info.schema == "https")
26+
conn.reset(factory());
27+
else
28+
throw std::runtime_error("Unknown url schema");
29+
30+
if (!conn->connect(info.hostname, info.port))
31+
return reply;
32+
33+
// Build the request
34+
{
35+
std::stringstream request;
36+
request << (req.method == HTTPSClient::Request::GET ? "GET " : "POST ") << info.query << " HTTP/1.1\r\n";
37+
38+
for (auto &header : req.headers)
39+
request << header.first << ": " << header.second << "\r\n";
40+
41+
request << "Connection: Close\r\n";
42+
43+
request << "Host: " << info.hostname << "\r\n";
44+
45+
if (req.method == HTTPSClient::Request::POST && req.headers.count("Content-Type") == 0)
46+
request << "Content-Type: application/x-www-form-urlencoded\r\n";
47+
48+
if (req.method == HTTPSClient::Request::POST)
49+
request << "Content-Length: " << req.postdata.size() << "\r\n";
50+
51+
request << "\r\n";
52+
53+
if (req.method == HTTPSClient::Request::POST)
54+
request << req.postdata;
55+
56+
// Send it
57+
std::string requestData = request.str();
58+
conn->write(requestData.c_str(), requestData.size());
59+
}
60+
61+
// Now receive the reply
62+
std::stringstream response;
63+
{
64+
char buffer[8192];
65+
66+
while (true)
67+
{
68+
size_t read = conn->read(buffer, sizeof(buffer));
69+
response.write(buffer, read);
70+
if (read == 0)
71+
break;
72+
}
73+
74+
conn->close();
75+
}
76+
77+
reply.responseCode = 500;
78+
// And parse it
79+
{
80+
std::string protocol;
81+
response >> protocol;
82+
if (protocol != "HTTP/1.1")
83+
return reply;
84+
85+
response >> reply.responseCode;
86+
response.ignore(1, '\n');
87+
88+
for (std::string line; getline(response, line, '\n') && line != "\r"; )
89+
{
90+
auto sep = line.find(':');
91+
reply.headers[line.substr(0, sep)] = line.substr(sep+1, line.size()-sep-1);
92+
}
93+
94+
auto begin = std::istreambuf_iterator<char>(response);
95+
auto end = std::istreambuf_iterator<char>();
96+
reply.body = std::string(begin, end);
97+
}
98+
99+
return reply;
100+
}
101+
102+
HTTPRequest::DissectedURL HTTPRequest::parseUrl(const std::string &url)
103+
{
104+
DissectedURL dis;
105+
dis.valid = false;
106+
107+
// Schema
108+
auto schemaStart = 0;
109+
auto schemaEnd = url.find("://");
110+
dis.schema = url.substr(schemaStart, schemaEnd-schemaStart);
111+
112+
// Auth+Hostname+Port
113+
auto connStart = schemaEnd+3;
114+
auto connEnd = url.find('/', connStart);
115+
if (connEnd == std::string::npos)
116+
connEnd = url.size();
117+
118+
// TODO: Auth
119+
if (url.find("@", connStart, connEnd-connStart) != std::string::npos)
120+
return dis;
121+
122+
// Port
123+
auto portStart = url.find(':', connStart);
124+
auto portEnd = connEnd;
125+
if (portStart == std::string::npos || portStart > portEnd)
126+
{
127+
dis.port = dis.schema == "http" ? 80 : 443;
128+
portStart = portEnd;
129+
}
130+
else
131+
dis.port = std::stoi(url.substr(portStart+1, portEnd-portStart-1));
132+
133+
// Hostname
134+
auto hostnameStart = connStart;
135+
auto hostnameEnd = portStart;
136+
dis.hostname = url.substr(hostnameStart, hostnameEnd-hostnameStart);
137+
138+
// And the query
139+
dis.query = url.substr(connEnd);
140+
if (dis.query.size() == 0)
141+
dis.query = "/";
142+
143+
dis.valid = true;
144+
145+
return dis;
146+
}

src/common/HTTPRequest.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#pragma once
2+
3+
#include <functional>
4+
5+
#include "HTTPSClient.h"
6+
#include "Connection.h"
7+
8+
class HTTPRequest
9+
{
10+
public:
11+
typedef std::function<Connection *()> ConnectionFactory;
12+
HTTPRequest(ConnectionFactory factory);
13+
14+
HTTPSClient::Reply request(const HTTPSClient::Request &req);
15+
16+
private:
17+
ConnectionFactory factory;
18+
19+
struct DissectedURL
20+
{
21+
bool valid;
22+
std::string schema;
23+
std::string hostname;
24+
uint16_t port;
25+
std::string query;
26+
// TODO: Auth?
27+
};
28+
29+
DissectedURL parseUrl(const std::string &url);
30+
};

src/common/HTTPSClient.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#include <algorithm>
2+
3+
#include "HTTPSClient.h"
4+
5+
// This may not be the order you expect, as shorter strings always compare less,
6+
// but it's sufficient for our map
7+
bool HTTPSClient::ci_string_less::operator()(const std::string &lhs, const std::string &rhs) const
8+
{
9+
const size_t lhs_size = lhs.size();
10+
const size_t rhs_size = rhs.size();
11+
const size_t steps = std::min(lhs_size, rhs_size);
12+
13+
if (lhs_size < rhs_size)
14+
return true;
15+
else if (lhs_size > rhs_size)
16+
return false;
17+
18+
for (size_t i = 0; i < steps; ++i)
19+
{
20+
char l = std::tolower(lhs[i]);
21+
char r = std::tolower(rhs[i]);
22+
if (l < r)
23+
return true;
24+
else if (l > r)
25+
return false;
26+
}
27+
28+
return false;
29+
}
30+
31+
HTTPSClient::Request::Request(const std::string &url)
32+
: url(url)
33+
, method(GET)
34+
{
35+
}
36+

0 commit comments

Comments
 (0)