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