#include "HttpClient.h"
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <sstream>
#include <netdb.h>
#include <unistd.h>
// Based on code from: https://github.com/reagent/http
#define debug(M, ...) std::fprintf(stderr, "DEBUG %s (in function '%s'):%d: " M "\n", __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define jump_unless(A) if (!(A)) { goto error; }
#define error_unless(A, M, ...) if (!(A)) { fprintf(stderr, M "\n", ##__VA_ARGS__); goto error; }
#define RECV_SIZE 1024
std::vector<std::string> split(const std::string &s, char delim)
{
std::vector<std::string> elems;
std::stringstream ss(s);
std::string item;
while (std::getline(ss, item, delim))
elems.push_back(item);
return elems;
}
std::vector<std::string> splitHeaderLine(const std::string &s)
{
std::vector<std::string> elems;
std::stringstream ss(s);
std::string item;
std::getline(ss, item, ':');
elems.push_back(item);
if (ss.peek() == ' ')
ss.get();
std::getline(ss, item, '\r');
elems.push_back(item);
return elems;
}
int MakeHttpRequest(std::vector<uint8_t>& response, const char* a_method, const char* a_url, const std::map<std::string,std::string>& args)
{
struct addrinfo *res = NULL;
int sockfd = 0;
int status = 0;
std::vector<std::string> url_parts = split(a_url, '/');
std::string url_scheme, url_hostname, url_port, url_path;
if (url_parts.size() >= 3)
{
for (int i = 3; i < url_parts.size(); i++)
url_path += "/" + url_parts[i];
for (std::map<std::string, std::string>::const_iterator i = args.begin(); i != args.end(); ++i)
url_path += (i==args.begin()?"?":"&") + i->first + "=" + i->second;
url_scheme = split(url_parts[0], ':')[0];
if (url_parts[2].size())
{
url_parts = split(url_parts[2], ':');
url_hostname = url_parts[0]; // "192.168.0.15";
if (url_parts.size() >= 2)
url_port = url_parts[1]; // "8000";
}
// std::string url_fragment = split(url_path, '#')[1]; // fragment is html document location
status = 1;
//debug("Scheme: '%s', Hostname: '%s', Port: '%s', Path: '%s'",
// url_scheme.c_str(), url_hostname.c_str(), url_port.c_str(), url_path.c_str());
}
error_unless(status > 0, "Invalid URL supplied: '%s'", a_url);
{
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
status = getaddrinfo(url_hostname.c_str(), url_port.c_str(), &hints, &res);
error_unless(status == 0, "Could not resolve host: %s\n", gai_strerror(status));
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sockfd > 0) {
if (connect(sockfd, res->ai_addr, res->ai_addrlen) != 0) {
close(sockfd);
sockfd = -1;
}
}
}
error_unless(sockfd > 0, "Could not make connection to '%s' on port '%s'", url_hostname.c_str(), url_port.c_str());
{
std::string str;
str += std::string(a_method) + " " + url_path + " HTTP/1.0\r\n";
str += "Host: " + url_hostname + "\r\n";
str += "Connection: close\r\n\r\n";
const char *request = str.c_str();
size_t bytes_sent = 0;
size_t total_bytes_sent = 0;
size_t bytes_to_send = str.size();
while (1) {
bytes_sent = send(sockfd, request, strlen(request), 0);
total_bytes_sent += bytes_sent;
if (total_bytes_sent >= bytes_to_send) {
break;
}
}
status = total_bytes_sent;
}
error_unless(status > 0, "Sending request failed");
{
status = 0;
while (1) {
char data[RECV_SIZE];
size_t bytes_received = recv(sockfd, data, RECV_SIZE, 0);
if (bytes_received == -1) {
status = -1;
break;
} else if (bytes_received == 0) {
status = 0;
break;
}
if (bytes_received > 0 && bytes_received <= RECV_SIZE) {
for (int i = 0; i < bytes_received; i++)
response.push_back((uint8_t)data[i]);
}
}
}
error_unless(status >= 0, "Fetching response failed");
close(sockfd);
freeaddrinfo(res);
return 0;
error:
printf("error case\n");
if (sockfd > 0) { close(sockfd); }
if (res != NULL) { freeaddrinfo(res); }
return 1;
}
HttpResponse MakeHttpRequestWrapper(const HttpRequest& request)
{
HttpResponse result;
std::vector<uint8_t> response;
std::vector<std::string> headerStrs;
result.result = MakeHttpRequest(response, request.method.c_str(), request.url.c_str(), request.args);
if (response.data())
{
bool inHeaders = true;
int c = 0;
int h = 0;
for (int i = 0; i < response.size(); i++) {
if (inHeaders) {
if (((c&1)==0) && response[i] == '\r') c++;
else if (((c&1)==1) && response[i] == '\n') c++;
else {
if (c==2) {
headerStrs.push_back(std::string(&response[h], &response[i]-2));
h = i;
}
c = 0;
}
if (c==4) {
headerStrs.push_back(std::string(&response[h], &response[i]-3));
inHeaders = false;
}
} else {
result.body.push_back(response[i]);
}
}
}
result.status = headerStrs[0];
for (int i = 1; i < headerStrs.size(); i++)
result.headers[splitHeaderLine(headerStrs[i])[0]] = splitHeaderLine(headerStrs[i])[1];
return result;
}
// Potential code to help extend to support auth
/*
void HTTPClient::basicAuth(const char* user, const char* password) //Basic Authentification
{
#if 1
if (m_basicAuthUser)
swFree(m_basicAuthUser);
m_basicAuthUser = (char *)swMalloc(strlen(user)+1);
strcpy(m_basicAuthUser, user);
if (m_basicAuthPassword)
swFree(m_basicAuthPassword);
m_basicAuthPassword = (char *)swMalloc(strlen(password)+1);
strcpy(m_basicAuthPassword, password);
#else
m_basicAuthUser = user;
m_basicAuthPassword = password;
#endif
}
void HTTPClient::createauth (const char *user, const char *pwd, char *buf, int len)
{
char tmp[80];
snprintf(tmp, sizeof(tmp), "%s:%s", user, pwd);
base64enc(tmp, strlen(tmp), &buf[strlen(buf)], len - strlen(buf));
}
// Copyright (c) 2010 Donatien Garnier (donatiengar [at] gmail [dot] com)
int base64enc(const char *input, unsigned int length, char *output, int len)
{
static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
unsigned int c, c1, c2, c3;
if ((uint16_t)len < ((((length-1)/3)+1)<<2)) return -1;
for(unsigned int i = 0, j = 0; i<length; i+=3,j+=4) {
c1 = ((((unsigned char)*((unsigned char *)&input[i]))));
c2 = (length>i+1)?((((unsigned char)*((unsigned char *)&input[i+1])))):0;
c3 = (length>i+2)?((((unsigned char)*((unsigned char *)&input[i+2])))):0;
c = ((c1 & 0xFC) >> 2);
output[j+0] = base64[c];
c = ((c1 & 0x03) << 4) | ((c2 & 0xF0) >> 4);
output[j+1] = base64[c];
c = ((c2 & 0x0F) << 2) | ((c3 & 0xC0) >> 6);
output[j+2] = (length>i+1)?base64[c]:'=';
c = (c3 & 0x3F);
output[j+3] = (length>i+2)?base64[c]:'=';
}
output[(((length-1)/3)+1)<<2] = '\0';
return 0;
}
*/