Minor tweaks to support longer header key value pairs
Dependents: Car_Bon_car_module
Fork of HTTPClient by
HTTPClient.cpp
- Committer:
- ansond
- Date:
- 2014-03-16
- Revision:
- 25:19392aabd83d
- Parent:
- 24:07dc6a23ca28
File content as of revision 25:19392aabd83d:
/* 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 DEBUG "HTCL" #include <cstdio> #if (defined(DEBUG) && !defined(TARGET_LPC11U24)) #define DBG(x, ...) std::printf("[DBG %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); #define WARN(x, ...) std::printf("[WRN %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); #define ERR(x, ...) std::printf("[ERR %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); #define INFO(x, ...) std::printf("[INF %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); #else #define DBG(x, ...) #define WARN(x, ...) #define ERR(x, ...) #define INFO(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 #include <cstring> #include "HTTPClient.h" HTTPClient::HTTPClient() : m_sock(), m_basicAuthUser(NULL), m_basicAuthPassword(NULL), m_httpResponseCode(0) { } HTTPClient::~HTTPClient() { } void HTTPClient::basicAuth(const char* user, const char* password) //Basic Authentification { if (m_basicAuthUser) free(m_basicAuthUser); if (user != NULL) { m_basicAuthUser = (char *)malloc(strlen(user)+1); strcpy(m_basicAuthUser, user); } if (m_basicAuthPassword) free(m_basicAuthPassword); if (password != NULL) { m_basicAuthPassword = (char *)malloc(strlen(password)+1); strcpy(m_basicAuthPassword, password); } } void HTTPClient::customHeaders(const char **headers, size_t pairs) { m_customHeaders = headers; m_nCustomHeaders = pairs; } 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; if (pDataIn) { pDataIn->writeReset(); } if( pDataOut ) { pDataOut->readReset(); } char scheme[8]; uint16_t port; char host[48]; char path[96]; //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); //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; } //Send request DBG("Sending request"); char buf[CHUNK_SIZE]; memset(buf,0,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; } // 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) { m_sock.close(); ERR("Could not write request"); 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]); 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()); 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); } } //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; } } } //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(); } 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(); } } 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[41]; char value[41]; memset(key,0,41); memset(value,0,41); int n = sscanf(buf, "%40[^:]: %40[^\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"); 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 = 0; 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", readLen); do { pDataIn->write(buf, MIN(trfLen, readLen)); if( trfLen > readLen ) { memmove(buf, &buf[readLen], trfLen - readLen); trfLen -= readLen; readLen = 0; } else { readLen -= trfLen; } if(readLen) { ret = recv(buf, 1, CHUNK_SIZE - trfLen - 1, &trfLen); CHECK_CONN_ERR(ret); } } while(readLen); 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; if (!m_sock.is_connected()) { WARN("Connection was closed by server"); return HTTP_CLOSED; //Connection was closed by server } int ret; while (readLen < maxLen) { if (readLen < minLen) { DBG("Trying to read at most %d bytes [Blocking]", minLen - readLen); m_sock.set_blocking(false, m_timeout); ret = m_sock.receive_all(buf + readLen, minLen - readLen); } else { DBG("Trying to read at most %d bytes [Not blocking]", maxLen - readLen); m_sock.set_blocking(false, 0); ret = m_sock.receive(buf + readLen, maxLen - readLen); } if (ret > 0) { readLen += ret; } else if ( ret == 0 ) { break; } else { if (!m_sock.is_connected()) { ERR("Connection error (recv returned %d)", ret); *pReadLen = readLen; return HTTP_CONN; } else { break; } } if (!m_sock.is_connected()) { break; } } 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("send(%s,%d)", buf, len); size_t writtenLen = 0; if(!m_sock.is_connected()) { WARN("Connection was closed by server"); return HTTP_CLOSED; //Connection was closed by server } m_sock.set_blocking(false, m_timeout); int ret = m_sock.send_all(buf, len); if(ret > 0) { writtenLen += ret; } else if( ret == 0 ) { WARN("Connection was closed by server"); return HTTP_CLOSED; //Connection was closed by server } else { ERR("Connection error (send returned %d)", ret); return HTTP_CONN; } 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; } 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 HTTPClient::base64enc(const char *input, unsigned int length, char *output, int len) { static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; unsigned int c, c1, c2, c3; if (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; }