|
/***************************************************************************
|
|
begin : Wed Apr 28 2010
|
|
copyright : (C) 2010 by Martin Preuss
|
|
email : martin@libchipcard.de
|
|
|
|
***************************************************************************
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Lesser General Public *
|
|
* License as published by the Free Software Foundation; either *
|
|
* version 2.1 of the License, or (at your option) any later version. *
|
|
* *
|
|
* This library is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
|
* Lesser General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Lesser General Public *
|
|
* License along with this library; if not, write to the Free Software *
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, *
|
|
* MA 02111-1307 USA *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#define DISABLE_DEBUGLOG
|
|
|
|
|
|
|
|
#include "syncio_http_p.h"
|
|
#include "i18n_l.h"
|
|
|
|
#include <gwenhywfar/misc.h>
|
|
#include <gwenhywfar/debug.h>
|
|
#include <gwenhywfar/gui.h>
|
|
#include <gwenhywfar/text.h>
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
|
|
|
|
GWEN_INHERIT(GWEN_SYNCIO, GWEN_SYNCIO_HTTP)
|
|
|
|
|
|
|
|
GWEN_SYNCIO *GWEN_SyncIo_Http_new(GWEN_SYNCIO *baseIo)
|
|
{
|
|
GWEN_SYNCIO *sio;
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
|
|
sio=GWEN_SyncIo_new(GWEN_SYNCIO_HTTP_TYPE, baseIo);
|
|
GWEN_NEW_OBJECT(GWEN_SYNCIO_HTTP, xio);
|
|
GWEN_INHERIT_SETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio, xio, GWEN_SyncIo_Http_FreeData);
|
|
|
|
GWEN_SyncIo_SetConnectFn(sio, GWEN_SyncIo_Http_Connect);
|
|
GWEN_SyncIo_SetDisconnectFn(sio, GWEN_SyncIo_Http_Disconnect);
|
|
GWEN_SyncIo_SetReadFn(sio, GWEN_SyncIo_Http_Read);
|
|
GWEN_SyncIo_SetWriteFn(sio, GWEN_SyncIo_Http_Write);
|
|
|
|
xio->dbCommandIn=GWEN_DB_Group_new("command");
|
|
xio->dbStatusIn=GWEN_DB_Group_new("status");
|
|
xio->dbHeaderIn=GWEN_DB_Group_new("header");
|
|
|
|
xio->dbCommandOut=GWEN_DB_Group_new("command");
|
|
xio->dbStatusOut=GWEN_DB_Group_new("status");
|
|
xio->dbHeaderOut=GWEN_DB_Group_new("header");
|
|
|
|
|
|
return sio;
|
|
}
|
|
|
|
|
|
|
|
void GWENHYWFAR_CB GWEN_SyncIo_Http_FreeData(GWEN_UNUSED void *bp, void *p)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
|
|
xio=(GWEN_SYNCIO_HTTP *) p;
|
|
|
|
GWEN_DB_Group_free(xio->dbCommandOut);
|
|
GWEN_DB_Group_free(xio->dbStatusOut);
|
|
GWEN_DB_Group_free(xio->dbHeaderOut);
|
|
|
|
GWEN_DB_Group_free(xio->dbCommandIn);
|
|
GWEN_DB_Group_free(xio->dbStatusIn);
|
|
GWEN_DB_Group_free(xio->dbHeaderIn);
|
|
|
|
GWEN_FREE_OBJECT(xio);
|
|
}
|
|
|
|
|
|
|
|
int GWENHYWFAR_CB GWEN_SyncIo_Http_Connect(GWEN_SYNCIO *sio)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
GWEN_SYNCIO *baseIo;
|
|
int rv;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
if (GWEN_SyncIo_GetStatus(sio)==GWEN_SyncIo_Status_Connected) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Already connected");
|
|
return 0;
|
|
}
|
|
|
|
baseIo=GWEN_SyncIo_GetBaseIo(sio);
|
|
assert(baseIo);
|
|
|
|
rv=GWEN_SyncIo_Connect(baseIo);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
return rv;
|
|
}
|
|
|
|
GWEN_SyncIo_SetStatus(sio, GWEN_SyncIo_Status_Connected);
|
|
GWEN_SyncIo_Http_SetReadIdle(sio);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int GWENHYWFAR_CB GWEN_SyncIo_Http_Disconnect(GWEN_SYNCIO *sio)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
GWEN_SYNCIO *baseIo;
|
|
int rv;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
if (GWEN_SyncIo_GetStatus(sio)!=GWEN_SyncIo_Status_Connected) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Not connected");
|
|
return GWEN_ERROR_NOT_CONNECTED;
|
|
}
|
|
|
|
baseIo=GWEN_SyncIo_GetBaseIo(sio);
|
|
assert(baseIo);
|
|
|
|
rv=GWEN_SyncIo_Disconnect(baseIo);
|
|
GWEN_SyncIo_SetStatus(sio, GWEN_SyncIo_Status_Disconnected);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
return rv;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
void GWEN_SyncIo_Http_SetReadIdle(GWEN_SYNCIO *sio)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
xio->readMode=GWEN_SyncIo_Http_Mode_Idle;
|
|
}
|
|
|
|
|
|
|
|
int GWENHYWFAR_CB GWEN_SyncIo_Http_Read(GWEN_SYNCIO *sio,
|
|
uint8_t *buffer,
|
|
uint32_t size)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
int rv;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
if (GWEN_SyncIo_GetStatus(sio)!=GWEN_SyncIo_Status_Connected) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Not connected");
|
|
return GWEN_ERROR_NOT_CONNECTED;
|
|
}
|
|
|
|
if (xio->readMode==GWEN_SyncIo_Http_Mode_Idle) {
|
|
const char *s;
|
|
|
|
/* reset status and headers */
|
|
GWEN_DB_ClearGroup(xio->dbCommandIn, NULL);
|
|
GWEN_DB_ClearGroup(xio->dbStatusIn, NULL);
|
|
GWEN_DB_ClearGroup(xio->dbHeaderIn, NULL);
|
|
|
|
if (GWEN_SyncIo_GetFlags(sio) & GWEN_SYNCIO_FLAGS_PASSIVE) {
|
|
/* read command */
|
|
rv=GWEN_SyncIo_Http_ReadCommand(sio);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
xio->readMode=GWEN_SyncIo_Http_Mode_Error;
|
|
return rv;
|
|
}
|
|
|
|
/* possibly read header */
|
|
s=GWEN_DB_GetCharValue(xio->dbCommandIn, "protocol", 0, "HTTP/1.0");
|
|
if (!(s && strcasecmp(s, "HTTP/0.9")==0)) {
|
|
rv=GWEN_SyncIo_Http_ReadHeader(sio);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
xio->readMode=GWEN_SyncIo_Http_Mode_Error;
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* read status */
|
|
rv=GWEN_SyncIo_Http_ReadStatus(sio);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
xio->readMode=GWEN_SyncIo_Http_Mode_Error;
|
|
return rv;
|
|
}
|
|
|
|
/* possibly read header */
|
|
s=GWEN_DB_GetCharValue(xio->dbStatusIn, "protocol", 0, "HTTP/1.0");
|
|
if (!(s && strcasecmp(s, "HTTP/0.9")==0)) {
|
|
rv=GWEN_SyncIo_Http_ReadHeader(sio);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
xio->readMode=GWEN_SyncIo_Http_Mode_Error;
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (xio->readMode==GWEN_SyncIo_Http_Mode_ChunkSize) {
|
|
rv=GWEN_SyncIo_Http_ReadChunkSize(sio);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
xio->readMode=GWEN_SyncIo_Http_Mode_Error;
|
|
return rv;
|
|
}
|
|
if (xio->currentReadChunkSize==0) {
|
|
int rv2;
|
|
GWEN_BUFFER *tbuf;
|
|
|
|
/* all chunks finished, read trailing CR/LF */
|
|
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
|
|
rv2=GWEN_SyncIo_Http_ReadLine(sio, tbuf);
|
|
if (rv2<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv2);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv2;
|
|
}
|
|
GWEN_Buffer_free(tbuf);
|
|
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Chunks finished.");
|
|
|
|
/* chunksize is 0, body ended */
|
|
GWEN_SyncIo_Http_SetReadIdle(sio);
|
|
return 0;
|
|
}
|
|
else if (xio->currentReadChunkSize==-1) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Undetermined chunksize in chunked mode? Aborting.");
|
|
xio->readMode=GWEN_SyncIo_Http_Mode_Error;
|
|
return GWEN_ERROR_BAD_DATA;
|
|
}
|
|
|
|
/* chunksize known, next will be to read that chunk */
|
|
xio->readMode=GWEN_SyncIo_Http_Mode_Chunk;
|
|
}
|
|
|
|
if (xio->readMode==GWEN_SyncIo_Http_Mode_Chunk) {
|
|
/* read chunk */
|
|
rv=GWEN_SyncIo_Http_ReadChunk(sio, buffer, size);
|
|
if (rv<0) {
|
|
xio->readMode=GWEN_SyncIo_Http_Mode_Error;
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
return rv;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
if (xio->readMode==GWEN_SyncIo_Http_Mode_Body) {
|
|
/* read chunk */
|
|
rv=GWEN_SyncIo_Http_ReadBody(sio, buffer, size);
|
|
if (rv<0) {
|
|
xio->readMode=GWEN_SyncIo_Http_Mode_Error;
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
return rv;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
if (xio->readMode==GWEN_SyncIo_Http_Mode_Error) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Previous read error");
|
|
return GWEN_ERROR_GENERIC;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int GWENHYWFAR_CB GWEN_SyncIo_Http_Write(GWEN_SYNCIO *sio,
|
|
const uint8_t *buffer,
|
|
uint32_t size)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
GWEN_SYNCIO *baseIo;
|
|
int rv;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
baseIo=GWEN_SyncIo_GetBaseIo(sio);
|
|
assert(baseIo);
|
|
|
|
if (GWEN_SyncIo_GetStatus(sio)!=GWEN_SyncIo_Status_Connected) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Not connected");
|
|
return GWEN_ERROR_NOT_CONNECTED;
|
|
}
|
|
|
|
if (xio->writeMode==GWEN_SyncIo_Http_Mode_Idle) {
|
|
const char *s;
|
|
|
|
if (GWEN_SyncIo_GetFlags(sio) & GWEN_SYNCIO_FLAGS_PASSIVE)
|
|
/* write status */
|
|
rv=GWEN_SyncIo_Http_WriteStatus(sio);
|
|
else
|
|
/* write command */
|
|
rv=GWEN_SyncIo_Http_WriteCommand(sio);
|
|
if (rv<0) {
|
|
xio->writeMode=GWEN_SyncIo_Http_Mode_Error;
|
|
return rv;
|
|
}
|
|
|
|
/* possibly write header */
|
|
s=GWEN_DB_GetCharValue(xio->dbCommandOut, "protocol", 0, "HTTP/1.0");
|
|
if (!(s && strcasecmp(s, "HTTP/0.9")==0)) {
|
|
rv=GWEN_SyncIo_Http_WriteHeader(sio);
|
|
if (rv<0) {
|
|
xio->writeMode=GWEN_SyncIo_Http_Mode_Error;
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (xio->writeMode==GWEN_SyncIo_Http_Mode_ChunkSize) {
|
|
rv=GWEN_SyncIo_Http_WriteChunkSize(sio, size);
|
|
if (rv<0) {
|
|
xio->writeMode=GWEN_SyncIo_Http_Mode_Error;
|
|
return rv;
|
|
}
|
|
if (size==0) {
|
|
/* chunksize is 0, body ended */
|
|
GWEN_SyncIo_Http_SetWriteIdle(sio);
|
|
return 0;
|
|
}
|
|
|
|
/* chunksize known, next will be to write that chunk */
|
|
xio->writeMode=GWEN_SyncIo_Http_Mode_Chunk;
|
|
}
|
|
|
|
if (xio->writeMode==GWEN_SyncIo_Http_Mode_Chunk) {
|
|
/* we want to write binary data transparently */
|
|
GWEN_SyncIo_AddFlags(baseIo, GWEN_SYNCIO_FLAGS_TRANSPARENT);
|
|
rv=GWEN_SyncIo_WriteForced(baseIo, buffer, size);
|
|
if (rv<0) {
|
|
xio->writeMode=GWEN_SyncIo_Http_Mode_Error;
|
|
return rv;
|
|
}
|
|
xio->writeMode=GWEN_SyncIo_Http_Mode_ChunkSize;
|
|
|
|
return rv;
|
|
}
|
|
|
|
if (xio->writeMode==GWEN_SyncIo_Http_Mode_Body) {
|
|
if ((xio->currentWriteBodySize!=-1) &&
|
|
((int)size>xio->currentWriteBodySize)) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Size is beyond total body size (%d)!", size);
|
|
xio->writeMode=GWEN_SyncIo_Http_Mode_Error;
|
|
return GWEN_ERROR_INVALID;
|
|
}
|
|
|
|
/* we want to write binary data transparently */
|
|
GWEN_SyncIo_AddFlags(baseIo, GWEN_SYNCIO_FLAGS_TRANSPARENT);
|
|
rv=GWEN_SyncIo_WriteForced(baseIo, buffer, size);
|
|
if (rv<0) {
|
|
xio->writeMode=GWEN_SyncIo_Http_Mode_Error;
|
|
return rv;
|
|
}
|
|
if (xio->currentWriteBodySize!=-1) {
|
|
xio->currentWriteBodySize-=rv;
|
|
if (xio->currentWriteBodySize==0)
|
|
GWEN_SyncIo_Http_SetWriteIdle(sio);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
if (xio->writeMode==GWEN_SyncIo_Http_Mode_Error) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Previous write error");
|
|
return GWEN_ERROR_GENERIC;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_SyncIo_Http_ReadLine(GWEN_SYNCIO *sio, GWEN_BUFFER *tbuf)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
GWEN_SYNCIO *baseIo;
|
|
int rv;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
baseIo=GWEN_SyncIo_GetBaseIo(sio);
|
|
assert(baseIo);
|
|
|
|
/* we want to read a text line, so we can't have a transparent mode in the base layer */
|
|
GWEN_SyncIo_SubFlags(baseIo, GWEN_SYNCIO_FLAGS_TRANSPARENT);
|
|
|
|
/* read a single line */
|
|
do {
|
|
uint8_t *p;
|
|
uint32_t l;
|
|
|
|
GWEN_Buffer_AllocRoom(tbuf, 1024);
|
|
p=(uint8_t *) GWEN_Buffer_GetPosPointer(tbuf);
|
|
l=GWEN_Buffer_GetMaxUnsegmentedWrite(tbuf);
|
|
rv=GWEN_SyncIo_Read(baseIo, p, l);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
return rv;
|
|
}
|
|
else if (rv>0) {
|
|
GWEN_Buffer_IncrementPos(tbuf, rv);
|
|
GWEN_Buffer_AdjustUsedBytes(tbuf);
|
|
if (p[rv-1]==10) {
|
|
p[rv-1]=0;
|
|
break;
|
|
}
|
|
}
|
|
else if (rv==0)
|
|
break;
|
|
}
|
|
while (rv>0);
|
|
|
|
if (GWEN_Buffer_GetUsedBytes(tbuf)<1) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Nothing received");
|
|
return GWEN_ERROR_EOF;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_SyncIo_Http_ParseStatus(GWEN_SYNCIO *sio, char *buffer)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
char *p;
|
|
char *s;
|
|
int code;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
s=buffer;
|
|
|
|
/* read protocol */
|
|
p=strchr(s, ' ');
|
|
if (!p) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN,
|
|
"Bad format of HTTP status (%s)", buffer);
|
|
return GWEN_ERROR_INVALID;
|
|
}
|
|
*p=0;
|
|
p++;
|
|
|
|
GWEN_DB_SetCharValue(xio->dbStatusIn, GWEN_DB_FLAGS_OVERWRITE_VARS, "protocol", s);
|
|
s=p;
|
|
|
|
/* read status code */
|
|
while (*p && isdigit((int)*p))
|
|
p++;
|
|
if (*p) {
|
|
*p=0;
|
|
p++;
|
|
}
|
|
if (1!=sscanf(s, "%d", &code)) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Bad request (status code \"%s\")", s);
|
|
return GWEN_ERROR_INVALID;
|
|
}
|
|
GWEN_DB_SetIntValue(xio->dbStatusIn, GWEN_DB_FLAGS_OVERWRITE_VARS, "code", code);
|
|
s=p;
|
|
|
|
/* read text */
|
|
GWEN_DB_SetCharValue(xio->dbStatusIn, GWEN_DB_FLAGS_OVERWRITE_VARS, "text", s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_SyncIo_Http_ParseCommand(GWEN_SYNCIO *sio, const char *buffer)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
char *tmp;
|
|
char *p;
|
|
char *s;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
tmp=strdup(buffer);
|
|
s=tmp;
|
|
|
|
/* read command */
|
|
p=strchr(s, ' ');
|
|
if (!p) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN,
|
|
"Bad format of HTTP request (%s)", buffer);
|
|
free(tmp);
|
|
return GWEN_ERROR_INVALID;
|
|
}
|
|
*p=0;
|
|
p++;
|
|
|
|
GWEN_DB_SetCharValue(xio->dbCommandIn, GWEN_DB_FLAGS_OVERWRITE_VARS, "command", s);
|
|
s=p;
|
|
|
|
/* read URL */
|
|
p=strchr(s, ' ');
|
|
if (!p) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN,
|
|
"Bad format of HTTP request (%s)", buffer);
|
|
free(tmp);
|
|
return GWEN_ERROR_INVALID;
|
|
}
|
|
*p=0;
|
|
p++;
|
|
|
|
GWEN_DB_SetCharValue(xio->dbCommandIn, GWEN_DB_FLAGS_OVERWRITE_VARS, "url", s);
|
|
s=p;
|
|
|
|
if (*s==0) {
|
|
/* no protocol information follows, so we assume HTTP/0.9 */
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Bad request (not in HTTP>=1.0)");
|
|
free(tmp);
|
|
return GWEN_ERROR_INVALID;
|
|
}
|
|
else {
|
|
GWEN_DB_SetCharValue(xio->dbCommandIn, GWEN_DB_FLAGS_OVERWRITE_VARS, "protocol", s);
|
|
}
|
|
|
|
free(tmp);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_SyncIo_Http_ReadStatus(GWEN_SYNCIO *sio)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
GWEN_SYNCIO *baseIo;
|
|
GWEN_BUFFER *tbuf;
|
|
int rv;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Reading status");
|
|
baseIo=GWEN_SyncIo_GetBaseIo(sio);
|
|
assert(baseIo);
|
|
|
|
/* read a single line */
|
|
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
|
|
rv=GWEN_SyncIo_Http_ReadLine(sio, tbuf);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv;
|
|
}
|
|
|
|
if (*GWEN_Buffer_GetStart(tbuf)==0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Empty line received while reading status response, assuming EOF");
|
|
GWEN_Buffer_free(tbuf);
|
|
return GWEN_ERROR_EOF;
|
|
}
|
|
|
|
if (GWEN_Logger_GetLevel(GWEN_LOGDOMAIN)>=GWEN_LoggerLevel_Debug) {
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Received HTTP status:");
|
|
GWEN_Text_LogString((const char *) GWEN_Buffer_GetStart(tbuf),
|
|
GWEN_Buffer_GetUsedBytes(tbuf),
|
|
GWEN_LOGDOMAIN,
|
|
GWEN_LoggerLevel_Debug);
|
|
}
|
|
|
|
rv=GWEN_SyncIo_Http_ParseStatus(sio, GWEN_Buffer_GetStart(tbuf));
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv;
|
|
}
|
|
|
|
GWEN_Buffer_free(tbuf);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_SyncIo_Http_ReadCommand(GWEN_SYNCIO *sio)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
GWEN_SYNCIO *baseIo;
|
|
GWEN_BUFFER *tbuf;
|
|
int rv;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Reading command");
|
|
baseIo=GWEN_SyncIo_GetBaseIo(sio);
|
|
assert(baseIo);
|
|
|
|
/* read a single line */
|
|
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
|
|
rv=GWEN_SyncIo_Http_ReadLine(sio, tbuf);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv;
|
|
}
|
|
|
|
rv=GWEN_SyncIo_Http_ParseCommand(sio, GWEN_Buffer_GetStart(tbuf));
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv;
|
|
}
|
|
|
|
GWEN_Buffer_free(tbuf);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_SyncIo_Http_ParseHeader(GWEN_SYNCIO *sio, char *buf)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
GWEN_SYNCIO *baseIo;
|
|
char *p;
|
|
const char *s;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
baseIo=GWEN_SyncIo_GetBaseIo(sio);
|
|
assert(baseIo);
|
|
|
|
/* resolve line continuations */
|
|
p=buf;
|
|
while (*p) {
|
|
p=strchr(p, 10);
|
|
if (p) {
|
|
if (p[1]==32 || p[1]==9)
|
|
/* found a continuation */
|
|
*p=32;
|
|
p++;
|
|
}
|
|
}
|
|
|
|
/* parse every line */
|
|
p=buf;
|
|
while (p && *p) {
|
|
char *pNext;
|
|
char *pVarBegin;
|
|
char *pVarEnd;
|
|
|
|
/* skip blanks */
|
|
pNext=strchr(p, 10);
|
|
if (pNext) {
|
|
*pNext=0;
|
|
pNext++;
|
|
}
|
|
while (*p && (*p==32 || *p==9))
|
|
p++;
|
|
if (*p) {
|
|
pVarBegin=p;
|
|
while (*p && *p!=':' && *p>32 && *p<127)
|
|
p++;
|
|
pVarEnd=p;
|
|
if (*p!=':') {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "No separator after variable name in received header");
|
|
return GWEN_ERROR_BAD_DATA;
|
|
}
|
|
*pVarEnd=0;
|
|
p++;
|
|
|
|
while (*p && (*p==32 || *p==9))
|
|
p++;
|
|
if (*p)
|
|
GWEN_DB_SetCharValue(xio->dbHeaderIn, GWEN_PATH_FLAGS_CREATE_VAR, pVarBegin, p);
|
|
}
|
|
p=pNext;
|
|
}
|
|
|
|
/* default next mode after reading the header is reading the body
|
|
* (if any, but that will be checked later) */
|
|
xio->readMode=GWEN_SyncIo_Http_Mode_Body;
|
|
|
|
/* header received, now read some settings from it */
|
|
s=GWEN_DB_GetCharValue(xio->dbHeaderIn, "Transfer-Encoding", 0, 0);
|
|
if (s && (-1!=GWEN_Text_ComparePattern(s, "*chunked*", 0))) {
|
|
/* chunked encoding, this means next we have to read the chunksize */
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Body is \"chunked\"");
|
|
xio->currentReadChunkSize=-1;
|
|
xio->readMode=GWEN_SyncIo_Http_Mode_ChunkSize;
|
|
}
|
|
|
|
/* get size of body */
|
|
xio->currentReadBodySize=GWEN_DB_GetIntValue(xio->dbHeaderIn, "Content-Length", 0, -1);
|
|
if (xio->currentReadBodySize==0) {
|
|
/* no body */
|
|
GWEN_SyncIo_Http_SetReadIdle(sio);
|
|
}
|
|
else if (xio->currentReadBodySize==-1) {
|
|
int rcode;
|
|
|
|
/* no length of body received, assume 0 in case of an error
|
|
* This eliminates the bug where this module waits for
|
|
* a timeout when receiving an error from a special server
|
|
*/
|
|
rcode=GWEN_DB_GetIntValue(xio->dbStatusIn, "code", 0, -1);
|
|
if (rcode<0 || rcode>=300) {
|
|
/* no body */
|
|
GWEN_SyncIo_Http_SetReadIdle(sio);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_SyncIo_Http_ReadHeader(GWEN_SYNCIO *sio)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
GWEN_SYNCIO *baseIo;
|
|
GWEN_BUFFER *tbuf;
|
|
int rv;
|
|
uint32_t pos;
|
|
int lines=0;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Reading header");
|
|
baseIo=GWEN_SyncIo_GetBaseIo(sio);
|
|
assert(baseIo);
|
|
|
|
/* we want to read a text line, so we can't have a transparent mode in the base layer */
|
|
GWEN_SyncIo_SubFlags(baseIo, GWEN_SYNCIO_FLAGS_TRANSPARENT);
|
|
|
|
/* read a single line */
|
|
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
|
|
pos=0;
|
|
do {
|
|
uint8_t *p;
|
|
|
|
GWEN_Buffer_AllocRoom(tbuf, 1024);
|
|
p=(uint8_t *) GWEN_Buffer_GetPosPointer(tbuf);
|
|
rv=GWEN_SyncIo_Read(baseIo, p, 1024);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv;
|
|
}
|
|
else if (rv>0) {
|
|
GWEN_Buffer_IncrementPos(tbuf, rv);
|
|
GWEN_Buffer_AdjustUsedBytes(tbuf);
|
|
if (p[rv-1]==10) {
|
|
uint32_t npos;
|
|
|
|
lines++;
|
|
npos=GWEN_Buffer_GetPos(tbuf);
|
|
if ((npos-pos)==1) {
|
|
/* empty line, header finished */
|
|
break;
|
|
}
|
|
pos=npos;
|
|
}
|
|
}
|
|
else if (rv==0)
|
|
break;
|
|
}
|
|
while (rv>0);
|
|
|
|
if (lines<1) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "No header line received");
|
|
GWEN_Buffer_free(tbuf);
|
|
return GWEN_ERROR_EOF;
|
|
}
|
|
|
|
if (GWEN_Logger_GetLevel(GWEN_LOGDOMAIN)>=GWEN_LoggerLevel_Debug) {
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Received HTTP header:");
|
|
GWEN_Text_LogString((const char *) GWEN_Buffer_GetStart(tbuf),
|
|
GWEN_Buffer_GetUsedBytes(tbuf),
|
|
GWEN_LOGDOMAIN,
|
|
GWEN_LoggerLevel_Debug);
|
|
}
|
|
|
|
rv=GWEN_SyncIo_Http_ParseHeader(sio, GWEN_Buffer_GetStart(tbuf));
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv;
|
|
}
|
|
|
|
GWEN_Buffer_free(tbuf);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_SyncIo_Http_ReadChunkSize(GWEN_SYNCIO *sio)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
GWEN_SYNCIO *baseIo;
|
|
GWEN_BUFFER *tbuf;
|
|
int rv;
|
|
int csize;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Reading chunksize");
|
|
baseIo=GWEN_SyncIo_GetBaseIo(sio);
|
|
assert(baseIo);
|
|
|
|
/* read a single line */
|
|
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
|
|
rv=GWEN_SyncIo_Http_ReadLine(sio, tbuf);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv;
|
|
}
|
|
|
|
if (*GWEN_Buffer_GetStart(tbuf)==0) {
|
|
GWEN_Buffer_Reset(tbuf);
|
|
rv=GWEN_SyncIo_Http_ReadLine(sio, tbuf);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (1!=sscanf(GWEN_Buffer_GetStart(tbuf), "%x", &csize)) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Bad data received (invalid chunksize specifier: [%s])",
|
|
GWEN_Buffer_GetStart(tbuf));
|
|
GWEN_Buffer_free(tbuf);
|
|
return GWEN_ERROR_BAD_DATA;
|
|
}
|
|
|
|
xio->currentReadChunkSize=csize;
|
|
|
|
GWEN_Buffer_free(tbuf);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_SyncIo_Http_ReadChunk(GWEN_SYNCIO *sio, uint8_t *buffer, uint32_t size)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
GWEN_SYNCIO *baseIo;
|
|
int rv;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Reading chunk (%d bytes)", (int) size);
|
|
baseIo=GWEN_SyncIo_GetBaseIo(sio);
|
|
assert(baseIo);
|
|
|
|
/* we want to read binary data transparently */
|
|
GWEN_SyncIo_AddFlags(baseIo, GWEN_SYNCIO_FLAGS_TRANSPARENT);
|
|
|
|
if ((int)size>xio->currentReadChunkSize)
|
|
size=xio->currentReadChunkSize;
|
|
|
|
rv=GWEN_SyncIo_Read(baseIo, buffer, size);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
return rv;
|
|
}
|
|
|
|
xio->currentReadChunkSize-=rv;
|
|
if (xio->currentReadBodySize>0)
|
|
xio->currentReadBodySize-=rv;
|
|
|
|
if (xio->currentReadChunkSize==0) {
|
|
int rv2;
|
|
GWEN_BUFFER *tbuf;
|
|
|
|
/* chunk finished, read trailing CR/LF */
|
|
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
|
|
rv2=GWEN_SyncIo_Http_ReadLine(sio, tbuf);
|
|
if (rv2<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv2);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv2;
|
|
}
|
|
GWEN_Buffer_free(tbuf);
|
|
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Chunk finished.");
|
|
|
|
/* change read mode */
|
|
xio->readMode=GWEN_SyncIo_Http_Mode_ChunkSize;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_SyncIo_Http_ReadBody(GWEN_SYNCIO *sio, uint8_t *buffer, uint32_t size)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
GWEN_SYNCIO *baseIo;
|
|
int rv;
|
|
|
|
assert(size);
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Reading body");
|
|
baseIo=GWEN_SyncIo_GetBaseIo(sio);
|
|
assert(baseIo);
|
|
|
|
/* we want to read binary data transparently */
|
|
GWEN_SyncIo_AddFlags(baseIo, GWEN_SYNCIO_FLAGS_TRANSPARENT);
|
|
|
|
if ((xio->currentReadBodySize>=0) &&
|
|
((int)size>xio->currentReadBodySize)) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Adjusting read body size from %d to %d",
|
|
size, xio->currentReadBodySize);
|
|
size=xio->currentReadBodySize;
|
|
}
|
|
|
|
rv=GWEN_SyncIo_Read(baseIo, buffer, size);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
return rv;
|
|
}
|
|
|
|
if (xio->currentReadBodySize>=0)
|
|
xio->currentReadBodySize-=rv;
|
|
|
|
if (xio->currentReadBodySize==0)
|
|
/* body finished, change read mode */
|
|
GWEN_SyncIo_Http_SetReadIdle(sio);
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_SyncIo_Http_WriteCommand(GWEN_SYNCIO *sio)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
GWEN_SYNCIO *baseIo;
|
|
int rv;
|
|
const char *s;
|
|
GWEN_BUFFER *tbuf;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
baseIo=GWEN_SyncIo_GetBaseIo(sio);
|
|
assert(baseIo);
|
|
|
|
/* we will construct the line including CR/LF ourselves */
|
|
GWEN_SyncIo_AddFlags(baseIo, GWEN_SYNCIO_FLAGS_TRANSPARENT);
|
|
|
|
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
|
|
|
|
s=GWEN_DB_GetCharValue(xio->dbCommandOut, "command", 0, "GET");
|
|
GWEN_Buffer_AppendString(tbuf, s);
|
|
GWEN_Buffer_AppendString(tbuf, " ");
|
|
|
|
s=GWEN_DB_GetCharValue(xio->dbCommandOut, "url", 0, "/");
|
|
GWEN_Buffer_AppendString(tbuf, s);
|
|
GWEN_Buffer_AppendString(tbuf, " ");
|
|
|
|
s=GWEN_DB_GetCharValue(xio->dbCommandOut, "protocol", 0, "HTTP/1.0");
|
|
GWEN_Buffer_AppendString(tbuf, s);
|
|
GWEN_Buffer_AppendString(tbuf, "\r\n");
|
|
|
|
if (GWEN_Logger_GetLevel(GWEN_LOGDOMAIN)>=GWEN_LoggerLevel_Debug) {
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Sending HTTP command:");
|
|
GWEN_Text_LogString((const char *) GWEN_Buffer_GetStart(tbuf),
|
|
GWEN_Buffer_GetUsedBytes(tbuf),
|
|
GWEN_LOGDOMAIN,
|
|
GWEN_LoggerLevel_Debug);
|
|
}
|
|
|
|
/* write */
|
|
rv=GWEN_SyncIo_WriteForced(baseIo,
|
|
(const uint8_t *) GWEN_Buffer_GetStart(tbuf),
|
|
GWEN_Buffer_GetUsedBytes(tbuf));
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv;
|
|
}
|
|
|
|
GWEN_Buffer_free(tbuf);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_SyncIo_Http_WriteStatus(GWEN_SYNCIO *sio)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
GWEN_SYNCIO *baseIo;
|
|
int rv;
|
|
const char *s;
|
|
GWEN_BUFFER *tbuf;
|
|
char numbuf[32];
|
|
int i;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
baseIo=GWEN_SyncIo_GetBaseIo(sio);
|
|
assert(baseIo);
|
|
|
|
/* we will construct the line including CR/LF ourselves */
|
|
GWEN_SyncIo_AddFlags(baseIo, GWEN_SYNCIO_FLAGS_TRANSPARENT);
|
|
|
|
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
|
|
|
|
s=GWEN_DB_GetCharValue(xio->dbStatusOut, "protocol", 0, "HTTP/1.0");
|
|
GWEN_Buffer_AppendString(tbuf, s);
|
|
GWEN_Buffer_AppendString(tbuf, " ");
|
|
|
|
i=GWEN_DB_GetIntValue(xio->dbStatusOut, "code", 0, -1);
|
|
if (i==-1) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Missing status code");
|
|
GWEN_Buffer_free(tbuf);
|
|
return GWEN_ERROR_NO_DATA;
|
|
}
|
|
snprintf(numbuf, sizeof(numbuf), "%d ", i);
|
|
GWEN_Buffer_AppendString(tbuf, numbuf);
|
|
|
|
s=GWEN_DB_GetCharValue(xio->dbStatusOut, "text", 0, "No text.");
|
|
GWEN_Buffer_AppendString(tbuf, s);
|
|
GWEN_Buffer_AppendString(tbuf, "\r\n");
|
|
|
|
/* write */
|
|
rv=GWEN_SyncIo_WriteForced(baseIo,
|
|
(const uint8_t *) GWEN_Buffer_GetStart(tbuf),
|
|
GWEN_Buffer_GetUsedBytes(tbuf));
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv;
|
|
}
|
|
|
|
GWEN_Buffer_free(tbuf);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_SyncIo_Http_WriteHeader(GWEN_SYNCIO *sio)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
GWEN_SYNCIO *baseIo;
|
|
int i;
|
|
GWEN_DB_NODE *dbVar;
|
|
GWEN_BUFFER *tbuf;
|
|
int rv;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
baseIo=GWEN_SyncIo_GetBaseIo(sio);
|
|
assert(baseIo);
|
|
|
|
/* we will construct the line including CR/LF ourselves */
|
|
GWEN_SyncIo_AddFlags(baseIo, GWEN_SYNCIO_FLAGS_TRANSPARENT);
|
|
|
|
/* default next mode after writing the header is writing the body
|
|
* (if any, but that will be checked later) */
|
|
xio->writeMode=GWEN_SyncIo_Http_Mode_Body;
|
|
|
|
tbuf=GWEN_Buffer_new(0, 256, 0, 1);
|
|
|
|
i=GWEN_DB_GetIntValue(xio->dbHeaderOut, "Content-Length", 0, -1);
|
|
xio->currentWriteBodySize=i;
|
|
|
|
dbVar=GWEN_DB_GetFirstVar(xio->dbHeaderOut);
|
|
while (dbVar) {
|
|
GWEN_DB_NODE *dbVal;
|
|
|
|
/* only handle first value */
|
|
dbVal=GWEN_DB_GetFirstValue(dbVar);
|
|
if (dbVal) {
|
|
GWEN_DB_NODE_TYPE vtype;
|
|
|
|
vtype=GWEN_DB_GetValueType(dbVal);
|
|
if (vtype==GWEN_DB_NodeType_ValueChar) {
|
|
const char *s;
|
|
|
|
GWEN_Buffer_AppendString(tbuf, GWEN_DB_VariableName(dbVar));
|
|
GWEN_Buffer_AppendString(tbuf, ":");
|
|
s=GWEN_DB_GetCharValueFromNode(dbVal);
|
|
if (s)
|
|
GWEN_Buffer_AppendString(tbuf, s);
|
|
GWEN_Buffer_AppendString(tbuf, "\r\n");
|
|
|
|
if (strcasecmp(GWEN_DB_VariableName(dbVar), "Transfer-Encoding")==0) {
|
|
if (s && (-1!=GWEN_Text_ComparePattern(s, "*chunked*", 0))) {
|
|
/* chunked encoding, this means next we have to write the chunksize */
|
|
xio->writeMode=GWEN_SyncIo_Http_Mode_ChunkSize;
|
|
}
|
|
}
|
|
}
|
|
else if (vtype==GWEN_DB_NodeType_ValueInt) {
|
|
i=GWEN_DB_GetIntValueFromNode(dbVal);
|
|
if (i!=-1 || strcasecmp(GWEN_DB_VariableName(dbVar), "Content-Length")==0) {
|
|
char numbuf[32];
|
|
|
|
/* don't write body size of -1 */
|
|
GWEN_Buffer_AppendString(tbuf, GWEN_DB_VariableName(dbVar));
|
|
GWEN_Buffer_AppendString(tbuf, ":");
|
|
snprintf(numbuf, sizeof(numbuf), "%d", i);
|
|
GWEN_Buffer_AppendString(tbuf, numbuf);
|
|
GWEN_Buffer_AppendString(tbuf, "\r\n");
|
|
}
|
|
}
|
|
else {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "Variable type %d of var [%s] not supported",
|
|
vtype, GWEN_DB_VariableName(dbVar));
|
|
return GWEN_ERROR_BAD_DATA;
|
|
}
|
|
}
|
|
dbVar=GWEN_DB_GetNextVar(dbVar);
|
|
}
|
|
|
|
/* finalize header */
|
|
GWEN_Buffer_AppendString(tbuf, "\r\n");
|
|
|
|
if (GWEN_Logger_GetLevel(GWEN_LOGDOMAIN)>=GWEN_LoggerLevel_Debug) {
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "Sending HTTP header:");
|
|
GWEN_Text_LogString((const char *) GWEN_Buffer_GetStart(tbuf),
|
|
GWEN_Buffer_GetUsedBytes(tbuf),
|
|
GWEN_LOGDOMAIN,
|
|
GWEN_LoggerLevel_Debug);
|
|
}
|
|
|
|
/* write */
|
|
rv=GWEN_SyncIo_WriteForced(baseIo,
|
|
(const uint8_t *) GWEN_Buffer_GetStart(tbuf),
|
|
GWEN_Buffer_GetUsedBytes(tbuf));
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Buffer_free(tbuf);
|
|
return rv;
|
|
}
|
|
GWEN_Buffer_free(tbuf);
|
|
|
|
if (xio->currentWriteBodySize==0)
|
|
GWEN_SyncIo_Http_SetWriteIdle(sio);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_SyncIo_Http_WriteChunkSize(GWEN_SYNCIO *sio, uint32_t size)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
GWEN_SYNCIO *baseIo;
|
|
int rv;
|
|
char numbuf[32];
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
baseIo=GWEN_SyncIo_GetBaseIo(sio);
|
|
assert(baseIo);
|
|
|
|
/* we will construct the line including CR/LF ourselves */
|
|
GWEN_SyncIo_AddFlags(baseIo, GWEN_SYNCIO_FLAGS_TRANSPARENT);
|
|
|
|
snprintf(numbuf, sizeof(numbuf)-1, "%x\r\n", size);
|
|
numbuf[sizeof(numbuf)-1]=0;
|
|
|
|
rv=GWEN_SyncIo_WriteForced(baseIo, (const uint8_t *) numbuf, strlen(numbuf));
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
return rv;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
void GWEN_SyncIo_Http_SetWriteIdle(GWEN_SYNCIO *sio)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
xio->writeMode=GWEN_SyncIo_Http_Mode_Idle;
|
|
GWEN_DB_ClearGroup(xio->dbStatusOut, NULL);
|
|
}
|
|
|
|
|
|
|
|
|
|
GWEN_DB_NODE *GWEN_SyncIo_Http_GetDbCommandIn(const GWEN_SYNCIO *sio)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
return xio->dbCommandIn;
|
|
}
|
|
|
|
|
|
|
|
GWEN_DB_NODE *GWEN_SyncIo_Http_GetDbStatusIn(const GWEN_SYNCIO *sio)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
return xio->dbStatusIn;
|
|
}
|
|
|
|
|
|
|
|
GWEN_DB_NODE *GWEN_SyncIo_Http_GetDbHeaderIn(const GWEN_SYNCIO *sio)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
return xio->dbHeaderIn;
|
|
}
|
|
|
|
|
|
|
|
GWEN_DB_NODE *GWEN_SyncIo_Http_GetDbCommandOut(const GWEN_SYNCIO *sio)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
return xio->dbCommandOut;
|
|
}
|
|
|
|
|
|
|
|
GWEN_DB_NODE *GWEN_SyncIo_Http_GetDbStatusOut(const GWEN_SYNCIO *sio)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
return xio->dbStatusOut;
|
|
}
|
|
|
|
|
|
|
|
GWEN_DB_NODE *GWEN_SyncIo_Http_GetDbHeaderOut(const GWEN_SYNCIO *sio)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
return xio->dbHeaderOut;
|
|
}
|
|
|
|
|
|
|
|
|
|
int GWEN_SyncIo_Http_RecvBody(GWEN_SYNCIO *sio, GWEN_BUFFER *buf)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
int rv=0;
|
|
int code=0;
|
|
int firstRead=1;
|
|
int bodySize=-1;
|
|
int bytesRead=0;
|
|
uint32_t pid;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
pid=GWEN_Gui_ProgressStart(GWEN_GUI_PROGRESS_DELAY |
|
|
GWEN_GUI_PROGRESS_SHOW_ABORT |
|
|
GWEN_GUI_PROGRESS_ALLOW_EMBED |
|
|
GWEN_GUI_PROGRESS_SHOW_PROGRESS,
|
|
I18N("Network Operation"),
|
|
I18N("Receiving data"),
|
|
0,
|
|
0);
|
|
|
|
|
|
/* recv packet (this reads the HTTP body) */
|
|
for (;;) {
|
|
uint8_t *p;
|
|
uint32_t l;
|
|
|
|
rv=GWEN_Buffer_AllocRoom(buf, 1024);
|
|
if (rv<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Gui_ProgressEnd(pid);
|
|
return rv;
|
|
}
|
|
|
|
p=(uint8_t *) GWEN_Buffer_GetPosPointer(buf);
|
|
l=GWEN_Buffer_GetMaxUnsegmentedWrite(buf);
|
|
do {
|
|
rv=GWEN_SyncIo_Read(sio, p, l-1);
|
|
}
|
|
while (rv==GWEN_ERROR_INTERRUPTED);
|
|
|
|
if (rv==0) /* EOF met */
|
|
break;
|
|
else if (rv<0) {
|
|
if (rv==GWEN_ERROR_EOF) {
|
|
if (bodySize!=-1 && bytesRead<bodySize) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN,
|
|
"EOF met prematurely (%d < %d)",
|
|
bytesRead, bodySize);
|
|
GWEN_Gui_ProgressEnd(pid);
|
|
return GWEN_ERROR_EOF;
|
|
}
|
|
}
|
|
else {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
/*return rv;*/
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
GWEN_Buffer_IncrementPos(buf, rv);
|
|
GWEN_Buffer_AdjustUsedBytes(buf);
|
|
if (firstRead) {
|
|
GWEN_DB_NODE *db;
|
|
|
|
db=GWEN_SyncIo_Http_GetDbHeaderIn(sio);
|
|
bodySize=GWEN_DB_GetIntValue(db, "Content-length", 0, -1);
|
|
|
|
if (bodySize!=-1)
|
|
GWEN_Gui_ProgressSetTotal(pid, bodySize);
|
|
}
|
|
bytesRead+=rv;
|
|
|
|
/* advance progress bar */
|
|
rv=GWEN_Gui_ProgressAdvance(pid, bytesRead);
|
|
if (rv==GWEN_ERROR_USER_ABORTED) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Gui_ProgressEnd(pid);
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (bodySize!=-1 && bytesRead>=bodySize) {
|
|
break;
|
|
}
|
|
|
|
firstRead=0;
|
|
}
|
|
GWEN_Gui_ProgressEnd(pid);
|
|
|
|
if (rv<0) {
|
|
if (GWEN_Buffer_GetUsedBytes(buf)) {
|
|
/* data received, check for common error codes */
|
|
if (rv==GWEN_ERROR_EOF || rv==GWEN_ERROR_IO || rv==GWEN_ERROR_SSL) {
|
|
DBG_INFO(GWEN_LOGDOMAIN,
|
|
"We received an error, but we still got data, "
|
|
"so we ignore the error here");
|
|
}
|
|
else {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "No message received (%d)", rv);
|
|
GWEN_Gui_ProgressLog(0,
|
|
GWEN_LoggerLevel_Error,
|
|
I18N("No message received"));
|
|
return rv;
|
|
}
|
|
}
|
|
else {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "No message received (%d)", rv);
|
|
GWEN_Gui_ProgressLog(0,
|
|
GWEN_LoggerLevel_Error,
|
|
I18N("No message received"));
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (GWEN_SyncIo_GetFlags(sio) & GWEN_SYNCIO_FLAGS_PASSIVE)
|
|
code=0;
|
|
else {
|
|
code=GWEN_DB_GetIntValue(xio->dbStatusIn, "code", 0, 0);
|
|
if (code) {
|
|
const char *s;
|
|
|
|
s=GWEN_DB_GetCharValue(xio->dbStatusIn, "text", 0, NULL);
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "HTTP-Status: %d (%s)",
|
|
code, s?s:"- no text -");
|
|
GWEN_Gui_ProgressLog2(0, GWEN_LoggerLevel_Debug,
|
|
I18N("HTTP-Status: %d (%s)"),
|
|
code, s?s:I18N("- no details -"));
|
|
}
|
|
else {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "No HTTP status code received");
|
|
GWEN_Gui_ProgressLog(0,
|
|
GWEN_LoggerLevel_Error,
|
|
I18N("No HTTP status code received"));
|
|
code=GWEN_ERROR_BAD_DATA;
|
|
}
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
|
|
|
|
int GWEN_SyncIo_Http_RecvBodyToSio(GWEN_SYNCIO *sio, GWEN_SYNCIO *sout)
|
|
{
|
|
GWEN_SYNCIO_HTTP *xio;
|
|
int rv;
|
|
int code=0;
|
|
int firstRead=1;
|
|
int bodySize=-1;
|
|
int bytesRead=0;
|
|
uint32_t pid;
|
|
|
|
assert(sio);
|
|
xio=GWEN_INHERIT_GETDATA(GWEN_SYNCIO, GWEN_SYNCIO_HTTP, sio);
|
|
assert(xio);
|
|
|
|
pid=GWEN_Gui_ProgressStart(GWEN_GUI_PROGRESS_DELAY |
|
|
GWEN_GUI_PROGRESS_SHOW_ABORT |
|
|
GWEN_GUI_PROGRESS_ALLOW_EMBED |
|
|
GWEN_GUI_PROGRESS_SHOW_PROGRESS,
|
|
I18N("Network Operation"),
|
|
I18N("Receiving data"),
|
|
0,
|
|
0);
|
|
|
|
/* recv packet (this reads the HTTP body) */
|
|
for (;;) {
|
|
uint8_t *p;
|
|
uint32_t l;
|
|
uint8_t rbuf[1024];
|
|
|
|
p=rbuf;
|
|
l=sizeof(rbuf);
|
|
|
|
do {
|
|
rv=GWEN_SyncIo_Read(sio, p, l-1);
|
|
}
|
|
while (rv==GWEN_ERROR_INTERRUPTED);
|
|
|
|
if (rv==0)
|
|
break;
|
|
else if (rv<0) {
|
|
if (rv==GWEN_ERROR_EOF) {
|
|
if (bodySize!=-1 && bytesRead<bodySize) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN,
|
|
"EOF met prematurely (%d < %d)",
|
|
bytesRead, bodySize);
|
|
GWEN_Gui_ProgressEnd(pid);
|
|
return GWEN_ERROR_EOF;
|
|
}
|
|
}
|
|
else {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
/*return rv;*/
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
int rv2;
|
|
|
|
rv2=GWEN_SyncIo_WriteForced(sout, rbuf, rv);
|
|
if (rv2<0) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv2);
|
|
GWEN_Gui_ProgressEnd(pid);
|
|
return rv2;
|
|
}
|
|
if (firstRead) {
|
|
GWEN_DB_NODE *db;
|
|
|
|
db=GWEN_SyncIo_Http_GetDbHeaderIn(sio);
|
|
bodySize=GWEN_DB_GetIntValue(db, "Content-length", 0, -1);
|
|
|
|
if (bodySize!=-1)
|
|
GWEN_Gui_ProgressSetTotal(pid, bodySize);
|
|
}
|
|
bytesRead+=rv;
|
|
|
|
/* advance progress bar */
|
|
rv=GWEN_Gui_ProgressAdvance(pid, bytesRead);
|
|
if (rv==GWEN_ERROR_USER_ABORTED) {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "here (%d)", rv);
|
|
GWEN_Gui_ProgressEnd(pid);
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (bodySize!=-1 && bytesRead>=bodySize) {
|
|
break;
|
|
}
|
|
firstRead=0;
|
|
}
|
|
GWEN_Gui_ProgressEnd(pid);
|
|
|
|
|
|
if (rv<0) {
|
|
if (bytesRead) {
|
|
/* data received, check for common error codes */
|
|
if (rv==GWEN_ERROR_EOF || rv==GWEN_ERROR_IO || rv==GWEN_ERROR_SSL) {
|
|
DBG_INFO(GWEN_LOGDOMAIN,
|
|
"We received an error, but we still got data, "
|
|
"so we ignore the error here");
|
|
}
|
|
else {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "No message received (%d)", rv);
|
|
GWEN_Gui_ProgressLog(0,
|
|
GWEN_LoggerLevel_Error,
|
|
I18N("No message received"));
|
|
return rv;
|
|
}
|
|
}
|
|
else {
|
|
DBG_INFO(GWEN_LOGDOMAIN, "No message received (%d)", rv);
|
|
GWEN_Gui_ProgressLog(0,
|
|
GWEN_LoggerLevel_Error,
|
|
I18N("No message received"));
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (GWEN_SyncIo_GetFlags(sio) & GWEN_SYNCIO_FLAGS_PASSIVE)
|
|
code=0;
|
|
else {
|
|
code=GWEN_DB_GetIntValue(xio->dbStatusIn, "code", 0, 0);
|
|
if (code) {
|
|
const char *s;
|
|
|
|
s=GWEN_DB_GetCharValue(xio->dbStatusIn, "text", 0, NULL);
|
|
DBG_DEBUG(GWEN_LOGDOMAIN, "HTTP-Status: %d (%s)",
|
|
code, s?s:"- no text -");
|
|
GWEN_Gui_ProgressLog2(0, GWEN_LoggerLevel_Debug,
|
|
I18N("HTTP-Status: %d (%s)"),
|
|
code, s?s:I18N("- no details -)"));
|
|
}
|
|
else {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "No HTTP status code received");
|
|
GWEN_Gui_ProgressLog(0,
|
|
GWEN_LoggerLevel_Error,
|
|
I18N("No HTTP status code received"));
|
|
code=GWEN_ERROR_BAD_DATA;
|
|
}
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
|
|
|
|
|