iSDIO Library for TOSHIBA FlashAir. include HTTP or HTTPS Client.
Dependents: FlashAir_Twitter Neon_F303K8_04
Fork of HTTPClient by
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; }