summaryrefslogblamecommitdiffstats
path: root/cpukit/httpd/webs.c
blob: 509e7061d8bedad5aadb726189e41a551e03e779 (plain) (tree)
1
2
3
4
5
6
7
8


                                              
                                                                       

                                                                               

       




                                                                                
                                                                           





                                                                                 
                            
                                  
      


                                                                                
                                                                              
                                                                                               




                                                                                                    
                                                                          
                                                                          










                                                                                
                                     
                                     

                                   


                                             

                                                               


                   
                       




                                                                                                     


                                                                                             


                                                                                
 
                                                  




                                                                             
 
                       
                                             
      
                               
                                                   










                                                                                



                                   


                               
                    


                      

                    


                                                                                
                                              











                                                                          
                       
 
                       


                                    
                                                                               


                                 
 







                                                                                
                          



                            



                                  




                                                                
  









                                                                 
                       





                                 
                    

                       
                           






























                                                                                 
         





                                                                                  


                                           

                                                     
                                                         
                
                                                                                      
                                                                       
                                          
         


                                                                        







                                                                                
                          





                                                      

                                           






                                                                                
                                                              
















                                                                              
                                  
 
                                                                                  




                                                                                

                                                               






                                                                            
                                                                             



                                                   

                                                                               






















                                                                                 
         
                                     
                                                       

                                               
         




                                                                                
                                                                       


                                                                          
                             

                      
                                                  



                                
                            





                                                                               
                







                                                                             
                                                                         













                                                                       
                                                             











                                                               
 





                                                                                     
                                                                  
                         
                                                       



                                    



                                                                                  
   






                                                                                               









                                                                              
                                            
   
                              
      


                                                                                    
                                                                                               














                                                                                                   








                                                                               











                                                                        





                                              

                                                                                  
   



                                            






                                                                 


                                                                                  
   










                                                                                               





















                                                                                           




                                 






                                                                                

                                                                            
                                                                                   


                                                                                   

                                                                              


                                         
                                                                






















                                                                                      
 








                                                                




                                                                                                      




                                                                                    



                                                                                                        
                                                                                  


                                           
                                                                           





                                                                             






                                                             
                                                    
      

                                 
                                


                                                


                                                      
                                                                                           














                                                                  
  

                                                                                         






                                                                  







                                                                                               





                                                                                      




                                                                                         
                                                                       

             



                                                                                     
      
















                                                                           



                                                                          

                                                 






                                                                                  















                                                                                


                                                                                
































                                                                  
                                             







                                                                                 
                                                                        





                                                        









                                                            


                                        

                                                  
 





                                                             






                                                  
                         
  
                                                                                  



                                                                                   

                                                           
















                                                                                       


                                                                               

                                       
                                                                       







                                                       
  


















                                                                              
                                                         






                                                                   
                                                                           
















                                                            
                                                                    
  
                                                                   
   

                                                        
  


                                                                         
                                                              
                                     
                         











                                                                                              





                                                                             





















                                                                                        
                                



























































































                                                                                               
 







                                                                      



                                                                    








                                                                              
                                                  





                                                                      
 





                                                                  
                              






                                                                   
                         










                                                                                 
                               







                                                            
                               







                                                                              
                                                                 


                                           
                                                           
 
                                                                     

                                                              
 
                                            
                                     



                 









                                                                                
                                                     





                                                             
                                                   





                                                     





                                                                         












                                                                               




                                      
  
                                                                                         








                                                                                    



                                                
          









                                                                                 
                                                                    










































                                                                                
                                                                      







                                                                   
 












                                                                                






                                                              
 







                                                                                


                                                                      
                                 


                                


                                                




                                                                                
                                                                           


















                                                                         








                                                                                         
  


                                                                                 
 
  










                                                                        
                                                                                            




                                                        
                                                                   

                                                            

                                                          
                                                            
                            
                                             

                                                                                                  
                                                                                









                                                                      



                                                                       
 
                                                                                    
                                                                
  

                                                                                     









                                                                       



                                                                               











                                                                                
                                               













                                                                         









                                                                 






                                                                            
                                            











                                                                                  
  


                                           


                                                              



                                                     



                          







                                
                                                     

                     


                     
        



















                                                                              






                                                                                 
                                                             











                                                                                

                                         
 
                            















                                                                                
                                          



                                    
 





                                
 

                                                                            
         
 











                                                                                
                                                                             
                                                                              
                                                                              
                                                                           

                                                                          



                                                      

                                  
 

                                


                              







                                                                               
                            























                                                                              
         


                             





                                                                                  



                                                                                  

   
                                                           
 
              





                                






                                                       
         





                                              








                                                                                

                            
                                  
 

                          

                     
                                                                       







                                                                                
                                                                


















                                                                                
                       








                                        





                          


                                








                                    
                                                                  

                                          
                                                                      
                                            
      















                                                                                
                                   



                                          
                          




                                             





                                       
                                  


                                          
                                              




















                                                                                
                         




                                              
                       










                                                












                                               
                                                                           
                                                        

                                          
                                                




                                                        
                                                                                      

                                              

                                                                                 



                                      

                                           









                                                                                
                      






                                                 
                                                       








                                                       
                         
                       

















                                







                                                                               
                                             








                                                                                
                        














                                             

                                         









                                          





























                                             




                                        
                                                





                                                                                
                                 

   
                         
 
                        



                                                                                
                                                             

   
                              
 
                             






                                                                                
                            








                                                                                
                     
































































                                                                                
                    


























                                                                                
                                         










                                                                                
                                     










                                                                                
                                         









































































































                                                                                
                  



























































                                                                                 



                                                                             
                                             
 
                       





















                                                                                
                               














                                                                                















                                                                                
                          




                         
                               
                                                                                

                                                                               


                                                                                  
  



                                                               
                                                                                






                                                                                             
  


                              
                                                           










                                                                                  
 







                                                                                
                                              
 

                                                       









































































                                                                                
  


                                                 
                                             











                                                             
                                                                          















                                                                                

                                                                                       











                                                                                   
                                 


                               
 


















                                                                                
                                                        


                       
                                                                  
                                         

                                                   



















                                                                                
                                           























                                                                                
  




                                                               
                                                                      



















                                                                                          
  



                                                                   
                                                                                                   
 
                                                        










                                                                                
                                             
 
  

































                                                                                    
                                                       


                           




                                                                       
        
                                                                  










                                                                                
                                                    
 
  













                                                                  

                                  
   
                              





                                                          
                                   




                                                                
                                           







                                                                      

                                                                        
   
                                                





                                                                                           
 







                                                                                
                                                     













                                                                             

                                   
   
                           

                                              
                                                
   
                                   



                                                                  
  



                                                      
  












                                                                           
 







                                                                                                 
                                                                                       


                                                                                                

                                                                                                                            












                                                                                
                                                
 
  












































































                                                                                                                              
  









                                                                                              
  











                                                                             
                                                                                
