Files
sicspsi/sinqhttpopt.c
2016-04-08 11:24:02 +02:00

564 lines
15 KiB
C

/**
* 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 <stdio.h>
#include <ascon.h>
#include <ascon.i>
#include <unistd.h>
#include <sicsdata.h>
#include <HistMem.h>
#include "sicshipadaba.h"
#include <stptok.h>
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:
* - <cr><ld><cr><lf> 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);
}