summaryrefslogblamecommitdiffstats
path: root/cpukit/httpd/webs.c
blob: 3181c602eca10cdf3b6c8e5dcac3bb9811b80100 (plain) (tree)
1
2
3
4
5
6
7
8
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838


                                              
                                                                       

                                                                               

       




                                                                                
                                                                           





                                                                                 


                            


                                                                                
                                                                              
                                                                                               




                                                                                                    
                                                                          
                                                                          












                                                                                

                                   


                                             

                                                               








                                                                                                     


                                                                                             


                                                                                
 
                                        




                                                                             

                    
                                             

                            
                                                   










                                                                                



                                   






                               

                    


                                                                                
                                              











                                                                          
                       






















                                                                                



                                  

























                                                                 
                           































                                                                                 





                                                                                  


                                           

                                                     
                                                         
                


                                                                                      
         


                                                                        














                                                                                

                                           






                                                                                
                                                              
















                                                                              
                                  















                                                                                
                                                                             



                                                   

                                                                               





































                                                                                 
                             

                      
                                                  










                                                                               
                










































                                                                                     
                                                                  
                         
                                                       



                                    



                                                                                  
   










                                                                                               




































                                                                                                       














                                                                 


                                                                                  
   










                                                                                               





















                                                                                           




                                 







                                                                                

                                                                                   































                                                                                      
 








                                                                












                                                                                                        
                                                                           





                                                                             






                                                             
                                                    
      

                                 
                                


                                                


















                                                                                            




































                                                                                          



                                                                          

                                                 






                                                                                  















                                                                                


                                                                                
































                                                                  
                                             














                                                                                 









                                                            


                                        








                                                             
































                                                                                       


                                                                               

                                       
                                                                       



































                                                                              
                                                                           
















                                                            
                                                                    
  
                                                                   
   







                                                                         
                         

































                                                                                              
                                



























































































                                                                                               
 







                                                                      







                                                                    





                                                                  



















                                                                                 
                               
















                                                                              
                                                                 


                                           
                                                           
 
                                                                     

                                                              
 
                                            
                                     



                 









                                                                                
                                                     





                                                             
                                                   





                                                     





                                                                         












                                                                               




                                      










                                                                                          


























































                                                                                 
                                                                      





















                                                                                















                                                                                


                                                                      
                                 


                                


                                                
























                                                                                




                                                                                 
 

































                                                                                                  



                                                                       
 
                                                                                    
                                                                












                                                                                      



                                                                               











                                                                                
                                               













                                                                         









                                                                 






                                                                            
                                             



























                                                                                  
                                                     








                                                                                 
                                                              













                                                                                
                            















                                                                                
                                          










                                    

                                                                            














                                                                                
                                                                               
                                                                           

                                                                          



                                                      

                                  
 

                                


                              
































                                                                               
         


                             





                                                                                  



                                                                                  

   
                                                           
 
              





                                






                                                       
         





                                              













                                                                                










                                                                                
                                                                
































                                                                                
                                                                       
















                                                                                
                                   



                                          
                          




                                             





                                       




                                          
                                              






































                                                                                












                                               



                                                                           
                                                







                                                                                       

                                                                                 



                                      

                                           









                                                                                
                      
















                                                         
                         
                       

















                                







                                                                               
                                             








                                                                                
                        














                                             

                                         









                                          





























                                             











                                                                                
                                 

   
                     
 
                        



                                                                                
                                                             

   
                          
 
                             






                                                                                
                        






































































































                                                                                
                                         










                                                                                
                                     










                                                                                
                                         






































































































































































                                                                                 



                                                                             
                                             
 
                       





















                                                                                
                            














                                                                                


































































































































































































































































































































































































































































































































































































































                                                                                                                              
/*
 * webs.c -- GoAhead Embedded HTTP webs server
 *
 * Copyright (c) GoAhead Software Inc., 1995-2000. All Rights Reserved.
 *
 * See the file "license.txt" for usage and redistribution license requirements
 *
 * $Id$
 */

/******************************** Description *********************************/

/*
 *	This module implements an embedded HTTP/1.1 web server. It supports
 *	loadable URL handlers that define the nature of URL processing performed.
 */

/********************************* Includes ***********************************/

#include	"wsIntrn.h"
#ifdef DIGEST_ACCESS_SUPPORT
#include	"websda.h"
#endif

/******************************** Global Data *********************************/

websStatsType	websStats;				/* Web access stats */
webs_t			*webs;					/* Open connection list head */
sym_fd_t		websMime;				/* Set of mime types */
int				websMax;				/* List size */
int				websPort;				/* Listen port for server */
char_t			websHost[64];			/* Host name for the server */
char_t			websIpaddr[64];			/* IP address for the server */
char_t			*websHostUrl = NULL;	/* URL to access server */
char_t			*websIpaddrUrl = NULL;	/* URL to access server */

/*********************************** Locals ***********************************/
/*
 *	Standard HTTP error codes
 */

websErrorType websErrors[] = {
	{ 200, T("Data follows") },
	{ 204, T("No Content") },
	{ 301, T("Redirect") },
	{ 302, T("Redirect") },
	{ 304, T("User local copy") },
	{ 400, T("Page not found") },
	{ 401, T("Unauthorized") },
	{ 403, T("Forbidden") },
	{ 404, T("Site or Page Not Found") },
	{ 405, T("Access Denied") },
	{ 500, T("Web Error") },
	{ 501, T("Not Implemented") },
	{ 503, T("Site Temporarily Unavailable. Try again.") },
	{ 0, NULL }
};

#if WEBS_LOG_SUPPORT
static char_t	websLogname[64] = T("log.txt");	/* Log filename */
static int 		websLogFd;						/* Log file handle */
#endif

static int		websListenSock;					/* Listen socket */
static char_t	websRealm[64] = T("GoAhead");	/* Realm name */

static int		websOpenCount = 0;		/* count of apps using this module */

/**************************** Forward Declarations ****************************/


static char_t 	*websErrorMsg(int code);
static int 		websGetInput(webs_t wp, char_t **ptext, int *nbytes);
static int 		websParseFirst(webs_t wp, char_t *text);
static void 	websParseRequest(webs_t wp);
static void		websSocketEvent(int sid, int mask, int data);
static int		websGetTimeSinceMark(webs_t wp);

#if WEBS_LOG_SUPPORT
static void 	websLog(webs_t wp, int code);
#endif
#if WEBS_IF_MODIFIED_SUPPORT
static time_t	dateParse(time_t tip, char_t *cmd);
#endif

/*********************************** Code *************************************/
/*
 *	Open the GoAhead WebServer
 */

int websOpenServer(int port, int retries)
{
	websMimeType	*mt;

	if (++websOpenCount != 1) {
		return websPort;
	}

	a_assert(port > 0);
	a_assert(retries >= 0);

#if WEBS_PAGE_ROM
	websRomOpen();
#endif

	webs = NULL;
	websMax = 0;
/*
 *	Create a mime type lookup table for quickly determining the content type
 */
	websMime = symOpen(WEBS_SYM_INIT * 4);
	a_assert(websMime >= 0);
	for (mt = websMimeList; mt->type; mt++) {
		symEnter(websMime, mt->ext, valueString(mt->type, 0), 0);
	}

/*
 *	Open the URL handler module. The caller should create the required
 *	URL handlers after calling this function.
 */
	if (websUrlHandlerOpen() < 0) {
		return -1;
	}
	websFormOpen();

#if WEBS_LOG_SUPPORT
/*
 *	Optional request log support
 */
	websLogFd = gopen(websLogname, O_CREAT | O_TRUNC | O_APPEND | O_WRONLY, 
		0666);
	a_assert(websLogFd >= 0);
#endif
	
	return websOpenListen(port, retries);
}

/******************************************************************************/
/*
 *	Close the GoAhead WebServer
 */

void websCloseServer()
{
	webs_t	wp;
	int		wid;

	if (--websOpenCount > 0) {
		return;
	}

/*
 *	Close the listen handle first then all open connections.
 */
	websCloseListen();

/* 
 *	Close each open browser connection and free all resources
 */
	for (wid = websMax; webs && wid >= 0; wid--) {
		if ((wp = webs[wid]) == NULL) {
			continue;
		}
		socketCloseConnection(wp->sid);
		websFree(wp);
	}

#if WEBS_LOG_SUPPORT
	if (websLogFd >= 0) {
		close(websLogFd);
		websLogFd = -1;
	}
#endif

#if WEBS_PAGE_ROM
	websRomClose();
#endif
	symClose(websMime);
	websFormClose();
	websUrlHandlerClose();
}

/******************************************************************************/
/*
 *	Open the GoAhead WebServer listen port
 */

int websOpenListen(int port, int retries)
{
	int		i, orig;

	a_assert(port > 0);
	a_assert(retries >= 0);

	orig = port;
/*
 *	Open the webs webs listen port. If we fail, try the next port.
 */
	for (i = 0; i <= retries; i++) {
		websListenSock = socketOpenConnection(NULL, port, websAccept, 0);
		if (websListenSock >= 0) {
			break;
		}
		port++;
	}
	if (i > retries) {
		error(E_L, E_USER, T("Couldn't open a socket on ports %d - %d"),
			orig, port - 1);
		return -1;
	} 

/*
 *	Determine the full URL address to access the home page for this web server
 */
	websPort = port;
	bfreeSafe(B_L, websHostUrl);
	bfreeSafe(B_L, websIpaddrUrl);
	websIpaddrUrl = websHostUrl = NULL;

	if (port == 80) {
		websHostUrl = bstrdup(B_L, websHost);
		websIpaddrUrl = bstrdup(B_L, websIpaddr);
	} else {
		fmtAlloc(&websHostUrl, WEBS_MAX_URL + 80, T("%s:%d"), websHost, port);
		fmtAlloc(&websIpaddrUrl, WEBS_MAX_URL + 80, T("%s:%d"), 
			websIpaddr, port);
	}
	trace(0, T("webs: Listening for HTTP requests at address %s\n"),
		websIpaddrUrl);

	return port;
}

