/*
 * Tcp4u v 3.31         Last Revision 27/06/1997  3.10
 *
 *===========================================================================
 *
 * Project: Tcp4u,      Library for tcp protocol
 * File:    http4u.c
 * Purpose: manage http 1.0 protocol
 *
 *===========================================================================
 *
 * This software is Copyright (c) 1996-1998 by Philippe Jounin and Laurent Le Bras
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 * 
 * You should have received a copy of the GNU Library 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.
 *
 *
 *  If you make modifications to this software that you feel
 *  increases it usefulness for the rest of the community, please
 *  email the changes, enhancements, bug fixes as well as any and
 *  all ideas to me. This software is going to be maintained and
 *  enhanced as deemed necessary by the community.
 *
 *
 *             Philippe Jounin (ph.jounin@computer.org)
 */


static char szWhat[]="@(#)http4u by Ph. Jounin and L. Le Bras version 3.11";

#include "build.h"

 /******************************
 * Http4u internal structures
 ******************************/

/* status-line for http 1.0 answers */
struct S_HttpStatus 
{
   int  nVersion;                      /* version should be 1       */
   int  nMinor;                        /* revision (0 or 1)         */
   int  code;                          /* status-code (ex: 200 )    */
   char reason[64];                    /* reason (ex: "OK")         */
};


/*******************************
 * A few globals variables
 *******************************/
static unsigned int s_uHttp4uTimeout    = DFLT_TIMEOUT;
static unsigned int s_uHttp4uBufferSize = DFLT_BUFFERSIZE;
static DO_NOT_LOG = HFILE_ERROR;

/*=====================================================================
 *                        PRIVATE FUNCTION SOURCES
 *===================================================================*/


/*######################################################################
 *##
 *## NAME:  HttpProcessAnswer
 *##
 *## PURPOSE: get HTTP version + HTTP return code + data string
 *##          fully reeentrant
 *##
 *####################################################################*/
static int HttpProcessAnswer (LPCSTR  szAns, int far *pnVer, int far *pnMinor, 
                              int far *pnAnswer, LPSTR szData, int nDataSize)
{
LPCSTR p;

   Tcp4uLog (LOG4U_INTERN, "HttpProcessAnswer");

   if (memcmp (szAns, "HTTP/", sizeof ("HTTP/") - 1)!=0)
   {
	   Tcp4uLog (LOG4U_ERROR, "HttpProcessAnswer: bad protocol returned (%s)", szAns);
       return HTTP4U_PROTOCOL_ERROR;             
   }
   /* 27/06/97: ignore version numbers (RFC2145) */
   *pnAnswer=1; *pnMinor=0;
   /* search for a space character, then skip it */
   p = szAns + sizeof "HTTP/";
   while (*p!=0  && !isspace(*p)) p++;
   while (*p!=0  &&  isspace(*p)) p++;
   if (*p==0)
   {
	   Tcp4uLog (LOG4U_ERROR, "HttpProcessAnswer: bad protocol returned (%s)", szAns);
       return HTTP4U_PROTOCOL_ERROR;             
   }
   *pnAnswer = Tcp4uAtoi (p);
   /* continue only if szData is to be filled */
   if (szData!=NULL && nDataSize>0)
   {
      /* search for a non-digit then skips spaces */
      while (*p!=0  && isdigit(*p)) p++;
      while (*p!=0  && isspace(*p)) p++;
      Strcpyn (szData, p, nDataSize);
   }
return HTTP4U_SUCCESS;
} /* HttpProcessAnswer */


/*######################################################################
 *##
 *## NAME:  HttpSendAdditionnalHeader
 *##
 *## PURPOSE: Send a http 1.0 general-header  
 *##
 *####################################################################*/
static int HttpSendAdditionnalHeader(SOCKET CSock)
{
  int Ark;
  int Rc;
  static LPCSTR szAdditionnalStrings[] = 
  {
    "Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*",
    "User-Agent: Http4u by Ph. Jounin and L. Le Bras",
    "",
  };

  Tcp4uLog (LOG4U_INTERN, "HttpSendAdditionnalHeader");

  /* sending request,  if successful send Request Header */
  for (Rc=TCP4U_SUCCESS,Ark=0 ; Rc==TCP4U_SUCCESS && Ark<SizeOfTab(szAdditionnalStrings); Ark++)
     Rc = TnSend (CSock, szAdditionnalStrings[Ark], FALSE, DO_NOT_LOG);

return Rc==TCP4U_SUCCESS ?  HTTP4U_SUCCESS :  HTTP4U_TCP_FAILED;
} /* END HttpSendAdditionnalHeader */



