564 lines
15 KiB
C
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);
|
|
}
|