/******************************************************************************/
/*
 *	Close webs listen port
 */

void websCloseListen()
{
	if (websListenSock >= 0) {
		socketCloseConnection(websListenSock);
		websListenSock = -1;
	}
	bfreeSafe(B_L, websHostUrl);
	bfreeSafe(B_L, websIpaddrUrl);
	websIpaddrUrl = websHostUrl = NULL;
}

/******************************************************************************/
/*
 *	Accept a connection
 */

int websAccept(int sid, char *ipaddr, int port, int listenSid)
{
	webs_t	wp;
	int		wid;

	a_assert(ipaddr && *ipaddr);
	a_assert(sid >= 0);
	a_assert(port >= 0);

/*
 *	Allocate a new handle for this accepted connection. This will allocate
 *	a webs_t structure in the webs[] list
 */
	if ((wid = websAlloc(sid)) < 0) {
		return -1;
	}
	wp = webs[wid];
	a_assert(wp);
	wp->listenSid = listenSid;

	ascToUni(wp->ipaddr, ipaddr, sizeof(wp->ipaddr));

/*
 *	Check if this is a request from a browser on this system. This is useful
 *	to know for permitting administrative operations only for local access
 */
	if (gstrcmp(wp->ipaddr, T("127.0.0.1")) == 0 || 
			gstrcmp(wp->ipaddr, websIpaddr) == 0 || 
			gstrcmp(wp->ipaddr, websHost) == 0) {
		wp->flags |= WEBS_LOCAL_REQUEST;
	}

/*
 *	Arrange for websSocketEvent to be called when read data is available
 */
	socketCreateHandler(sid, SOCKET_READABLE, websSocketEvent, (int) wp);

/*
 *	Arrange for a timeout to kill hung requests
 */
	wp->timeout = emfSchedCallback(WEBS_TIMEOUT, websTimeout, (void *) wp);
	trace(8, T("webs: accept request\n"));
	return 0;
}

/******************************************************************************/
/*
 *	The webs socket handler.  Called in response to I/O. We just pass control
 *	to the relevant read or write handler. A pointer to the webs structure
 *	is passed as an (int) in iwp.
 */

static void websSocketEvent(int sid, int mask, int iwp)
{
	webs_t	wp;

	wp = (webs_t) iwp;
	a_assert(wp);

	if (! websValid(wp)) {
		return;
	}

	if (mask & SOCKET_READABLE) {
		websReadEvent(wp);
	} 
	if (mask & SOCKET_WRITABLE) {
		if (wp->writeSocket) {
			(*wp->writeSocket)(wp);
		}
	} 
}

/******************************************************************************/
/*
 *	The webs read handler. This is the primary read event loop. It uses a
 *	state machine to track progress while parsing the HTTP request. 
 *	Note: we never block as the socket is always in non-blocking mode.
 */

void websReadEvent(webs_t wp)
{
	char_t 	*text;
	int		rc, nbytes, len, done, fd;

	a_assert(wp);
	a_assert(websValid(wp));

	websMarkTime(wp);

/*
 *	Read as many lines as possible. socketGets is called to read the header
 *	and socketRead is called to read posted data.
 */
	text = NULL;
	fd = -1;
	for (done = 0; !done; ) {
		if (text) {
			bfree(B_L, text);
			text = NULL;
		}

/*
 *		Get more input into "text". Returns 0, if more data is needed
 *		to continue, -1 if finished with the request, or 1 if all 
 *		required data is available for current state.
 */
		while ((rc = websGetInput(wp, &text, &nbytes)) == 0) {
			;
		}

/*
 *		websGetInput returns -1 if it finishes with the request
 */
		if (rc < 0) {
			break;
		}

/*
 *		This is the state machine for the web server. 
 */
		switch(wp->state) {
		case WEBS_BEGIN:
/*
 *			Parse the first line of the Http header
 */
			if (websParseFirst(wp, text) < 0) {
				done++;
				break;
			}
			wp->state = WEBS_HEADER;
			break;
		
		case WEBS_HEADER:
/*
 *			Store more of the HTTP header. As we are doing line reads, we
 *			need to separate the lines with '\n'
 */
			if (ringqLen(&wp->header) > 0) {
				ringqPutStr(&wp->header, T("\n"));
			}
			ringqPutStr(&wp->header, text);
			break;

		case WEBS_POST_CLEN:
/*
 *			POST request with content specified by a content length.
 *			If this is a CGI request, write the data to the cgi stdin.
 *			socketGets was used to get the data and it strips \n's so
 *			add them back in here.
 */
#ifndef __NO_CGI_BIN
			if (wp->flags & WEBS_CGI_REQUEST) {
				if (fd == -1) {
					fd = gopen(wp->cgiStdin, O_CREAT | O_WRONLY | O_BINARY,
						0666);
				}
				gwrite(fd, text, gstrlen(text));
				gwrite(fd, T("\n"), sizeof(char_t));
				nbytes += 1;
			} else 
#endif
			if (wp->query) {
				if (wp->query[0] && !(wp->flags & WEBS_POST_DATA)) {
/*
 *					Special case where the POST request also had query data 
 *					specified in the URL, ie. url?query_data. In this case
 *					the URL query data is separated by a '&' from the posted
 *					query data.
 */
					len = gstrlen(wp->query);
					wp->query = brealloc(B_L, wp->query, (len + gstrlen(text) +
						2) * sizeof(char_t));
					wp->query[len++] = '&';
					gstrcpy(&wp->query[len], text);

				} else {
/*
 *					The existing query data came from the POST request so just
 *					append it.
 */
					len = gstrlen(wp->query);
					wp->query = brealloc(B_L, wp->query, (len +	gstrlen(text) +
						1) * sizeof(char_t));
					if (wp->query) {
						gstrcpy(&wp->query[len], text);
					}
				}

			} else {
				wp->query = bstrdup(B_L, text);
			}
/*
 *			Calculate how much more post data is to be read.
 */
			wp->flags |= WEBS_POST_DATA;
			wp->clen -= nbytes;
			if (wp->clen > 0) {
				if (nbytes > 0) {
					break;
				}
				done++;
				break;
			}
/*
 *			No more data so process the request
 */
			websUrlHandlerRequest(wp);
			done++;
			break;

		case WEBS_POST:
/*
 *			POST without content-length specification
 *			If this is a CGI request, write the data to the cgi stdin.
 *			socketGets was used to get the data and it strips \n's so
 *			add them back in here.
 */

#ifndef __NO_CGI_BIN
			if (wp->flags & WEBS_CGI_REQUEST) {
				if (fd == -1) {
					fd = gopen(wp->cgiStdin, O_CREAT | O_WRONLY | O_BINARY,
						0666);
				}
				gwrite(fd, text, gstrlen(text));
				gwrite(fd, T("\n"), sizeof(char_t));
			} else
#endif
			if (wp->query && *wp->query && !(wp->flags & WEBS_POST_DATA)) {
				len = gstrlen(wp->query);
				wp->query = brealloc(B_L, wp->query, (len + gstrlen(text) +
					2) * sizeof(char_t));
				if (wp->query) {
					wp->query[len++] = '&';
					gstrcpy(&wp->query[len], text);
				}

			} else {
				wp->query = bstrdup(B_L, text);
			}
			wp->flags |= WEBS_POST_DATA;
			done++;
			break;

		default:
			websError(wp, 404, T("Bad state"));
			done++;
			break;
		}
	}

	if (fd != -1) {
		fd = gclose (fd);
	}

	if (text) {
		bfree(B_L, text);
	}
}

/******************************************************************************/
/*
 *	Get input from the browser. Return TRUE (!0) if the request has been 
 *	handled. Return -1 on errors or if the request has been processed, 
 *	1 if input read, and 0 to instruct the caller to call again for more input.
 *
 *	Note: socketRead will Return the number of bytes read if successful. This
 *	may be less than the requested "bufsize" and may be zero. It returns -1 for
 *	errors. It returns 0 for EOF. Otherwise it returns the number of bytes 
 *	read. Since this may be zero, callers should use socketEof() to 
 *	distinguish between this and EOF.
 */

