/*
* GTimer
*
* Copyright:
* (C) 1999 Craig Knudsen, cknudsen@cknudsen.com
* See accompanying file "COPYING".
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place,
* Suite 330, Boston, MA 02111-1307, USA
*
* Description:
* Helps you keep track of time spent on different tasks.
*
* Author:
* Craig Knudsen, cknudsen@cknudsen.com, http://www.cknudsen.com
*
* Home Page:
* http://www.cknudsen.com/gtimer/
*
* History:
* 19-May-1999 Created (based on an 1995 app I wrote)
*
****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef AIX
#include <sys/select.h>
#endif
#include <sys/ioctl.h>
#ifdef WIN32
#include <winsock.h> /* winsock API */
#else
#include <unistd.h>
#include <netdb.h> /* for gethostbyaddr() */
#include <sys/socket.h>
#include <netinet/in.h>
#endif
#include <fcntl.h>
#include <time.h>
#include "project.h"
#include "task.h"
#include "gtimer.h"
#include "tcpt.h"
#include "http.h"
#ifdef GTIMER_MEMDEBUG
#include "memdebug/memdebug.h"
#endif
static char *my_strtok (
#ifndef _NO_PROTO
char *ptr1, char *tok
#endif
);
/*
** Max number of bytes to read at once.
*/
#define MAX_READ_SIZE 1024
/*
** Structure for states.
*/
typedef struct _httpRequest {
sockfd connection; /* connection to server */
char *request; /* text message to send */
httpError (*read_function)(); /* read function */
#ifdef _NO_PROTO
void (*callback)(); /* callback when results received */
#else
void (*callback)(char *); /* callback when results received */
#endif
#ifdef _NO_PROTO
void (*gen_callback)(); /* callback when results received */
#else
void (*gen_callback)(char *,int); /* callback when results received */
#endif
int sent; /* has request been sent */
char *data_read; /* data that was read */
int data_len; /* size of above data */
int content_length; /* length according to header */
int pass; /* no. reads done */
} httpRequest;
#define MAX_REQUESTS_QUEUED 2048
static char http_other_error[256];
static httpRequest *requests[MAX_REQUESTS_QUEUED];
static int num_requests = 0;
static char *http_proxy = NULL;
static char http_proxy_string[1024];
static int http_proxy_port;
static char *user_agent () {
static char ret[100];
sprintf ( ret, "GTimer/%s", GTIMER_VERSION );
return ( ret );
}
/*
** Local functions.
*/
static char *httpTruncateHeader (
#ifndef _NO_PROTO
char *text,
int *len
#endif
);
static httpError httpReadGet (
#ifndef _NO_PROTO
httpRequest *request
#endif
);
static httpError httpReadGetArticles (
#ifndef _NO_PROTO
httpRequest *request
#endif
);
static httpError httpReadGetAds (
#ifndef _NO_PROTO
httpRequest *request
#endif
);
static httpError httpReadList (
#ifndef _NO_PROTO
httpRequest *request
#endif
);
static httpError httpReadGetUserId (
#ifndef _NO_PROTO
httpRequest *request
#endif
);
static httpError httpReadGetSessionId (
#ifndef _NO_PROTO
httpRequest *request
#endif
);
static httpError httpSendRequest (
#ifndef _NO_PROTO
httpRequest *request
#endif
);
static char *encode_for_use_in_url (
#ifndef _NO_PROTO
char *str
#endif
);
/*
** URL-encode a string
** See RFC 1738 (http://andrew2.andrew.cmu.edu/rfc/rfc1738.html)
** Caller should free result.
*/
static char *encode_for_use_in_url ( str )
char *str;
{
char *ret, *ptr1, *ptr2, *ptr3;
static char unsafe_chars[] = {
' ', '{', '}', '|', '/', '\\', '^', '~', '[', ']', '`', '%', '<', '>', '"', '#', ')', '(', '\0',
};
int unsafe;
/* just to be safe... */
ret = (char *) malloc ( strlen ( str ) * 3 + 1 );
for ( ptr1 = str, ptr2 = ret; *ptr1 != '\0'; ptr1++ ) {
unsafe = 0;
/* is unsafe??? */
for ( ptr3 = unsafe_chars; *ptr3 != '\0'; ptr3++ ) {
if ( *ptr3 == *ptr1 ) {
unsafe = 1;
break;
}
}
/*
if ( ! unsafe ) {
if ( ( (unsigned int)*ptr1 < 0x80 || (unsigned int)*ptr1 > 0xFF ) ||
( (unsigned int)*ptr1 >= 0x00 && (unsigned int)*ptr1 <= 0x1F ) ||
( (unsigned int)*ptr1 == 0x7F ) )
unsafe = 1;
}
*/
if ( unsafe ) {
sprintf ( ptr2, "%%%02X", (unsigned int)*ptr1 );
ptr2 += 3;
}
else {
*ptr2 = *ptr1;
ptr2++;
}
}
*ptr2 = '\0';
return ( ret );
}
void httpEnableProxy ( server, port )
char *server;
int port;
{
if ( http_proxy )
free ( http_proxy );
http_proxy = (char *) malloc ( strlen ( server ) + 1 );
strcpy ( http_proxy, server );
http_proxy_port = port;
}
void httpDisableProxy ()
{
if ( http_proxy )
free ( http_proxy );
http_proxy_string[0] = '\0';
http_proxy = NULL;
}
/*
** Put a request onto the end of the queue.
** Current request is always reqeusts[0].
*/
httpError httpEnqueueRequest ( connection, msg, read_function, callback,
gen_callback )
sockfd connection;
char *msg;
#ifdef _NO_PROTO
httpError (*read_function)();
void (*callback)();
void (*gen_callback)();
#else
httpError (*read_function)(sockfd);
void (*callback)(char *);
void (*gen_callback)(char *,int);
#endif
{
httpRequest *request;
if ( num_requests >= MAX_REQUESTS_QUEUED - 1 ) {
fprintf ( stderr, "Too many errors...\n" );
return ( HTTP_TOO_MANY_REQUESTS );
}
/* put request at end of queue. */
request = (httpRequest *) malloc ( sizeof ( httpRequest ) );
memset ( request, '\0', sizeof ( httpRequest ) );
if ( msg ) {
request->request = (char *) malloc ( strlen ( msg ) + 1 );
strcpy ( request->request, msg );
request->connection = connection;
}
else {
/* NULL msg indicates no message to send */
request->request = NULL;
request->sent = 1;
}
request->connection = connection;
request->read_function = read_function;
request->callback = callback;
request->gen_callback = gen_callback;
request->data_read = NULL;
requests[num_requests++] = request;
/* now send the request if this is the only request */
if ( num_requests == 1 ) {
httpSendRequest ( requests[0] );
}
return ( HTTP_NO_ERROR );
}
/*
** Send a request to the server.
** This should not be called directly, only from httpDequeueRequest and
** httpEnqueueRequest.
*/
static httpError httpSendRequest ( request )
httpRequest *request;
{
int rval;
if ( ! request->sent ) {
rval = send ( request->connection, request->request,
strlen ( request->request ), 0 );
request->sent = 1;
if ( rval >= 0 )
return ( HTTP_NO_ERROR );
else
return ( HTTP_SOCKET_ERROR );
}
else
return ( HTTP_NO_ERROR );
}
/*
** Remove the most recent request from the queue.
** It is not an error to call this function with nothing in the queue.
*/
httpError httpDequeueRequest ()
{
int loop;
if ( num_requests ) {
if ( requests[0]->data_read )
free ( requests[0]->data_read );
if ( requests[0]->request )
free ( requests[0]->request );
free ( requests[0] );
/* move each request up one slot */
for ( loop = 1; loop < num_requests; loop++ )
requests[loop-1] = requests[loop];
num_requests--;
}
if ( num_requests )
httpSendRequest ( requests[0] );
return ( HTTP_NO_ERROR );
}
/*
** Calling app calls this when select() indicates data is ready to be
** read on socket. This will figure out where we left off and
** continue from there.
*/
httpError httpProcessRead ( connection )
sockfd connection;
{
int loop;
int num = -1;
for ( loop = 0; loop < num_requests; loop++ ) {
if ( requests[loop]->connection == connection ) {
num = loop;
break;
}
}
if ( num < 0 )
return ( HTTP_NO_REQUESTS );
return ( requests[loop]->read_function ( requests[loop] ) );
}
/*
** Connect to an HTTP server.
** Returns socket file descriptor.
** Note that this is the one place where we must wait for a response
** that could cause things to hang a bit.
*/
httpError httpOpenConnection ( http_host, port, connection )
char *http_host;
int port;
sockfd *connection;
{
sockfd sock;
static struct sockaddr_in server;
struct hostent *hp, *gethostbyname ();
tcptError ret;
memset ( &server, '\0', sizeof ( struct sockaddr_in ) );
/* Verify all necessary data is available */
if ( ! http_host || ! strlen ( http_host ) )
return ( HTTP_INVALID_HOST );
if ( http_proxy ) {
/* save info for next request */
sprintf ( http_proxy_string, "http://%s:%d", http_host, port );
/* now change to http proxy */
http_host = http_proxy;
port = http_proxy_port;
}
hp = gethostbyname ( http_host );
if ( ! hp ) {
return ( HTTP_HOST_LOOKUP_FAILED );
}
/* Init windows winsock DLL */
if ( tcptInit () ) {
return ( HTTP_SOCKET_ERROR );
}
sock = socket ( AF_INET, SOCK_STREAM, 0 );
if ( sock < 0 ) {
tcptCleanup ();
return ( HTTP_SOCKET_ERROR );
}
server.sin_family = AF_INET;
memcpy ( (char *)&server.sin_addr, (char*)hp->h_addr, hp->h_length );
/* Get HTTP port (80) */
if ( port )
server.sin_port = htons ( port );
else
server.sin_port = htons ( HTTP_PORT );
if ( ( ret = tcptConnect ( sock, (struct sockaddr *)&server,
sizeof ( server ) ) ) ) {
strcpy ( http_other_error, tcptErrorString ( ret ) );
return ( HTTP_OTHER_ERROR );
}
/* success. return socket */
*connection = sock;
/* Set to non-blocking */
/*ioctl ( *connection, FIONBIO, 1 );*/
return ( HTTP_NO_ERROR );
}
/*
** Close the connection and remove all requests from the queue.
*/
httpError httpKillConnection ( connection )
sockfd connection;
{
int loop, newnum;
/*
** Remove all requests in the queue that reference this connection.
*/
for ( loop = 0, newnum = 0; loop < num_requests; loop++ ) {
if ( requests[loop]->connection == connection ) {
/* NULL out requests we are killing */
if ( requests[loop]->data_read )
free ( requests[loop]->data_read );
if ( requests[loop]->request )
free ( requests[loop]->request );
free ( requests[loop] );
requests[loop] = NULL;
}
else {
requests[newnum] = requests[loop];
}
}
for ( loop = 0, newnum = 0; loop < num_requests; loop++ ) {
if ( requests[loop] && loop != newnum ) {
requests[newnum] = requests[loop];
requests[loop] = NULL;
newnum++;
}
}
num_requests = newnum;
closesocket ( connection );
return ( HTTP_NO_ERROR );
}
/*
** Do a generic get request.
*/
httpError httpGet ( connection, virthost, path, qs_names, qs_values,
num, callback )
sockfd connection;
char *virthost;
char *path;
char *qs_names[];
char *qs_values[];
int num;
#ifdef _NO_PROTO
void (*callback)();
#else
void (*callback)(char *,int);
#endif
{
char *command, *temp, temp2[256], *ret1, *ret2;
int loop;
temp = (char *) malloc ( strlen ( path ) + strlen ( http_proxy_string ) +
5 );
sprintf ( temp, "GET %s%s", http_proxy_string, path );
command = (char *) malloc ( strlen ( temp ) + 2 );
strcpy ( command, temp );
free ( temp );
if ( num ) {
strcat ( command, "?" );
for ( loop = 0; loop < num; loop++ ) {
ret1 = encode_for_use_in_url ( qs_names[loop] );
ret2 = encode_for_use_in_url ( qs_values[loop] );
command = (char *) realloc ( command, strlen ( command ) +
strlen ( ret1 ) + strlen ( ret2 ) + 3 );
if ( loop )
strcat ( command, "&" );
strcat ( command, ret1 );
strcat ( command, "=" );
strcat ( command, ret2 );
free ( ret1 );
free ( ret2 );
}
}
strcpy ( temp2, " HTTP/1.0\r\n" );
strcat ( temp2, "User-Agent: " );
strcat ( temp2, user_agent() );
strcat ( temp2, "\r\n" );
if ( virthost != NULL ) {
strcat ( temp2, "Host: " );
strcat ( temp2, virthost );
strcat ( temp2, "\r\n" );
}
strcat ( temp2, "\r\n" );
command = (char *) realloc ( command, strlen ( command ) +
strlen ( temp2 ) + 1 );
strcat ( command, temp2 );
httpEnqueueRequest ( connection, command, httpReadGet, NULL, callback );
return ( HTTP_NO_ERROR );
}
static int my_strncasecmp ( str1, str2, len )
char *str1, *str2;
int len;
{
char *a1, *a2, *ptr;
int ret, loop;
a1 = (char *) malloc ( len + 1 );
strncpy ( a1, str1, len );
a1[len] = '\0';
a2 = (char *) malloc ( len + 1 );
strncpy ( a2, str2, len );
a2[len] = '\0';
for ( ptr = a1, loop = 0; *ptr != '\0' && loop < len; loop++, ptr++ )
*ptr = toupper ( *ptr );
for ( ptr = a2, loop = 0; *ptr != '\0' && loop < len; loop++, ptr++ )
*ptr = toupper ( *ptr );
ret = strncmp ( a1, a2, len );
free ( a1 );
free ( a2 );
return ( ret );
}
static httpError httpReadGet ( request )
httpRequest *request;
{
int rval;
char data[MAX_READ_SIZE], *alldata, *ptr;
int len;
memset ( data, '\0', MAX_READ_SIZE );
rval = recv ( request->connection, data, MAX_READ_SIZE, 0 );
if ( rval < 0 )
return ( HTTP_SOCKET_ERROR );
else if ( rval == 0 ) {
/* we are done. */
request->gen_callback ( request->data_read, request->data_len );
request->gen_callback ( NULL, 0 );
httpDequeueRequest ();
return ( HTTP_NO_ERROR );
}
if ( request->data_read ) {
alldata = (char *) malloc ( request->data_len + rval );
memcpy ( alldata, request->data_read, request->data_len );
memcpy ( alldata + request->data_len, data, rval );
len = request->data_len + rval;
}
else {
alldata = (char *) malloc ( rval );
memcpy ( alldata, data, rval );
len = rval;
}
if ( request->data_read )
free ( request->data_read );
if ( ! request->content_length ) {
request->data_read = httpTruncateHeader ( alldata, &len );
if ( request->data_read )
request->data_len = len;
if ( request->data_read ) {
/* complete header found (starts in alldata) */
ptr = strtok ( alldata, "\r\n" );
while ( ptr ) {
if ( strncmp ( ptr, "HTTP/1.0 200", 12 ) == 0 ||
strncmp ( ptr, "HTTP/1.1 200", 12 ) == 0 ) {
/* ok status. ignore */
}
else if ( strncmp ( ptr, "HTTP/1.", 7 ) == 0 ) {
/* some other status -- error */
request->gen_callback ( ptr, strlen ( ptr ) ); /* indicates error */
request->gen_callback ( NULL, 0 ); /* indicates error */
httpDequeueRequest ();
free ( alldata );
return ( HTTP_HTTP_ERROR );
}
else if ( my_strncasecmp ( ptr, "Content-Length:", 15 ) == 0 ) {
/* specifies length of content */
request->content_length = atoi ( ptr + 15 );
break;
}
else {
/* ignore all others.... */
}
ptr = strtok ( NULL, "\r\n" );
}
}
}
else {
if ( request->content_length == len ) {
/* we're done */
request->gen_callback ( alldata, len );
request->gen_callback ( NULL, 0 );
httpDequeueRequest ();
free ( alldata );
return ( HTTP_HTTP_ERROR );
}
}
free ( alldata );
return ( HTTP_NO_ERROR );
}
char *httpErrorString ( num )
httpError num;
{
static char msg[200];
switch ( num ) {
case HTTP_NO_ERROR:
return ( "No error." );
case HTTP_INVALID_HOST:
return ( "Unable to resolve server name." );
case HTTP_SYSTEM_ERROR:
sprintf ( msg, "System error (%d)", errno );
return ( msg );
case HTTP_SOCKET_ERROR:
sprintf ( msg, "System error (%d)", errno );
return ( msg );
case HTTP_HTTP_ERROR:
return ( "HTTP protocol error." );
case HTTP_NO_REQUESTS:
return ( "No requests on queue." );
case HTTP_TOO_MANY_REQUESTS:
return ( "Too many requests on queue." );
case HTTP_HOST_LOOKUP_FAILED:
return ( "Unable to resolve server hostname" );
case HTTP_OTHER_ERROR:
return ( http_other_error );
case HTTP_UNKNOWN_ERROR:
default:
return ( "Unknown error." );
}
}
/*
** Take the given text and truncate after the http header
*/
static char *httpTruncateHeader ( text, len )
char *text;
int *len;
{
char *ptr;
char *ret;
char *end_header1 = "\n\n";
char *end_header2 = "\r\n\r\n";
int loop;
int newlen;
/* check to see it we've reached the end. */
if ( strlen ( text ) < 10 )
return ( NULL );
/* back up to the last separator */
for ( loop = 0, ptr = text; loop < *len; loop++, ptr++ ) {
if ( strncmp ( ptr, end_header1, strlen ( end_header1 ) ) == 0 ) {
/* end of http header found */
*ptr = '\0';
ptr += strlen ( end_header1 );
newlen = *len - strlen ( text ) - strlen ( end_header1 );
ret = (char *) malloc ( newlen );
memcpy ( ret, ptr, newlen );
*len = newlen;
return ( ret );
}
else if ( strncmp ( ptr, end_header2, strlen ( end_header2 ) ) == 0 ) {
/* end of http header found */
*ptr = '\0';
ptr += strlen ( end_header2 );
newlen = *len - strlen ( text ) - strlen ( end_header2 );
ret = (char *) malloc ( newlen );
memcpy ( ret, ptr, newlen );
*len = newlen;
return ( ret );
}
}
return ( NULL );
}
static char *my_strtok ( ptr1, tok )
char *ptr1;
char *tok;
{
static char *last;
char *p;
if ( ! ptr1 )
ptr1 = last;
else
last = ptr1;
if ( ! ptr1 )
return ( NULL );
for ( p = ptr1; *p != '\0'; p++ ) {
if ( strncmp ( p, tok, strlen ( tok ) ) == 0 ) {
*p = '\0';
last = p + strlen ( tok );
return ( ptr1 );
}
}
last = NULL;
return ( ptr1 );
}