/***************************************************************************
 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;
}