/*######################################################################
 *##
 *## NAME:  HttpSendRequest10
 *##
 *## PURPOSE: Send an http 1.0 method request-line  
 *##          Note: szReq can be "HEAD ", "POST ", "GET ", ..
 *##
 *####################################################################*/
static int HttpSendRequest10(SOCKET CSock,       /* socket decriptor   */
                             LPCSTR szReq,  /* request to be sent */
                             LPCSTR szURL   /* URL or URI         */)
{
LPSTR sRequest =  NULL;
int   Rc;

   Tcp4uLog (LOG4U_INTERN, "HttpSendRequest10");
  /* allocate buffer wide eough to contain all data */
  sRequest = Calloc (sizeof(" HTTP/1.0 ") + Strlen (szURL) + Strlen(szReq),
                     sizeof(char));
  if (sRequest == NULL)    return HTTP4U_INSMEMORY;

  /* compose request with reentrant functions */
  Strcpy(sRequest, szReq);
  Strcat(sRequest, szURL);
  Strcat(sRequest, " HTTP/1.0");

  /* send the request then forget it */
  Rc = TnSend (CSock, sRequest, FALSE, DO_NOT_LOG);
  Free (sRequest);
  if (Rc!=TCP4U_SUCCESS)    return HTTP4U_TCP_FAILED ;

  /* Send general-header */
return  HttpSendAdditionnalHeader(CSock);
} /* END HttpSendRequestHEAD10 */




/*######################################################################
 *##
 *## NAME:  HttpRecvRespStatus
 *##
 *## PURPOSE:  Get the status-line of the http answer
 *##           The data are copied into saRespStatus and szAnswer
 *##
 *####################################################################*/
static int HttpRecvRespStatus(SOCKET CSock,
                              struct S_HttpStatus far *saRespStatus,
                              LPSTR  szAnswer, int nAnswerSize)
{
#define RECV_BUF_SIZE 1024

LPSTR     sBufStatus;
int       nBufStatusLen = RECV_BUF_SIZE ;
int       Rc;

  Tcp4uLog (LOG4U_INTERN, "HttpRecvRespStatus");

  if (szAnswer !=NULL  &&  nAnswerSize > 0)    szAnswer[0]=0;
  /* If ye can keep user's buffer */
  if (szAnswer!=NULL && nAnswerSize >= RECV_BUF_SIZE)
  {
       sBufStatus    = szAnswer;
       nBufStatusLen = nAnswerSize;
  }
  else
  {
      sBufStatus = Calloc (RECV_BUF_SIZE, sizeof (char));
      if (sBufStatus == NULL)   return HTTP4U_INSMEMORY; 
  }

  /* receive data */
  Rc = TnReadLine (CSock,sBufStatus,nBufStatusLen,s_uHttp4uTimeout, DO_NOT_LOG);
  switch(Rc) 
  {
     case TCP4U_SUCCESS:       break;
     case TCP4U_OVERFLOW:      return HTTP4U_OVERFLOW;
     case TCP4U_TIMEOUT:       return HTTP4U_TIMEOUT;
     case TCP4U_CANCELLED:     return HTTP4U_CANCELLED;
     case TCP4U_ERROR:         return HTTP4U_TCP_FAILED;
     case TCP4U_SOCKETCLOSED:  return HTTP4U_PROTOCOL_ERROR;
     default:                  return HTTP4U_TCP_FAILED;
  }
 /* control format */
  Rc = HttpProcessAnswer (   sBufStatus, 
                             & saRespStatus->nVersion, 
                             & saRespStatus->nMinor, 
                             & saRespStatus->code, 
                               saRespStatus->reason, 
                               sizeof saRespStatus->reason);
  /* Copy data, free buffer */
  if (sBufStatus != szAnswer)    
  {
      if (szAnswer != NULL)  Strcpyn (szAnswer, sBufStatus, nAnswerSize);
      Free (sBufStatus);
  }

return Rc;
#undef RECV_BUF_SIZE
} /* END HttpRecvRespStatus */


/*######################################################################
 *##
 *## NAME:  HttpRecvHeaders10
 *##
 *## PURPOSE:  Return the headers section of the http respons
 *##           sBufHeaders Can not be NULL, but sBufHeadersLen can !! 
 *##
 *####################################################################*/
