1 /*
2  * GTimer
3  *
4  * Copyright:
5  *	(C) 1999 Craig Knudsen, cknudsen@cknudsen.com
6  *	See accompanying file "COPYING".
7  *
8  *	This program is free software; you can redistribute it and/or
9  *	modify it under the terms of the GNU General Public License
10  *	as published by the Free Software Foundation; either version 2
11  *	of the License, or (at your option) any later version.
12  *
13  *	This program is distributed in the hope that it will be useful,
14  *	but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *	GNU General Public License for more details.
17  *
18  *	You should have received a copy of the GNU General Public License
19  *	along with this program; if not, write to the
20  *	Free Software Foundation, Inc., 59 Temple Place,
21  *	Suite 330, Boston, MA  02111-1307, USA
22  *
23  * Description:
24  *	Helps you keep track of time spent on different tasks.
25  *
26  * Author:
27  *	Craig Knudsen, cknudsen@cknudsen.com, http://www.cknudsen.com
28  *
29  * Home Page:
30  *	http://www.cknudsen.com/gtimer/
31  *
32  * History:
33  *	19-May-1999	Created (based on an 1995 app I wrote)
34  *
35  ****************************************************************************/
36 
37 
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <ctype.h>
42 #include <errno.h>
43 #include <sys/time.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #ifdef AIX
47 #include <sys/select.h>
48 #endif
49 #include <sys/ioctl.h>
50 #ifdef WIN32
51 #include <winsock.h>	/* winsock API */
52 #else
53 #include <unistd.h>
54 #include <netdb.h>	/* for gethostbyaddr() */
55 #include <sys/socket.h>
56 #include <netinet/in.h>
57 #endif
58 #include <fcntl.h>
59 #include <time.h>
60 
61 #include "project.h"
62 #include "task.h"
63 #include "gtimer.h"
64 
65 #include "tcpt.h"
66 #include "http.h"
67 
68 #ifdef GTIMER_MEMDEBUG
69 #include "memdebug/memdebug.h"
70 #endif
71 
72 
73 static char *my_strtok (
74 #ifndef _NO_PROTO
75   char *ptr1, char *tok
76 #endif
77 );
78 
79 
80 /*
81 ** Max number of bytes to read at once.
82 */
83 #define MAX_READ_SIZE		1024
84 
85 /*
86 ** Structure for states.
87 */
88 typedef struct _httpRequest {
89   sockfd connection;			/* connection to server */
90   char *request;			/* text message to send */
91   httpError (*read_function)();		/* read function */
92 #ifdef _NO_PROTO
93   void (*callback)();			/* callback when results received */
94 #else
95   void (*callback)(char *);		/* callback when results received */
96 #endif
97 #ifdef _NO_PROTO
98   void (*gen_callback)();		/* callback when results received */
99 #else
100   void (*gen_callback)(char *,int);	/* callback when results received */
101 #endif
102   int sent;				/* has request been sent */
103   char *data_read;			/* data that was read */
104   int data_len;				/* size of above data */
105   int content_length;			/* length according to header */
106   int pass;				/* no. reads done */
107 } httpRequest;
108 
109 #define MAX_REQUESTS_QUEUED	2048
110 
111 static char http_other_error[256];
112 static httpRequest *requests[MAX_REQUESTS_QUEUED];
113 static int num_requests = 0;
114 static char *http_proxy = NULL;
115 static char http_proxy_string[1024];
116 static int http_proxy_port;
117 
118 
user_agent()119 static char *user_agent () {
120   static char ret[100];
121 
122   sprintf ( ret, "GTimer/%s", GTIMER_VERSION );
123   return ( ret );
124 }
125 
126 /*
127 ** Local functions.
128 */
129 static char *httpTruncateHeader (
130 #ifndef _NO_PROTO
131   char *text,
132   int *len
133 #endif
134 );
135 static httpError httpReadGet (
136 #ifndef _NO_PROTO
137   httpRequest *request
138 #endif
139 );
140 static httpError httpReadGetArticles (
141 #ifndef _NO_PROTO
142   httpRequest *request
143 #endif
144 );
145 static httpError httpReadGetAds (
146 #ifndef _NO_PROTO
147   httpRequest *request
148 #endif
149 );
150 static httpError httpReadList (
151 #ifndef _NO_PROTO
152   httpRequest *request
153 #endif
154 );
155 static httpError httpReadGetUserId (
156 #ifndef _NO_PROTO
157   httpRequest *request
158 #endif
159 );
160 static httpError httpReadGetSessionId (
161 #ifndef _NO_PROTO
162   httpRequest *request
163 #endif
164 );
165 static httpError httpSendRequest (
166 #ifndef _NO_PROTO
167   httpRequest *request
168 #endif
169 );
170 static char *encode_for_use_in_url (
171 #ifndef _NO_PROTO
172   char *str
173 #endif
174 );
175 
176 
177 
178 
179 
180 /*
181 ** URL-encode a string
182 ** See RFC 1738 (http://andrew2.andrew.cmu.edu/rfc/rfc1738.html)
183 ** Caller should free result.
184 */
encode_for_use_in_url(str)185 static char *encode_for_use_in_url ( str )
186 char *str;
187 {
188   char *ret, *ptr1, *ptr2, *ptr3;
189   static char unsafe_chars[] = {
190     ' ', '{', '}', '|', '/', '\\', '^', '~', '[', ']', '`', '%', '<', '>', '"', '#', ')', '(', '\0',
191   };
192   int unsafe;
193 
194   /* just to be safe... */
195   ret = (char *) malloc ( strlen ( str ) * 3 + 1 );
196 
197   for ( ptr1 = str, ptr2 = ret; *ptr1 != '\0'; ptr1++ ) {
198     unsafe = 0;
199     /* is unsafe??? */
200     for ( ptr3 = unsafe_chars; *ptr3 != '\0'; ptr3++ ) {
201       if ( *ptr3 == *ptr1 ) {
202         unsafe = 1;
203         break;
204       }
205     }
206 /*
207     if ( ! unsafe ) {
208       if ( ( (unsigned int)*ptr1 < 0x80 || (unsigned int)*ptr1 > 0xFF ) ||
209         ( (unsigned int)*ptr1 >= 0x00 && (unsigned int)*ptr1 <= 0x1F ) ||
210         ( (unsigned int)*ptr1 == 0x7F ) )
211         unsafe = 1;
212     }
213 */
214     if ( unsafe ) {
215       sprintf ( ptr2, "%%%02X", (unsigned int)*ptr1 );
216       ptr2 += 3;
217     }
218     else {
219       *ptr2 = *ptr1;
220       ptr2++;
221     }
222   }
223   *ptr2 = '\0';
224 
225   return ( ret );
226 }
227 
228 
229 
httpEnableProxy(server,port)230 void httpEnableProxy ( server, port )
231 char *server;
232 int port;
233 {
234   if ( http_proxy )
235     free ( http_proxy );
236   http_proxy = (char *) malloc ( strlen ( server ) + 1 );
237   strcpy ( http_proxy, server );
238   http_proxy_port = port;
239 }
240 
241 
httpDisableProxy()242 void httpDisableProxy ()
243 {
244   if ( http_proxy )
245     free ( http_proxy );
246   http_proxy_string[0] = '\0';
247   http_proxy = NULL;
248 }
249 
250 
251 /*
252 ** Put a request onto the end of the queue.
253 ** Current request is always reqeusts[0].
254 */
httpEnqueueRequest(connection,msg,read_function,callback,gen_callback)255 httpError httpEnqueueRequest ( connection, msg, read_function, callback,
256   gen_callback )
257 sockfd connection;
258 char *msg;
259 #ifdef _NO_PROTO
260 httpError (*read_function)();
261 void (*callback)();
262 void (*gen_callback)();
263 #else
264 httpError (*read_function)(sockfd);
265 void (*callback)(char *);
266 void (*gen_callback)(char *,int);
267 #endif
268 {
269   httpRequest *request;
270 
271   if ( num_requests >= MAX_REQUESTS_QUEUED - 1 ) {
272     fprintf ( stderr, "Too many errors...\n" );
273     return ( HTTP_TOO_MANY_REQUESTS );
274   }
275 
276   /* put request at end of queue. */
277   request = (httpRequest *) malloc ( sizeof ( httpRequest ) );
278   memset ( request, '\0', sizeof ( httpRequest ) );
279   if ( msg ) {
280     request->request = (char *) malloc ( strlen ( msg ) + 1 );
281     strcpy ( request->request, msg );
282     request->connection = connection;
283   }
284   else {
285     /* NULL msg indicates no message to send */
286     request->request = NULL;
287     request->sent = 1;
288   }
289   request->connection = connection;
290   request->read_function = read_function;
291   request->callback = callback;
292   request->gen_callback = gen_callback;
293   request->data_read = NULL;
294   requests[num_requests++] = request;
295 
296   /* now send the request if this is the only request */
297   if ( num_requests == 1 ) {
298     httpSendRequest ( requests[0] );
299   }
300 
301   return ( HTTP_NO_ERROR );
302 }
303 
304 
305 
306 /*
307 ** Send a request to the server.
308 ** This should not be called directly, only from httpDequeueRequest and
309 ** httpEnqueueRequest.
310 */
httpSendRequest(request)311 static httpError httpSendRequest ( request )
312 httpRequest *request;
313 {
314   int rval;
315 
316   if ( ! request->sent ) {
317     rval = send ( request->connection, request->request,
318       strlen ( request->request ), 0 );
319 
320     request->sent = 1;
321 
322     if ( rval >= 0 )
323       return ( HTTP_NO_ERROR );
324     else
325       return ( HTTP_SOCKET_ERROR );
326   }
327   else
328     return ( HTTP_NO_ERROR );
329 }
330 
331 
332 
333 /*
334 ** Remove the most recent request from the queue.
335 ** It is not an error to call this function with nothing in the queue.
336 */
httpDequeueRequest()337 httpError httpDequeueRequest ()
338 {
339   int loop;
340 
341   if ( num_requests ) {
342     if ( requests[0]->data_read )
343       free ( requests[0]->data_read );
344     if ( requests[0]->request )
345       free ( requests[0]->request );
346     free ( requests[0] );
347     /* move each request up one slot */
348     for ( loop = 1; loop < num_requests; loop++ )
349       requests[loop-1] = requests[loop];
350     num_requests--;
351   }
352 
353   if ( num_requests )
354     httpSendRequest ( requests[0] );
355 
356   return ( HTTP_NO_ERROR );
357 }
358 
359 
360 
361 /*
362 ** Calling app calls this when select() indicates data is ready to be
363 ** read on socket.  This will figure out where we left off and
364 ** continue from there.
365 */
httpProcessRead(connection)366 httpError httpProcessRead ( connection )
367 sockfd connection;
368 {
369   int loop;
370   int num = -1;
371 
372   for ( loop = 0; loop < num_requests; loop++ ) {
373     if ( requests[loop]->connection == connection ) {
374       num = loop;
375       break;
376     }
377   }
378 
379   if ( num < 0 )
380     return ( HTTP_NO_REQUESTS );
381 
382   return ( requests[loop]->read_function ( requests[loop] ) );
383 }
384 
385 
386 
387 /*
388 ** Connect to an HTTP server.
389 ** Returns socket file descriptor.
390 ** Note that this is the one place where we must wait for a response
391 ** that could cause things to hang a bit.
392 */
httpOpenConnection(http_host,port,connection)393 httpError httpOpenConnection ( http_host, port, connection )
394 char *http_host;
395 int port;
396 sockfd *connection;
397 {
398   sockfd sock;
399   static struct sockaddr_in server;
400   struct hostent *hp, *gethostbyname ();
401   tcptError ret;
402 
403   memset ( &server, '\0', sizeof ( struct sockaddr_in ) );
404 
405   /* Verify all necessary data is available */
406   if ( ! http_host || ! strlen ( http_host ) )
407     return ( HTTP_INVALID_HOST );
408 
409   if ( http_proxy ) {
410     /* save info for next request */
411     sprintf ( http_proxy_string, "http://%s:%d", http_host, port );
412     /* now change to http proxy */
413     http_host = http_proxy;
414     port = http_proxy_port;
415   }
416 
417   hp = gethostbyname ( http_host );
418 
419   if ( ! hp ) {
420     return ( HTTP_HOST_LOOKUP_FAILED );
421   }
422 
423   /* Init windows winsock DLL */
424   if ( tcptInit () ) {
425     return ( HTTP_SOCKET_ERROR );
426   }
427 
428   sock = socket ( AF_INET, SOCK_STREAM, 0 );
429   if ( sock < 0 ) {
430     tcptCleanup ();
431     return ( HTTP_SOCKET_ERROR );
432   }
433 
434   server.sin_family = AF_INET;
435   memcpy ( (char *)&server.sin_addr, (char*)hp->h_addr, hp->h_length );
436 
437   /* Get HTTP port (80) */
438   if ( port )
439     server.sin_port = htons ( port );
440   else
441     server.sin_port = htons ( HTTP_PORT );
442 
443   if ( ( ret = tcptConnect ( sock, (struct sockaddr *)&server,
444      sizeof ( server ) ) ) ) {
445     strcpy ( http_other_error, tcptErrorString ( ret ) );
446     return ( HTTP_OTHER_ERROR );
447   }
448 
449   /* success.  return socket */
450   *connection = sock;
451 
452   /* Set to non-blocking */
453   /*ioctl ( *connection, FIONBIO, 1 );*/
454 
455   return ( HTTP_NO_ERROR );
456 }
457 
458 
459 
460 
461 
462 /*
463 ** Close the connection and remove all requests from the queue.
464 */
httpKillConnection(connection)465 httpError httpKillConnection ( connection )
466 sockfd connection;
467 {
468   int loop, newnum;
469 
470   /*
471   ** Remove all requests in the queue that reference this connection.
472   */
473   for ( loop = 0, newnum = 0; loop < num_requests; loop++ ) {
474     if ( requests[loop]->connection == connection ) {
475       /* NULL out requests we are killing */
476       if ( requests[loop]->data_read )
477         free ( requests[loop]->data_read );
478       if ( requests[loop]->request )
479         free ( requests[loop]->request );
480       free ( requests[loop] );
481       requests[loop] = NULL;
482     }
483     else {
484       requests[newnum] = requests[loop];
485     }
486   }
487 
488   for ( loop = 0, newnum = 0; loop < num_requests; loop++ ) {
489     if ( requests[loop] && loop != newnum ) {
490       requests[newnum] = requests[loop];
491       requests[loop] = NULL;
492       newnum++;
493     }
494   }
495 
496   num_requests = newnum;
497 
498   closesocket ( connection );
499   return ( HTTP_NO_ERROR );
500 }
501 
502 
503 
504 /*
505 ** Do a generic get request.
506 */
httpGet(connection,virthost,path,qs_names,qs_values,num,callback)507 httpError httpGet ( connection, virthost, path, qs_names, qs_values,
508   num, callback )
509 sockfd connection;
510 char *virthost;
511 char *path;
512 char *qs_names[];
513 char *qs_values[];
514 int num;
515 #ifdef _NO_PROTO
516 void (*callback)();
517 #else
518 void (*callback)(char *,int);
519 #endif
520 {
521   char *command, *temp, temp2[256], *ret1, *ret2;
522   int loop;
523 
524   temp = (char *) malloc ( strlen ( path ) + strlen ( http_proxy_string ) +
525     5 );
526   sprintf ( temp, "GET %s%s", http_proxy_string, path );
527   command = (char *) malloc ( strlen ( temp ) + 2 );
528   strcpy ( command, temp );
529   free ( temp );
530   if ( num ) {
531     strcat ( command, "?" );
532     for ( loop = 0; loop < num; loop++ ) {
533       ret1 = encode_for_use_in_url ( qs_names[loop] );
534       ret2 = encode_for_use_in_url ( qs_values[loop] );
535       command = (char *) realloc ( command, strlen ( command ) +
536         strlen ( ret1 ) + strlen ( ret2 ) + 3 );
537       if ( loop )
538         strcat ( command, "&" );
539       strcat ( command, ret1 );
540       strcat ( command, "=" );
541       strcat ( command, ret2 );
542       free ( ret1 );
543       free ( ret2 );
544     }
545   }
546   strcpy ( temp2, " HTTP/1.0\r\n" );
547   strcat ( temp2, "User-Agent: " );
548   strcat ( temp2, user_agent() );
549   strcat ( temp2, "\r\n" );
550   if ( virthost != NULL ) {
551     strcat ( temp2, "Host: " );
552     strcat ( temp2, virthost );
553     strcat ( temp2, "\r\n" );
554   }
555   strcat ( temp2, "\r\n" );
556   command = (char *) realloc ( command, strlen ( command ) +
557     strlen ( temp2 ) + 1 );
558   strcat ( command, temp2 );
559 
560   httpEnqueueRequest ( connection, command, httpReadGet, NULL, callback );
561 
562   return ( HTTP_NO_ERROR );
563 }
564 
my_strncasecmp(str1,str2,len)565 static int my_strncasecmp ( str1, str2, len )
566 char *str1, *str2;
567 int len;
568 {
569   char *a1, *a2, *ptr;
570   int ret, loop;
571 
572   a1 = (char *) malloc ( len + 1 );
573   strncpy ( a1, str1, len );
574   a1[len] = '\0';
575   a2 = (char *) malloc ( len + 1 );
576   strncpy ( a2, str2, len );
577   a2[len] = '\0';
578   for ( ptr = a1, loop = 0; *ptr != '\0' && loop < len; loop++, ptr++ )
579     *ptr = toupper ( *ptr );
580   for ( ptr = a2, loop = 0; *ptr != '\0' && loop < len; loop++, ptr++ )
581     *ptr = toupper ( *ptr );
582 
583   ret = strncmp ( a1, a2, len );
584   free ( a1 );
585   free ( a2 );
586 
587   return ( ret );
588 }
589 
httpReadGet(request)590 static httpError httpReadGet ( request )
591 httpRequest *request;
592 {
593   int rval;
594   char data[MAX_READ_SIZE], *alldata, *ptr;
595   int len;
596 
597   memset ( data, '\0', MAX_READ_SIZE );
598   rval = recv ( request->connection, data, MAX_READ_SIZE, 0 );
599   if ( rval < 0 )
600     return ( HTTP_SOCKET_ERROR );
601   else if ( rval == 0 ) {
602     /* we are done. */
603     request->gen_callback ( request->data_read, request->data_len );
604     request->gen_callback ( NULL, 0 );
605     httpDequeueRequest ();
606     return ( HTTP_NO_ERROR );
607   }
608   if ( request->data_read ) {
609     alldata = (char *) malloc ( request->data_len + rval );
610     memcpy ( alldata, request->data_read, request->data_len );
611     memcpy ( alldata + request->data_len, data, rval );
612     len = request->data_len + rval;
613   }
614   else {
615     alldata = (char *) malloc ( rval );
616     memcpy ( alldata, data, rval );
617     len = rval;
618   }
619   if ( request->data_read )
620     free ( request->data_read );
621   if ( ! request->content_length ) {
622     request->data_read = httpTruncateHeader ( alldata, &len );
623     if ( request->data_read )
624       request->data_len = len;
625     if ( request->data_read ) {
626       /* complete header found (starts in alldata) */
627       ptr = strtok ( alldata, "\r\n" );
628       while ( ptr ) {
629         if ( strncmp ( ptr, "HTTP/1.0 200", 12 ) == 0 ||
630           strncmp ( ptr, "HTTP/1.1 200", 12 ) == 0 ) {
631           /* ok status. ignore */
632         }
633         else if ( strncmp ( ptr, "HTTP/1.", 7 ) == 0 ) {
634           /* some other status -- error */
635           request->gen_callback ( ptr, strlen ( ptr ) );   /* indicates error */
636           request->gen_callback ( NULL, 0 );        /* indicates error */
637           httpDequeueRequest ();
638           free ( alldata );
639           return ( HTTP_HTTP_ERROR );
640         }
641         else if ( my_strncasecmp ( ptr, "Content-Length:", 15 ) == 0 ) {
642           /* specifies length of content */
643           request->content_length = atoi ( ptr + 15 );
644           break;
645         }
646         else {
647            /* ignore all others.... */
648         }
649         ptr = strtok ( NULL, "\r\n" );
650       }
651     }
652   }
653   else {
654     if ( request->content_length == len ) {
655       /* we're done */
656       request->gen_callback ( alldata, len );
657       request->gen_callback ( NULL, 0 );
658       httpDequeueRequest ();
659       free ( alldata );
660       return ( HTTP_HTTP_ERROR );
661     }
662   }
663 
664   free ( alldata );
665   return ( HTTP_NO_ERROR );
666 }
667 
668 
669 
670 
671 
672 
httpErrorString(num)673 char *httpErrorString ( num )
674 httpError num;
675 {
676   static char msg[200];
677 
678   switch ( num ) {
679     case HTTP_NO_ERROR:
680       return ( "No error." );
681     case HTTP_INVALID_HOST:
682       return ( "Unable to resolve server name." );
683     case HTTP_SYSTEM_ERROR:
684       sprintf ( msg, "System error (%d)", errno );
685       return ( msg );
686     case HTTP_SOCKET_ERROR:
687       sprintf ( msg, "System error (%d)", errno );
688       return ( msg );
689     case HTTP_HTTP_ERROR:
690       return ( "HTTP protocol error." );
691     case HTTP_NO_REQUESTS:
692       return ( "No requests on queue." );
693     case HTTP_TOO_MANY_REQUESTS:
694       return ( "Too many requests on queue." );
695     case HTTP_HOST_LOOKUP_FAILED:
696       return ( "Unable to resolve server hostname" );
697     case HTTP_OTHER_ERROR:
698       return ( http_other_error );
699     case HTTP_UNKNOWN_ERROR:
700     default:
701       return ( "Unknown error." );
702   }
703 }
704 
705 
706 
707 
708 
709 
710 
711 
712 
713 /*
714 ** Take the given text and truncate after the http header
715 */
httpTruncateHeader(text,len)716 static char *httpTruncateHeader ( text, len )
717 char *text;
718 int *len;
719 {
720   char *ptr;
721   char *ret;
722   char *end_header1 = "\n\n";
723   char *end_header2 = "\r\n\r\n";
724   int loop;
725   int newlen;
726 
727   /* check to see it we've reached the end. */
728   if ( strlen ( text ) < 10 )
729     return ( NULL );
730 
731   /* back up to the last separator */
732   for ( loop = 0, ptr = text; loop < *len; loop++, ptr++ ) {
733     if ( strncmp ( ptr, end_header1, strlen ( end_header1 ) ) == 0 ) {
734       /* end of http header found */
735       *ptr = '\0';
736       ptr += strlen ( end_header1 );
737       newlen = *len - strlen ( text ) - strlen ( end_header1 );
738       ret = (char *) malloc ( newlen );
739       memcpy ( ret, ptr, newlen );
740       *len = newlen;
741       return ( ret );
742     }
743     else if ( strncmp ( ptr, end_header2, strlen ( end_header2 ) ) == 0 ) {
744       /* end of http header found */
745       *ptr = '\0';
746       ptr += strlen ( end_header2 );
747       newlen = *len - strlen ( text ) - strlen ( end_header2 );
748       ret = (char *) malloc ( newlen );
749       memcpy ( ret, ptr, newlen );
750       *len = newlen;
751       return ( ret );
752     }
753   }
754 
755   return ( NULL );
756 }
757 
758 
759 
760 
my_strtok(ptr1,tok)761 static char *my_strtok ( ptr1, tok )
762 char *ptr1;
763 char *tok;
764 {
765   static char *last;
766   char *p;
767 
768   if ( ! ptr1 )
769     ptr1 = last;
770   else
771     last = ptr1;
772 
773   if ( ! ptr1 )
774     return ( NULL );
775 
776   for ( p = ptr1; *p != '\0'; p++ ) {
777     if ( strncmp ( p, tok, strlen ( tok ) ) == 0 ) {
778       *p = '\0';
779       last = p + strlen ( tok );
780       return ( ptr1 );
781     }
782   }
783 
784   last = NULL;
785   return ( ptr1 );
786 }
787 
788