static int websGetInput(webs_t wp, char_t **ptext, int *pnbytes) 
{
	char_t	*text;
	char	buf[WEBS_SOCKET_BUFSIZ+1];
	int		nbytes, len, clen;

	a_assert(websValid(wp));
	a_assert(ptext);
	a_assert(pnbytes);

	*ptext = text = NULL;
	*pnbytes = 0;

/*
 *	If this request is a POST with a content length, we know the number
 *	of bytes to read so we use socketRead().
 */
	if (wp->state == WEBS_POST_CLEN) {
		len = (wp->clen > WEBS_SOCKET_BUFSIZ) ? WEBS_SOCKET_BUFSIZ : wp->clen;
	} else {
		len = 0;
	}

	if (len > 0) {

#ifdef WEBS_SSL_SUPPORT
		if (wp->flags & WEBS_SECURE) {
			nbytes = websSSLRead(wp->wsp, buf, len);
		} else {
			nbytes = socketRead(wp->sid, buf, len);
		}
#else
		nbytes = socketRead(wp->sid, buf, len);
#endif
		if (nbytes < 0) {						/* Error */
			websDone(wp, 0);
			return -1;

		}  else if (nbytes == 0) {				/* EOF or No data available */
			return -1;

		} else {								/* Valid data */
/*
 *			Convert to UNICODE if necessary.  First be sure the string 
 *			is NULL terminated.
 */
			buf[nbytes] = '\0';
			if ((text = ballocAscToUni(buf, nbytes)) == NULL) {
				websError(wp, 503, T("Insufficient memory"));
				return -1;
			}
		}

	} else {
#ifdef WEBS_SSL_SUPPORT
		if (wp->flags & WEBS_SECURE) {
			nbytes = websSSLGets(wp->wsp, &text);
		} else {
			nbytes = socketGets(wp->sid, &text);
		}
#else
		nbytes = socketGets(wp->sid, &text);
#endif

		if (nbytes < 0) {
			int eof;
/*
 *			Error, EOF or incomplete
 */
#ifdef WEBS_SSL_SUPPORT
			if (wp->flags & WEBS_SECURE) {
/*
 *				If state is WEBS_BEGIN and the request is secure, a -1 will 
 *				usually	indicate SSL negotiation
 */
				if (wp->state == WEBS_BEGIN) {
					eof = 1;
				} else {
					eof = websSSLEof(wp->wsp);
				}
			} else {
				eof = socketEof(wp->sid);
			}
#else
			eof = socketEof(wp->sid);
#endif

			if (eof) {
/*
 *				If this is a post request without content length, process 
 *				the request as we now have all the data. Otherwise just 
 *				close the connection.
 */
				if (wp->state == WEBS_POST) {
					websUrlHandlerRequest(wp);
				} else {
					websDone(wp, 0);
				}
			}
/*
 *			If state is WEBS_HEADER and the ringq is empty, then this is a
 *			simple request with no additional header fields to process and
 *			no empty line terminator.
 */
			if (wp->state == WEBS_HEADER && ringqLen(&wp->header) <= 0) {
				websParseRequest(wp);
				websUrlHandlerRequest(wp);
			}
			return -1;

		} else if (nbytes == 0) {
			if (wp->state == WEBS_HEADER) {
/*
 *				Valid empty line, now finished with header
 */
				websParseRequest(wp);
				if (wp->flags & WEBS_POST_REQUEST) {
					if (wp->flags & WEBS_CLEN) {
						wp->state = WEBS_POST_CLEN;
						clen = wp->clen;
					} else {
						wp->state = WEBS_POST;
						clen = 1;
					}
					if (clen > 0) {
/*
 *						Return 0 to get more data.
 */
						return 0;
					}
					return 1;
				}
/*
 *				We've read the header so go and handle the request
 */
				websUrlHandlerRequest(wp);
			}
			return -1;
		}
	}
	a_assert(text);
	a_assert(nbytes > 0);
	*ptext = text;
	*pnbytes = nbytes;
	return 1;
}

/******************************************************************************/
/*
 *	Parse the first line of a HTTP request
 */

static int websParseFirst(webs_t wp, char_t *text)
{
	char_t 	*op, *proto, *protoVer, *url, *host, *query, *path, *port, *ext;
	char_t	*buf;
	int		testPort;

	a_assert(websValid(wp));
	a_assert(text && *text);

/*
 *	Determine the request type: GET, HEAD or POST
 */
	op = gstrtok(text, T(" \t"));
	if (op == NULL || *op == '\0') {
		websError(wp, 400, T("Bad HTTP request"));
		return -1;
	}
	if (gstrcmp(op, T("GET")) != 0) {
		if (gstrcmp(op, T("POST")) == 0) {
			wp->flags |= WEBS_POST_REQUEST;
		} else if (gstrcmp(op, T("HEAD")) == 0) {
			wp->flags |= WEBS_HEAD_REQUEST;
		} else {
			websError(wp, 400, T("Bad request type"));
			return -1;
		}
	}

/*
 *	Store result in the form (CGI) variable store
 */
	websSetVar(wp, T("REQUEST_METHOD"), op);

	url = gstrtok(NULL, T(" \t\n"));
	if (url == NULL || *url == '\0') {
		websError(wp, 400, T("Bad HTTP request"));
		return -1;
	}
	protoVer = gstrtok(NULL, T(" \t\n"));

/*
 *	Parse the URL and store all the various URL components. websUrlParse
 *	returns an allocated buffer in buf which we must free. We support both
 *	proxied and non-proxied requests. Proxied requests will have http://host/
 *	at the start of the URL. Non-proxied will just be local path names.
 */
	host = path = port = proto = query = ext = NULL;
	if (websUrlParse(url, &buf, &host, &path, &port, &query, &proto, 
			NULL, &ext) < 0) {
		websError(wp, 400, T("Bad URL format"));
		return -1;
	}

	wp->url = bstrdup(B_L, url);

#ifndef __NO_CGI_BIN
	if (gstrstr(url, CGI_BIN) != NULL) {
		wp->flags |= WEBS_CGI_REQUEST;
		if (wp->flags & WEBS_POST_REQUEST) {
			wp->cgiStdin = websGetCgiCommName();
		}
	}
#endif

	wp->query = bstrdup(B_L, query);
	wp->host = bstrdup(B_L, host);
	wp->path = bstrdup(B_L, path);
	wp->protocol = bstrdup(B_L, proto);
	wp->protoVersion = bstrdup(B_L, protoVer);
	
	if ((testPort = socketGetPort(wp->listenSid)) >= 0) {
		wp->port = testPort;
	} else {
		wp->port = gatoi(port);
	}

	if (gstrcmp(ext, T(".asp")) == 0) {
		wp->flags |= WEBS_ASP;
	}
	bfree(B_L, buf);

	websUrlType(url, wp->type, TSZ(wp->type));

#if WEBS_PROXY_SUPPORT
/*
 *	Determine if this is a request for local webs data. If it is not a proxied 
 *	request from the browser, we won't see the "http://" or the system name, so
 *	we assume it must be talking to us directly for local webs data.
 *	Note: not fully implemented yet.
 */
	if (gstrstr(wp->url, T("http://")) == NULL || 
		((gstrcmp(wp->host, T("localhost")) == 0 || 
			gstrcmp(wp->host, websHost) == 0) && (wp->port == websPort))) {
		wp->flags |= WEBS_LOCAL_PAGE;
		if (gstrcmp(wp->path, T("/")) == 0) {
			wp->flags |= WEBS_HOME_PAGE;
		}
	}
#endif

	ringqFlush(&wp->header);
	return 0;
}

/******************************************************************************/
/*
 *	Parse a full request
 */

#define isgoodchar(s) (gisalnum((s)) || ((s) == '/') || ((s) == '_') || \
						((s) == '.')  || ((s) == '-') )