static int HttpRecvHeaders10 (SOCKET   CSock,
                              LPSTR    sBufHeaders,
                              int      nBufHeadersLen)
{
int   Rc;
int   nRcvd, nLineLength;
LPSTR p;
#define EMPTY(s) ((s)[0]=='\r' || (s)[0]=='\n')

  Tcp4uLog (LOG4U_INTERN, "Http4RecvHeaders10");

  if (sBufHeaders==NULL) return HTTP4U_BAD_PARAM;
  memset(sBufHeaders, 0, nBufHeadersLen);
  /* Keep space for last ending line and nul character */
  nBufHeadersLen -= sizeof SYSTEM_EOL;
 
  /* receive data from distant http server. Must use loop on TcpRecvUntilStr */
  /* since some unix servers send  \n\n instead of regular \n\r\n....  */
  nRcvd = 0 ;  
  do
  { 
     p = & sBufHeaders[nRcvd];
     Rc = InternalTnReadLine (CSock,  
                              p, nBufHeadersLen-nRcvd,
                              s_uHttp4uTimeout, DO_NOT_LOG); 
     Strcat (p, SYSTEM_EOL);
     nLineLength = Strlen (p); /* 0 on error */
     nRcvd += nLineLength;
  } /* loop until error or empty line */
  while (Rc==TCP4U_SUCCESS && !EMPTY(p) && nRcvd < nBufHeadersLen);
  /* translate return code */
  switch(Rc) 
  {
     /* remember to put the last \n into destination string */
     case TCP4U_SUCCESS:       Tcp4uDump (sBufHeaders, nRcvd, DUMP4U_RCVD);
                               return nRcvd >= nBufHeadersLen ?
                                      HTTP4U_OVERFLOW : HTTP4U_SUCCESS;
     case TCP4U_OVERFLOW:      return HTTP4U_OVERFLOW;
     case TCP4U_TIMEOUT:       return HTTP4U_TIMEOUT;
     case TCP4U_CANCELLED:     return HTTP4U_CANCELLED;
     case TCP4U_ERROR:         return HTTP4U_TCP_FAILED;
     case TCP4U_INSMEMORY:     return HTTP4U_INSMEMORY;
     case TCP4U_SOCKETCLOSED:  return HTTP4U_PROTOCOL_ERROR;
     default:                  return HTTP4U_TCP_FAILED;
  } /* END switch(Rc) */
#undef EMPTY
} /* END HttpRecvHeaders10 */



/*=====================================================================
 *                        PUBLIC FUNCTION SOURCES
 *===================================================================*/
 
/*######################################################################
 *##
 *## NAME: HttpGetHeaders10  
 *##
 *## PURPOSE: Return the header section of the http request
 *##
 *####################################################################*/
int HttpGetHeaders10( LPCSTR   szURL,     /* URL target    */
                      LPSTR    szResponse, /* user's buffer for HTTP response */ 
                      int      nResponseSize, /* */
                      LPSTR    szData,    /* user's buffer for HTTP headers   */ 
                      int      nDataSize /* */)
{
int                 Rc;
SOCKET              CSock = INVALID_SOCKET;
char                szService[SERVICE_LENGTH];
char                szHost[HOST_LENGTH];
char                szFichier[FILE_LENGTH];
unsigned short      usPort;
struct S_HttpStatus saRespStatus;

   Tcp4uLog (LOG4U_INTERN, "HttpGetHeaders10");

 /* control the URL's validity and receive the URL distinct components */
 if (!HttpIsValidURL(szURL,
                     &usPort,
                     szService, sizeof szService ,
                     szHost,    sizeof szHost ,
                     szFichier, sizeof szFichier )) 
   return HTTP4U_BAD_URL;

  /* connect to the http server */
  Rc = TcpConnect(&CSock,
                  szHost,
                  szService,
                  &usPort);
  switch (Rc)
  {
     case TCP4U_SUCCESS     :  break;  /* continue */
     case TCP4U_HOSTUNKNOWN :  return HTTP4U_HOST_UNKNOWN;
     default                :  return HTTP4U_TCP_CONNECT;
  }

  /* Send request-line method "HEAD" then receive the status-line answer */
  /* then receive headers                                                */
     (Rc = HttpSendRequest10 (CSock,"HEAD ", szURL)) == HTTP4U_SUCCESS
  && (Rc = HttpRecvRespStatus (CSock, & saRespStatus, 
                               szResponse, nResponseSize)) == HTTP4U_SUCCESS
  && (Rc = HttpRecvHeaders10 (CSock, szData, nDataSize)) == HTTP4U_SUCCESS ;

  TcpClose (&CSock);
return Rc;
} /* END HttpGetHeaders10 */