/*
 * 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("Use 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 }
};

#ifdef 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);

#ifdef WEBS_LOG_SUPPORT
static void 	websLog(webs_t wp, int code);
#endif
#ifdef 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);

#ifdef 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();

#ifdef 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(void)
{
	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);
	}

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

#ifdef 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(void)
{
	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, min(sizeof(wp->ipaddr), strlen(ipaddr) + 1));

/*
 *	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 (websValid(wp) && 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));

	websSetTimeMark(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));
            /*
             * NOTE that the above comment is wrong -- if the content length
             * is set, websGetInput() does NOT use socketGets(), it uses
             * socketRead(), so the line below that adds an additional newline
             * is destructive.
             */
				/*gwrite(fd, T("\n"), sizeof(char_t));*/
/*
 *				Line removed as per BUG02488
 *
				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.
 */
               if (text != NULL)
               {
                  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, (but be sure to close
 *			the input file first!).
 */
			if (fd != -1) {
				gclose (fd);
				fd = -1;
			}
			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 */
	                /* Bugfix for POST DoS attack with invalid content length */
                  	if (socketEof(wp->sid)) {
                    		websDone(wp, 0);
                  	}
	                /* End of bugfix */
			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);
				}
			} else {
/*
 *				If an error occurred and it wasn't an eof, close the connection
 */
#ifdef HP_FIX
				websDone(wp, 0);
#endif /*HP_FIX*/

			}
/*
 *			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.
 */
/*
 *			NOTE: this fix for earlier versions of browsers is troublesome
 *			because if we don't receive the entire header in the first pass
 *			this code assumes we were only expecting a one line header, which
 *			is not necessarily the case. So we weren't processing the whole
 *			header and weren't fufilling requests properly.
 */
#ifdef UNUSED
			if (wp->state == WEBS_HEADER && ringqLen(&wp->header) <= 0) {
				websParseRequest(wp);
				websUrlHandlerRequest(wp);
			}
