forked from weirdhome

Fork of HTTPClient by David Smart

Revision:
24:eee214e3e806
Parent:
22:d6b08d9749d6
Child:
25:76084defa790
diff -r 517fec8b8b99 -r eee214e3e806 HTTPClient.cpp
--- a/HTTPClient.cpp	Sat Mar 15 18:40:29 2014 +0000
+++ b/HTTPClient.cpp	Sat Mar 15 22:18:30 2014 +0000
@@ -46,7 +46,8 @@
 #include "HTTPClient.h"
 
 HTTPClient::HTTPClient() :
-    m_sock(), m_basicAuthUser(NULL), m_basicAuthPassword(NULL), m_httpResponseCode(0)
+    m_sock(), m_basicAuthUser(NULL), m_basicAuthPassword(NULL), m_httpResponseCode(0), 
+    m_maxredirections(1), m_location(NULL)
 {
 
 }
@@ -58,7 +59,7 @@
 
 void HTTPClient::basicAuth(const char* user, const char* password) //Basic Authentification
 {
-    #if 1
+#if 1
     if (m_basicAuthUser)
         free(m_basicAuthUser);
     m_basicAuthUser = (char *)malloc(strlen(user)+1);
@@ -66,11 +67,11 @@
     if (m_basicAuthPassword)
         free(m_basicAuthPassword);
     m_basicAuthPassword = (char *)malloc(strlen(password)+1);
-    strcpy(m_basicAuthPassword, password);    
-    #else
+    strcpy(m_basicAuthPassword, password);
+#else
     m_basicAuthUser = user;
     m_basicAuthPassword = password;
-    #endif
+#endif
 }
 
 void HTTPClient::customHeaders(const char **headers, size_t pairs)
@@ -112,6 +113,13 @@
     return m_httpResponseCode;
 }
 
+void HTTPClient::setMaxRedirections(int i)
+{
+    if (i < 1)
+        i = 1;
+    m_maxredirections = i;
+}
+
 #define CHECK_CONN_ERR(ret) \
   do{ \
     if(ret) { \
@@ -142,230 +150,255 @@
     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) { //TODO do handle HTTPS->443
-        port = 80;
-    }
-
-    DBG("Scheme: %s", scheme);
-    DBG("Host: %s", host);
-    DBG("Port: %d", port);
-    DBG("Path: %s", path);
+    size_t recvContentLength = 0;
+    bool recvChunked = false;
+    int crlfPos = 0;
+    char buf[CHUNK_SIZE];
+    size_t trfLen;
+    int ret = 0;
+    
+    int maxRedirect = m_maxredirections;
 
-    //Connect
-    DBG("Connecting socket to server");
-    int ret = m_sock.connect(host, port);
-    if (ret < 0) {
-        m_sock.close();
-        ERR("Could not connect");
-        return HTTP_CONN;
-    }
+    while (maxRedirect--) {
+        bool takeRedirect = false;
+        
+        INFO("parse: [%s]", url);
+        //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;
+        }
 
-    //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:%d\r\nConnection: keep-alive\r\n", meth, path, host, port); //Write request
-    ret = send(buf);
-    if (ret) {
-        m_sock.close();
-        ERR("Could not write request");
-        return HTTP_CONN;
-    }
+        if(port == 0) { //TODO do handle HTTPS->443
+            port = 80;
+        }
 
-    // send authorization
-    if (m_basicAuthUser && m_basicAuthPassword) {
-        strcpy(buf, "Authorization: Basic ");
-        createauth(m_basicAuthUser, m_basicAuthPassword, buf+strlen(buf), sizeof(buf)-strlen(buf));
-        strcat(buf, "\r\n");
-        INFO(" (%s,%s) => (%s)", m_basicAuthUser, m_basicAuthPassword, buf);
-        ret = send(buf);
-        INFO(" ret = %d", ret);
-        if(ret) {
+        DBG("Scheme: %s", scheme);
+        DBG("Host: %s", host);
+        DBG("Port: %d", port);
+        DBG("Path: %s", path);
+
+        //Connect
+        DBG("Connecting socket to server");
+        ret = m_sock.connect(host, port);
+        if (ret < 0) {
             m_sock.close();
-            ERR("Could not write request");
+            ERR("Could not connect");
             return HTTP_CONN;
         }
-    }
 
-    //Send all headers
-    for (size_t nh = 0; nh < m_nCustomHeaders * 2; nh+=2) {
-        INFO("hdr[%d] %s:", nh, m_customHeaders[nh]);
-        INFO("        %s", m_customHeaders[nh+1]);
-        snprintf(buf, sizeof(buf), "%s: %s\r\n", m_customHeaders[nh], m_customHeaders[nh+1]);
+        //Send request
+        DBG("Sending request");
+        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:%d\r\nConnection: keep-alive\r\n", meth, path, host, port); //Write request
         ret = send(buf);
         if (ret) {
-            ERR("closing");
-            wait_ms(50);
             m_sock.close();
             ERR("Could not write request");
             return HTTP_CONN;
         }
-        INFO(" hdr %d", ret);
-    }
 
-    //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());
+        // send authorization
+        if (m_basicAuthUser && m_basicAuthPassword) {
+            strcpy(buf, "Authorization: Basic ");
+            createauth(m_basicAuthUser, m_basicAuthPassword, buf+strlen(buf), sizeof(buf)-strlen(buf));
+            strcat(buf, "\r\n");
+            INFO(" (%s,%s) => (%s)", m_basicAuthUser, m_basicAuthPassword, buf);
             ret = send(buf);
-            CHECK_CONN_ERR(ret);
+            INFO(" ret = %d", ret);
+            if(ret) {
+                m_sock.close();
+                ERR("Could not write request");
+                return HTTP_CONN;
+            }
         }
-        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);
-        }
-    }
 
