It is the new miniTLS program which build the connect with the website httpbin.org

Fork of MiniTLS-HTTPSClient by Donatien Garnier

Revision:
0:62a4a8ec4ab5
Child:
1:4309aff9b1db
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/HTTPClient.cpp	Tue Jun 10 14:23:22 2014 +0000
@@ -0,0 +1,729 @@
+/* HTTPClient.cpp */
+/* Copyright (C) 2012 mbed.org, MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+ * and associated documentation files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
+ * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+  
+//Debug is disabled by default
+
+#define __MODULE__ "HTTPClient"
+#define __DEBUG__ 4
+
+#include "core/fwk.h"
+#include "HTTPClient.h"
+#include "MiniTLS-GPL/cpp/MiniTLS.h"
+
+#define HTTP_PORT 80
+#define HTTPS_PORT 443
+
+#define OK 0
+/*
+#define MIN(x,y) (((x)<(y))?(x):(y))
+#define MAX(x,y) (((x)>(y))?(x):(y))
+*/
+#define CHUNK_SIZE 256
+
+#include <cstring>
+
+extern MiniTLS miniTLS;
+
+HTTPClient::HTTPClient() :
+m_sock(&miniTLS), m_basicAuthUser(NULL), m_basicAuthPassword(NULL), m_httpResponseCode(0)
+{
+
+}
+
+HTTPClient::~HTTPClient()
+{
+
+}
+
+#if 0
+void HTTPClient::basicAuth(const char* user, const char* password) //Basic Authentification
+{
+  m_basicAuthUser = user;
+  m_basicAuthPassword = password;
+}
+#endif
+
+HTTPResult HTTPClient::get(const char* url, IHTTPDataIn* pDataIn, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking
+{
+  return connect(url, HTTP_GET, NULL, pDataIn, timeout);
+}
+
+HTTPResult HTTPClient::get(const char* url, char* result, size_t maxResultLen, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking
+{
+  HTTPText str(result, maxResultLen);
+  return get(url, &str, timeout);
+}
+
+HTTPResult HTTPClient::post(const char* url, const IHTTPDataOut& dataOut, IHTTPDataIn* pDataIn, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking
+{
+  return connect(url, HTTP_POST, (IHTTPDataOut*)&dataOut, pDataIn, timeout);
+}
+
+HTTPResult HTTPClient::put(const char* url, const IHTTPDataOut& dataOut, IHTTPDataIn* pDataIn, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking
+{
+  return connect(url, HTTP_PUT, (IHTTPDataOut*)&dataOut, pDataIn, timeout);
+}
+
+HTTPResult HTTPClient::del(const char* url, IHTTPDataIn* pDataIn, int timeout /*= HTTP_CLIENT_DEFAULT_TIMEOUT*/) //Blocking
+{
+  return connect(url, HTTP_DELETE, NULL, pDataIn, timeout);
+}
+
+
+int HTTPClient::getHTTPResponseCode()
+{
+  return m_httpResponseCode;
+}
+
+#define CHECK_CONN_ERR(ret) \
+  do{ \
+    if(ret) { \
+      m_sock.close(); \
+      ERR("Connection error (%d)", ret); \
+      return HTTP_CONN; \
+    } \
+  } while(0)
+
+#define PRTCL_ERR() \
+  do{ \
+    m_sock.close(); \
+    ERR("Protocol error"); \
+    return HTTP_PRTCL; \
+  } while(0)
+
+HTTPResult HTTPClient::connect(const char* url, HTTP_METH method, IHTTPDataOut* pDataOut, IHTTPDataIn* pDataIn, int timeout) //Execute request
+{ 
+  m_httpResponseCode = 0; //Invalidate code
+  m_timeout = timeout;
+  
+  pDataIn->writeReset();
+  if( pDataOut )
+  {
+    pDataOut->readReset();
+  }
+
+  char scheme[8];
+  uint16_t port;
+  char host[32];
+  char path[64];
+  //First we need to parse the url (http[s]://host[:port][/[path]]) -- HTTPS not supported (yet?)
+  HTTPResult res = parseURL(url, scheme, sizeof(scheme), host, sizeof(host), &port, path, sizeof(path));
+  if(res != HTTP_OK)
+  {
+    ERR("parseURL returned %d", res);
+    return res;
+  }
+
+  if(port == 0)
+  {
+    if(!strcmp(scheme, "http"))
+    {
+      port = 80;
+    }
+    else if(!strcmp(scheme, "https"))
+    {
+      port = 443;
+    }
+    else
+    {
+      ERR("Unknown port for scheme");
+      return HTTP_PARSE;
+    }
+  }
+
+  DBG("Scheme: %s", scheme);
+  DBG("Host: %s", host);
+  DBG("Port: %d", port);
+  DBG("Path: %s", path);
+  
+  //Init socket
+  int ret = m_sock.init();
+  if (ret)
+  {
+    ERR("Could not connect");
+    return HTTP_CONN;
+  }
+
+  //Connect
+  DBG("Connecting socket to server");
+  ret = m_sock.connect(host, port, timeout);
+  if (ret)
+  {
+    m_sock.close();
+    ERR("Could not connect");
+    return HTTP_CONN;
+  }
+
+  //Send request
+  DBG("Sending request");
+  char buf[CHUNK_SIZE];
+  const char* meth = (method==HTTP_GET)?"GET":(method==HTTP_POST)?"POST":(method==HTTP_PUT)?"PUT":(method==HTTP_DELETE)?"DELETE":"";
+  snprintf(buf, sizeof(buf), "%s %s HTTP/1.1\r\nHost: %s\r\n", meth, path, host); //Write request
+  ret = send(buf);
+  if(ret)
+  {
+    m_sock.close();
+    ERR("Could not write request");
+    return HTTP_CONN;
+  }
+
+  //Send all headers
+
+  //Send default headers
+  DBG("Sending headers");
+  if( pDataOut != NULL )
+  {
+    if( pDataOut->getIsChunked() )
+    {
+      ret = send("Transfer-Encoding: chunked\r\n");
+      CHECK_CONN_ERR(ret);
+    }
+    else
+    {
+      snprintf(buf, sizeof(buf), "Content-Length: %d\r\n", pDataOut->getDataLen());
+      ret = send(buf);
+      CHECK_CONN_ERR(ret);
+    }
+    char type[48];
+    if( pDataOut->getDataType(type, 48) == HTTP_OK )
+    {
+      snprintf(buf, sizeof(buf), "Content-Type: %s\r\n", type);
+      ret = send(buf);
+      CHECK_CONN_ERR(ret);
+    }
+    
+    //Send specific headers
+    while( pDataOut->getHeader(buf, sizeof(buf) - 3) ) //must have space left for CRLF + 0 terminating char
+    {
+      size_t headerlen = strlen(buf);
+      snprintf(buf + headerlen, sizeof(buf) - headerlen, "\r\n");
+      ret = send(buf);
+      CHECK_CONN_ERR(ret);
+    }
+  }
+  
+  //Send specific headers
+  while( pDataIn->getHeader(buf, sizeof(buf) - 3) )
+  {
+    size_t headerlen = strlen(buf);
+    snprintf(buf + headerlen, sizeof(buf) - headerlen, "\r\n");
+    ret = send(buf);
+    CHECK_CONN_ERR(ret);
+  }
+  
+  //Close headers
+  DBG("Headers sent");
+  ret = send("\r\n", 0, true); //Flush
+  CHECK_CONN_ERR(ret);
+
+  size_t trfLen;
+  
+  //Send data (if available)
+  if( pDataOut != NULL )
+  {
+    DBG("Sending data");
+    while(true)
+    {
+      size_t writtenLen = 0;
+      pDataOut->read(buf, CHUNK_SIZE, &trfLen);
+      if( pDataOut->getIsChunked() )
+      {
+        //Write chunk header
+        char chunkHeader[16];
+        snprintf(chunkHeader, sizeof(chunkHeader), "%X\r\n", trfLen); //In hex encoding
+        ret = send(chunkHeader);
+        CHECK_CONN_ERR(ret);
+      }
+      else if( trfLen == 0 )
+      {
+        break;
+      }
+      if( trfLen != 0 )
+      {
+        ret = send(buf, trfLen);
+        CHECK_CONN_ERR(ret);
+      }
+
+      if( pDataOut->getIsChunked()  )
+      {
+        ret = send("\r\n"); //Chunk-terminating CRLF
+        CHECK_CONN_ERR(ret);
+      }
+      else
+      {
+        writtenLen += trfLen;
+        if( writtenLen >= pDataOut->getDataLen() )
+        {
+          break;
+        }
+      }
+
+      if( trfLen == 0 )
+      {
+        break;
+      }
+    }
+    
+    //Flush
+    ret = send(NULL, 0, true);
+    CHECK_CONN_ERR(ret);
+  }
+  
+  //Receive response
+  DBG("Receiving response");
+  ret = recv(buf, 1, CHUNK_SIZE - 1, &trfLen); //Read n bytes
+  CHECK_CONN_ERR(ret);
+
+  buf[trfLen] = '\0';
+
+  //Make sure we got the first response line
+  char* crlfPtr = NULL;
+  while( true )
+  {
+    crlfPtr = strstr(buf, "\r\n");
+  if(crlfPtr == NULL)
+  {
+      if( trfLen < CHUNK_SIZE - 1 )
+      {
+        size_t newTrfLen;
+        ret = recv(buf + trfLen, 1, CHUNK_SIZE - trfLen - 1, &newTrfLen);
+        trfLen += newTrfLen;
+        buf[trfLen] = '\0';
+        DBG("Read %d chars; In buf: [%s]", newTrfLen, buf);
+        CHECK_CONN_ERR(ret);
+        continue;
+      }
+      else
+      {
+    PRTCL_ERR();
+  }
+    }
+    break;
+  }
+
+  int crlfPos = crlfPtr - buf;
+  buf[crlfPos] = '\0';
+
+  //Parse HTTP response
+  //if( sscanf(buf, "HTTP/%*d.%*d %d %*[^\r\n]", &m_httpResponseCode) != 1 )
+  if(crlfPos > 13)
+  {
+    buf[13] = '\0';
+  }
+  if( sscanf(buf, "HTTP/%*d.%*d %d", &m_httpResponseCode) != 1 ) //Kludge for newlib nano
+  {
+    //Cannot match string, error
+    ERR("Not a correct HTTP answer : %s\n", buf);
+    PRTCL_ERR();
+  }
+
+  if( (m_httpResponseCode < 200) || (m_httpResponseCode >= 300) )
+  {
+    //Did not return a 2xx code; TODO fetch headers/(&data?) anyway and implement a mean of writing/reading headers 
+    WARN("Response code %d", m_httpResponseCode);
+    PRTCL_ERR();
+  }
+
+  DBG("Reading headers");
+
+  memmove(buf, &buf[crlfPos+2], trfLen - (crlfPos + 2) + 1); //Be sure to move NULL-terminating char as well
+  trfLen -= (crlfPos + 2);
+
+  size_t recvContentLength = 0;
+  bool recvChunked = false;
+  bool recvLengthUnknown = true;
+  //Now get headers
+  while( true )
+  {
+    crlfPtr = strstr(buf, "\r\n");
+    if(crlfPtr == NULL)
+    {
+      if( trfLen < CHUNK_SIZE - 1 )
+      {
+        size_t newTrfLen;
+        ret = recv(buf + trfLen, 1, CHUNK_SIZE - trfLen - 1, &newTrfLen);
+        trfLen += newTrfLen;
+        buf[trfLen] = '\0';
+        DBG("Read %d chars; In buf: [%s]", newTrfLen, buf);
+        CHECK_CONN_ERR(ret);
+        continue;
+      }
+      else
+      {
+        PRTCL_ERR();
+      }
+    }
+
+    crlfPos = crlfPtr - buf;
+
+    if(crlfPos == 0) //End of headers
+    {
+      DBG("Headers read");
+      memmove(buf, &buf[2], trfLen - 2 + 1); //Be sure to move NULL-terminating char as well
+      trfLen -= 2;
+      break;
+    }
+
+    buf[crlfPos] = '\0';
+
+    char key[32];
+    char value[32];
+
+    //key[31] = '\0';
+    //value[31] = '\0';
+
+    memset(key, 0, 32);
+    memset(value, 0, 32);
+
+    //int n = sscanf(buf, "%31[^:]: %31[^\r\n]", key, value);
+    
+    int n = 0;
+    
+    char* keyEnd = strchr(buf, ':');
+    if(keyEnd != NULL)
+    {
+      *keyEnd = '\0';
+      if(strlen(buf) < 32)
+      {
+        strcpy(key, buf);
+        n++;
+        char* valueStart = keyEnd + 2;
+        if( (valueStart - buf) < crlfPos )
+        {
+          if(strlen(valueStart) < 32)
+          { 
+            strcpy(value, valueStart);
+            n++;
+          }
+        }
+      }
+    }
+    if ( n == 2 )
+    {
+      DBG("Read header : %s: %s\n", key, value);
+      if( !strcmp(key, "Content-Length") )
+      {
+        sscanf(value, "%d", &recvContentLength);
+        recvLengthUnknown = false;
+        pDataIn->setDataLen(recvContentLength);
+      }
+      else if( !strcmp(key, "Transfer-Encoding") )
+      {
+        if( !strcmp(value, "Chunked") || !strcmp(value, "chunked") )
+        {
+          recvChunked = true;
+          recvLengthUnknown = false;
+          pDataIn->setIsChunked(true);
+        }
+      }
+      else if( !strcmp(key, "Content-Type") )
+      {
+        pDataIn->setDataType(value);
+      }
+
+      memmove(buf, &buf[crlfPos+2], trfLen - (crlfPos + 2) + 1); //Be sure to move NULL-terminating char as well
+      trfLen -= (crlfPos + 2);
+
+    }
+    else
+    {
+      ERR("Could not parse header");
+      PRTCL_ERR();
+    }
+
+  }
+
+  //Receive data
+  DBG("Receiving data");
+  while(true)
+  {
+    size_t readLen = 0;
+
+    if( recvChunked )
+    {
+      //Read chunk header
+      bool foundCrlf;
+      do
+      {
+        foundCrlf = false;
+        crlfPos=0;
+        buf[trfLen]=0;
+        if(trfLen >= 2)
+        {
+          for(; crlfPos < trfLen - 2; crlfPos++)
+          {
+            if( buf[crlfPos] == '\r' && buf[crlfPos + 1] == '\n' )
+            {
+              foundCrlf = true;
+              break;
+            }
+          }
+        }
+        if(!foundCrlf) //Try to read more
+        {
+          if( trfLen < CHUNK_SIZE )
+          {
+            size_t newTrfLen;
+            ret = recv(buf + trfLen, 0, CHUNK_SIZE - trfLen - 1, &newTrfLen);
+            trfLen += newTrfLen;
+            CHECK_CONN_ERR(ret);
+            continue;
+          }
+          else
+          {
+            PRTCL_ERR();
+          }
+        }
+      } while(!foundCrlf);
+      buf[crlfPos] = '\0';
+      int n = sscanf(buf, "%x", &readLen);
+      if(n!=1)
+      {
+        ERR("Could not read chunk length");
+        PRTCL_ERR();
+      }
+
+      memmove(buf, &buf[crlfPos+2], trfLen - (crlfPos + 2)); //Not need to move NULL-terminating char any more
+      trfLen -= (crlfPos + 2);
+
+      if( readLen == 0 )
+      {
+        //Last chunk
+        break;
+      }
+    }
+    else
+    {
+      readLen = recvContentLength;
+    }
+
+    DBG("Retrieving %d bytes (%d bytes in buffer)", readLen, trfLen);
+
+    do
+    {
+      if(recvLengthUnknown )
+      {
+        readLen = trfLen;
+      }
+      pDataIn->write(buf, MIN(trfLen, readLen));
+      if(!recvLengthUnknown)
+      {
+        if( trfLen > readLen )
+        {
+          memmove(buf, &buf[readLen], trfLen - readLen);
+          trfLen -= readLen;
+          readLen = 0;
+        }
+        else
+        {
+          readLen -= trfLen;
+        }
+      }
+      else
+      {
+        trfLen = 0;
+      }
+
+      if(readLen || recvLengthUnknown)
+      {
+        ret = recv(buf, 1, CHUNK_SIZE - trfLen - 1, &trfLen);
+        if(recvLengthUnknown && (ret == HTTP_CLOSED))
+        {
+          //Write and exit
+          pDataIn->write(buf, trfLen);
+          break;
+        }
+        CHECK_CONN_ERR(ret);
+        if(recvLengthUnknown && (trfLen == 0))
+        {
+          break;
+        }
+      }
+    } while(readLen || recvLengthUnknown);
+
+    if( recvChunked )
+    {
+      if(trfLen < 2)
+      {
+        size_t newTrfLen;
+        //Read missing chars to find end of chunk
+        ret = recv(buf + trfLen, 2 - trfLen, CHUNK_SIZE - trfLen - 1, &newTrfLen);
+        CHECK_CONN_ERR(ret);
+        trfLen += newTrfLen;
+      }
+      if( (buf[0] != '\r') || (buf[1] != '\n') )
+      {
+        ERR("Format error");
+        PRTCL_ERR();
+      }
+      memmove(buf, &buf[2], trfLen - 2);
+      trfLen -= 2;
+    }
+    else
+    {
+      break;
+    }
+
+  }
+
+  m_sock.close();
+  DBG("Completed HTTP transaction");
+
+  return HTTP_OK;
+}
+
+HTTPResult HTTPClient::recv(char* buf, size_t minLen, size_t maxLen, size_t* pReadLen) //0 on success, err code on failure
+{
+  DBG("Trying to read between %d and %d bytes", minLen, maxLen);
+  size_t readLen = 0;
+   
+  /* TODO */
+  /*
+  if(!m_sock.is_connected())
+  {
+    WARN("Connection was closed by server");
+    return HTTP_CLOSED; //Connection was closed by server 
+  }
+  */
+    
+  minitls_err_t ret = m_sock.read((uint8_t*)buf, minLen, maxLen, pReadLen, m_timeout);
+  if(ret)
+  {
+    WARN("TLS Error %d", ret);
+    return HTTP_CONN;
+  }
+  
+  DBG("Read %d bytes", *pReadLen);
+  
+  if(*pReadLen < minLen)
+  {
+    WARN("Connection was closed by server");
+    return HTTP_CLOSED;
+  }
+  
+  return HTTP_OK;
+}
+
+HTTPResult HTTPClient::send(char* buf, size_t len, bool flush) //0 on success, err code on failure
+{
+  if(len == 0)
+  {
+    len = strlen(buf);
+  }
+
+  size_t writtenLen = 0;
+  minitls_err_t ret;
+  if(len > 0)
+  {
+    ret = m_sock.write((uint8_t*)buf, len, &writtenLen, m_timeout);
+    if(ret)
+    {
+    WARN("TLS Error %d", ret);
+    return HTTP_CONN;
+    }
+    
+    DBG("Wrote %d bytes", writtenLen);
+    
+    if(writtenLen < len)
+    {
+    WARN("Connection was closed by server");
+    return HTTP_CLOSED;
+    }
+  }
+  
+  if(flush)
+  {
+    m_sock.flush(m_timeout);
+  }
+  
+  return HTTP_OK;
+}
+
+HTTPResult HTTPClient::parseURL(const char* url, char* scheme, size_t maxSchemeLen, char* host, size_t maxHostLen, uint16_t* port, char* path, size_t maxPathLen) //Parse URL
+{
+  char* schemePtr = (char*) url;
+  char* hostPtr = (char*) strstr(url, "://");
+  if(hostPtr == NULL)
+  {
+    WARN("Could not find host");
+    return HTTP_PARSE; //URL is invalid
+  }
+
+  if( maxSchemeLen < hostPtr - schemePtr + 1 ) //including NULL-terminating char
+  {
+    WARN("Scheme str is too small (%d >= %d)", maxSchemeLen, hostPtr - schemePtr + 1);
+    return HTTP_PARSE;
+  }
+  memcpy(scheme, schemePtr, hostPtr - schemePtr);
+  scheme[hostPtr - schemePtr] = '\0';
+
+  hostPtr+=3;
+
+  size_t hostLen = 0;
+
+  char* portPtr = strchr(hostPtr, ':');
+  if( portPtr != NULL )
+  {
+    hostLen = portPtr - hostPtr;
+    portPtr++;
+    if( sscanf(portPtr, "%hu", port) != 1)
+    {
+      WARN("Could not find port");
+      return HTTP_PARSE;
+    }
+  }
+  else
+  {
+    *port=0;
+  }
+  char* pathPtr = strchr(hostPtr, '/');
+  if( hostLen == 0 )
+  {
+    hostLen = pathPtr - hostPtr;
+  }
+
+  if( maxHostLen < hostLen + 1 ) //including NULL-terminating char
+  {
+    WARN("Host str is too small (%d >= %d)", maxHostLen, hostLen + 1);
+    return HTTP_PARSE;
+  }
+  memcpy(host, hostPtr, hostLen);
+  host[hostLen] = '\0';
+
+  size_t pathLen;
+  char* fragmentPtr = strchr(hostPtr, '#');
+  if(fragmentPtr != NULL)
+  {
+    pathLen = fragmentPtr - pathPtr;
+  }
+  else
+  {
+    pathLen = strlen(pathPtr);
+  }
+
+  if( maxPathLen < pathLen + 1 ) //including NULL-terminating char
+  {
+    WARN("Path str is too small (%d >= %d)", maxPathLen, pathLen + 1);
+    return HTTP_PARSE;
+  }
+  memcpy(path, pathPtr, pathLen);
+  path[pathLen] = '\0';
+
+  return HTTP_OK;
+}