static void websParseRequest(webs_t wp)
{
	char_t	*authType, *upperKey, *cp, *browser, *lp, *key, *value;

	a_assert(websValid(wp));

/*
 *	Define default CGI values
 */
	websSetVar(wp, T("HTTP_AUTHORIZATION"), T(""));

/* 
 *	Parse the header and create the Http header keyword variables
 *	We rewrite the header as we go for non-local requests.  NOTE: this
 * 	modifies the header string directly and tokenizes each line with '\0'.
 */
	browser = NULL;
	for (lp = (char_t*) wp->header.servp; lp && *lp; ) {
		cp = lp;
		if ((lp = gstrchr(lp, '\n')) != NULL) {
			lp++;
		}

		if ((key = gstrtok(cp, T(": \t\n"))) == NULL) {
			continue;
		}

		if ((value = gstrtok(NULL, T("\n"))) == NULL) {
			value = T("");
		}

		while (gisspace(*value)) {
			value++;
		}
		strlower(key);

/*
 *		Create a variable (CGI) for each line in the header
 */
		fmtAlloc(&upperKey, (gstrlen(key) + 6), T("HTTP_%s"), key);
		for (cp = upperKey; *cp; cp++) {
			if (*cp == '-')
				*cp = '_';
		}
		strupper(upperKey);
		websSetVar(wp, upperKey, value);
		bfree(B_L, upperKey);

/*
 *		Track the requesting agent (browser) type
 */
		if (gstrcmp(key, T("user-agent")) == 0) {
			wp->userAgent = bstrdup(B_L, value);

/*
 *		Parse the user authorization. ie. password
 */
		} else if (gstricmp(key, T("authorization")) == 0) {
/*
 *			Determine the type of Authorization Request
 */
			authType = bstrdup (B_L, value);
			a_assert (authType);
/*			
 *			Truncate authType at the next non-alpha character
 */
			cp = authType;
			while (gisalpha(*cp)) {
				cp++;
			}
			*cp = '\0';

			wp->authType = bstrdup(B_L, authType);
			bfree(B_L, authType);

			if (gstricmp(wp->authType, T("basic")) == 0) {
				char_t	userAuth[FNAMESIZE];
/*
 *				The incoming value is username:password (Basic authentication)
 */
				if ((cp = gstrchr(value, ' ')) != NULL) {
					*cp = '\0';
					wp->authType = bstrdup(B_L, value);
					websDecode64(userAuth, ++cp, sizeof(userAuth));
				} else {
					websDecode64(userAuth, value, sizeof(userAuth));
				}
/*
 *				Split userAuth into userid and password
 */
				if ((cp = gstrchr(userAuth, ':')) != NULL) {
					*cp++ = '\0';
				}
				if (cp) {
					wp->userName = bstrdup(B_L, userAuth);
					wp->password = bstrdup(B_L, cp);
				} else {
					wp->userName = bstrdup(B_L, T(""));
					wp->password = bstrdup(B_L, T(""));
				}
/*
 *				Set the flags to indicate digest authentication
 */
				wp->flags |= WEBS_AUTH_BASIC;
			} else {
#ifdef DIGEST_ACCESS_SUPPORT
/*
 *				The incoming value is slightly more complicated (Digest)
 */
				char_t *np;		/* pointer to end of tag name */
				char_t tp;		/* temporary character holding space */
				char_t *vp;		/* pointer to value */
				char_t *npv;	/* pointer to end of value, "next" pointer */
				char_t tpv;		/* temporary character holding space */
/*
 *				Set the flags to indicate digest authentication
 */
				wp->flags |= WEBS_AUTH_DIGEST;
/*
 *				Move cp to Next word beyond "Digest",
 *				vp to first char after '='.
 */
 				cp = value;
				while (isgoodchar(*cp)) {
					cp++;
				}
				while (!isgoodchar(*cp)) {
					cp++;
				}

/*
 *				Find beginning of value
 */
				vp = gstrchr(cp, '=');
				while (vp) {
/*
 *					Zero-terminate tag name
 */
					np = cp;
					while (isgoodchar(*np)) {
						np++;
					}
					tp = *np;
					*np = 0;
/*
 *					Advance value pointer to first legit character
 */
					vp++;
					while (!isgoodchar(*vp)) {
						vp++;
					}
/*
 *					Zero-terminate value
 */
					npv = vp;
					while (isgoodchar(*npv)) {
						npv++;
					}
					tpv = *npv;
					*npv = 0;
/*
 *					Extract the fields
 */
					if (gstricmp(cp, T("username")) == 0) {
						wp->userName = bstrdup(B_L, vp);
					} else if (gstricmp(cp, T("response")) == 0) {
						wp->digest = bstrdup(B_L, vp);
					} else if (gstricmp(cp, T("opaque")) == 0) {
						wp->opaque = bstrdup(B_L, vp);
					} else if (gstricmp(cp, T("uri")) == 0) {
						wp->uri = bstrdup(B_L, vp);
					} else if (gstricmp(cp, T("realm")) == 0) {
						wp->realm = bstrdup(B_L, vp);
					} else if (gstricmp(cp, T("nonce")) == 0) {
						wp->nonce = bstrdup(B_L, vp);
					} else if (gstricmp(cp, T("nc")) == 0) {
						wp->nc = bstrdup(B_L, vp);
					} else if (gstricmp(cp, T("cnonce")) == 0) {
						wp->cnonce = bstrdup(B_L, vp);
					} else if (gstricmp(cp, T("qop")) == 0) {
						wp->qop = bstrdup(B_L, vp);
					}
/*
 *					Restore tag name and value zero-terminations
 */
					*np = tp;
					*npv = tpv;
/*
 *					Advance tag name and value pointers
 */
 					cp = npv;
					while (*cp && isgoodchar(*cp)) {
						cp++;
					}
					while (*cp && !isgoodchar(*cp)) {
						cp++;
					}

					if (*cp) {
						vp = gstrchr(cp, '=');
					} else {
						vp = NULL;
					}
				}
#endif /* DIGEST_ACCESS_SUPPORT */
			} /* if (gstrcmp(wp->authType)) */
/*
 *		Parse the content length
 */
		} else if (gstrcmp(key, T("content-length")) == 0) {
			wp->flags |= WEBS_CLEN;
			wp->clen = gatoi(value);
			websSetVar(wp, T("CONTENT_LENGTH"), value);

/*
 *		Parse the content type
 */
		} else if (gstrcmp(key, T("content-type")) == 0) {
			websSetVar(wp, T("CONTENT_TYPE"), value);

#if WEBS_KEEP_ALIVE_SUPPORT
		} else if (gstrcmp(key, T("connection")) == 0) {
			strlower(value);
			if (gstrcmp(value, T("keep-alive")) == 0) {
				wp->flags |= WEBS_KEEP_ALIVE;
			}
#endif

#if WEBS_PROXY_SUPPORT
/*
 *		This may be useful if you wish to keep a local cache of web pages
 *		for proxied requests.
 */
		} else if (gstrcmp(key, T("pragma")) == 0) {
			char_t	tmp[256];
			gstrncpy(tmp, value, TSZ(tmp));
			strlower(tmp);
			if (gstrstr(tmp, T("no-cache"))) {
				wp->flags |= WEBS_DONT_USE_CACHE;
			}
#endif /* WEBS_PROXY_SUPPORT */

/*
 *		Store the cookie
 */
		} else if (gstrcmp(key, T("cookie")) == 0) {
			wp->flags |= WEBS_COOKIE;
			wp->cookie = bstrdup(B_L, value);

#if WEBS_IF_MODIFIED_SUPPORT
/*
 *		See if the local page has been modified since the browser last
 *		requested this document. If not, just return a 302
 */
		} else if (gstrcmp(key, T("if-modified-since")) == 0) {
			char_t *cmd;
			time_t tip = 0;

			if ((cp = gstrchr(value, ';')) != NULL) {
				*cp = '\0';
			}

			fmtAlloc(&cmd, 64, T("%s"), value);

			if ((wp->since = dateParse(tip, cmd)) != 0) {
				wp->flags |= WEBS_IF_MODIFIED;
			}

			bfreeSafe(B_L, cmd);
#endif /* WEBS_IF_MODIFIED_SUPPORT */
		}
	}
}

/******************************************************************************/
/*
 *	Set the variable (CGI) environment for this request. Create variables
 *	for all standard CGI variables. Also decode the query string and create
 *	a variable for each name=value pair.
 */

void websSetEnv(webs_t wp)
{
	char_t	portBuf[8];
	char_t	*keyword, *value, *valCheck, *valNew;

	a_assert(websValid(wp));

	websSetVar(wp, T("QUERY_STRING"), wp->query);
	websSetVar(wp, T("GATEWAY_INTERFACE"), T("CGI/1.1"));
	websSetVar(wp, T("SERVER_HOST"), websHost);
	websSetVar(wp, T("SERVER_NAME"), websHost);
	websSetVar(wp, T("SERVER_URL"), websHostUrl);
	websSetVar(wp, T("REMOTE_HOST"), wp->ipaddr);
	websSetVar(wp, T("REMOTE_ADDR"), wp->ipaddr);
	websSetVar(wp, T("PATH_INFO"), wp->path);
	stritoa(websPort, portBuf, sizeof(portBuf));
	websSetVar(wp, T("SERVER_PORT"), portBuf);
	websSetVar(wp, T("SERVER_ADDR"), websIpaddr);
	fmtAlloc(&value, FNAMESIZE, T("%s/%s"), WEBS_NAME, WEBS_VERSION);
	websSetVar(wp, T("SERVER_SOFTWARE"), value);
	bfreeSafe(B_L, value);
	websSetVar(wp, T("SERVER_PROTOCOL"), wp->protoVersion);

/*
 *	Decode and create an environment query variable for each query keyword.
 *	We split into pairs at each '&', then split pairs at the '='.
 *	Note: we rely on wp->decodedQuery preserving the decoded values in the
 *	symbol table.
 */
	wp->decodedQuery = bstrdup(B_L, wp->query);
	keyword = gstrtok(wp->decodedQuery, T("&"));
	while (keyword != NULL) {
		if ((value = gstrchr(keyword, '=')) != NULL) {
			*value++ = '\0';
			websDecodeUrl(keyword, keyword, gstrlen(keyword));
			websDecodeUrl(value, value, gstrlen(value));
		} else {
			value = T("");
		}

		if (*keyword) {
/*
 *			If keyword has already been set, append the new value to what has 
 *			been stored.
 */
			if ((valCheck = websGetVar(wp, keyword, NULL)) != 0) {
				fmtAlloc(&valNew, 256, T("%s %s"), valCheck, value);
				websSetVar(wp, keyword, valNew);
				bfreeSafe(B_L, valNew);
			} else {
				websSetVar(wp, keyword, value);
			}
		}
		keyword = gstrtok(NULL, T("&"));
	}

#if EMF
/*
 *	Add GoAhead Embedded Management Framework defines
 */
	websSetEmfEnvironment(wp);
#endif
}

/******************************************************************************/
/*
 *	Define a webs (CGI) variable for this connection. Also create in relevant
 *	scripting engines. Note: the incoming value may be volatile. 
 */

void websSetVar(webs_t wp, char_t *var, char_t *value)
{
	value_t		 v;

	a_assert(websValid(wp));

/*
 *	value_instring will allocate the string if required.
 */
	if (value) {
		v = valueString(value, VALUE_ALLOCATE);
	} else {
		v = valueString(T(""), VALUE_ALLOCATE);
	}
	symEnter(wp->cgiVars, var, v, 0);
}

/******************************************************************************/
/*
 *	Return TRUE if a webs variable exists for this connection.
 */

int websTestVar(webs_t wp, char_t *var)
{
	sym_t		*sp;

	a_assert(websValid(wp));

	if (var == NULL || *var == '\0') {
		return 0;
	}

	if ((sp = symLookup(wp->cgiVars, var)) == NULL) {
		return 0;
	}
	return 1;
}

/******************************************************************************/
/*
 *	Get a webs variable but return a default value if string not found.
 *	Note, defaultGetValue can be NULL to permit testing existence.
 */

char_t *websGetVar(webs_t wp, char_t *var, char_t *defaultGetValue)
{
	sym_t	*sp;

	a_assert(websValid(wp));
	a_assert(var && *var);
 
	if ((sp = symLookup(wp->cgiVars, var)) != NULL) {
		a_assert(sp->content.type == string);
		if (sp->content.value.string) {
			return sp->content.value.string;
		} else {
			return T("");
		}
	}
	return defaultGetValue;
}

/******************************************************************************/
/*
 *	Return TRUE if a webs variable is set to a given value
 */

int websCompareVar(webs_t wp, char_t *var, char_t *value)
{
	a_assert(websValid(wp));
	a_assert(var && *var);
 
	if (gstrcmp(value, websGetVar(wp, var, T(" __UNDEF__ "))) == 0) {
		return 1;
	}
	return 0;
}

/******************************************************************************/
/*
 *	Cancel the request timeout. Note may be called multiple times.
 */

void websTimeoutCancel(webs_t wp)
{
	a_assert(websValid(wp));

	if (wp->timeout >= 0) {
		emfUnschedCallback(wp->timeout);
		wp->timeout = -1;
	}
}

/******************************************************************************/
/*
 *	Output a HTTP response back to the browser. If redirect is set to a 
 *	URL, the browser will be sent to this location.
 */