/*######################################################################
 *##
 *## NAME:  Http4uSetTimeout
 *##
 *## PURPOSE: Sets user preference of the timeout value
 *##
 *####################################################################*/
void  API4U Http4uSetTimeout(
                       unsigned int uTimeout /* timeout value in sec */)
{
   Tcp4uLog (LOG4U_HIPROC, "Http4uSetTimeout");
   s_uHttp4uTimeout = uTimeout;
} /* END Http4uSetTimeout */


/*######################################################################
 *##
 *## NAME:  Http4uSetBufferSize
 *##
 *## PURPOSE:  Sets user preference of the buffer size
 *##
 *####################################################################*/
void  API4U Http4uSetBufferSize(
                          unsigned int uBufferSize /* buffer size in bytes */)
{
   Tcp4uLog (LOG4U_HIPROC, "Http4uSetbufferSize");
   s_uHttp4uBufferSize = uBufferSize;
} /* END Http4uSetBufferSize */


/*######################################################################
 *##
 *## NAME:  HttpGetFileEx
 *##
 *## PURPOSE:  Return headers and body of a http request 
 *##
 *####################################################################*/
int API4U HttpGetFileEx( 
                   LPCSTR szURL, 
                   LPCSTR szProxyURL, 
                   LPCSTR szLocalFile,
                   LPCSTR szHeaderFile,
                   HTTP4U_CALLBACK CbkTransmit,
                   long   lUserValue,
                   LPSTR  szResponse, int nResponseSize,
                   LPSTR  szHeaders,  int nHeadersSize
                   )
{
SOCKET  CSock = INVALID_SOCKET;
int     Rc;
int     hHeaderFile = HFILE_ERROR;
char    szService[SERVICE_LENGTH];
char    szHost[HOST_LENGTH];
char    szFichier[FILE_LENGTH];
LPSTR   szData = NULL;
LPSTR   p;
LPCSTR  szRequest;
long    RealBodySize = -1;

struct S_HttpStatus saRespStatus;
unsigned short usPort = 0;

   Tcp4uLog (LOG4U_HIPROC, "HttpGetFileEx");

#define XX_RETURN(a) {if (szData!=NULL) Free(szData);\
                     if (hHeaderFile!=HFILE_ERROR){\
                        Close(hHeaderFile);\
                        Unlink(szHeaderFile);\
                     }\
                     if (CSock != INVALID_SOCKET) TcpClose(&CSock);\
				     Tcp4uLog (LOG4U_HIEXIT, "HttpGetFileEx with return code %d", a); \
                     return a;\
                     }
#ifdef UNIX
  /* use "hidden" env variable in order to send logs to stdout       */
  if (getenv ("http4u_log")!=NULL)   DO_NOT_LOG = fileno(stdout);
