iSDIO Library for TOSHIBA FlashAir. include HTTP or HTTPS Client.

Dependencies:   SDFileSystem

Dependents:   FlashAir_Twitter Neon_F303K8_04

Fork of HTTPClient by Donatien Garnier

HTTPClient.cpp

Committer:
ban4jp
Date:
2014-12-15
Revision:
20:51abf34bcc06
Parent:
19:3b1625dbd7e9

File content as of revision 20:51abf34bcc06:

/* 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
#if 1
//Enable debug
#include <cstdio>
#define DBG(x, ...) std::printf("[HTTPClient : DBG]"x"\r\n", ##__VA_ARGS__);
#define WARN(x, ...) std::printf("[HTTPClient : WARN]"x"\r\n", ##__VA_ARGS__);
#define ERR(x, ...) std::printf("[HTTPClient : ERR]"x"\r\n", ##__VA_ARGS__);

#else
//Disable debug
#define DBG(x, ...)
#define WARN(x, ...)
#define ERR(x, ...)

#endif

#define HTTP_PORT 80

#define OK 0

#define MIN(x,y) (((x)<(y))?(x):(y))
#define MAX(x,y) (((x)>(y))?(x):(y))

#define CHUNK_SIZE 256
#define HEADER_KEY_MAXLENGTH   64
#define HEADER_VALUE_MAXLENGTH 128

#include <cstring>

#include "HTTPClient.h"

HTTPClient::HTTPClient() :
    m_basicAuthUser(NULL), m_basicAuthPassword(NULL), m_httpResponseCode(0)
{
    bufferPos = buffer;
    recvCount = 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) { \
      ERR("Connection error (%d)", ret); \
      return HTTP_CONN; \
    } \
  } while(0)

#define PRTCL_ERR() \
  do{ \
    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;
    uint16_t cmd;
    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;
    }
    if(strcmp(scheme, "http") == 0) {
        cmd = 0x21; //SendHTTPMessageByRegister
    } else if(strcmp(scheme, "https") == 0) {
        cmd = 0x23; //SendHTTPSSLMessageByRegister
    } else {
        ERR("Not support scheme %s", scheme);
        return HTTP_PARSE;
    }

    DBG("Scheme: %s", scheme);
    DBG("Host: %s", host);
    DBG("Port: %d", port);
    DBG("Cmd: %x", cmd);
    DBG("Path: %s", path);

    int ret;

    //Get card instance
    card = SD_iSDIO::getInstance();
    sequenceId = card->getSequenceId();

    //Create card command
    memset(buffer, 0, 1024);
    bufferPos = buffer;
    bufferPos = put_command_header(bufferPos, 1, 0);
    bufferPos = put_command_info_header(bufferPos, cmd, sequenceId, 2);
    bufferPos = put_str_arg(bufferPos, host);

    uint8_t* bodyLenPos = bufferPos;
    bufferPos += 4; //skip value of data size

    //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) {
        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");
    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;
            }
        }
    }

    put_u32(bodyLenPos, (bufferPos - bodyLenPos - 4));
    put_command_header(buffer, 1, (bufferPos - buffer));

#if 0
    for (int i = 0; i < 0x400; i++) {
        printf("%2x ", buffer[i]);
        if ((i & 0xf) == 0xf) printf("\n");
        if (i == 0x200-1) printf("----\n");
    }
    printf((char *)bodyLenPos + 4);
    printf("\n");
#endif

    DBG("Send data size %d", (bufferPos - buffer));

    if( card->writeExtDataPort(1, 1, 0x000, buffer) != true ) {
        ERR("writeExtDataPort error (1)\n");
        return HTTP_PRTCL;
    }
    if( (bufferPos - buffer) > 512 ) {
        if( card->writeExtDataPort(1, 1, 0x000, &buffer[512]) != true ) {
            ERR("writeExtDataPort error (2)\n");
            return HTTP_PRTCL;
        }
    }

    DBG("waitResponse");
    card->waitResponse(sequenceId);

    bufferPos = buffer;
    recvCount = 0;

    //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) ) {
    if( (m_httpResponseCode < 100) || (m_httpResponseCode >= 600) ) {
        //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[HEADER_KEY_MAXLENGTH];
        char value[HEADER_VALUE_MAXLENGTH];

        //key[31] = '\0';
        //value[31] = '\0';

        memset(key, 0, HEADER_KEY_MAXLENGTH);
        memset(value, 0, HEADER_VALUE_MAXLENGTH);

        //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) < HEADER_KEY_MAXLENGTH) {
                strcpy(key, buf);
                n++;
                char* valueStart = keyEnd + 2;
                if( (valueStart - buf) < crlfPos ) {
                    if(strlen(valueStart) < HEADER_VALUE_MAXLENGTH) {
                        strcpy(value, valueStart);
                        n++;
                    }
                }
            }
        }
        if ( n == 2 ) {
            DBG("Read header : %s: %s", 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;
        }

    }

    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;

    DBG("  recvCount: %d", recvCount);

    if (recvCount == 0) {

        // Read header and data.
        if (!card->readExtDataPort(1, 1, 0x200, buffer)) {
            return HTTP_PRTCL;
        }
#if 0
        for (int i = 0; i < 0x200; i++) {
            printf("%2x ", buffer[i]);
            if ((i & 0xf) == 0xf) printf("\n");
        }
#endif
        if (buffer[0] != 0x02) {
            return HTTP_CONN;
        }

        recvTotalSize = get_u32(buffer + 20);
        uint32_t recvSize = recvTotalSize > 488 ? 488 : recvTotalSize;
        uint32_t pos = 24;

        if (recvSize < maxLen) {
            memcpy(buf, &buffer[pos], recvSize);
            readLen = recvSize;
        } else {
            memcpy(buf, &buffer[pos], maxLen);
            readLen = maxLen;
        }
        bufferPos = &buffer[pos] + readLen;
        recvAvailableSize = recvTotalSize - readLen;

        DBG("  recvTotalSize: %d", recvTotalSize);
        DBG("  recvAvailableSize: %d", recvAvailableSize);

        recvCount++;

    } else {

        uint32_t recvSize = 512 - (bufferPos - buffer);
        if (recvAvailableSize < recvSize) recvSize = recvAvailableSize;

        DBG("  recvAvailableSize: %d", recvAvailableSize);
        DBG("  recvSize: %d", recvSize);

        if (recvSize > 0) {

            if (recvSize < maxLen) {
                memcpy(buf, bufferPos, recvSize);
                readLen = recvSize;
            } else {
                memcpy(buf, bufferPos, maxLen);
                readLen = maxLen;
            }
            bufferPos += readLen;
            recvAvailableSize -= readLen;

        } else if (recvAvailableSize > 0) {

            // Read next data.
            if (!card->readExtDataPort(1, 1, 0x200, buffer)) {
                return HTTP_PRTCL;
            }
#if 0
            for (int i = 0; i < 0x200; i++) {
                printf("%2x ", buffer[i]);
                if ((i & 0xf) == 0xf) printf("\n");
            }
#endif
            recvSize = recvAvailableSize > 512 ? 512 : recvAvailableSize;

            if (recvSize < maxLen) {
                memcpy(buf, buffer, recvSize);
                readLen = recvSize;
            } else {
                memcpy(buf, buffer, maxLen);
                readLen = maxLen;
            }
            bufferPos = buffer + readLen;
            recvAvailableSize -= readLen;

            recvCount++;

        }

    }

    DBG("Read %d bytes", readLen);
    *pReadLen = readLen;
    return HTTP_OK;
}

HTTPResult HTTPClient::send(char* buf, size_t len) //0 on success, err code on failure
{
    if(len == 0) {
        len = strlen(buf);
    }
    DBG("Trying to write %d bytes", len);
    size_t writtenLen = 0;

    if(((buffer + 1024) - bufferPos) >= len) {
        writtenLen = len;
    } else {
        writtenLen = ((buffer + 1024) - bufferPos);
    }
    memcpy(bufferPos, buf, writtenLen);
    bufferPos += writtenLen;

    DBG("Written %d bytes", writtenLen);
    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;
}