void websResponse(webs_t wp, int code, char_t *message, char_t *redirect)
{
	char_t		*date;

	a_assert(websValid(wp));

/*
 *	IE3.0 needs no Keep Alive for some return codes.
 */
	wp->flags &= ~WEBS_KEEP_ALIVE;

/*
 *	Only output the header if a header has not already been output.
 */
	if ( !(wp->flags & WEBS_HEADER_DONE)) {
		wp->flags |= WEBS_HEADER_DONE;
		websWrite(wp, T("HTTP/1.1 %d %s\r\n"), code, websErrorMsg(code));
/*		
 *		By license terms the following line of code must not be modified.
 */
		websWrite(wp, T("Server: %s\r\n"), WEBS_NAME);

/*		
 *		Timestamp/Date is usually the next to go
 */
		if ((date = websGetDateString(NULL)) != NULL) {
			websWrite(wp, T("Date: %s\r\n"), date);
			bfree(B_L, date);
		}
/*
 *		If authentication is required, send the auth header info
 */
		if (code == 401) {
			if (!(wp->flags & WEBS_AUTH_DIGEST)) {
				websWrite(wp, T("WWW-Authenticate: Basic realm=\"%s\"\r\n"), 
					websGetRealm());
#ifdef DIGEST_ACCESS_SUPPORT
			} else {
				char_t *nonce, *opaque;

				nonce = websCalcNonce(wp), 
				opaque = websCalcOpaque(wp), 
				websWrite(wp, 
					T("WWW-Authenticate: Digest realm=\"%s\", domain=\"%s\",")
					T("qop=\"%s\", nonce=\"%s\", opaque=\"%s\",")
					T("algorithm=\"%s\", stale=\"%s\"\r\n"), 
					websGetRealm(),
					websGetHostUrl(),
					T("auth"),
					nonce,
					opaque, T("MD5"), T("FALSE"));
				bfree(B_L, nonce);
				bfree(B_L, opaque);
#endif
			}
		}

		if (wp->flags & WEBS_KEEP_ALIVE) {
			websWrite(wp, T("Connection: keep-alive\r\n"));
		}

		websWrite(wp, T("Pragma: no-cache\r\nCache-Control: no-cache\r\n"));
		websWrite(wp, T("Content-Type: text/html\r\n"));
/*
 *		We don't do a string length here as the message may be multi-line. 
 *		Ie. <CR><LF> will count as only one and we will have a content-length 
 *		that is too short.
 *
 *		websWrite(wp, T("Content-Length: %s\r\n"), message);
 */
		if (redirect) {
			websWrite(wp, T("Location: %s\r\n"), redirect);
		}
		websWrite(wp, T("\r\n"));
	}

/*
 *	If the browser didn't do a HEAD only request, send the message as well.
 */
	if ((wp->flags & WEBS_HEAD_REQUEST) == 0 && message && *message) {
		websWrite(wp, T("%s\r\n"), message);
	}
	websDone(wp, code);
}

/******************************************************************************/
/*
 *	Redirect the user to another webs page
 */

void websRedirect(webs_t wp, char_t *url)
{
	char_t	*msgbuf, *urlbuf, *redirectFmt;

	a_assert(websValid(wp));
	a_assert(url);

	websStats.redirects++;
	msgbuf = urlbuf = NULL;

/*
 *	Some browsers require a http://host qualified URL for redirection
 */
	if (gstrstr(url, T("http://")) == NULL) {
		if (*url == '/') {
			url++;
		}

		redirectFmt = T("http://%s/%s");

#ifdef WEBS_SSL_SUPPORT
		if (wp->flags & WEBS_SECURE) {
			redirectFmt = T("https://%s/%s");
		}
#endif

		fmtAlloc(&urlbuf, WEBS_MAX_URL + 80, redirectFmt,
			websGetVar(wp, T("HTTP_HOST"), 	websHostUrl), url);
		url = urlbuf;
	}

/*
 *	Add human readable message for completeness. Should not be required.
 */
	fmtAlloc(&msgbuf, WEBS_MAX_URL + 80, 
		T("<html><head></head><body>\r\n\
		This document has moved to a new <a href=\"%s\">location</a>.\r\n\
		Please update your documents to reflect the new location.\r\n\
		</body></html>\r\n"), url);

	websResponse(wp, 302, msgbuf, url);

	bfreeSafe(B_L, msgbuf);
	bfreeSafe(B_L, urlbuf);
}

/******************************************************************************/
/*	
 *	Output an error message and cleanup
 */

void websError(webs_t wp, int code, char_t *fmt, ...)
{
	va_list		args;
	char_t		*msg, *userMsg, *buf;

	a_assert(websValid(wp));
	a_assert(fmt);

	websStats.errors++;

	va_start(args, fmt);
	userMsg = NULL;
	fmtValloc(&userMsg, WEBS_BUFSIZE, fmt, args);
	va_end(args);

	msg = T("<html><head><title>Document Error: %s</title></head>\r\n\
		<body><h2>Access Error: %s</h2>\r\n\
		when trying to obtain <b>%s</b><br><p>%s</p></body></html>\r\n");
/*
 *	Ensure we have plenty of room
 */
	buf = NULL;
	fmtAlloc(&buf, WEBS_BUFSIZE, msg, websErrorMsg(code), 
		websErrorMsg(code), wp->url, userMsg);

	websResponse(wp, code, buf, NULL);
	bfreeSafe(B_L, buf);
	bfreeSafe(B_L, userMsg);
}

/******************************************************************************/
/*
 *	Return the error message for a given code
 */

static char_t *websErrorMsg(int code)
{
	websErrorType	*ep;

	for (ep = websErrors; ep->code; ep++) {
		if (code == ep->code) {
			return ep->msg;
		}
	}
	a_assert(0);
	return T("");
}

/******************************************************************************/
/*
 *	Do formatted output to the browser. This is the public ASP and form
 *	write procedure.
 */

int websWrite(webs_t wp, char_t *fmt, ...)
{
	va_list		 vargs;
	char_t		*buf;
	int			 rc;
	
	a_assert(websValid(wp));

	va_start(vargs, fmt);

	buf = NULL;
	rc = 0;
	if (fmtValloc(&buf, WEBS_BUFSIZE, fmt, vargs) >= WEBS_BUFSIZE) {
		trace(0, T("webs: websWrite lost data, buffer overflow\n"));
	}
	va_end(vargs);
	a_assert(buf);
	if (buf) {
		rc = websWriteBlock(wp, buf, gstrlen(buf));
		bfree(B_L, buf);
	}
	return rc;
}

/******************************************************************************/
/*
 *	Write a block of data of length "nChars" to the user's browser. Public
 *	write block procedure.  If unicode is turned on this function expects 
 *	buf to be a unicode string and it converts it to ASCII before writing.
 *	See websWriteDataNonBlock to always write binary or ASCII data with no 
 *	unicode conversion.  This returns the number of char_t's processed.
 *	It spins until nChars are flushed to the socket.  For non-blocking
 *	behavior, use websWriteDataNonBlock.
 */

int websWriteBlock(webs_t wp, char_t *buf, int nChars)
{
	int		len, done;
	char	*asciiBuf, *pBuf;

	a_assert(wp);
	a_assert(websValid(wp));
	a_assert(buf);
	a_assert(nChars >= 0);

	done = len = 0;

/*
 *	ballocUniToAsc will convert Unicode to strings to Ascii.  If Unicode is
 *	not turned on then ballocUniToAsc will not do the conversion.
 */
	pBuf = asciiBuf = ballocUniToAsc(buf, nChars);

	while (nChars > 0) {  
#ifdef WEBS_SSL_SUPPORT
		if (wp->flags & WEBS_SECURE) {
			if ((len = websSSLWrite(wp->wsp, pBuf, nChars)) < 0) {
				bfree(B_L, asciiBuf);
				return -1;
			}
			websSSLFlush(wp->wsp);
		} else {
			if ((len = socketWrite(wp->sid, pBuf, nChars)) < 0) {
				bfree(B_L, asciiBuf);
				return -1;
			}
			socketFlush(wp->sid);
		}
#else /* ! WEBS_SSL_SUPPORT */
		if ((len = socketWrite(wp->sid, pBuf, nChars)) < 0) {
			bfree(B_L, asciiBuf);
			return -1;
		}
		socketFlush(wp->sid);
#endif /* WEBS_SSL_SUPPORT */
		nChars -= len;
		pBuf += len;
		done += len;
	}

	bfree(B_L, asciiBuf);
	return done;
}

/******************************************************************************/
/*
 *	Write a block of data of length "nChars" to the user's browser. Same as
 *	websWriteBlock except that it expects straight ASCII or binary and does no
 *	unicode conversion before writing the data.  If the socket cannot hold all
 *	the data, it will return the number of bytes flushed to the socket before
 *	it would have blocked.  This returns the number of chars processed or -1
 *	if socketWrite fails.
 */

int websWriteDataNonBlock(webs_t wp, char *buf, int nChars)
{
	int r;

	a_assert(wp);
	a_assert(websValid(wp));
	a_assert(buf);
	a_assert(nChars >= 0);

#ifdef WEBS_SSL_SUPPORT
	if (wp->flags & WEBS_SECURE) {
		r = websSSLWrite(wp->wsp, buf, nChars);
		websSSLFlush(wp->wsp);
	} else {
		r = socketWrite(wp->sid, buf, nChars);
		socketFlush(wp->sid);
	}
#else
	r = socketWrite(wp->sid, buf, nChars);
	socketFlush(wp->sid);
#endif

	return r;
}

/******************************************************************************/
/*
 *	Decode a URL (or part thereof). Allows insitu decoding.
 */