#endif

  /* control URL's validity and receive URL's components. If a proxy */
  /* is used, send the connection components into usPort, szService  */
  /* and szHost.                                                     */
  if (     ! HttpIsValidURL(  szURL,
                            & usPort,
                              szService, sizeof szService ,
                              szHost, sizeof szHost ,
                              szFichier, sizeof szFichier )
       ||  (     szProxyURL!=NULL   
             &&  ! HttpIsValidURL (szProxyURL, & usPort, 
                                   szService, sizeof szService,
                                   szHost, sizeof szHost, NULL, 0))
     )
   {
      XX_RETURN (HTTP4U_BAD_URL);
   }

   /* allocate buffer */
   if ( (szData = Calloc(1,s_uHttp4uBufferSize)) == NULL) 
   {
     XX_RETURN (HTTP4U_INSMEMORY);
   } 

   /* connect to http server, or proxy server : we don't care now */
   Rc = TcpConnect(& CSock,
                      szHost,
                      usPort==0 ? szService : NULL,
                    & usPort);
  switch (Rc)
  {
     case TCP4U_SUCCESS     :  break;  /* continue */
     case TCP4U_HOSTUNKNOWN :  XX_RETURN (HTTP4U_HOST_UNKNOWN);
     default                :  XX_RETURN (HTTP4U_TCP_CONNECT);
  }

   /* send a request-line method "GET", receive reply, receive data */
   szRequest= szProxyURL==NULL? szFichier : szURL; /* if no proxy, simple ! */
   if (   (Rc=HttpSendRequest10  (CSock, "GET ", szRequest)) != HTTP4U_SUCCESS
       || (Rc=HttpRecvRespStatus (CSock, & saRespStatus, 
                                  szResponse,nResponseSize)) != HTTP4U_SUCCESS )
   {
      XX_RETURN (Rc);
    }

   /* an answer has been received, let us have a look on it */
   switch(saRespStatus.code) 
   {
       case 200: break; /* reason-phrase OK */ 
       case 204: XX_RETURN (HTTP4U_NO_CONTENT);
       case 300:
       case 301: XX_RETURN (HTTP4U_MOVED);
       case 400: XX_RETURN (HTTP4U_BAD_REQUEST);
       case 401:
       case 403: XX_RETURN (HTTP4U_FORBIDDEN);
       case 404: XX_RETURN (HTTP4U_NOT_FOUND);
       default:  XX_RETURN (HTTP4U_PROTOCOL_ERROR);
   }

   /* read headers */
   Rc = HttpRecvHeaders10(CSock, szData, s_uHttp4uBufferSize);
   /* copy headers into user buffer even if return incorrect */
   if (szHeaders != NULL) 
     Strcpyn (szHeaders, szData, min(s_uHttp4uBufferSize, (unsigned) nHeadersSize));

   if (Rc!=HTTP4U_SUCCESS)     XX_RETURN (Rc);

   /* write headers into the user local file */
  if (szHeaderFile != NULL ) 
  {
    if ((hHeaderFile = Open(szHeaderFile, WRITE_CR)) == HFILE_ERROR) 
    {
       XX_RETURN (HTTP4U_FILE_ERROR);
    }
    /* write */
    if (Write(hHeaderFile, szData, Strlen(szData)) == HFILE_ERROR) 
    {
        XX_RETURN(HTTP4U_FILE_ERROR);
    }
    Close(hHeaderFile);
    hHeaderFile = HFILE_ERROR;
  } /* szHeaderFile not NULL */

  /* if we do not need something else, just close the connection */
  /* not really nice, but HTTP servers are used to deal with it  */ 
  if (szLocalFile==NULL  &&  CbkTransmit==NULL) 
  {
     XX_RETURN (HTTP4U_SUCCESS);
  }

  /* search real length of the body */ 
  RealBodySize = -1;         /* can not compute it */
  szData[s_uHttp4uBufferSize-1] = '\0';
  p = Tcp4uStrIStr (szData, "content-length:");
  if (p!=NULL) 
  {
    p += sizeof("Content-Length:");
    while (isspace(*p)) p++;  /* skip space character */
    RealBodySize = Tcp4uAtol (p);
  }
    
  /* read Body of the respons */
  Rc=TcpRecvUntilClosedEx (& CSock,
                             szLocalFile,
                             (FARPROC) CbkTransmit,
                             s_uHttp4uTimeout,
                             s_uHttp4uBufferSize,
                             lUserValue,
                             RealBodySize);

  switch (Rc)
  {
    case TCP4U_SUCCESS:     Rc = HTTP4U_SUCCESS;    break; 
    case TCP4U_TIMEOUT:     Rc = HTTP4U_TIMEOUT;    break; 
    case TCP4U_FILE_ERROR : Rc = HTTP4U_FILE_ERROR; break; 
    case TCP4U_INSMEMORY :  Rc = HTTP4U_INSMEMORY;  break; 
    case TCP4U_CANCELLED :  Rc = HTTP4U_CANCELLED;  break; 
    default:                Rc = HTTP4U_TCP_FAILED; break; 
                                                                           
  }
XX_RETURN (Rc);
#undef XX_RETURN
} /* HttpGetFileEx */


/*######################################################################
 *##
 *## NAME: HttpGetFile   
 *##
 *## PURPOSE: Return body associate with the URL's parameter 
 *##
 *####################################################################*/
int API4U HttpGetFile( 
                 LPCSTR  szURL, 
                 LPCSTR  szProxyURL, 
                 LPCSTR  szLocalFile
                   )
{
int Rc;
   Tcp4uLog (LOG4U_HIPROC, "HttpGetFile");
  /* Appel sans callback */
  Rc = HttpGetFileEx(szURL,        /* the URL to be retrieved          */
                       szProxyURL,   /* The proxy to be used or NULL     */
                       szLocalFile,  /* the file to be written           */
                       NULL,         /* No header file                   */
                       NULL, 0,      /* No callback                      */
                       NULL, 0,      /* No memory allocated for response */
                       NULL, 0       /* No memory allocated for header   */
                       ); 
   Tcp4uLog (LOG4U_HIEXIT, "HttpGetFile");
return Rc;
} /* END HttpGetFile */


syntax highlighted by Code2HTML, v. 0.9.1