-    //Close headers
-    DBG("Headers sent");
-    ret = send("\r\n");
-    CHECK_CONN_ERR(ret);
-
-    size_t trfLen;
+        //Send all headers
+        for (size_t nh = 0; nh < m_nCustomHeaders * 2; nh+=2) {
+            INFO("hdr[%d] %s:", nh, m_customHeaders[nh]);
+            INFO("        %s", m_customHeaders[nh+1]);
+            snprintf(buf, sizeof(buf), "%s: %s\r\n", m_customHeaders[nh], m_customHeaders[nh+1]);
+            ret = send(buf);
+            if (ret) {
+                ERR("closing");
+                wait_ms(50);
+                m_sock.close();
+                ERR("Could not write request");
+                return HTTP_CONN;
+            }
+            INFO(" hdr %d", ret);
+        }
 
-    //Send data (if available)
-    if( pDataOut != NULL ) {
-        DBG("Sending data");
-        while(true) {
-            size_t writtenLen = 0;
-            pDataOut->read(buf, CHUNK_SIZE, &trfLen);
+        //Send default headers
+        DBG("Sending headers");
+        if( pDataOut != NULL ) {
             if( pDataOut->getIsChunked() ) {
-                //Write chunk header
-                char chunkHeader[16];
-                snprintf(chunkHeader, sizeof(chunkHeader), "%X\r\n", trfLen); //In hex encoding
-                ret = send(chunkHeader);
+                ret = send("Transfer-Encoding: chunked\r\n");
                 CHECK_CONN_ERR(ret);
-            } else if( trfLen == 0 ) {
-                break;
-            }
-            if( trfLen != 0 ) {
-                ret = send(buf, trfLen);
+            } else {
+                snprintf(buf, sizeof(buf), "Content-Length: %d\r\n", pDataOut->getDataLen());
+                ret = send(buf);
                 CHECK_CONN_ERR(ret);
             }
-
-            if( pDataOut->getIsChunked()  ) {
-                ret = send("\r\n"); //Chunk-terminating CRLF
+            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);
-            } else {
-                writtenLen += trfLen;
-                if( writtenLen >= pDataOut->getDataLen() ) {
+            }
+        }
+
+        //Close headers
+        DBG("Headers sent");
+        ret = send("\r\n");
+        CHECK_CONN_ERR(ret);
+
+        //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;
                 }
             }
 
-            if( trfLen == 0 ) {
-                break;
-            }
         }
 