void websDecodeUrl(char_t *decoded, char_t *token, int len)
{
	char_t	*ip,  *op;
	int		num, i, c;
	
	a_assert(decoded);
	a_assert(token);

	op = decoded;
	for (ip = token; *ip && len > 0; ip++, op++) {
		if (*ip == '+') {
			*op = ' ';
		} else if (*ip == '%' && gisxdigit(ip[1]) && gisxdigit(ip[2])) {

/*
 *			Convert %nn to a single character
 */
			ip++;
			for (i = 0, num = 0; i < 2; i++, ip++) {
				c = tolower(*ip);
				if (c >= 'a' && c <= 'f') {
					num = (num * 16) + 10 + c - 'a';
				} else {
					num = (num * 16) + c - '0';
				}
			}
			*op = (char_t) num;
			ip--;

		} else {
			*op = *ip;
		}
		len--;
	}
	*op = '\0';
}

/******************************************************************************/
#if WEBS_LOG_SUPPORT
/*
 *	Output a log message
 */

static void websLog(webs_t wp, int code)
{
	char_t	*buf;
	char	*abuf;
	int		len;

	a_assert(websValid(wp));

	buf = NULL;
	fmtAlloc(&buf, WEBS_MAX_URL + 80, T("%d %s %d %d\n"), time(0), 
		wp->url, code, wp->written);
	len = gstrlen(buf);
	abuf = ballocUniToAsc(buf, len+1);
	write(websLogFd, abuf, len);
	bfreeSafe(B_L, buf);
	bfreeSafe(B_L, abuf);
}

#endif /* WEBS_LOG_SUPPORT */

/******************************************************************************/
/*
 *	Request timeout. The timeout triggers if we have not read any data from
 *	the users browser in the last WEBS_TIMEOUT period. If we have heard from
 *	the browser, simply re-issue the timeout.
 */

void websTimeout(void *arg, int id)
{
	webs_t		wp;
	int			delay, tm;

	wp = (webs_t) arg;
	a_assert(websValid(wp));

	tm = websGetTimeSinceMark(wp) * 1000;
	if (tm >= WEBS_TIMEOUT) {
		websStats.timeouts++;
		emfUnschedCallback(id);

/*
 *		Clear the timeout id
 */
		wp->timeout = -1;
		websDone(wp, 404);

	} else {
		delay = WEBS_TIMEOUT - tm;
		a_assert(delay > 0);
		emfReschedCallback(id, delay);
	}
}

/******************************************************************************/
/*
 *	Called when the request is done.
 */

void websDone(webs_t wp, int code)
{
	a_assert(websValid(wp));

/*
 * 	Disable socket handler in case keep alive set.
 */
	socketDeleteHandler(wp->sid);

	if (code != 200) {
		wp->flags &= ~WEBS_KEEP_ALIVE;
	}

#if WEBS_PROXY_SUPPORT
	if (! (wp->flags & WEBS_LOCAL_PAGE)) {
		websStats.activeNetRequests--;
	}
#endif

#if WEBS_LOG_SUPPORT
	if (! (wp->flags & WEBS_REQUEST_DONE)) {
		websLog(wp, code);
	}
#endif

/*
 *	Close any opened document by a handler
 */
	websPageClose(wp);

/*
 *	Exit if secure.
 */
#ifdef WEBS_SSL_SUPPORT
	if (wp->flags & WEBS_SECURE) {
		websTimeoutCancel(wp);
		websSSLFlush(wp->wsp);
		socketCloseConnection(wp->sid);
		websFree(wp);
		return;
	}
#endif

/*
 *	If using Keep Alive (HTTP/1.1) we keep the socket open for a period
 *	while waiting for another request on the socket. 
 */
	if (wp->flags & WEBS_KEEP_ALIVE) {
		if (socketFlush(wp->sid) == 0) {
			wp->state = WEBS_BEGIN;
			wp->flags |= WEBS_REQUEST_DONE;
			if (wp->header.buf) {
				ringqFlush(&wp->header);
			}
			socketCreateHandler(wp->sid, SOCKET_READABLE, websSocketEvent, 
				(int) wp);
			websTimeoutCancel(wp);
			wp->timeout = emfSchedCallback(WEBS_TIMEOUT, websTimeout,
				(void *) wp);
			return;
		}
	} else {
		websTimeoutCancel(wp);
		socketSetBlock(wp->sid, 1);
		socketFlush(wp->sid);
		socketCloseConnection(wp->sid);
	}
	websFree(wp);
}

/******************************************************************************/
/*
 *	Allocate a new webs structure
 */

int websAlloc(int sid)
{
	webs_t		wp;
	int			wid;

/*
 *	Allocate a new handle for this connection
 */
	if ((wid = hAllocEntry((void***) &webs, &websMax,
			sizeof(struct websRec))) < 0) {
		return -1;
	}
	wp = webs[wid];

	wp->wid = wid;
	wp->sid = sid;
	wp->state = WEBS_BEGIN;
	wp->docfd = -1;
	wp->timeout = -1;
	wp->dir = NULL;
	wp->authType = NULL;
	wp->protocol = NULL;
	wp->protoVersion = NULL;
	wp->password = NULL;
	wp->userName = NULL;
#ifdef DIGEST_ACCESS_SUPPORT
	wp->realm = NULL;
	wp->nonce = NULL;
	wp->digest = NULL;
	wp->uri = NULL;
	wp->opaque = NULL;
	wp->nc = NULL;
	wp->cnonce = NULL;
	wp->qop = NULL;
#endif
#ifdef WEBS_SSL_SUPPORT
	wp->wsp = NULL;
#endif

	ringqOpen(&wp->header, WEBS_HEADER_BUFINC, WEBS_MAX_HEADER);

/*
 *	Create storage for the CGI variables. We supply the symbol tables for
 *	both the CGI variables and for the global functions. The function table
 *	is common to all webs instances (ie. all browsers)
 */
	wp->cgiVars = symOpen(WEBS_SYM_INIT);

	return wid;
}

/******************************************************************************/
/*
 *	Free a webs structure
 */

void websFree(webs_t wp)
{
	a_assert(websValid(wp));

	if (wp->path)
		bfree(B_L, wp->path);
	if (wp->url)
		bfree(B_L, wp->url);
	if (wp->host)
		bfree(B_L, wp->host);
	if (wp->lpath)
		bfree(B_L, wp->lpath);
	if (wp->query)
		bfree(B_L, wp->query);
	if (wp->decodedQuery)
		bfree(B_L, wp->decodedQuery);
	if (wp->authType)
		bfree(B_L, wp->authType);
	if (wp->password)
		bfree(B_L, wp->password);
	if (wp->userName)
		bfree(B_L, wp->userName);
	if (wp->cookie)
		bfree(B_L, wp->cookie);
	if (wp->userAgent)
		bfree(B_L, wp->userAgent);
	if (wp->dir)
		bfree(B_L, wp->dir);
	if (wp->protocol)
		bfree(B_L, wp->protocol);
	if (wp->protoVersion)
		bfree(B_L, wp->protoVersion);
	if (wp->cgiStdin)
		bfree(B_L, wp->cgiStdin);


#ifdef DIGEST_ACCESS_SUPPORT
	if (wp->realm)
		bfree(B_L, wp->realm);
	if (wp->uri)
		bfree(B_L, wp->uri);
	if (wp->digest)
		bfree(B_L, wp->digest);
	if (wp->opaque)
		bfree(B_L, wp->opaque);
	if (wp->nonce)
		bfree(B_L, wp->nonce);
	if (wp->nc)
		bfree(B_L, wp->nc);
	if (wp->cnonce)
		bfree(B_L, wp->cnonce);
	if (wp->qop)
		bfree(B_L, wp->qop);
#endif
#ifdef WEBS_SSL_SUPPORT
	websSSLFree(wp->wsp);
#endif
	symClose(wp->cgiVars);

	if (wp->header.buf) {
		ringqClose(&wp->header);
	}

	websMax = hFree((void***) &webs, wp->wid);
	bfree(B_L, wp);
	a_assert(websMax >= 0);
}

/******************************************************************************/
/*
 *	Return the server address
 */

char_t *websGetHost()
{
	return websHost;
}

/******************************************************************************/
/*
 *	Return the the url to access the server. (ip address)
 */

char_t *websGetIpaddrUrl()
{
	return websIpaddrUrl;
}

/******************************************************************************/
/*
 *	Return the server address
 */

char_t *websGetHostUrl()
{
	return websHostUrl;
}

/******************************************************************************/
/*
 *	Return the listen port
 */

int websGetPort()
{
	return websPort;
}

/******************************************************************************/
/*
 *	Get the number of bytes to write
 */

int websGetRequestBytes(webs_t wp)
{
	a_assert(websValid(wp));

	return wp->numbytes;
}

/******************************************************************************/
/*
 *	Get the directory for this request
 */

char_t *websGetRequestDir(webs_t wp)
{
	a_assert(websValid(wp));

	if (wp->dir == NULL) {
		return T("");
	}

	return wp->dir;
}

/******************************************************************************/
/*
 *	Get the flags for this request
 */

int websGetRequestFlags(webs_t wp)
{
	a_assert(websValid(wp));

	return wp->flags;
}

/******************************************************************************/
/*
 *	Return the IP address
 */

char_t *websGetRequestIpaddr(webs_t wp)
{
	a_assert(websValid(wp));

	return wp->ipaddr;
}

/******************************************************************************/
/*
 *	Set the local path for the request
 */

char_t *websGetRequestLpath(webs_t wp)
{
	a_assert(websValid(wp));

#if WEBS_PAGE_ROM
	return wp->path;
#else
	return wp->lpath;
#endif
}

/******************************************************************************/
/*
 *	Get the path for this request
 */

char_t *websGetRequestPath(webs_t wp)
{
	a_assert(websValid(wp));

	if (wp->path == NULL) {
		return T("");
	}

	return wp->path;
}

/******************************************************************************/
/*
 *	Return the password
 */

char_t *websGetRequestPassword(webs_t wp)
{
	a_assert(websValid(wp));

	return wp->password;
}

/******************************************************************************/
/*
 *	Return the request type
 */

char_t *websGetRequestType(webs_t wp)
{
	a_assert(websValid(wp));

	return wp->type;
}

/******************************************************************************/
/*
 *	Return the username
 */