#endif
			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));

#ifdef 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((unsigned char)*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((unsigned char)*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';
               /*
                * bugfix 5/24/02 -- we were leaking the memory pointed to by
                * wp->authType that was allocated just before the if()
                * statement that we are currently in. Thanks to Simon Byholm.
                */
               bfree(B_L, wp->authType);
					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) {
         /*
          * 11 Oct 02 BgP -- The server would crash if an attacker sent a POST
          * message with a content-length value <= 0. We assume that anyone
          * sending this is malicious, and the POST is read from the socket,
          * but it is ignored, and the socket is closed.
          */
         wp->clen = gatoi(value);
         if (wp->clen > 0)
         {
			   wp->flags |= WEBS_CLEN;
			   websSetVar(wp, T("CONTENT_LENGTH"), value);
         }
         else
         {
            wp->clen = 0;
         }

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

#ifdef 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

#ifdef 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);

#ifdef 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("&"));
	}

#ifdef 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;
/*
 *		Redirect behaves much better when sent with HTTP/1.0
 */
		if (redirect != NULL) {
			websWrite(wp, T("HTTP/1.0 %d %s\r\n"), code, websErrorMsg(code));
		} else {
			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;

            /* $$$ before... (note commas instead of semicolons...)
				nonce = websCalcNonce(wp),
				opaque = websCalcOpaque(wp),
            $$$ after */
				nonce = websCalcNonce(wp);
				opaque = websCalcOpaque(wp);
            /* ...$$$ end */
				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
 */

#ifdef qRichErrorPage
extern int dmfRichError(webs_t wp, int code, char_t* userMsg);
#endif
void websError(webs_t wp, int code, char_t *fmt, ...)
{
	va_list		args;
	char_t		*msg, *userMsg, *buf;
#ifdef qRichErrorPage
   static int reEntry = 0;
   int errorOk;
#endif

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

	websStats.errors++;

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

#ifdef qRichErrorPage
   if (!reEntry)
   {
      /*
       * The dmfRichError function that we're about to call may very well call
       * websError() as part of its work. If that happens, we do NOT want to
       * get into a never-ending recursive call chain. When we get back here
       * in a call from inside dmfRichError(), we check to see if we're
       * already trying to call dmfRichError. If we are, we just revert to the
       * old non-rich behavior and display a black on white error page.
       */

      reEntry = 1;
      errorOk = dmfRichError(wp, code, userMsg);
      reEntry = 0;
      if (errorOk)
      {
         return;
      }
      /* ...else we need to fall through and execute the simple error page. */
   }
   /* implicit else... */
#endif

	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)*/
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)
{
	unsigned char	*ip;
	char_t	*op;
	int		num, i, c;

	a_assert(decoded);
	a_assert(token);

	op = decoded;
	for (ip = (unsigned char *)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';
}

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

static void websLog(webs_t wp, int code)
{
	char_t	*buf;
	char	*abuf;
	int		len;
#define qAnlLog 1
#ifdef qAnlLog
   time_t timer;
   char_t* newLine = NULL;
   char_t* timeStr = NULL;
#endif
	a_assert(websValid(wp));

	buf = NULL;

#ifdef qAnlLog
   time(&timer);
   timeStr = ctime(&timer);
   newLine = gstrchr(timeStr, '\n');
   if (newLine)
   {
      *newLine = '\0';
   }
   fmtAlloc(&buf, WEBS_MAX_URL + 80, T("%s\t%s\t%s\tcode = %d\n"),
      timeStr, wp->ipaddr, wp->url, code);
#else
	fmtAlloc(&buf, WEBS_MAX_URL + 80, T("%d %s %d %d\n"), time(0),
		wp->url, code, wp->written);
#endif
	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;
	}

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

#ifdef 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(void)
{
	return websHost;
}

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

char_t *websGetIpaddrUrl(void)
{
	return websIpaddrUrl;
}

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

char_t *websGetHostUrl(void)
{
	return websHostUrl;
}

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

int websGetPort(void)
{
	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));

#ifdef 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 websSetTimeMark(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(void)
{
	return websRealm;
}


#ifdef 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;

     /*
      * Bug fix by Jeff Reeder (Jun 14, 2002): The 'month' parameter is
      * numbered from  0 (Jan == 0), but FixedFromGregorian() takes
      * months numbered from 1 (January == 1). We need to add 1
      * to the month
      */
	dayDifference = FixedFromGregorian(month + 1, 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 */

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