-    }
-
-    //Receive response
-    DBG("Receiving response");
-    ret = recv(buf, CHUNK_SIZE - 1, CHUNK_SIZE - 1, &trfLen); //Read n bytes
-    CHECK_CONN_ERR(ret);
-    buf[trfLen] = '\0';
-    INFO("Received \r\n(%s\r\n)", buf);
-
-    char* crlfPtr = strstr(buf, "\r\n");
-    if( crlfPtr == NULL) {
-        PRTCL_ERR();
-    }
-
-    int crlfPos = crlfPtr - buf;
-    buf[crlfPos] = '\0';
-
-    //Parse HTTP response
-    if( sscanf(buf, "HTTP/%*d.%*d %d %*[^\r\n]", &m_httpResponseCode) != 1 ) {
-        //Cannot match string, error
-        ERR("Not a correct HTTP answer : %s\n", buf);
-        PRTCL_ERR();
-    }
+        //Receive response
+        DBG("Receiving response");
+        ret = recv(buf, CHUNK_SIZE - 1, CHUNK_SIZE - 1, &trfLen); //Read n bytes
+        CHECK_CONN_ERR(ret);
+        buf[trfLen] = '\0';
+        INFO("Received \r\n(%s\r\n)", buf);
 
-    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;
-    //Now get headers
-    while( true ) {
-        crlfPtr = strstr(buf, "\r\n");
-        if(crlfPtr == NULL) {
-            if( trfLen < CHUNK_SIZE - 1 ) {
-                size_t newTrfLen = 0;
-                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();
-            }
+        char* crlfPtr = strstr(buf, "\r\n");
+        if( crlfPtr == NULL) {
+            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';
+        //Parse HTTP response
+        if( sscanf(buf, "HTTP/%*d.%*d %d %*[^\r\n]", &m_httpResponseCode) != 1 ) {
+            //Cannot match string, error
+            ERR("Not a correct HTTP answer : %s\n", buf);
+            PRTCL_ERR();
+        }
 
-        int n = sscanf(buf, "%31[^:]: %31[^\r\n]", key, value);
-        if ( n == 2 ) {
-            DBG("Read header : %s: %s\n", key, value);
-            if( !strcmp(key, "Content-Length") ) {
-                sscanf(value, "%d", &recvContentLength);
-                pDataIn->setDataLen(recvContentLength);
-            } else if( !strcmp(key, "Transfer-Encoding") ) {
-                if( !strcmp(value, "Chunked") || !strcmp(value, "chunked") ) {
-                    recvChunked = true;
-                    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");
+        if( (m_httpResponseCode < 200) || (m_httpResponseCode >= 400) ) {
+            //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);
+
+        recvContentLength = 0;
+        recvChunked = false;
+        //Now get headers
+        while( true ) {
+            crlfPtr = strstr(buf, "\r\n");
+            if(crlfPtr == NULL) {
+                if( trfLen < CHUNK_SIZE - 1 ) {
+                    size_t newTrfLen = 0;
+                    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[64];
+
+            key[31] = '\0';
+            value[63] = '\0';
+
+            int n = sscanf(buf, "%31[^:]: %63[^\r\n]", key, value);
+            if ( n == 2 ) {
+                DBG("Read header : %s: %s\n", key, value);
+                if( !strcmp(key, "Content-Length") ) {
+                    sscanf(value, "%d", &recvContentLength);
+                    pDataIn->setDataLen(recvContentLength);
+                } else if( !strcmp(key, "Transfer-Encoding") ) {
+                    if( !strcmp(value, "Chunked") || !strcmp(value, "chunked") ) {
+                        recvChunked = true;
+                        pDataIn->setIsChunked(true);
+                    }
+                } else if( !strcmp(key, "Content-Type") ) {
+                    pDataIn->setDataType(value);
+                } else if ( !strcmp(key, "Location") ) {
+                    if (m_location)
+                        free(m_location);
+                    m_location = (char *)malloc(strlen(value)+1);
+                    if (m_location) {
+                        strcpy(m_location,value);
+                        url = m_location;
+                        INFO("Following redirect[%d] to [%s]", maxRedirect, url);
+                        m_sock.close();
+                        takeRedirect = true;
+                        break;   // exit the while(true) header to follow the redirect
+                    }
+                }
+
+                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();
+            }
+
+        } // while(true) // get headers
+        if (!takeRedirect)
+            break;
+    } // while (maxRedirect)
 
     //Receive data
     DBG("Receiving data");