Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added support for https (using openssl) #2

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ It was born out of the need to quickly do very simple HTTP requests, without add

License: MIT

* no complex features, no chunking, no ssl, no keepalive ...
* no complex features, no chunking, no keepalive ...
* not very tested, use at your own risk!
* it PURPOSELY does not use any feature-complete libraries (like cURL) to stay lean and header-only.
* it does not use C++11 features to fit well in legacy code bases
Expand All @@ -17,3 +17,17 @@ Usage as easy as (see `main.cpp` for an example):
HTTPResponse response = HTTPClient::request(HTTPClient::GET, URI("http://example.com"));
cout << response.body << endl;
```

optional support for https
--------------------------

1) You need to set a macro :
```C++
#define PICOHTTP_SSL
#include "picohttpclient.hpp"
```

2) You need to add two extra linker flags (openssl library)
```sh
c++ -lssl -lcrypto snippet.cpp
```
99 changes: 87 additions & 12 deletions picohttpclient.hpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
picohttpclient.hpp ... generic, lightweight HTTP 1.1 client

... no complex features, no chunking, no ssl, no keepalive ...
... no complex features, no chunking, no keepalive ...
... not very tested, use at your own risk!
... it PURPOSELY does not use any feature-complete libraries
(like cURL) to stay lean and header-only.
Expand Down Expand Up @@ -44,9 +44,12 @@
#include <sys/socket.h>
#include <netdb.h>

// possibly add SSL?
// https://wiki.openssl.org/index.php/SSL/TLS_Client
#ifdef PICOHTTP_SSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif

#include <iostream>
using namespace std;

class tokenizer {
Expand Down Expand Up @@ -126,15 +129,16 @@ struct HTTPResponse {
string protocol;
string response;
string responseString;

string errorMsg;
stringMap header;

string body;

inline HTTPResponse() : success(true){};
inline static HTTPResponse fail() {
inline static HTTPResponse fail(const string &msg="") {
HTTPResponse result;
result.success = false;
result.errorMsg = msg;
return result;
}
};
Expand Down Expand Up @@ -167,6 +171,11 @@ struct HTTPClient {

if (uri.port == "") {
uri.port = "80";
#ifdef PICOHTTP_SSL
if(uri.protocol.compare("https") == 0) {
uri.port = "443";
}
#endif
}

int getaddrinfo_result =
Expand Down Expand Up @@ -202,17 +211,30 @@ struct HTTPClient {
return fd;
};

inline static string bufferedRead(int fd) {
#ifdef PICOHTTP_SSL
inline static string bufferedRead(SSL* ssl, int fd) {
#else
inline static string bufferedRead(int fd) {
#endif
size_t initial_factor = 4, buffer_increment_size = 8192, buffer_size = 0,
bytes_read = 0;
string buffer;

buffer.resize(initial_factor * buffer_increment_size);

do {
bytes_read = read(fd, ((char *)buffer.c_str()) + buffer_size,
buffer.size() - buffer_size);

#ifdef PICOHTTP_SSL
if(ssl == nullptr) {
#endif
bytes_read = read(fd, ((char *)buffer.c_str()) + buffer_size,
buffer.size() - buffer_size);
#ifdef PICOHTTP_SSL
} else {
bytes_read = SSL_read(ssl, ((char *)buffer.c_str()) + buffer_size,
buffer.size() - buffer_size);
}
#endif

buffer_size += bytes_read;

if (bytes_read > 0 &&
Expand All @@ -230,23 +252,76 @@ struct HTTPClient {
#define HTTP_SPACE " "
#define HTTP_HEADER_SEPARATOR ": "

#ifdef PICOHTTP_SSL
const bool is_ssl = uri.protocol.compare("https") == 0;
SSL_CTX *ctx = nullptr;
SSL *ssl = nullptr;

if(is_ssl) {
const SSL_METHOD *sslmethod = TLS_client_method();
ctx = SSL_CTX_new(sslmethod);
if(ctx == nullptr) {
return HTTPResponse::fail("ssl context initialization failed");
}
ssl = SSL_new(ctx);
if(ssl == nullptr) {
return HTTPResponse::fail("ssl initialization failed");
}
}
#endif

int fd = connectToURI(uri);
if (fd < 0)
return HTTPResponse::fail();

if (fd < 0)
return HTTPResponse::fail("could not open tcp socket");

#ifdef PICOHTTP_SSL
if(ssl != nullptr) {
SSL_set_fd(ssl, fd);
const int connstatus = SSL_connect(ssl);
if(connstatus != 1) {
return HTTPResponse::fail("SSL_connect failed (code " + to_string(connstatus) + ")");
}
}
#endif

string request = string(method2string(method)) + string(" /") +
uri.address + ((uri.querystring == "") ? "" : "?") +
uri.querystring + " HTTP/1.1" HTTP_NEWLINE "Host: " +
uri.host + HTTP_NEWLINE
"Accept: */*" HTTP_NEWLINE
"Connection: close" HTTP_NEWLINE HTTP_NEWLINE;

#ifdef PICOHTTP_SSL
int bytes_written;
if(ssl != nullptr) {
bytes_written = SSL_write(ssl, request.c_str(), request.size());
} else {
bytes_written = write(fd, request.c_str(), request.size());
}
#else
int bytes_written = write(fd, request.c_str(), request.size());
#endif

#ifdef PICOHTTP_SSL
string buffer = bufferedRead(ssl, fd);
#else
string buffer = bufferedRead(fd);
#endif

#ifdef PICOHTTP_SSL
if(ssl != nullptr) {
SSL_free(ssl);
}
#endif

close(fd);

#ifdef PICOHTTP_SSL
if(ctx != nullptr) {
SSL_CTX_free(ctx);
}
#endif

HTTPResponse result;

tokenizer bt(buffer);
Expand Down