HTTPClient for CyaSSL
Dependents: kintone-POST kintone-POST
Fork of HTTPClient by
HTTPClient.cpp
- Committer:
- wolfSSL
- Date:
- 2014-04-07
- Revision:
- 17:c73d8e61d391
- Parent:
- 16:1f743885e7de
- Child:
- 18:d89df40b4cf3
File content as of revision 17:c73d8e61d391:
/* 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 0 //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 HTTPS_PORT 443 #define OK 0 #define MIN(x,y) (((x)<(y))?(x):(y)) #define MAX(x,y) (((x)>(y))?(x):(y)) #include <cstring> #include <../CyaSSL/cyassl/ctaocrypt/settings.h> #include <../CyaSSL/cyassl/ctaocrypt/types.h> #include <../CyaSSL/cyassl/internal.h> #include <../CyaSSL/cyassl/ssl.h> #include "HTTPClient.h" #include "TCPSocketConnection.h" class TCPSocketConnection_fd: public TCPSocketConnection { public: int get_fd() { return _sock_fd ; } } ; static TCPSocketConnection_fd m_sock; #define CHUNK_SIZE 256 #define SEND_BUF_SIZE 512 static char send_buf[SEND_BUF_SIZE] ; static char *send_buf_p ; static int SocketReceive(CYASSL* ssl, char *buf, int sz, void *ctx) { int n ; int i ; #define RECV_RETRY 3 for(i=0; i<RECV_RETRY; i++) { n = m_sock.receive(buf, sz) ; if(n >= 0)return n ; } ERR("SocketReceive:%d/%d\n", n, sz) ; return n ; } static int SocketSend(CYASSL* ssl, char *buf, int sz, void *ctx) { int n ; n = m_sock.send(buf, sz); if(n > 0) { return n ; } else ERR("SocketSend:%d/%d\n", n, sz); return n ; } HTTPClient::HTTPClient() : m_basicAuthUser(NULL), m_basicAuthPassword(NULL), m_httpResponseCode(0) { //CyaSSL_Debugging_ON() ; ctx = 0 ; ssl = 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; } void HTTPClient::setHeader(char * h) { header = h ; } #define CHECK_CONN_ERR(ret) \ do{ \ if(ret) { \ cyassl_free() ;\ m_sock.close(); \ ERR("Connection error (%d)", ret); \ return HTTP_CONN; \ } \ } while(0) #define PRTCL_ERR() \ do{ \ cyassl_free() ;\ m_sock.close(); \ ERR("Protocol error"); \ return HTTP_PRTCL; \ } while(0) void HTTPClient::cyassl_free(void) { if(ssl) CyaSSL_free(ssl) ; if(ctx) CyaSSL_CTX_free(ctx) ; } 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]; char host[32]; char path[64]; int ret ; //First we need to parse the url (http[s]://host[:port][/[path]]) 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 { if(strcmp(scheme, "http") == 0) port = HTTP_PORT ; else if(strcmp(scheme, "https") == 0) port = HTTPS_PORT ; } DBG("Scheme: %s", scheme); DBG("Host: %s", host); DBG("Port: %d", port); DBG("Path: %s", path); //Connect DBG("Connecting socket to server"); sockfd = m_sock.get_fd() ; #define MAX_RETRY 5 int retry ; for(retry=0; retry<MAX_RETRY; retry++) { int ret = m_sock.connect(host, port); if(ret == 0)break ; } if(retry == MAX_RETRY) { m_sock.close(); ERR("Could not connect"); return HTTP_CONN; } if(port == HTTPS_PORT) { /* Start SSL connect */ ctx = CyaSSL_CTX_new( CyaTLSv1_2_client_method //CyaSSLv3_client_method ()); if (ctx == NULL) { ERR("unable to get ctx"); return HTTP_CONN; } CyaSSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, 0); ssl = CyaSSL_new(ctx); if (ssl == NULL) { ERR("unable to get SSL object"); cyassl_free() ; return HTTP_CONN; } CyaSSL_SetVersion(ssl, CYASSL_TLSV1_2) ; CyaSSL_set_fd(ssl, sockfd); CyaSSL_SetIORecv(ctx, SocketReceive) ; CyaSSL_SetIOSend(ctx, SocketSend) ; DBG("ctx=%x, ssl=%x, ssl->ctx->CBIORecv, CBIOSend=%x, %x\n", ctx, ssl, SocketReceive, SocketSend ) ; if (CyaSSL_connect(ssl) != SSL_SUCCESS) { ERR("SSL_connect failed"); cyassl_free() ; return HTTP_CONN; } } /* SSL connect complete */ //Send request DBG("Sending request"); char buf[CHUNK_SIZE]; send_buf_p = send_buf ; // Reset send buffer ; 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); } } //Add user headers if(header) { ret = send(header); 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); buf[trfLen] = 0x0 ; DBG("buf:%s", buf) ; 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; } } } ret = flush() ; // flush the send buffer ; CHECK_CONN_ERR(ret); //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'; 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; 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'; 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"); 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", 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; } } cyassl_free() ; 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; if(port == HTTPS_PORT) { DBG("Enter CyaSSL_read") ; m_sock.set_blocking(false, m_timeout); readLen = CyaSSL_read(ssl, buf, maxLen); if (readLen > 0) { buf[readLen] = 0; DBG("CyaSSL_read:%s\n", buf); } else { ERR("CyaSSL_read, ret = %d", readLen) ; return HTTP_ERROR ; } DBG("Read %d bytes", readLen); *pReadLen = readLen; return HTTP_OK; } 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 { HTTPResult ret ; int cp_len ; if(len == 0) { len = strlen(buf); } do { if((SEND_BUF_SIZE - (send_buf_p - send_buf)) >= len){ cp_len = len ; } else { cp_len = send_buf_p - send_buf ; } memcpy(send_buf_p, buf, cp_len) ; send_buf_p += cp_len ; len -= cp_len ; if(send_buf_p == send_buf + SEND_BUF_SIZE){ ret = flush() ; if(ret)return(ret) ; } } while(len) ; return HTTP_OK ; } HTTPResult HTTPClient::flush() //0 on success, err code on failure { int len ; char * buf ; buf = send_buf ; len = send_buf_p - send_buf ; send_buf_p = send_buf ; // reset send buffer DBG("Trying to write %d bytes:%s\n", len, buf); size_t writtenLen = 0; if(!m_sock.is_connected()) { WARN("Connection was closed by server"); return HTTP_CLOSED; //Connection was closed by server } if(port == HTTPS_PORT) { DBG("Enter CyaSSL_write") ; if (CyaSSL_write(ssl, buf, len) != len) { ERR("SSL_write failed"); return HTTP_ERROR ; } DBG("Written %d bytes", writtenLen); return HTTP_OK; } 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; }