/** * This is an asynchronous protocol implementation for HTTP. * It includes special features to store binary data coming * from a SINQ http histogram memory in a sinqdata object. * Which has to be specified on initialisation. * * copyright: see file COPYRIGHT * * After finding that libghttp actually blocks on sockets, this is * a reimplementation doing everything itself. * * Mark Koennecke, November 2010 */ #include #include #include #include #include #include #include "sicshipadaba.h" #include extern char *trim(char *txt); /*------------------------------------------------------------------------------- * taken from libghttp */ static const char b64_alphabet[65] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/=" }; static char * http_base64_encode(const char *text) { /* The tricky thing about this is doing the padding at the end, * doing the bit manipulation requires a bit of concentration only */ char *buffer = NULL; char *point = NULL; int inlen = 0; int outlen = 0; /* check our args */ if (text == NULL) return NULL; /* Use 'buffer' to store the output. Work out how big it should be... * This must be a multiple of 4 bytes */ inlen = strlen( text ); /* check our arg...avoid a pesky FPE */ if (inlen == 0) { buffer = malloc(sizeof(char)); buffer[0] = '\0'; return buffer; } outlen = (inlen*4)/3; if( (inlen % 3) > 0 ) /* got to pad */ outlen += 4 - (inlen % 3); buffer = malloc( outlen + 1 ); /* +1 for the \0 */ memset(buffer, 0, outlen + 1); /* initialize to zero */ /* now do the main stage of conversion, 3 bytes at a time, * leave the trailing bytes (if there are any) for later */ for( point=buffer; inlen>=3; inlen-=3, text+=3 ) { *(point++) = b64_alphabet[ *text>>2 ]; *(point++) = b64_alphabet[ (*text<<4 & 0x30) | *(text+1)>>4 ]; *(point++) = b64_alphabet[ (*(text+1)<<2 & 0x3c) | *(text+2)>>6 ]; *(point++) = b64_alphabet[ *(text+2) & 0x3f ]; } /* Now deal with the trailing bytes */ if( inlen ) { /* We always have one trailing byte */ *(point++) = b64_alphabet[ *text>>2 ]; *(point++) = b64_alphabet[ (*text<<4 & 0x30) | (inlen==2?*(text+1)>>4:0) ]; *(point++) = (inlen==1?'=':b64_alphabet[ *(text+1)<<2 & 0x3c ] ); *(point++) = '='; } *point = '\0'; return buffer; } /*---------------------------------------------------------------------*/ typedef struct { char *userName; char *password; pSICSData binData; pHdb node; int sockHandle; int bytesExpected; char *contentType; int headerReceived; int byteSwap; } HttpProt, *pHttpProt; /*---------------------------------------------------------------------*/ static int moreToReadThen(Ascon *a, int length) { pHttpProt pHttp = NULL; pHttp = (pHttpProt)a->private; if(pHttp->headerReceived == 0) { return 0; } if(pHttp->bytesExpected - GetDynStringLength(a->rdBuffer) > length){ return 1; } else { return 0; } } /*---------------------------------------------------------------------*/ static int HTTPcallback(int handle, void *userData) { Ascon *a = (Ascon *)userData; pHttpProt pHttp = NULL; unsigned char ch; int length; char *data; int blockSize = 65536; if(a == NULL){ printf("really serious error in HTTPcallback\n"); return 0; } pHttp = (pHttpProt)a->private; data = (char *)ANETreadPtr(handle,&length); if(data != NULL){ /* This code here should optimize away excessive calls to memcpy which I have seen in a sysprof test on SICS @ BOA */ if(moreToReadThen(a,blockSize) == 1 && length < blockSize){ // skip } else { DynStringConcatBytes(a->rdBuffer,data,length); ANETreadConsume(handle,length); } } return 1; } /*---------------------------------------------------------------------*/ static void sendAuth(pHttpProt pHttp) { char buffer[256]; char *encoded = NULL; if(pHttp->userName != NULL){ snprintf(buffer,255,"%s:%s", pHttp->userName,pHttp->password); encoded = http_base64_encode(buffer); if(encoded != NULL){ snprintf(buffer,255,"Authorization: Basic %s\r\n\r\n", encoded); free(encoded); } } else { strcpy(buffer,"\r\n"); } ANETwrite(pHttp->sockHandle,buffer,strlen(buffer)); } /*---------------------------------------------------------------------*/ static void sendGet(pHttpProt pHttp, char *path) { char buffer[1024]; char hostname[256]; ANETinfo(pHttp->sockHandle,hostname,sizeof(hostname)); snprintf(buffer,1024,"GET %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n", path, hostname); ANETwrite(pHttp->sockHandle, buffer,strlen(buffer)); sendAuth(pHttp); } /*---------------------------------------------------------------------*/ static void sendPost(pHttpProt pHttp, char *path, char *data) { char buffer[1024]; char hostname[256]; ANETinfo(pHttp->sockHandle,hostname,sizeof(hostname)); snprintf(buffer,1024,"POST %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n", path, hostname); ANETwrite(pHttp->sockHandle, buffer,strlen(buffer)); snprintf(buffer,1024,"Content-Length: %d\r\n", (int)strlen(data)); ANETwrite(pHttp->sockHandle, buffer,strlen(buffer)); sendAuth(pHttp); ANETwrite(pHttp->sockHandle,data,strlen(data)); } /*---------------------------------------------------------------------*/ static void sendRequest(pHttpProt pHttp, char *data) { char *path, *dataCopy, *pPtr; dataCopy = strdup(data); pHttp->node = NULL; pHttp->headerReceived = 0; pPtr = strchr(dataCopy,':'); if(pPtr == NULL){ path = dataCopy; sendGet(pHttp,path); free(dataCopy); return; } else { if(strstr(dataCopy,"node") != NULL){ path = pPtr+1; pPtr = strchr(path,':'); *pPtr = '\0'; pHttp->node = FindHdbNode(NULL,path,pServ->dummyCon); path = pPtr+1; sendGet(pHttp,path); free(dataCopy); return; } else if(strstr(dataCopy,"post") != NULL){ path = pPtr+1; pPtr = strchr(path,':'); *pPtr = '\0'; sendPost(pHttp,path, pPtr+1); free(dataCopy); } else if(strstr(dataCopy,"processhmdata.egi") != NULL){ path = dataCopy; sendGet(pHttp,path); free(dataCopy); return; } } } /*---------------------------------------------------------------------*/ static void handleReply(Ascon * a) { char *pPtr = NULL, *pType = NULL, *path = NULL; int len, i, *dataPtr = NULL; HistInt *hmData = NULL; pHttpProt pHttp = (pHttpProt) a->private; pPtr = GetCharArray(a->rdBuffer); len = GetDynStringLength(a->rdBuffer); if (strstr(pPtr, "ERROR") != NULL) { AsconError(a, pPtr, 0); } else if (strstr(pPtr, "Authentication Error") != NULL) { AsconError(a, pPtr, 0); } else { pType = pHttp->contentType; if (strstr(pType, "sinqhm") != NULL) { hmData = (HistInt *) pPtr; len = len / sizeof(HistInt); /* if(len == 0) { printf("Blllllllllaaaaaaaaaaeeeeeeeeeerrrrrrrrrrrkkkk!\n"); } */ if(pHttp->node == NULL){ clearSICSData(pHttp->binData); dataPtr = getSICSDataPointer(pHttp->binData, 0, len); if(pHttp->byteSwap == 1){ for (i = 0; i < len; i++) { dataPtr[i] = ntohl(hmData[i]); } } else { for (i = 0; i < len; i++) { dataPtr[i] = hmData[i]; } } assignSICSType(pHttp->binData, 0, len, INTTYPE); DynStringClear(a->rdBuffer); DynStringCopy(a->rdBuffer, "SICSDATA"); /* printf("SICSDATA received..........\n"); */ } else { if(pHttp->node->value.arrayLength != len){ if(pHttp->node->value.v.intArray != NULL){ free(pHttp->node->value.v.intArray); } pHttp->node->value.v.intArray = malloc(len*sizeof(int)); if(pHttp->node->value.v.intArray == NULL){ AsconError(a,"Out of memory ",0); return; } pHttp->node->value.arrayLength = len; /* printf("SINQHTTPOPT setting length to %d\n", len); */ } if(pHttp->byteSwap == 1){ for(i = 0; i < len; i++){ pHttp->node->value.v.intArray[i] = ntohl(hmData[i]); } } else { for(i = 0; i < len; i++){ pHttp->node->value.v.intArray[i] = hmData[i]; } } NotifyHipadabaPar(pHttp->node,NULL); DynStringClear(a->rdBuffer); DynStringCopy(a->rdBuffer, "NODEDATA"); /* path = GetHipadabaPath(pHttp->node); if(path != NULL){ printf("Sinqhttpopt has updated node: %s, length = %d\n", path, len); free(path); } */ } } } } /*---------------------------------------------------------------------*/ static int processHeader(Ascon *a) { char line[132], *pPtr, *data, *colon; pHttpProt pHttp = (pHttpProt) a->private; int status, length; data = GetCharArray(a->rdBuffer); assert(data != NULL); /* * printf("%s",data); */ pPtr = data; pPtr = stptok(pPtr, line, 132, "\n"); sscanf(line,"HTTP/1.%*d %03d", &status); if(status != 200 && status != 201){ AsconError(a,line,AsconFailure); return 0; } while((pPtr = stptok(pPtr,line,132,"\n")) != NULL){ colon = strchr(line,':'); if(colon != NULL){ *colon = '\0'; colon++; strtolower(line); if(strstr(line,"content-length") != NULL){ pHttp->bytesExpected = atoi(trim(colon)); } if(strstr(line,"content-type") != NULL){ if(pHttp->contentType != NULL){ free(pHttp->contentType); } pHttp->contentType = strdup(trim(colon)); } if(strstr(line,"connection") != NULL){ strtolower(colon); if(strstr(colon,"close") != NULL){ pHttp->bytesExpected = INT32_MAX; } } } } pHttp->headerReceived = 1; /** * Hack off the header */ pPtr = strstr(data, "\r\n\r\n"); length = GetDynStringLength(a->rdBuffer); length -= 4 + (pPtr - data); if(length > 0){ data = malloc(length*sizeof(char)); if(data == NULL){ AsconError(a,"Out of memory", AsconFailure); return 0; } memcpy(data,pPtr+4,length); DynStringClear(a->rdBuffer); DynStringConcatBytes(a->rdBuffer,data,length); free(data); } else { DynStringClear(a->rdBuffer); } DynStringCapacity(a->rdBuffer,pHttp->bytesExpected); if(pHttp->bytesExpected < 0){ return 0; } else { return 1; } } /*---------------------------------------------------------------------*/ static int HttpHandler(Ascon * a) { pHttpProt pHttp = (pHttpProt) a->private; int socke, selStat, port, status; fd_set rmask; struct timeval tmo = { 0, 0 }; char buffer[1024]; char *hostport, *colon; switch (a->state) { case AsconConnectStart: a->state = AsconConnecting; break; case AsconConnecting: a->state = AsconConnectDone; /* success */ break; case AsconWriteStart: if(strcmp(GetCharArray(a->wrBuffer),"byteswapoff") == 0) { pHttp->byteSwap = 0; a->state = AsconReadDone; return 0; } if(!ANETvalidHandle(pHttp->sockHandle)){ hostport = strdup(a->hostport); colon = strchr(hostport, ':'); if (colon == NULL){ port = 80; } else { *colon = '\0'; port = atoi(colon + 1); } if (port <= 0) { AsconError(a, "bad port number", 0); return 1; } pHttp->sockHandle = ANETconnect(hostport,port); free(hostport); if(pHttp->sockHandle < 0){ AsconError(a,"Failed to connect", 0); } else { ANETsetReadCallback(pHttp->sockHandle, HTTPcallback, a,NULL); a->state = AsconWriting; } } else { a->state = AsconWriting; } return 1; break; case AsconWriting: sendRequest(pHttp,GetCharArray(a->wrBuffer)); a->state = AsconWriteDone; a->start = DoubleTime(); pHttp->bytesExpected = -1; DynStringClear(a->rdBuffer); return 1; break; case AsconReadStart: a->state= AsconReading; return 1; break; case AsconReading: ANETprocess(); /** * Here we have basically four conditions to check: * - detected means that we received * the complete header. * - enough bytes read: termination * - socket closed means all data has been read * - timeout waiting for a response */ if(strstr(GetCharArray(a->rdBuffer), "\r\n\r\n") != NULL){ status = processHeader(a); if(status != 1){ a->state = AsconReadDone; break; } } if(!ANETvalidHandle(pHttp->sockHandle)){ if(pHttp->headerReceived) { handleReply(a); a->state = AsconReadDone; } else { /* * We only noticed when attempting to read that the WWW-server has closed * the connection. ANETclose will have killed the read and write buffers. * So redo everything...... */ a->state = AsconWriteStart; return 1; } break; } if(pHttp->bytesExpected > 0 && GetDynStringLength(a->rdBuffer) >= pHttp->bytesExpected ){ handleReply(a); a->state = AsconReadDone; break; } if (a->timeout > 0) { if (DoubleTime() - a->start > a->timeout) { AsconError(a, "no response", 0); printf("Timeout on httpopt\n"); ANETclose(pHttp->sockHandle); a->state = AsconTimeout; } } return 0; break; default: return AsconBaseHandler(a); } return 1; } /*------------------------------------------------------------------------*/ static void killHttp(void *data) { pHttpProt prot = (pHttpProt) data; if (prot == NULL) { return; } if (prot->password != NULL) { free(prot->password); } if (prot->userName != NULL) { free(prot->userName); } if(prot->contentType != NULL){ free(prot->contentType); } free(prot); } /*------------------------------------------------------------------------*/ static int HttpProtInit(Ascon * a, SConnection * con, int argc, char *argv[]) { pHttpProt pHttp = NULL; pHttp = calloc(sizeof(HttpProt), 1); if (pHttp == NULL) { SCWrite(con, "ERROR: out of memory in HttpProtInit", eError); return 0; } if (argc < 3) { return 0; } a->hostport = strdup(argv[1]); pHttp->binData = (pSICSData) FindCommandData(pServ->pSics, argv[2], "SICSData"); if (pHttp->binData == NULL) { SCWrite(con, "ERROR: SICSData object not found", eError); return 0; } if (argc > 3) { a->timeout = atof(argv[3]); } else { a->timeout = 10.; } if (argc > 5) { pHttp->userName = strdup(argv[4]); pHttp->password = strdup(argv[5]); } pHttp->sockHandle = -10; pHttp->contentType = strdup("text/html"); pHttp->byteSwap = 1; a->private = pHttp; a->killPrivate = killHttp; a->state = AsconConnectStart; return 1; } /*-------------------------------------------------------------------------*/ void AddHttpOptProtocoll() { AsconProtocol *prot = NULL; prot = calloc(sizeof(AsconProtocol), 1); prot->name = strdup("sinqhttpopt"); prot->init = HttpProtInit; prot->handler = HttpHandler; AsconInsertProtocol(prot); }