Newer
Older
Import / applications / HighwayDash / ports / Framework / HttpClient.cpp~
#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;
}
 
*/