char_t *websGetRequestUserName(webs_t wp)
{
	a_assert(websValid(wp));

	return wp->userName;
}

/******************************************************************************/
/*
 *	Get the number of bytes written
 */

int websGetRequestWritten(webs_t wp)
{
	a_assert(websValid(wp));

	return wp->written;
}

/******************************************************************************/
/*
 *	Set the hostname
 */

void websSetHost(char_t *host)
{
	gstrncpy(websHost, host, TSZ(websHost));
}

/******************************************************************************/
/*
 *	Set the host URL
 */

void websSetHostUrl(char_t *url)
{
	a_assert(url && *url);

	bfreeSafe(B_L, websHostUrl);
	websHostUrl = gstrdup(B_L, url);
}

/******************************************************************************/
/*
 *	Set the IP address
 */

void websSetIpaddr(char_t *ipaddr)
{
	a_assert(ipaddr && *ipaddr);

	gstrncpy(websIpaddr, ipaddr, TSZ(websIpaddr));
}

/******************************************************************************/
/*
 *	Set the number of bytes to write
 */

void websSetRequestBytes(webs_t wp, int bytes)
{
	a_assert(websValid(wp));
	a_assert(bytes >= 0);

	wp->numbytes = bytes;
}

/******************************************************************************/
/*
 *	Set the flags for this request
 */

void websSetRequestFlags(webs_t wp, int flags)
{
	a_assert(websValid(wp));

	wp->flags = flags;
}

/******************************************************************************/
/*
 *	Set the local path for the request
 */

void websSetRequestLpath(webs_t wp, char_t *lpath)
{
	a_assert(websValid(wp));
	a_assert(lpath && *lpath);

	if (wp->lpath) {
		bfree(B_L, wp->lpath);
	}
	wp->lpath = bstrdup(B_L, lpath);
	websSetVar(wp, T("PATH_TRANSLATED"), wp->lpath);
}

/******************************************************************************/
/*
 *	Update the URL path and the directory containing the web page
 */

void websSetRequestPath(webs_t wp, char_t *dir, char_t *path)
{
	char_t	*tmp;

	a_assert(websValid(wp));

	if (dir) { 
		tmp = wp->dir;
		wp->dir = bstrdup(B_L, dir);
		if (tmp) {
			bfree(B_L, tmp);
		}
	}
	if (path) {
		tmp = wp->path;
		wp->path = bstrdup(B_L, path);
		websSetVar(wp, T("PATH_INFO"), wp->path);
		if (tmp) {
			bfree(B_L, tmp);
		}
	}
}

/******************************************************************************/
/*
 *	Set the Write handler for this socket
 */

void websSetRequestSocketHandler(webs_t wp, int mask, void (*fn)(webs_t wp))
{
	a_assert(websValid(wp));

	wp->writeSocket = fn;
	socketCreateHandler(wp->sid, SOCKET_WRITABLE, websSocketEvent, (int) wp);
}

/******************************************************************************/
/*
 *	Set the number of bytes written
 */

void websSetRequestWritten(webs_t wp, int written)
{
	a_assert(websValid(wp));

	wp->written = written;
}

/******************************************************************************/
/*
 *	Reurn true if the webs handle is valid
 */

int websValid(webs_t wp)
{
	int		wid;

	for (wid = 0; wid < websMax; wid++) {
		if (wp == webs[wid]) {
			return 1;
		}
	}
	return 0;
}

/******************************************************************************/
/*
 *	Build an ASCII time string.  If sbuf is NULL we use the current time,
 *	else we use the last modified time of sbuf;
 */

char_t *websGetDateString(websStatType *sbuf)
{
	char_t*	cp, *r;
	time_t	now;

	if (sbuf == NULL) {
		time(&now);
	} else {
		now = sbuf->mtime;
	}
	if ((cp = gctime(&now)) != NULL) {
		cp[gstrlen(cp) - 1] = '\0';
		r = bstrdup(B_L, cp);
		return r;
	}
	return NULL;
}

/******************************************************************************/
/*
 *	Mark time. Set a timestamp so that, later, we can return the number of
 *	seconds since we made the mark. Note that the mark my not be a
 *	"real" time, but rather a relative marker.
 */

void websMarkTime(webs_t wp)
{
	wp->timestamp = time(0);
}

/******************************************************************************/
/*
 *	Get the number of seconds since the last mark.
 */

static int websGetTimeSinceMark(webs_t wp)
{
	return time(0) - wp->timestamp;
}

/******************************************************************************/
/*
 *	Store the new realm name
 */

void websSetRealm(char_t *realmName)
{
	a_assert(realmName);

	gstrncpy(websRealm, realmName, TSZ(websRealm));
}

/******************************************************************************/
/*
 *	Return the realm name (used for authorization)
 */

char_t *websGetRealm()
{
	return websRealm;
}


#if WEBS_IF_MODIFIED_SUPPORT
/******************************************************************************/
/*	
 *	These functions are intended to closely mirror the syntax for HTTP-date 
 *	from RFC 2616 (HTTP/1.1 spec).  This code was submitted by Pete Bergstrom.
 */

/*	
 *	RFC1123Date	= wkday "," SP date1 SP time SP "GMT"
 *	RFC850Date	= weekday "," SP date2 SP time SP "GMT"
 *	ASCTimeDate	= wkday SP date3 SP time SP 4DIGIT
 *
 *	Each of these functions tries to parse the value and update the index to 
 *	the point it leaves off parsing.
 */

typedef enum { JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC } MonthEnumeration;
typedef enum { SUN, MON, TUE, WED, THU, FRI, SAT } WeekdayEnumeration;

/******************************************************************************/
/*	
 *	Parse an N-digit value
 */

static int parseNDIGIT(char_t *buf, int digits, int *index) 
{
	int tmpIndex, returnValue;

	returnValue = 0;

	for (tmpIndex = *index; tmpIndex < *index+digits; tmpIndex++) {
		if (gisdigit(buf[tmpIndex])) {
			returnValue = returnValue * 10 + (buf[tmpIndex] - T('0'));
		}
	}
	*index = tmpIndex;
	
	return returnValue;
}

/******************************************************************************/
/*
 *	Return an index into the month array
 */

static int parseMonth(char_t *buf, int *index) 
{
/*	
 *	"Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" | 
 *	"Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec"
 */
	int tmpIndex, returnValue;

	returnValue = -1;
	tmpIndex = *index;

	switch (buf[tmpIndex]) {
		case 'A':
			switch (buf[tmpIndex+1]) {
				case 'p':
					returnValue = APR;
					break;
				case 'u':
					returnValue = AUG;
					break;
			}
			break;
		case 'D':
			returnValue = DEC;
			break;
		case 'F':
			returnValue = FEB;
			break;
		case 'J':
			switch (buf[tmpIndex+1]) {
				case 'a':
					returnValue = JAN;
					break;
				case 'u':
					switch (buf[tmpIndex+2]) {
						case 'l':
							returnValue = JUL;
							break;
						case 'n':
							returnValue = JUN;
							break;
					}
					break;
			}
			break;
		case 'M':
			switch (buf[tmpIndex+1]) {
				case 'a':
					switch (buf[tmpIndex+2]) {
						case 'r':
							returnValue = MAR;
							break;
						case 'y':
							returnValue = MAY;
							break;
					}
					break;
			}
			break;
		case 'N':
			returnValue = NOV;
			break;
		case 'O':
			returnValue = OCT;
			break;
		case 'S':
			returnValue = SEP;
			break;
	}

	if (returnValue >= 0) {
		*index += 3;
	}

	return returnValue;
}

/******************************************************************************/
/* 
 *	Parse a year value (either 2 or 4 digits)
 */

static int parseYear(char_t *buf, int *index) 
{
	int tmpIndex, returnValue;

	tmpIndex = *index;
	returnValue = parseNDIGIT(buf, 4, &tmpIndex);

	if (returnValue >= 0) {
		*index = tmpIndex;
	} else {
		returnValue = parseNDIGIT(buf, 2, &tmpIndex);
		if (returnValue >= 0) {
/*
 *			Assume that any year earlier than the start of the 
 *			epoch for time_t (1970) specifies 20xx
 */
			if (returnValue < 70) {
				returnValue += 2000;
			} else {
				returnValue += 1900;
			}

			*index = tmpIndex;
		}
	}

	return returnValue;
}

/******************************************************************************/
/* 
 *	The formulas used to build these functions are from "Calendrical Calculations", 
 *	by Nachum Dershowitz, Edward M. Reingold, Cambridge University Press, 1997.
 */

#include <math.h>

const int GregorianEpoch = 1;

/******************************************************************************/
/*
 *  Determine if year is a leap year
 */

int GregorianLeapYearP(long year) 
{
	int		result;
	long	tmp;
	
	tmp = year % 400;

	if ((year % 4 == 0) &&
		(tmp != 100) &&
		(tmp != 200) &&
		(tmp != 300)) {
		result = TRUE;
	} else {
		result = FALSE;
	}

	return result;
}

/******************************************************************************/
/*
 *  Return the fixed date from the gregorian date
 */

long FixedFromGregorian(long month, long day, long year) 
{
	long fixedDate;

	fixedDate = (long)(GregorianEpoch - 1 + 365 * (year - 1) + 
		floor((year - 1) / 4.0) -
		floor((double)(year - 1) / 100.0) + 
		floor((double)(year - 1) / 400.0) + 
		floor((367.0 * ((double)month) - 362.0) / 12.0));

	if (month <= 2) {
		fixedDate += 0;
	} else if (TRUE == GregorianLeapYearP(year)) {
		fixedDate += -1;
	} else {
		fixedDate += -2;
	}

	fixedDate += day;

	return fixedDate;
}

/******************************************************************************/
/*
 *  Return the gregorian year from a fixed date
 */

