/*H********************************************************************************/ /*! \File http.c \Description Implements basic http get and post client. \Copyright Copyright (c) 2005 Electronic Arts Inc. \Version 10/28/2005 (jbrookes) First Version */ /********************************************************************************H*/ /*** Include files ****************************************************************/ #include #include #include #include #include #include "DirtySDK/platform.h" #include "DirtySDK/dirtysock.h" #include "DirtySDK/proto/protohttp.h" #include "DirtySDK/proto/protossl.h" #include "libsample/zlib.h" #include "libsample/zmem.h" #include "libsample/zfile.h" #include "testermodules.h" /*** Defines **********************************************************************/ #define HTTP_BUFSIZE (4096) #define HTTP_RATE (1) #define HTTP_XTRAHDR0 "" #define HTTP_XTRAHDR1 "X-Agent: DirtySock HTTP Tester\r\n" // test "normal" replacement (replaces Accept: header) #define HTTP_XTRAHDR2 "User-Agent: DirtySock HTTP Tester\r\n" // test "extended" replacement (replaces User-Agent: and Accept: headers) //$$ tmp -- special test header used for hard-coded multipart/form-data testing -- this should be removed at some point when we have real multipart/form-data support #define HTTP_XTRAHDR3 "Content-Type: multipart/form-data; boundary=TeStInG\r\n" \ "User-Agent: DirtySock HTTP Tester\r\n" \ "Accept: */*\r\n" #define HTTP_APNDHDR HTTP_XTRAHDR0 /*** Function Prototypes **********************************************************/ static int32_t _CmdHttpIdleCB(ZContext *argz, int32_t argc, char *argv[]); /*** Type Definitions *************************************************************/ typedef struct HttpRefT // module state storage { ProtoHttpRefT *http; int64_t show; int64_t count; int32_t sttime; int64_t iDataSize; int64_t iSentSize; int32_t iSendBufData; int32_t iSendBufSent; ZFileT iInpFile; ZFileT iOutFile; int32_t iOutSize; char *pOutData; char *pClientCert; int32_t iCertSize; char *pClientKey; int32_t iKeySize; int32_t iDebugLevel; uint8_t bStreaming; uint8_t bUseWriteCb; uint8_t bRecvAll; char strModuleName[32]; char strHost[128]; char strCookie[2048]; char strApndHdr[2048]; char strFileBuffer[64*1024]; // must be at a minimum 4k or protohttp buffer size, whichever is larger // module state enum { IDLE, DNLOAD, UPLOAD } state; } HttpRefT; /*** Variables ********************************************************************/ static HttpRefT _Http_Ref; static uint8_t _Http_bInitialized = FALSE; /*** Private Functions ************************************************************/ /*F********************************************************************************/ /*! \Function _GetIntArg \Description Get fourcc/integer from command-line argument \Input *pArg - pointer to argument \Version 10/20/2011 (jbrookes) */ /********************************************************************************F*/ static int32_t _GetIntArg(const char *pArg) { int32_t iValue; // check for possible fourcc value if ((strlen(pArg) == 4) && (isalpha(pArg[0]) || isalpha(pArg[1]) || isalpha(pArg[2]) || isalpha(pArg[3]))) { iValue = pArg[0] << 24; iValue |= pArg[1] << 16; iValue |= pArg[2] << 8; iValue |= pArg[3]; } else { iValue = (signed)strtol(pArg, NULL, 10); } return(iValue); } /*F********************************************************************************/ /*! \Function _CmdHttpUsage \Description Display usage information. \Input argc - argument count \Input *argv[] - argument list \Version 02/18/2008 (jbrookes) */ /********************************************************************************F*/ static void _CmdHttpUsage(int argc, char *argv[]) { if (argc == 2) { ZPrintf(" execute http get or put operations\n"); ZPrintf(" usage: %s [cclr|cert|cer2|cver|create|ctrl|debug|destroy|get|head|put|post|puts|delete|options|parse|stat|urlparse]", argv[0]); } else if (argc == 3) { if (!strcmp(argv[2], "cclr") || !strcmp(argv[2], "cert") || !strcmp(argv[2], "cer2") || !strcmp(argv[2], "cver")) { ZPrintf(" usage: %s cert|cer2 - load certificate file\n", argv[0]); ZPrintf(" %s cclr - clear dynamic CA certs\n"); ZPrintf(" %s cver - verify dynamic CA certs\n"); } else if (!strcmp(argv[2], "create")) { ZPrintf(" usage: %s create \n", argv[0]); } else if (!strcmp(argv[2], "destroy")) { ZPrintf(" usage: %s destroy\n", argv[0]); } else if (!strcmp(argv[2], "get") || !strcmp(argv[2], "head") || !strcmp(argv[2], "options") || !strcmp(argv[2], "delete")) { ZPrintf(" usage: %s %s [url] \n", argv[0], argv[2]); } else if (!strcmp(argv[2], "put") || !strcmp(argv[2], "puts") || !strcmp(argv[2], "post")) { ZPrintf(" usage: %s %s [url] [infile] \n", argv[0], argv[2]); } else if (!strcmp(argv[2], "debug")) { ZPrintf(" usage: %s debug [debuglevel]\n", argv[0]); } else if (!strcmp(argv[2], "parse")) { ZPrintf(" usage: %s parse [url]\n", argv[0]); } } } static int32_t _HttpUrlParseTest(const char *pModuleName, const char *pUrl) { char strKind[16], strHost[256]; int32_t iPort, iSecure; uint8_t bPortSpecified; const char *pUri; ZPrintf("%s: parsing url %s\n", pModuleName, pUrl); pUri = ProtoHttpUrlParse2(pUrl, strKind, sizeof(strKind), strHost, sizeof(strHost), &iPort, &iSecure, &bPortSpecified); ZPrintf("%s: uri=%s\n", pModuleName, pUri); ZPrintf("%s: kind=%s\n", pModuleName, strKind); ZPrintf("%s: host=%s\n", pModuleName, strHost); ZPrintf("%s: iPort=%d\n", pModuleName, iPort); ZPrintf("%s: iSecure=%d\n", pModuleName, iSecure); ZPrintf("%s: bPortSpecified=%s\n", pModuleName, bPortSpecified ? "TRUE" : "FALSE"); return(0); } static int32_t _HttpUrlEncodeTest(const char *pModuleName, char **pArgs, int32_t iNumArgs) { char strUrl[128]; int32_t iArg, iOff; ds_snzprintf(strUrl, sizeof(strUrl), "https://urltest.ea.com/"); for (iArg = 0, iOff = (int32_t)strlen(strUrl); iArg < iNumArgs; iArg += 2) { iOff += ProtoHttpUrlEncodeStrParm(strUrl+iOff, sizeof(strUrl)-iOff, pArgs[iArg], pArgs[iArg+1]); } ProtoHttpUrlEncodeIntParm(strUrl, sizeof(strUrl), "&test=", 2918123); ZPrintf("%s: url=%s\n", pModuleName, strUrl); return(0); } /*F********************************************************************************/ /*! \Function _HttpLoadCertificate \Description Display usage information. \Input *pFilename - certificate filename \Input *pCertSize - [out] storage for cert size \Output char * - pointer to certificate, or NULL on error \Version 10/20/2013 (jbrookes) */ /********************************************************************************F*/ static char *_HttpLoadCertificate(const char *pFilename, int32_t *pCertSize) { char *pCertBuf; int32_t iFileSize; // load certificate file if ((pCertBuf = (char *)ZFileLoad(pFilename, &iFileSize, ZFILE_OPENFLAG_RDONLY)) == NULL) { return(NULL); } // calculate length *pCertSize = iFileSize; // return to caller return(pCertBuf); } /*F********************************************************************************/ /*! \Function _HttpReset \Description Reset transaction state \Input *pRef - module state \Version 03/21/2014 (jbrookes) */ /********************************************************************************F*/ static void _HttpReset(HttpRefT *pRef) { // clear any previous stats pRef->count = 0; pRef->show = 0; pRef->iDataSize = 0; pRef->iSentSize = 0; pRef->iSendBufSent = 0; // clear previous options pRef->bStreaming = FALSE; pRef->bUseWriteCb = FALSE; pRef->bRecvAll = FALSE; } /*F********************************************************************************/ /*! \Function _HttpCustomHeaderCallback \Description ProtoHttp custom header callback. \Input *pProtoHttp - protohttp module state \Input *pHeader - header to be sent \Input uHeaderSize - header size \Input *pUserData - user ref (HttpRefT) \Output int32_t - zero \Notes The header returned should be terminated by a *single* CRLF; ProtoHttp will append the final CRLF to complete the header. The callback may return the size of the header, or zero, in which case ProtoHttp will calculate the headersize using strlen(). \Version 02/24/2009 (jbrookes) */ /********************************************************************************F*/ static int32_t _HttpCustomHeaderCallback(ProtoHttpRefT *pProtoHttp, char *pHeader, uint32_t uHeaderSize, const char *pData, int64_t iDataLen, void *pUserRef) { int32_t iOffset; NetPrintf(("http: custom header callback, size=%d, data=%d, pData=%p\n", uHeaderSize, iDataLen, pData)); // find end of header iOffset = (int32_t)strlen(pHeader); // append a header iOffset += ds_snzprintf(pHeader+iOffset, uHeaderSize-iOffset, "X-CustomHeaderCallback: Testing custom header callback\r\n"); // we can either return the header length or zero; in the latter case protohttp will re-calculate for us return(iOffset); } /*F********************************************************************************/ /*! \Function _HttpRecvHeaderCallback \Description ProtoHttp recv header callback. \Input *pProtoHttp - protohttp module state \Input *pHeader - received header \Input uHeaderSize - header size \Input *pUserData - user ref (HttpRefT) \Output int32_t - zero \Version 02/24/2009 (jbrookes) */ /********************************************************************************F*/ static int32_t _HttpRecvHeaderCallback(ProtoHttpRefT *pProtoHttp, const char *pHeader, uint32_t uHeaderSize, void *pUserRef) { HttpRefT *pHttp = (HttpRefT *)pUserRef; char strBuffer[2048], strName[128]; const char *pHdrTmp; int32_t iLocnSize; // check for location header if ((iLocnSize = ProtoHttpGetHeaderValue(pProtoHttp, pHeader, "location", NULL, 0, NULL)) > 0) { ZPrintf("http: location header size=%d\n", iLocnSize); if (ProtoHttpGetHeaderValue(pProtoHttp, pHeader, "location", strBuffer, sizeof(strBuffer), NULL) == 0) { ZPrintf("http: location url='%s'\n", strBuffer); } else { ZPrintf("http: error querying location url\n"); } } // test ProtoHttpGetNextHeader() for (pHdrTmp = pHeader; ProtoHttpGetNextHeader(pProtoHttp, pHdrTmp, strName, sizeof(strName), strBuffer, sizeof(strBuffer), &pHdrTmp) == 0; ) { #if 0 ZPrintf("http: ===%s: %s\n", strName, strBuffer); #endif } // parse any set-cookie requests for (pHdrTmp = pHeader; ProtoHttpGetHeaderValue(pProtoHttp, pHdrTmp, "set-cookie", strBuffer, sizeof(strBuffer), &pHdrTmp) == 0; ) { // print the cookie ZPrintf("http: parsed cookie '%s'\n", strBuffer); // add field seperator if (pHttp->strCookie[0] != '\0') { ds_strnzcat(pHttp->strCookie, ", ", sizeof(pHttp->strCookie)); } // append to cookie list ds_strnzcat(pHttp->strCookie, strBuffer, sizeof(pHttp->strCookie)); } // if this is a redirection update append header with any new cookies if ((PROTOHTTP_GetResponseClass(ProtoHttpStatus(pProtoHttp, 'code', NULL, 0)) == PROTOHTTP_RESPONSE_REDIRECTION) && (pHttp->strCookie[0] != '\0')) { // format append header ds_snzprintf(strBuffer, sizeof(strBuffer), "Cookie: %s\r\n%s", pHttp->strCookie, HTTP_APNDHDR); ds_strnzcat(strBuffer, pHttp->strApndHdr, sizeof(strBuffer)); ProtoHttpControl(pProtoHttp, 'apnd', 0, 0, strBuffer); } return(0); } /*F********************************************************************************/ /*! \Function _HttpReallocBuff \Description Realloc buffer used for RecvAll operation \Input *pRef - pointer to http ref \Version 07/29/2012 (jbrookes) */ /********************************************************************************F*/ static void _HttpReallocBuff(HttpRefT *pRef) { char *pNewData; int32_t iNewSize; // calc new buffer size if ((iNewSize = pRef->iOutSize) == 0) { // try getting body size if ((iNewSize = ProtoHttpStatus(pRef->http, 'body', NULL, 0)) > 0) { // bump up buffer size for recvall null terminator //$$ TODO V9 -- why 2 required, not 1?? iNewSize += 2; } else { // assign a fixed size, since we didn't get a body size iNewSize = 4096; } } else { iNewSize *= 2; } // allocate new buffer if ((pNewData = ZMemAlloc(iNewSize)) == NULL) { return; } // if realloc, copy old data and free old pointer if (pRef->pOutData != NULL) { ds_memcpy(pNewData, pRef->pOutData, pRef->iOutSize); ZMemFree(pRef->pOutData); } // save new pointer pRef->pOutData = pNewData; pRef->iOutSize = iNewSize; } /*F********************************************************************************/ /*! \Function _HttpDownloadProcessData \Description Process data received in a download operation \Input *pRef - module state \Input iLen - data length or PROTOHTTP_RECV* \Version 05/03/2012 (jbrookes) */ /********************************************************************************F*/ static void _HttpDownloadProcessData(HttpRefT *pRef, int32_t iLen) { int32_t iResult; // see if we should show progress if ((pRef->count/1024) != (pRef->show/1024)) { pRef->show = pRef->count; ZPrintf("%s: downloaded %qd bytes\n", pRef->strModuleName, pRef->count); } // see if we are done if ((iLen < 0) && (iLen != PROTOHTTP_RECVWAIT)) { // completed successfully? if ((iLen == PROTOHTTP_RECVDONE) || (iLen == PROTOHTTP_RECVHEAD)) { int32_t iDlTime = NetTickDiff(NetTick(), pRef->sttime); int64_t iBodySize; char strRespHdr[1024]; ZPrintf("%s: download complete (%qd bytes)\n", pRef->strModuleName, pRef->count); ZPrintf("%s: download time: %qd bytes in %.2f seconds (%.3f k/sec)\n", pRef->strModuleName, pRef->count, (float)iDlTime/1000.0f, ((float)pRef->count * 1000.0f) / ((float)iDlTime * 1024.0f)); // make sure we got it all if ((iBodySize = ProtoHttpStatus(pRef->http, 'body', NULL, 0)) != pRef->count) { ZPrintf("%s: WARNING -- mismatch between expected size (%d) and actual size (%d)\n", pRef->strModuleName, iBodySize, pRef->count); } // display response header if (ProtoHttpStatus(pRef->http, 'htxt', strRespHdr, sizeof(strRespHdr)) >= 0) { ZPrintf("%s response header:\n%s\n", pRef->strModuleName, strRespHdr); } // display a couple of parsed fields if (ProtoHttpStatus(pRef->http, 'head', NULL, 0) > 0) { time_t tLastMod = ProtoHttpStatus(pRef->http, 'date', NULL, 0); const char *pTime; if (tLastMod != 0) { if ((pTime = ctime(&tLastMod)) != NULL) { ZPrintf("%s: Last-Modified: %s", pRef->strModuleName, pTime); } } // get content-length; by passing in a 64bit int we support 64bit transfer sizes ProtoHttpStatus(pRef->http, 'body', &iBodySize, sizeof(iBodySize)); ZPrintf("%s: Content-Length: %qd\n", pRef->strModuleName, iBodySize); } } else // failure { ProtoSSLAlertDescT AlertDesc; int32_t iSockErr = ProtoHttpStatus(pRef->http, 'serr', NULL, 0); int32_t iSslFail = ProtoHttpStatus(pRef->http, 'essl', NULL, 0); int32_t iAlert = ProtoHttpStatus(pRef->http, 'alrt', &AlertDesc, sizeof(AlertDesc)); ZPrintf("%s: download failed (err=%d, sockerr=%d sslerr=%d)\n", pRef->strModuleName, iLen, iSockErr, iSslFail); if ((iSslFail == PROTOSSL_ERROR_CERT_INVALID) || (iSslFail == PROTOSSL_ERROR_CERT_HOST) || (iSslFail == PROTOSSL_ERROR_CERT_NOTRUST) || (iSslFail == PROTOSSL_ERROR_CERT_REQUEST)) { ProtoSSLCertInfoT CertInfo; if (ProtoHttpStatus(pRef->http, 'cert', &CertInfo, sizeof(CertInfo)) == 0) { ZPrintf("%s: cert failure (%d): (C=%s, ST=%s, L=%s, O=%s, OU=%s, CN=%s)\n", pRef->strModuleName, iSslFail, CertInfo.Ident.strCountry, CertInfo.Ident.strState, CertInfo.Ident.strCity, CertInfo.Ident.strOrg, CertInfo.Ident.strUnit, CertInfo.Ident.strCommon); } else { ZPrintf("%s: could not get cert info\n", pRef->strModuleName); } } if (iAlert > 0) { ZPrintf("%s: %s ssl alert %s (%d)\n", pRef->strModuleName, (iAlert == 1) ? "recv" : "sent", AlertDesc.pAlertDesc, AlertDesc.iAlertType); } } ZPrintf("%s: hResult=0x%08x\n", pRef->strModuleName, ProtoHttpStatus(pRef->http, 'hres', NULL, 0)); // if file exists, close it if (pRef->iOutFile != ZFILE_INVALID) { if ((iResult = ZFileClose(pRef->iOutFile)) != 0) { ZPrintf("%s: error %d closing output file\n", pRef->strModuleName, iResult); } pRef->iOutFile = ZFILE_INVALID; } // if output buffer exists, free it if (pRef->pOutData != NULL) { pRef->pOutData = NULL; } pRef->iOutSize = 0; // set to idle state pRef->state = IDLE; } } /*F********************************************************************************/ /*! \Function _HttpWriteCb \Description Implementation of ProtoHttp write callback \Input *pState - http module state \Input *pWriteInfo - callback info \Input *pData - transaction data pointer \Input iDataSize - size of data \Input *pUserData - user callback data \Output int32_t - zero \Version 05/03/2012 (jbrookes) */ /********************************************************************************F*/ static int32_t _HttpWriteCb(ProtoHttpRefT *pState, const ProtoHttpWriteCbInfoT *pWriteInfo, const char *pData, int32_t iDataSize, void *pUserData) { HttpRefT *pRef = (HttpRefT *)pUserData; //NetPrintf(("http: writecb (%d,%d) %d bytes\n", pWriteInfo->eRequestType, pWriteInfo->eRequestResponse, iDataSize)); // if we got data, update count and write to output file if available if (iDataSize > 0) { pRef->count += iDataSize; if (pRef->iOutFile != ZFILE_INVALID) { ZFileWrite(pRef->iOutFile, (void *)pData, iDataSize); } } // process _HttpDownloadProcessData(pRef, iDataSize); return(0); } /*F********************************************************************************/ /*! \Function _HttpRecvData \Description Receive data from ProtoHttp using either ProtoHttpRecv() or ProtoHttpRecvAll(). \Input *pRef - module state \Input *pState - transaction state \Version 07/02/2013 (jbrookes) brought over from httpmgr */ /********************************************************************************F*/ static int32_t _HttpRecvData(HttpRefT *pRef) { char strBuf[16*1024]; int32_t iLen; // check for data if (!pRef->bRecvAll) { while ((iLen = ProtoHttpRecv(pRef->http, strBuf, 1, sizeof(strBuf))) > 0) { pRef->count += iLen; if (pRef->iOutFile != ZFILE_INVALID) { ZFileWrite(pRef->iOutFile, strBuf, iLen); } } } else { // receive all the data if ((iLen = ProtoHttpRecvAll(pRef->http, pRef->pOutData, pRef->iOutSize)) > 0) { pRef->count = iLen; if (pRef->iOutFile != ZFILE_INVALID) { ZFileWrite(pRef->iOutFile, pRef->pOutData, iLen); } iLen = PROTOHTTP_RECVDONE; } else if (iLen == PROTOHTTP_RECVBUFF) { // grow the buffer _HttpReallocBuff(pRef); // swallow error code iLen = 0; } } return(iLen); } /*F********************************************************************************/ /*! \Function _HttpDownloadProcess \Description Process a download transaction, using polling method \Input *pRef - module state \Version 10/28/2005 (jbrookes) */ /********************************************************************************F*/ static void _HttpDownloadProcess(HttpRefT *pRef) { int32_t iLen; // if we're not doing the write callback thing, poll for data for (iLen = 1; (iLen != PROTOHTTP_RECVWAIT) && (iLen != 0) && (pRef->state != IDLE); ) { // receive data iLen = _HttpRecvData(pRef); // process data _HttpDownloadProcessData(pRef, iLen); } } /*F********************************************************************************/ /*! \Function _HttpUploadProcess \Description Process an upload transaction. \Input *pRef - module state \Version 10/28/2005 (jbrookes) */ /********************************************************************************F*/ static void _HttpUploadProcess(HttpRefT *pRef) { int32_t iResult; // if no input file, nothing to send if (pRef->iInpFile == ZFILE_INVALID) { return; } // send all the data while ((pRef->state == UPLOAD) && (pRef->iSentSize < pRef->iDataSize)) { // do we need more data? if (pRef->iSendBufSent == pRef->iSendBufData) { if ((pRef->iSendBufData = ZFileRead(pRef->iInpFile, pRef->strFileBuffer, sizeof(pRef->strFileBuffer))) > 0) { ZPrintf("%s: read %d bytes from file\n", pRef->strModuleName, pRef->iSendBufData); pRef->iSendBufSent = 0; } else { ZPrintf("%s: error %d reading from file\n", pRef->strModuleName, pRef->iSendBufData); pRef->state = IDLE; } } // do we have buffered data to send? if (pRef->iSendBufSent < pRef->iSendBufData) { iResult = ProtoHttpSend(pRef->http, pRef->strFileBuffer + pRef->iSendBufSent, pRef->iSendBufData - pRef->iSendBufSent); if (iResult > 0) { pRef->iSentSize += iResult; pRef->iSendBufSent += iResult; ZPrintf("%s: sent %d bytes (%qd total)\n", pRef->strModuleName, iResult, pRef->iSentSize); } else if (iResult < 0) { ZPrintf("%s: ProtoHttpSend() failed; error %d\n", pRef->strModuleName, iResult); pRef->state = IDLE; } else { break; } } } // check for upload completion if (pRef->iSentSize == pRef->iDataSize) { int32_t ultime = NetTick() - pRef->sttime; // if streaming upload, signal we are done if (pRef->bStreaming == TRUE) { ProtoHttpSend(pRef->http, NULL, PROTOHTTP_STREAM_END); pRef->bStreaming = FALSE; } // done uploading ZPrintf("%s: upload complete (%qd bytes)\n", pRef->strModuleName, pRef->iSentSize); ZPrintf("%s: upload time: %d bytes in %.2f seconds (%.3f k/sec)\n", pRef->strModuleName, pRef->iSentSize, (float)ultime/1000.0f, ((float)pRef->iSentSize * 1000.0f) / ((float)ultime * 1024.0f)); // close the file ZFileClose(pRef->iInpFile); pRef->iInpFile = ZFILE_INVALID; // transition to download state to receive server response pRef->state = DNLOAD; } } /*F********************************************************************************/ /*! \Function _CmdHttpIdleCB \Description Callback to process while idle \Input *argz - \Input argc - \Input *argv[] - \Output int32_t - \Version 09/26/2007 (jbrookes) */ /********************************************************************************F*/ static int32_t _CmdHttpIdleCB(ZContext *argz, int32_t argc, char *argv[]) { HttpRefT *pRef = &_Http_Ref; // shut down? if ((argc == 0) || (pRef->http == NULL)) { if (pRef->http != NULL) { ProtoHttpDestroy(pRef->http); pRef->http = NULL; } return(0); } // give ref processing time ProtoHttpUpdate(pRef->http); // download processing (if not using write callback) if ((pRef->state == DNLOAD) && (!pRef->bUseWriteCb)) { _HttpDownloadProcess(pRef); } // upload processing if (pRef->state == UPLOAD) { _HttpUploadProcess(pRef); } // keep on idling return(ZCallback(&_CmdHttpIdleCB, HTTP_RATE)); } /*** Public Functions *************************************************************/ /*F********************************************************************************/ /*! \Function CmdHttp \Description Initiate an HTTP transaction. \Input *argz - \Input argc - \Input *argv[] - \Output int32_t - \Version 10/28/2005 (jbrookes) */ /********************************************************************************F*/ int32_t CmdHttp(ZContext *argz, int32_t argc, char *argv[]) { const char *pFileName = NULL, *pUrl; HttpRefT *pRef = &_Http_Ref; int32_t iResult = 0, iBufSize = HTTP_BUFSIZE; char strBuffer[4096]; int32_t iStartArg = 2; // first arg past get/put/delete/whatever if (argc < 2) { return(0); } // check for help if ((argc >= 2) && !strcmp(argv[1], "help")) { _CmdHttpUsage(argc, argv); return(iResult); } // check for 'parse' command if ((argc == 3) && !strcmp(argv[1], "parse")) { char strKind[8], strHost[128]; const char *pUri; int32_t iPort, iSecure; ds_memclr(strKind, sizeof(strKind)); ds_memclr(strHost, sizeof(strHost)); pUri = ProtoHttpUrlParse(argv[2], strKind, sizeof(strKind), strHost, sizeof(strHost), &iPort, &iSecure); ZPrintf("parsed url: kind=%s, host=%s, port=%d, secure=%d, uri=%s\n", strKind, strHost, iPort, iSecure, pUri); return(0); } // if not initialized yet, do so now if (_Http_bInitialized == FALSE) { ds_memclr(pRef, sizeof(*pRef)); ds_strnzcpy(pRef->strModuleName, argv[0], sizeof(pRef->strModuleName)); pRef->iInpFile = ZFILE_INVALID; pRef->iOutFile = ZFILE_INVALID; _Http_bInitialized = TRUE; } // check for explicit destroy if ((argc == 2) && !ds_stricmp(argv[1], "destroy")) { if (pRef->http != NULL) { ProtoHttpDestroy(pRef->http); pRef->http = NULL; } if (pRef->pClientCert != NULL) { ZMemFree(pRef->pClientCert); } if (pRef->pClientKey != NULL) { ZMemFree(pRef->pClientKey); } _Http_bInitialized = FALSE; return(iResult); } // check for create request if ((argc == 3) && !strcmp(argv[1], "create")) { iBufSize = (int32_t)strtol(argv[2], NULL, 10); } // check for request to set a CA certificate if ((argc == 3) && ((!strcmp(argv[1], "cert")) || (!strcmp(argv[1], "cer2")))) { const uint8_t *pFileData; int32_t iFileSize; // try and open file if ((pFileData = (const uint8_t *)ZFileLoad(argv[2], &iFileSize, ZFILE_OPENFLAG_RDONLY|ZFILE_OPENFLAG_BINARY)) != NULL) { if (!strcmp(argv[1], "cert")) { iResult = ProtoHttpSetCACert(pFileData, iFileSize); } else { iResult = ProtoHttpSetCACert2(pFileData, iFileSize); } ZMemFree((void *)pFileData); } else { ZPrintf("%s: unable to load certificate file '%s'\n", argv[0], argv[2]); } return((iResult >= 0) ? 0 : -1); } else if (!strcmp(argv[1], "cclr")) { ZPrintf("%s: clearing dynamic CA certs\n", argv[0]); ProtoHttpClrCACerts(); return(0); } else if (!strcmp(argv[1], "cver")) { int32_t iInvalid; ZPrintf("%s: verifying dynamic CA certs\n", argv[0]); if ((iInvalid = ProtoHttpValidateAllCA()) > 0) { ZPrintf("%s: could not verify %d CA certs\n", argv[0], iInvalid); iResult = -1; } return(iResult); } else if ((argc == 3) && !strcmp(argv[1], "scrt")) { if ((pRef->pClientCert = (char *)_HttpLoadCertificate(argv[2], &pRef->iCertSize)) == NULL) { ZPrintf("%s: could not load client certificate '%s'\n", argv[0], argv[2]); iResult = -1; } return(iResult); } else if ((argc == 3) && !strcmp(argv[1], "skey")) { if ((pRef->pClientKey = (char *)_HttpLoadCertificate(argv[2], &pRef->iKeySize)) == NULL) { ZPrintf("%s: could not load client private key '%s'\n", argv[0], argv[2]); iResult = -1; } return(iResult); } // test url parsing if ((argc == 3) && !strcmp(argv[1], "urlparse")) { return(_HttpUrlParseTest(argv[0], argv[2])); } // test url encoding if ((argc >= 3) && !strcmp(argv[1], "urlencode")) { return(_HttpUrlEncodeTest(argv[0], argv+2, argc-2)); } // create protohttp module if necessary if (pRef->http == NULL) { // disable the secure flags to help with our testing setup #if DIRTYCODE_GDK ProtoHttpControl(pRef->http, 'secu', 0, 0, NULL); #endif ZPrintf("%s: creating module with a %dkbyte buffer\n", argv[0], iBufSize); pRef->http = ProtoHttpCreate(iBufSize); if (pRef->http != NULL) { ProtoHttpCallback(pRef->http, _HttpCustomHeaderCallback, _HttpRecvHeaderCallback, pRef); pRef->iDebugLevel = 1; } } // check for create request -- if so, we're done if ((argc <= 3) && !strcmp(argv[1], "create")) { return(iResult); } else if ((argc > 2) && (argc < 7) && !strcmp(argv[1], "ctrl")) { int32_t iCmd, iValue = 0, iValue2 = 0; const char *pValue = NULL; iCmd = argv[2][0] << 24; iCmd |= argv[2][1] << 16; iCmd |= argv[2][2] << 8; iCmd |= argv[2][3]; if (argc > 3) { iValue = _GetIntArg(argv[3]); } if (argc > 4) { iValue2 = _GetIntArg(argv[4]); } if (argc > 5) { pValue = argv[5]; } // snoop 'spam' if (iCmd == 'spam') { pRef->iDebugLevel = iValue; } return(ProtoHttpControl(pRef->http, iCmd, iValue, iValue2, (void *)pValue)); } else if ((argc == 3) && !strcmp(argv[1], "stat")) { int32_t iCmd; iCmd = argv[2][0] << 24; iCmd |= argv[2][1] << 16; iCmd |= argv[2][2] << 8; iCmd |= argv[2][3]; iResult = ProtoHttpStatus(pRef->http, iCmd, strBuffer, sizeof(strBuffer)); ZPrintf("http: ProtoHttpStatus('%s') returned %d\n", argv[2], iResult); if (strBuffer[0] != '\0') { ZPrintf("%s\n", strBuffer); } return(0); } // check for setting of base url else if ((argc == 3) && !strcmp(argv[1], "base")) { ProtoHttpSetBaseUrl(pRef->http, argv[2]); return(iResult); } // check for debug setting else if (!strcmp(argv[1], "debug") && (argc == 3)) { int32_t iDebugLevel = (int32_t)strtol(argv[2], NULL, 10); pRef->iDebugLevel = iDebugLevel; return(iResult); } else if (!ds_stricmp(argv[1], "abrt")) { ProtoHttpAbort(pRef->http); pRef->state = IDLE; return(iResult); } // check for valid get/put request else if ((!ds_stricmp(argv[1], "get") || !ds_stricmp(argv[1], "head") || !ds_stricmp(argv[1], "put") || !ds_stricmp(argv[1], "post") || !ds_stricmp(argv[1], "puts") || !ds_stricmp(argv[1], "delete") || !ds_stricmp(argv[1], "options")) && ((argc > 2) || (argc < 6))) { int32_t iArg; // reset to clear any previous stats _HttpReset(pRef); // init start timer pRef->sttime = NetTick(); // set up append header ds_strnzcpy(pRef->strApndHdr, HTTP_APNDHDR, sizeof(pRef->strApndHdr)); // check for args for (iArg = 2; (iArg < argc) && (argv[iArg][0] == '-'); iArg += 1) { if (!ds_strnicmp(argv[iArg], "-header=", 8)) { ds_strnzcat(pRef->strApndHdr, argv[iArg]+8, sizeof(pRef->strApndHdr)); ds_strnzcat(pRef->strApndHdr, "\r\n", sizeof(pRef->strApndHdr)); } if (!ds_strnicmp(argv[iArg], "-writecb", 8)) { pRef->bUseWriteCb = TRUE; } if (!ds_strnicmp(argv[iArg], "-recvall", 8)) { pRef->bRecvAll = TRUE; } // skip any option arguments to find url and (optionally) filename iStartArg += 1; } // set client cert if specified if (pRef->pClientCert != NULL) { ProtoHttpControl(pRef->http, 'scrt', pRef->iCertSize, 0, pRef->pClientCert); } // set client key if specified if (pRef->pClientKey != NULL) { ProtoHttpControl(pRef->http, 'skey', pRef->iKeySize, 0, pRef->pClientKey); } // fall-through to code below } else { ZPrintf(" unrecognized or badly formatted command '%s'\n", argv[1]); _CmdHttpUsage(argc, argv); return(iResult); } // locate url and filename pUrl = argv[iStartArg]; if (argc > iStartArg) { pFileName = argv[iStartArg+1]; } // do we have a url? if (pUrl != NULL) { const char* pTemp = pUrl; //< Used for parsing the query param char strKind[5], strHost[128], strName[32], strValue[32]; int32_t iPort, iSecure; // get url info ProtoHttpUrlParse(pUrl, strKind, sizeof(strKind), strHost, sizeof(strHost), &iPort, &iSecure); // if the host has changed, reset cookie buffer if (ds_stricmp(pRef->strHost, strHost) && (pRef->strCookie[0] != '\0')) { ZPrintf("http: host change to %s; resetting cookies from %s\n", strHost, pRef->strHost); pRef->strCookie[0] = '\0'; } // save host ds_strnzcpy(pRef->strHost, strHost, sizeof(strHost)); while (ProtoHttpGetNextParam(pRef->http, pTemp, strName, sizeof(strName), strValue, sizeof(strValue), &pTemp) == 0) { ProtoHttpUrlDecodeStrParm(strValue, strValue, sizeof(strValue)); #if 0 ZPrintf("http query param: key=%s, value=%s\n", strName, strValue); #endif } } else { return(0); } // set append header strBuffer[0] = '\0'; if (pRef->strCookie[0] != '\0') { ds_snzprintf(strBuffer, sizeof(strBuffer), "Cookie: %s\r\n", pRef->strCookie); } ds_strnzcat(strBuffer, pRef->strApndHdr, sizeof(strBuffer)); ProtoHttpControl(pRef->http, 'apnd', 0, 0, strBuffer); // set debug level ProtoHttpControl(pRef->http, 'spam', pRef->iDebugLevel, 0, NULL); // if we're uploading, open required input file if (!ds_stricmp(argv[1], "put") || !ds_stricmp(argv[1], "post") || !ds_stricmp(argv[1], "puts")) { ZPrintf("%s: uploading %s to %s\n", argv[0], pFileName, pUrl); // assume failure iResult = -1; // try and open file if ((pRef->iInpFile = ZFileOpen(pFileName, ZFILE_OPENFLAG_RDONLY|ZFILE_OPENFLAG_BINARY)) != ZFILE_INVALID) { // get the file size if ((pRef->iDataSize = ZFileSize(pRef->iInpFile)) > 0) { // load data from file if ((pRef->iSendBufData = ZFileRead(pRef->iInpFile, pRef->strFileBuffer, sizeof(pRef->strFileBuffer))) > 0) { if (ds_stricmp(argv[1], "puts")) { // initiate put/post transaction ZPrintf("%s: uploading %d bytes\n", argv[0], pRef->iDataSize); if ((pRef->iSendBufSent = ProtoHttpPost(pRef->http, pUrl, pRef->strFileBuffer, pRef->iDataSize, !ds_stricmp(argv[1], "put") ? PROTOHTTP_PUT : PROTOHTTP_POST)) < 0) { ZPrintf("%s: error %d initiating send\n", argv[0], pRef->iSendBufSent); iResult = -1; } else if (pRef->iSendBufSent >= 0) { ZPrintf("%s: sent %d bytes\n", argv[0], pRef->iSendBufSent); pRef->iSentSize = pRef->iSendBufSent; } } else { // initiate streaming put if ((iResult = ProtoHttpPost(pRef->http, pUrl, NULL, PROTOHTTP_STREAM_BEGIN, PROTOHTTP_POST)) >= 0) { pRef->bStreaming = TRUE; } } // wait for reply pRef->state = UPLOAD; // locate output file pFileName = (argc > (iStartArg+2)) ? argv[iStartArg+2] : NULL; } else { ZPrintf("%s: error %d reading data from file\n", argv[0], pRef->iSendBufData, pFileName); } } else { ZPrintf("%s: error %d getting size of file %s\n", argv[0], pRef->iDataSize, pFileName); } } else { ZPrintf("%s: unable to load file '%s'\n", argv[0], pFileName); } } // open output file? if (pFileName != NULL) { ZPrintf("%s: saving %s data to %s\n", argv[0], pUrl, pFileName); if ((pRef->iOutFile = ZFileOpen(pFileName, ZFILE_OPENFLAG_WRONLY|ZFILE_OPENFLAG_BINARY|ZFILE_OPENFLAG_CREATE)) == ZFILE_INVALID) { ZPrintf("%s: error opening file '%s' for writing\n", argv[0], pFileName); } } // issue request if (!ds_stricmp(argv[1], "get") || !ds_stricmp(argv[1], "head") || !ds_stricmp(argv[1], "delete") || !ds_stricmp(argv[1], "options")) { ProtoHttpRequestTypeE eRequestType; ZPrintf("%s: downloading %s\n", argv[0], pUrl); // map to protohttp request type if (!ds_stricmp(argv[1], "head")) { eRequestType = PROTOHTTP_REQUESTTYPE_HEAD; } else if (!ds_stricmp(argv[1], "get")) { eRequestType = PROTOHTTP_REQUESTTYPE_GET; } else if (!ds_stricmp(argv[1], "delete")) { eRequestType = PROTOHTTP_REQUESTTYPE_DELETE; } else if (!ds_stricmp(argv[1], "options")) { eRequestType = PROTOHTTP_REQUESTTYPE_OPTIONS; } else { ZPrintf("%s: unrecognized request %s\n", argv[0], argv[1]); return(-1); } if (pRef->bUseWriteCb) { iResult = ProtoHttpRequestCb(pRef->http, pUrl, NULL, 0, eRequestType, _HttpWriteCb, pRef); } else { iResult = ProtoHttpRequest(pRef->http, pUrl, NULL, 0, eRequestType); } if (iResult == 0) { pRef->state = DNLOAD; } } // set up recurring callback to process transaction if (pRef->state != IDLE) { iResult = ZCallback(_CmdHttpIdleCB, HTTP_RATE); } return(iResult); }