long GregorianYearFromFixed(long fixedDate) 
{
	long result, d0, n400, d1, n100, d2, n4, d3, n1, d4, year;

	d0 =	fixedDate - GregorianEpoch;
	n400 =	(long)(floor((double)d0 / (double)146097));
	d1 =	d0 % 146097;
	n100 =	(long)(floor((double)d1 / (double)36524));
	d2 =	d1 % 36524;
	n4 =	(long)(floor((double)d2 / (double)1461));
	d3 =	d2 % 1461;
	n1 =	(long)(floor((double)d3 / (double)365));
	d4 =	(d3 % 365) + 1;
	year =	400 * n400 + 100 * n100 + 4 * n4 + n1;

	if ((n100 == 4) || (n1 == 4)) {
		result = year;
	} else {
		result = year + 1;
	}

	return result;
}

/******************************************************************************/
/* 
 *	Returns the Gregorian date from a fixed date
 *	(not needed for this use, but included for completeness
 */

#if 0
GregorianFromFixed(long fixedDate, long *month, long *day, long *year) 
{
	long priorDays, correction;

	*year =			GregorianYearFromFixed(fixedDate);
	priorDays =		fixedDate - FixedFromGregorian(1, 1, *year);

	if (fixedDate < FixedFromGregorian(3,1,*year)) {
		correction = 0;
	} else if (true == GregorianLeapYearP(*year)) {
		correction = 1;
	} else {
		correction = 2;
	}

	*month = (long)(floor((12.0 * (double)(priorDays + correction) + 373.0) / 367.0));
	*day = fixedDate - FixedFromGregorian(*month, 1, *year);
}
#endif

/******************************************************************************/
/* 
 *	Returns the difference between two Gregorian dates
 */

long GregorianDateDifference(	long month1, long day1, long year1,
								long month2, long day2, long year2) 
{
	return FixedFromGregorian(month2, day2, year2) - 
		FixedFromGregorian(month1, day1, year1);
}


/******************************************************************************/
/*
 *	Return the number of seconds into the current day
 */

#define SECONDS_PER_DAY 24*60*60

static int parseTime(char_t *buf, int *index) 
{
/*	
 *	Format of buf is - 2DIGIT ":" 2DIGIT ":" 2DIGIT
 */
	int returnValue, tmpIndex, hourValue, minuteValue, secondValue;

	hourValue = minuteValue = secondValue = -1;
	returnValue = -1;
	tmpIndex = *index;

	hourValue = parseNDIGIT(buf, 2, &tmpIndex);

	if (hourValue >= 0) {
		tmpIndex++;
		minuteValue = parseNDIGIT(buf, 2, &tmpIndex);
		if (minuteValue >= 0) {
			tmpIndex++;
			secondValue = parseNDIGIT(buf, 2, &tmpIndex);
		}
	}

	if ((hourValue >= 0) &&
		(minuteValue >= 0) &&
		(secondValue >= 0)) {
		returnValue = (((hourValue * 60) + minuteValue) * 60) + secondValue;
		*index = tmpIndex;
	}

	return returnValue;
}

/******************************************************************************/
/*
 *	Return the equivalent of time() given a gregorian date
 */

static time_t dateToTimet(int year, int month, int day) 
{
	long dayDifference;

	dayDifference = FixedFromGregorian(month, day, year) - 
		FixedFromGregorian(1, 1, 1970);

	return dayDifference * SECONDS_PER_DAY;
}

/******************************************************************************/
/*
 *	Return the number of seconds between Jan 1, 1970 and the parsed date
 *	(corresponds to documentation for time() function)
 */

static time_t parseDate1or2(char_t *buf, int *index) 
{
/*	
 *	Format of buf is either
 *	2DIGIT SP month SP 4DIGIT
 *	or
 *	2DIGIT "-" month "-" 2DIGIT
 */
	int		dayValue, monthValue, yearValue, tmpIndex;
	time_t	returnValue;

	returnValue = (time_t) -1;
	tmpIndex = *index;

	dayValue = monthValue = yearValue = -1;

	if (buf[tmpIndex] == T(',')) {
/* 
 *		Skip over the ", " 
 */
		tmpIndex += 2; 

		dayValue = parseNDIGIT(buf, 2, &tmpIndex);
		if (dayValue >= 0) {
/*
 *			Skip over the space or hyphen
 */
			tmpIndex++; 
			monthValue = parseMonth(buf, &tmpIndex);
			if (monthValue >= 0) {
/*
 *				Skip over the space or hyphen
 */
				tmpIndex++; 
				yearValue = parseYear(buf, &tmpIndex);
			}
		}

		if ((dayValue >= 0) &&
			(monthValue >= 0) &&
			(yearValue >= 0)) {
			if (yearValue < 1970) {
/*				
 *				Allow for Microsoft IE's year 1601 dates 
 */
				returnValue = 0; 
			} else {
				returnValue = dateToTimet(yearValue, monthValue, dayValue);
			}
			*index = tmpIndex;
		}
	}
	
	return returnValue;
}

/******************************************************************************/
/*
 *	Return the number of seconds between Jan 1, 1970 and the parsed date
 */

static time_t parseDate3Time(char_t *buf, int *index) 
{
/*
 *	Format of buf is month SP ( 2DIGIT | ( SP 1DIGIT ))
 */
	int		dayValue, monthValue, yearValue, timeValue, tmpIndex;
	time_t	returnValue;

	returnValue = (time_t) -1;
	tmpIndex = *index;

	dayValue = monthValue = yearValue = timeValue = -1;

	monthValue = parseMonth(buf, &tmpIndex);
	if (monthValue >= 0) {
/*		
 *		Skip over the space 
 */
		tmpIndex++; 
		if (buf[tmpIndex] == T(' ')) {
/*
 *			Skip over this space too 
 */
			tmpIndex++; 
			dayValue = parseNDIGIT(buf, 1, &tmpIndex);
		} else {
			dayValue = parseNDIGIT(buf, 2, &tmpIndex);
		}
/*		
 *		Now get the time and time SP 4DIGIT
 */
		timeValue = parseTime(buf, &tmpIndex);
		if (timeValue >= 0) {
/*			
 *			Now grab the 4DIGIT year value
 */
			yearValue = parseYear(buf, &tmpIndex);
		}
	}

	if ((dayValue >= 0) &&
		(monthValue >= 0) &&
		(yearValue >= 0)) {
		returnValue = dateToTimet(yearValue, monthValue, dayValue);
		returnValue += timeValue;
		*index = tmpIndex;
	}
	
	return returnValue;
}


/******************************************************************************/
/*
 *	Although this looks like a trivial function, I found I was replicating the implementation
 *	seven times in the parseWeekday function. In the interests of minimizing code size
 *	and redundancy, it is broken out into a separate function. The cost of an extra 
 *	function call I can live with given that it should only be called once per HTTP request.
 */

static int bufferIndexIncrementGivenNTest(char_t *buf, int testIndex, char_t testChar, 
										  int foundIncrement, int notfoundIncrement) 
{
	if (buf[testIndex] == testChar) {
		return foundIncrement;
	}

	return notfoundIncrement;
}

/******************************************************************************/
/*
 *	Return an index into a logical weekday array
 */

static int parseWeekday(char_t *buf, int *index) 
{
/*	
 *	Format of buf is either
 *	"Mon" | "Tue" | "Wed" | "Thu" | "Fri" | "Sat" | "Sun"
 *	or
 *	"Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday" | "Sunday"
 */
	int tmpIndex, returnValue;

	returnValue = -1;
	tmpIndex = *index;

	switch (buf[tmpIndex]) {
		case 'F':
			returnValue = FRI;
			*index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'd', sizeof("Friday"), 3);
			break;
		case 'M':
			returnValue = MON;
			*index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'd', sizeof("Monday"), 3);
			break;
		case 'S':
			switch (buf[tmpIndex+1]) {
				case 'a':
					returnValue = SAT;
					*index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'u', sizeof("Saturday"), 3);
					break;
				case 'u':
					returnValue = SUN;
					*index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'd', sizeof("Sunday"), 3);
					break;
			}
			break;
		case 'T':
			switch (buf[tmpIndex+1]) {
				case 'h':
					returnValue = THU;
					*index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'r', sizeof("Thursday"), 3);
					break;
				case 'u':
					returnValue = TUE;
					*index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 's', sizeof("Tuesday"), 3);
					break;
			}
			break;
		case 'W':
			returnValue = WED;
			*index += bufferIndexIncrementGivenNTest(buf, tmpIndex+3, 'n', sizeof("Wednesday"), 3);
			break;
	}
	return returnValue;
}

/******************************************************************************/
/*
 *		Parse the date and time string.
 */

static time_t dateParse(time_t tip, char_t *cmd)
{
	int index, tmpIndex, weekday, timeValue;
	time_t parsedValue, dateValue;

	parsedValue = (time_t) 0;
	index =	timeValue = 0;
	weekday = parseWeekday(cmd, &index);

	if (weekday >= 0) {
		tmpIndex = index;
		dateValue = parseDate1or2(cmd, &tmpIndex);
		if (dateValue >= 0) {
			index = tmpIndex + 1;
/*
 *			One of these two forms is being used
 *			wkday "," SP date1 SP time SP "GMT"
 *			weekday "," SP date2 SP time SP "GMT"
 */
			timeValue = parseTime(cmd, &index);
			if (timeValue >= 0) {
/*				
 *				Now match up that "GMT" string for completeness
 *				Compute the final value if there were no problems in the parse
 */
				if ((weekday >= 0) &&
					(dateValue >= 0) &&
					(timeValue >= 0)) {
					parsedValue = dateValue + timeValue;
				}
			}
		} else {
/* 
 *			Try the other form - wkday SP date3 SP time SP 4DIGIT
 */
			tmpIndex = index;
			parsedValue = parseDate3Time(cmd, &tmpIndex);
		}
	}

	return parsedValue;
}

#endif /* WEBS_IF_MODIFIED_SUPPORT */


/******************************************************************************/