File Annotation
Not logged in
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Copyright (c) 2006 D. Richard Hipp
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** This program is free software; you can redistribute it and/or
dbda8d6ce9 2007-07-21       drh: ** modify it under the terms of the GNU General Public
dbda8d6ce9 2007-07-21       drh: ** License version 2 as published by the Free Software Foundation.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** This program is distributed in the hope that it will be useful,
dbda8d6ce9 2007-07-21       drh: ** but WITHOUT ANY WARRANTY; without even the implied warranty of
dbda8d6ce9 2007-07-21       drh: ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
dbda8d6ce9 2007-07-21       drh: ** General Public License for more details.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** You should have received a copy of the GNU General Public
dbda8d6ce9 2007-07-21       drh: ** License along with this library; if not, write to the
dbda8d6ce9 2007-07-21       drh: ** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
dbda8d6ce9 2007-07-21       drh: ** Boston, MA  02111-1307, USA.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** Author contact information:
dbda8d6ce9 2007-07-21       drh: **   drh@hwaci.com
dbda8d6ce9 2007-07-21       drh: **   http://www.hwaci.com/drh/
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: *******************************************************************************
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** This file contains C functions and procedures that provide useful
dbda8d6ce9 2007-07-21       drh: ** services to CGI programs.  There are procedures for parsing and
dbda8d6ce9 2007-07-21       drh: ** dispensing QUERY_STRING parameters and cookies, the "mprintf()"
dbda8d6ce9 2007-07-21       drh: ** formatting function and its cousins, and routines to encode and
dbda8d6ce9 2007-07-21       drh: ** decode strings in HTML or HTTP.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: #include "config.h"
83c876b447 2007-09-21 anonymous: #ifdef __MINGW32__
83c876b447 2007-09-21 anonymous: #  include <windows.h>           /* for Sleep once server works again */
8372cc0b81 2007-09-22       jnc: #  include <winsock2.h>          /* socket operations */
83c876b447 2007-09-21 anonymous: #  define sleep Sleep            /* windows does not have sleep, but Sleep */
e7cf189265 2007-10-04  mjanssen: #  include <ws2tcpip.h>
83c876b447 2007-09-21 anonymous: #else
83c876b447 2007-09-21 anonymous: #  include <sys/socket.h>
83c876b447 2007-09-21 anonymous: #  include <netinet/in.h>
83c876b447 2007-09-21 anonymous: #  include <arpa/inet.h>
83c876b447 2007-09-21 anonymous: #  include <sys/times.h>
83c876b447 2007-09-21 anonymous: #  include <sys/time.h>
83c876b447 2007-09-21 anonymous: #  include <sys/wait.h>
83c876b447 2007-09-21 anonymous: #  include <sys/select.h>
83c876b447 2007-09-21 anonymous: #endif
83c876b447 2007-09-21 anonymous: #include <time.h>
dbda8d6ce9 2007-07-21       drh: #include <stdio.h>
dbda8d6ce9 2007-07-21       drh: #include <stdlib.h>
dbda8d6ce9 2007-07-21       drh: #include <unistd.h>
dbda8d6ce9 2007-07-21       drh: #include "cgi.h"
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: #if INTERFACE
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Shortcuts for cgi_parameter.  P("x") returns the value of query parameter
dbda8d6ce9 2007-07-21       drh: ** or cookie "x", or NULL if there is no such parameter or cookie.  PD("x","y")
dbda8d6ce9 2007-07-21       drh: ** does the same except "y" is returned in place of NULL if there is not match.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: #define P(x)        cgi_parameter((x),0)
dbda8d6ce9 2007-07-21       drh: #define PD(x,y)     cgi_parameter((x),(y))
dbda8d6ce9 2007-07-21       drh: #define QP(x)       quotable_string(cgi_parameter((x),0))
dbda8d6ce9 2007-07-21       drh: #define QPD(x,y)    quotable_string(cgi_parameter((x),(y)))
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: #endif /* INTERFACE */
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Provide a reliable implementation of a caseless string comparison
dbda8d6ce9 2007-07-21       drh: ** function.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: #define stricmp sqlite3StrICmp
dbda8d6ce9 2007-07-21       drh: extern int sqlite3StrICmp(const char*, const char*);
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** The body of the HTTP reply text is stored here.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: static Blob cgiContent = BLOB_INITIALIZER;
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Append reply content to what already exists.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_append_content(const char *zData, int nAmt){
dbda8d6ce9 2007-07-21       drh:   blob_append(&cgiContent, zData, nAmt);
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Reset the HTTP reply text to be an empty string.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_reset_content(void){
dbda8d6ce9 2007-07-21       drh:   blob_reset(&cgiContent);
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
a48936e834 2007-08-03       drh: ** Return a pointer to the CGI output blob.
a48936e834 2007-08-03       drh: */
a48936e834 2007-08-03       drh: Blob *cgi_output_blob(void){
a48936e834 2007-08-03       drh:   return &cgiContent;
6dab6149b1 2007-08-01       drh: }
6dab6149b1 2007-08-01       drh: 
6dab6149b1 2007-08-01       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Return a pointer to the HTTP reply text.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: char *cgi_extract_content(int *pnAmt){
dbda8d6ce9 2007-07-21       drh:   return blob_buffer(&cgiContent);
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Additional information used to form the HTTP reply
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: static char *zContentType = "text/html";     /* Content type of the reply */
dbda8d6ce9 2007-07-21       drh: static char *zReplyStatus = "OK";            /* Reply status description */
dbda8d6ce9 2007-07-21       drh: static int iReplyStatus = 200;               /* Reply status code */
dbda8d6ce9 2007-07-21       drh: static Blob extraHeader = BLOB_INITIALIZER;  /* Extra header text */
dbda8d6ce9 2007-07-21       drh: static int fullHttpReply = 0;      /* True for a full-blown HTTP header */
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Set the reply content type
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_set_content_type(const char *zType){
dbda8d6ce9 2007-07-21       drh:   zContentType = mprintf("%s", zType);
6dab6149b1 2007-08-01       drh: }
6dab6149b1 2007-08-01       drh: 
6dab6149b1 2007-08-01       drh: /*
6dab6149b1 2007-08-01       drh: ** Set the reply content to the specified BLOB.
6dab6149b1 2007-08-01       drh: */
6dab6149b1 2007-08-01       drh: void cgi_set_content(Blob *pNewContent){
6dab6149b1 2007-08-01       drh:   blob_reset(&cgiContent);
6dab6149b1 2007-08-01       drh:   cgiContent = *pNewContent;
6dab6149b1 2007-08-01       drh:   blob_zero(pNewContent);
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Set the reply status code
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_set_status(int iStat, const char *zStat){
dbda8d6ce9 2007-07-21       drh:   zReplyStatus = mprintf("%s", zStat);
dbda8d6ce9 2007-07-21       drh:   iReplyStatus = iStat;
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Append text to the header of an HTTP reply
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_append_header(const char *zLine){
dbda8d6ce9 2007-07-21       drh:   blob_append(&extraHeader, zLine, -1);
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Set a cookie.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** Zero lifetime implies a session cookie.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_set_cookie(
dbda8d6ce9 2007-07-21       drh:   const char *zName,    /* Name of the cookie */
dbda8d6ce9 2007-07-21       drh:   const char *zValue,   /* Value of the cookie.  Automatically escaped */
dbda8d6ce9 2007-07-21       drh:   const char *zPath,    /* Path cookie applies to.  NULL means "/" */
dbda8d6ce9 2007-07-21       drh:   int lifetime          /* Expiration of the cookie in seconds from now */
dbda8d6ce9 2007-07-21       drh: ){
68a202e101 2007-11-21       drh:   if( zPath==0 ) zPath = g.zTop;
dbda8d6ce9 2007-07-21       drh:   if( lifetime>0 ){
dbda8d6ce9 2007-07-21       drh:     lifetime += (int)time(0);
dbda8d6ce9 2007-07-21       drh:     blob_appendf(&extraHeader,
dbda8d6ce9 2007-07-21       drh:        "Set-Cookie: %s=%t; Path=%s; expires=%s; Version=1\r\n",
dbda8d6ce9 2007-07-21       drh:         zName, zValue, zPath, cgi_rfc822_datestamp(lifetime));
dbda8d6ce9 2007-07-21       drh:   }else{
dbda8d6ce9 2007-07-21       drh:     blob_appendf(&extraHeader,
dbda8d6ce9 2007-07-21       drh:        "Set-Cookie: %s=%t; Path=%s; Version=1\r\n",
dbda8d6ce9 2007-07-21       drh:        zName, zValue, zPath);
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: #if 0
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Add an ETag header line
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: static char *cgi_add_etag(char *zTxt, int nLen){
dbda8d6ce9 2007-07-21       drh:   MD5Context ctx;
dbda8d6ce9 2007-07-21       drh:   unsigned char digest[16];
dbda8d6ce9 2007-07-21       drh:   int i, j;
dbda8d6ce9 2007-07-21       drh:   char zETag[64];
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   MD5Init(&ctx);
dbda8d6ce9 2007-07-21       drh:   MD5Update(&ctx,zTxt,nLen);
dbda8d6ce9 2007-07-21       drh:   MD5Final(digest,&ctx);
dbda8d6ce9 2007-07-21       drh:   for(j=i=0; i<16; i++,j+=2){
dbda8d6ce9 2007-07-21       drh:     bprintf(&zETag[j],sizeof(zETag)-j,"%02x",(int)digest[i]);
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   blob_appendf(&extraHeader, "ETag: %s\r\n", zETag);
dbda8d6ce9 2007-07-21       drh:   return strdup(zETag);
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Do some cache control stuff. First, we generate an ETag and include it in
dbda8d6ce9 2007-07-21       drh: ** the response headers. Second, we do whatever is necessary to determine if
dbda8d6ce9 2007-07-21       drh: ** the request was asking about caching and whether we need to send back the
dbda8d6ce9 2007-07-21       drh: ** response body. If we shouldn't send a body, return non-zero.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** Currently, we just check the ETag against any If-None-Match header.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** FIXME: In some cases (attachments, file contents) we could check
dbda8d6ce9 2007-07-21       drh: ** If-Modified-Since headers and always include Last-Modified in responses.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: static int check_cache_control(void){
dbda8d6ce9 2007-07-21       drh:   /* FIXME: there's some gotchas wth cookies and some headers. */
dbda8d6ce9 2007-07-21       drh:   char *zETag = cgi_add_etag(blob_buffer(&cgiContent),blob_size(&cgiContent));
dbda8d6ce9 2007-07-21       drh:   char *zMatch = P("HTTP_IF_NONE_MATCH");
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   if( zETag!=0 && zMatch!=0 ) {
dbda8d6ce9 2007-07-21       drh:     char *zBuf = strdup(zMatch);
dbda8d6ce9 2007-07-21       drh:     if( zBuf!=0 ){
dbda8d6ce9 2007-07-21       drh:       char *zTok = 0;
dbda8d6ce9 2007-07-21       drh:       char *zPos;
dbda8d6ce9 2007-07-21       drh:       for( zTok = strtok_r(zBuf, ",\"",&zPos);
dbda8d6ce9 2007-07-21       drh:            zTok && strcasecmp(zTok,zETag);
dbda8d6ce9 2007-07-21       drh:            zTok =  strtok_r(0, ",\"",&zPos)){}
dbda8d6ce9 2007-07-21       drh:       free(zBuf);
dbda8d6ce9 2007-07-21       drh:       if(zTok) return 1;
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   return 0;
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: #endif
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Do a normal HTTP reply
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_reply(void){
dbda8d6ce9 2007-07-21       drh:   if( iReplyStatus<=0 ){
dbda8d6ce9 2007-07-21       drh:     iReplyStatus = 200;
dbda8d6ce9 2007-07-21       drh:     zReplyStatus = "OK";
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: #if 0
dbda8d6ce9 2007-07-21       drh:   if( iReplyStatus==200 && check_cache_control() ) {
dbda8d6ce9 2007-07-21       drh:     /* change the status to "unchanged" and we can skip sending the
dbda8d6ce9 2007-07-21       drh:     ** actual response body. Obviously we only do this when we _have_ a
dbda8d6ce9 2007-07-21       drh:     ** body (code 200).
dbda8d6ce9 2007-07-21       drh:     */
dbda8d6ce9 2007-07-21       drh:     iReplyStatus = 304;
dbda8d6ce9 2007-07-21       drh:     zReplyStatus = "Not Modified";
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: #endif
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   if( fullHttpReply ){
dbda8d6ce9 2007-07-21       drh:     printf("HTTP/1.0 %d %s\r\n", iReplyStatus, zReplyStatus);
dbda8d6ce9 2007-07-21       drh:     printf("Date: %s\r\n", cgi_rfc822_datestamp(time(0)));
dbda8d6ce9 2007-07-21       drh:     printf("Connection: close\r\n");
dbda8d6ce9 2007-07-21       drh:   }else{
dbda8d6ce9 2007-07-21       drh:     printf("Status: %d %s\r\n", iReplyStatus, zReplyStatus);
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   if( blob_size(&extraHeader)>0 ){
dbda8d6ce9 2007-07-21       drh:     printf("%s", blob_buffer(&extraHeader));
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   if( g.isConst ){
dbda8d6ce9 2007-07-21       drh:     /* constant means that the input URL will _never_ generate anything
dbda8d6ce9 2007-07-21       drh:     ** else. In the case of attachments, the contents won't change because
dbda8d6ce9 2007-07-21       drh:     ** an attempt to change them generates a new attachment number. In the
dbda8d6ce9 2007-07-21       drh:     ** case of most /getfile calls for specific versions, the only way the
dbda8d6ce9 2007-07-21       drh:     ** content changes is if someone breaks the SCM. And if that happens, a
dbda8d6ce9 2007-07-21       drh:     ** stale cache is the least of the problem. So we provide an Expires
dbda8d6ce9 2007-07-21       drh:     ** header set to a reasonable period (default: one week).
dbda8d6ce9 2007-07-21       drh:     */
dbda8d6ce9 2007-07-21       drh:     /*time_t expires = time(0) + atoi(db_config("constant_expires","604800"));*/
dbda8d6ce9 2007-07-21       drh:     time_t expires = time(0) + 604800;
dbda8d6ce9 2007-07-21       drh:     printf( "Expires: %s\r\n", cgi_rfc822_datestamp(expires));
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   /* Content intended for logged in users should only be cached in
dbda8d6ce9 2007-07-21       drh:   ** the browser, not some shared location.
dbda8d6ce9 2007-07-21       drh:   */
dbda8d6ce9 2007-07-21       drh:   printf("Cache-control: private\r\n");
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: #if FOSSIL_I18N
dbda8d6ce9 2007-07-21       drh:   printf( "Content-Type: %s; charset=%s\r\n", zContentType, nl_langinfo(CODESET));
dbda8d6ce9 2007-07-21       drh: #else
dbda8d6ce9 2007-07-21       drh:   printf( "Content-Type: %s; charset=ISO-8859-1\r\n", zContentType);
dbda8d6ce9 2007-07-21       drh: #endif
dbda8d6ce9 2007-07-21       drh:   if( strcmp(zContentType,"application/x-fossil")==0 ){
dbda8d6ce9 2007-07-21       drh:     blob_compress(&cgiContent, &cgiContent);
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   if( iReplyStatus != 304 ) {
dbda8d6ce9 2007-07-21       drh:     printf( "Content-Length: %d\r\n", blob_size(&cgiContent) );
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   printf("\r\n");
dbda8d6ce9 2007-07-21       drh:   if( blob_size(&cgiContent)>0 && iReplyStatus != 304 ){
dbda8d6ce9 2007-07-21       drh:     fwrite(blob_buffer(&cgiContent), 1, blob_size(&cgiContent), stdout);
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   CGIDEBUG(("DONE\n"));
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Do a redirect request to the URL given in the argument.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** The URL must be relative to the base of the fossil server.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_redirect(const char *zURL){
dbda8d6ce9 2007-07-21       drh:   char *zLocation;
dbda8d6ce9 2007-07-21       drh:   CGIDEBUG(("redirect to %s\n", zURL));
dbda8d6ce9 2007-07-21       drh:   if( strncmp(zURL,"http:",5)==0 || strncmp(zURL,"https:",6)==0 || *zURL=='/' ){
f66089ec43 2007-10-23       drh:     zLocation = mprintf("Location: %s\r\n", zURL);
f66089ec43 2007-10-23       drh:   }else{
f66089ec43 2007-10-23       drh:     zLocation = mprintf("Location: %s/%s\r\n", g.zBaseURL, zURL);
f66089ec43 2007-10-23       drh:   }
dbda8d6ce9 2007-07-21       drh:   cgi_append_header(zLocation);
dbda8d6ce9 2007-07-21       drh:   cgi_reset_content();
dbda8d6ce9 2007-07-21       drh:   cgi_printf("<html>\n<p>Redirect to %h</p>\n</html>\n", zURL);
dbda8d6ce9 2007-07-21       drh:   cgi_set_status(302, "Moved Temporarily");
dbda8d6ce9 2007-07-21       drh:   free(zLocation);
dbda8d6ce9 2007-07-21       drh:   cgi_reply();
dbda8d6ce9 2007-07-21       drh:   exit(0);
677aa71bca 2007-10-12       drh: }
677aa71bca 2007-10-12       drh: void cgi_redirectf(const char *zFormat, ...){
677aa71bca 2007-10-12       drh:   va_list ap;
677aa71bca 2007-10-12       drh:   va_start(ap, zFormat);
677aa71bca 2007-10-12       drh:   cgi_redirect(vmprintf(zFormat, ap));
677aa71bca 2007-10-12       drh:   va_end(ap);
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Information about all query parameters and cookies are stored
dbda8d6ce9 2007-07-21       drh: ** in these variables.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: static int nAllocQP = 0; /* Space allocated for aParamQP[] */
dbda8d6ce9 2007-07-21       drh: static int nUsedQP = 0;  /* Space actually used in aParamQP[] */
dbda8d6ce9 2007-07-21       drh: static int sortQP = 0;   /* True if aParamQP[] needs sorting */
dbda8d6ce9 2007-07-21       drh: static int seqQP = 0;    /* Sequence numbers */
dbda8d6ce9 2007-07-21       drh: static struct QParam {   /* One entry for each query parameter or cookie */
dbda8d6ce9 2007-07-21       drh:   const char *zName;        /* Parameter or cookie name */
dbda8d6ce9 2007-07-21       drh:   const char *zValue;       /* Value of the query parameter or cookie */
dbda8d6ce9 2007-07-21       drh:   int seq;                  /* Order of insertion */
dbda8d6ce9 2007-07-21       drh: } *aParamQP;             /* An array of all parameters and cookies */
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Add another query parameter or cookie to the parameter set.
dbda8d6ce9 2007-07-21       drh: ** zName is the name of the query parameter or cookie and zValue
dbda8d6ce9 2007-07-21       drh: ** is its fully decoded value.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** zName and zValue are not copied and must not change or be
dbda8d6ce9 2007-07-21       drh: ** deallocated after this routine returns.
dbda8d6ce9 2007-07-21       drh: */
677aa71bca 2007-10-12       drh: void cgi_set_parameter_nocopy(const char *zName, const char *zValue){
dbda8d6ce9 2007-07-21       drh:   if( nAllocQP<=nUsedQP ){
dbda8d6ce9 2007-07-21       drh:     nAllocQP = nAllocQP*2 + 10;
dbda8d6ce9 2007-07-21       drh:     aParamQP = realloc( aParamQP, nAllocQP*sizeof(aParamQP[0]) );
dbda8d6ce9 2007-07-21       drh:     if( aParamQP==0 ) exit(1);
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   aParamQP[nUsedQP].zName = zName;
dbda8d6ce9 2007-07-21       drh:   aParamQP[nUsedQP].zValue = zValue;
dbda8d6ce9 2007-07-21       drh:   aParamQP[nUsedQP].seq = seqQP++;
dbda8d6ce9 2007-07-21       drh:   nUsedQP++;
dbda8d6ce9 2007-07-21       drh:   sortQP = 1;
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Add another query parameter or cookie to the parameter set.
dbda8d6ce9 2007-07-21       drh: ** zName is the name of the query parameter or cookie and zValue
dbda8d6ce9 2007-07-21       drh: ** is its fully decoded value.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** Copies are made of both the zName and zValue parameters.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_set_parameter(const char *zName, const char *zValue){
dbda8d6ce9 2007-07-21       drh:   cgi_set_parameter_nocopy(mprintf("%s",zName), mprintf("%s",zValue));
555911dff5 2007-11-21       drh: }
555911dff5 2007-11-21       drh: 
555911dff5 2007-11-21       drh: /*
555911dff5 2007-11-21       drh: ** Replace a parameter with a new value.
555911dff5 2007-11-21       drh: */
555911dff5 2007-11-21       drh: void cgi_replace_parameter(const char *zName, const char *zValue){
555911dff5 2007-11-21       drh:   int i;
555911dff5 2007-11-21       drh:   for(i=0; i<nUsedQP; i++){
555911dff5 2007-11-21       drh:     if( strcmp(aParamQP[i].zName,zName)==0 ){
555911dff5 2007-11-21       drh:       aParamQP[i].zValue = zValue;
555911dff5 2007-11-21       drh:     }
555911dff5 2007-11-21       drh:   }
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Add a query parameter.  The zName portion is fixed but a copy
dbda8d6ce9 2007-07-21       drh: ** must be made of zValue.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_setenv(const char *zName, const char *zValue){
dbda8d6ce9 2007-07-21       drh:   cgi_set_parameter_nocopy(zName, mprintf("%s",zValue));
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Add a list of query parameters or cookies to the parameter set.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** Each parameter is of the form NAME=VALUE.  Both the NAME and the
dbda8d6ce9 2007-07-21       drh: ** VALUE may be url-encoded ("+" for space, "%HH" for other special
dbda8d6ce9 2007-07-21       drh: ** characters).  But this routine assumes that NAME contains no
dbda8d6ce9 2007-07-21       drh: ** special character and therefore does not decode it.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** If NAME begins with another other than a lower-case letter then
dbda8d6ce9 2007-07-21       drh: ** the entire NAME=VALUE term is ignored.  Hence:
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: **      *  cookies and query parameters that have uppercase names
dbda8d6ce9 2007-07-21       drh: **         are ignored.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: **      *  it is impossible for a cookie or query parameter to
dbda8d6ce9 2007-07-21       drh: **         override the value of an environment variable since
dbda8d6ce9 2007-07-21       drh: **         environment variables always have uppercase names.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** Parameters are separated by the "terminator" character.  Whitespace
dbda8d6ce9 2007-07-21       drh: ** before the NAME is ignored.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** The input string "z" is modified but no copies is made.  "z"
dbda8d6ce9 2007-07-21       drh: ** should not be deallocated or changed again after this routine
dbda8d6ce9 2007-07-21       drh: ** returns or it will corrupt the parameter table.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: static void add_param_list(char *z, int terminator){
dbda8d6ce9 2007-07-21       drh:   while( *z ){
dbda8d6ce9 2007-07-21       drh:     char *zName;
dbda8d6ce9 2007-07-21       drh:     char *zValue;
dbda8d6ce9 2007-07-21       drh:     while( isspace(*z) ){ z++; }
dbda8d6ce9 2007-07-21       drh:     zName = z;
dbda8d6ce9 2007-07-21       drh:     while( *z && *z!='=' && *z!=terminator ){ z++; }
dbda8d6ce9 2007-07-21       drh:     if( *z=='=' ){
dbda8d6ce9 2007-07-21       drh:       *z = 0;
dbda8d6ce9 2007-07-21       drh:       z++;
dbda8d6ce9 2007-07-21       drh:       zValue = z;
dbda8d6ce9 2007-07-21       drh:       while( *z && *z!=terminator ){ z++; }
dbda8d6ce9 2007-07-21       drh:       if( *z ){
dbda8d6ce9 2007-07-21       drh:         *z = 0;
dbda8d6ce9 2007-07-21       drh:         z++;
dbda8d6ce9 2007-07-21       drh:       }
dbda8d6ce9 2007-07-21       drh:       dehttpize(zValue);
dbda8d6ce9 2007-07-21       drh:     }else{
dbda8d6ce9 2007-07-21       drh:       if( *z ){ *z++ = 0; }
dbda8d6ce9 2007-07-21       drh:       zValue = "";
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:     if( islower(zName[0]) ){
dbda8d6ce9 2007-07-21       drh:       cgi_set_parameter_nocopy(zName, zValue);
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** *pz is a string that consists of multiple lines of text.  This
dbda8d6ce9 2007-07-21       drh: ** routine finds the end of the current line of text and converts
dbda8d6ce9 2007-07-21       drh: ** the "\n" or "\r\n" that ends that line into a "\000".  It then
dbda8d6ce9 2007-07-21       drh: ** advances *pz to the beginning of the next line and returns the
dbda8d6ce9 2007-07-21       drh: ** previous value of *pz (which is the start of the current line.)
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: static char *get_line_from_string(char **pz, int *pLen){
dbda8d6ce9 2007-07-21       drh:   char *z = *pz;
dbda8d6ce9 2007-07-21       drh:   int i;
dbda8d6ce9 2007-07-21       drh:   if( z[0]==0 ) return 0;
dbda8d6ce9 2007-07-21       drh:   for(i=0; z[i]; i++){
dbda8d6ce9 2007-07-21       drh:     if( z[i]=='\n' ){
dbda8d6ce9 2007-07-21       drh:       if( i>0 && z[i-1]=='\r' ){
dbda8d6ce9 2007-07-21       drh:         z[i-1] = 0;
dbda8d6ce9 2007-07-21       drh:       }else{
dbda8d6ce9 2007-07-21       drh:         z[i] = 0;
dbda8d6ce9 2007-07-21       drh:       }
dbda8d6ce9 2007-07-21       drh:       i++;
dbda8d6ce9 2007-07-21       drh:       break;
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   *pz = &z[i];
dbda8d6ce9 2007-07-21       drh:   *pLen -= i;
dbda8d6ce9 2007-07-21       drh:   return z;
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** The input *pz points to content that is terminated by a "\r\n"
dbda8d6ce9 2007-07-21       drh: ** followed by the boundry marker zBoundry.  An extra "--" may or
dbda8d6ce9 2007-07-21       drh: ** may not be appended to the boundry marker.  There are *pLen characters
dbda8d6ce9 2007-07-21       drh: ** in *pz.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** This routine adds a "\000" to the end of the content (overwriting
dbda8d6ce9 2007-07-21       drh: ** the "\r\n") and returns a pointer to the content.  The *pz input
dbda8d6ce9 2007-07-21       drh: ** is adjusted to point to the first line following the boundry.
dbda8d6ce9 2007-07-21       drh: ** The length of the content is stored in *pnContent.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: static char *get_bounded_content(
dbda8d6ce9 2007-07-21       drh:   char **pz,         /* Content taken from here */
dbda8d6ce9 2007-07-21       drh:   int *pLen,         /* Number of bytes of data in (*pz)[] */
dbda8d6ce9 2007-07-21       drh:   char *zBoundry,    /* Boundry text marking the end of content */
dbda8d6ce9 2007-07-21       drh:   int *pnContent     /* Write the size of the content here */
dbda8d6ce9 2007-07-21       drh: ){
dbda8d6ce9 2007-07-21       drh:   char *z = *pz;
dbda8d6ce9 2007-07-21       drh:   int len = *pLen;
dbda8d6ce9 2007-07-21       drh:   int i;
dbda8d6ce9 2007-07-21       drh:   int nBoundry = strlen(zBoundry);
dbda8d6ce9 2007-07-21       drh:   *pnContent = len;
dbda8d6ce9 2007-07-21       drh:   for(i=0; i<len; i++){
dbda8d6ce9 2007-07-21       drh:     if( z[i]=='\n' && strncmp(zBoundry, &z[i+1], nBoundry)==0 ){
dbda8d6ce9 2007-07-21       drh:       if( i>0 && z[i-1]=='\r' ) i--;
dbda8d6ce9 2007-07-21       drh:       z[i] = 0;
dbda8d6ce9 2007-07-21       drh:       *pnContent = i;
dbda8d6ce9 2007-07-21       drh:       i += nBoundry;
dbda8d6ce9 2007-07-21       drh:       break;
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   *pz = &z[i];
dbda8d6ce9 2007-07-21       drh:   get_line_from_string(pz, pLen);
dbda8d6ce9 2007-07-21       drh:   return z;
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Tokenize a line of text into as many as nArg tokens.  Make
dbda8d6ce9 2007-07-21       drh: ** azArg[] point to the start of each token.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** Tokens consist of space or semi-colon delimited words or
dbda8d6ce9 2007-07-21       drh: ** strings inside double-quotes.  Example:
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: **    content-disposition: form-data; name="fn"; filename="index.html"
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** The line above is tokenized as follows:
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: **    azArg[0] = "content-disposition:"
dbda8d6ce9 2007-07-21       drh: **    azArg[1] = "form-data"
dbda8d6ce9 2007-07-21       drh: **    azArg[2] = "name="
dbda8d6ce9 2007-07-21       drh: **    azArg[3] = "fn"
dbda8d6ce9 2007-07-21       drh: **    azArg[4] = "filename="
dbda8d6ce9 2007-07-21       drh: **    azArg[5] = "index.html"
dbda8d6ce9 2007-07-21       drh: **    azArg[6] = 0;
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** '\000' characters are inserted in z[] at the end of each token.
dbda8d6ce9 2007-07-21       drh: ** This routine returns the total number of tokens on the line, 6
dbda8d6ce9 2007-07-21       drh: ** in the example above.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: static int tokenize_line(char *z, int mxArg, char **azArg){
dbda8d6ce9 2007-07-21       drh:   int i = 0;
dbda8d6ce9 2007-07-21       drh:   while( *z ){
dbda8d6ce9 2007-07-21       drh:     while( isspace(*z) || *z==';' ){ z++; }
dbda8d6ce9 2007-07-21       drh:     if( *z=='"' && z[1] ){
dbda8d6ce9 2007-07-21       drh:       *z = 0;
dbda8d6ce9 2007-07-21       drh:       z++;
dbda8d6ce9 2007-07-21       drh:       if( i<mxArg-1 ){ azArg[i++] = z; }
dbda8d6ce9 2007-07-21       drh:       while( *z && *z!='"' ){ z++; }
dbda8d6ce9 2007-07-21       drh:       if( *z==0 ) break;
dbda8d6ce9 2007-07-21       drh:       *z = 0;
dbda8d6ce9 2007-07-21       drh:       z++;
dbda8d6ce9 2007-07-21       drh:     }else{
dbda8d6ce9 2007-07-21       drh:       if( i<mxArg-1 ){ azArg[i++] = z; }
dbda8d6ce9 2007-07-21       drh:       while( *z && !isspace(*z) && *z!=';' && *z!='"' ){ z++; }
dbda8d6ce9 2007-07-21       drh:       if( *z && *z!='"' ){
dbda8d6ce9 2007-07-21       drh:         *z = 0;
dbda8d6ce9 2007-07-21       drh:         z++;
dbda8d6ce9 2007-07-21       drh:       }
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   azArg[i] = 0;
dbda8d6ce9 2007-07-21       drh:   return i;
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Scan the multipart-form content and make appropriate entries
dbda8d6ce9 2007-07-21       drh: ** into the parameter table.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** The content string "z" is modified by this routine but it is
dbda8d6ce9 2007-07-21       drh: ** not copied.  The calling function must not deallocate or modify
dbda8d6ce9 2007-07-21       drh: ** "z" after this routine finishes or it could corrupt the parameter
dbda8d6ce9 2007-07-21       drh: ** table.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: static void process_multipart_form_data(char *z, int len){
dbda8d6ce9 2007-07-21       drh:   char *zLine;
dbda8d6ce9 2007-07-21       drh:   int nArg, i;
dbda8d6ce9 2007-07-21       drh:   char *zBoundry;
dbda8d6ce9 2007-07-21       drh:   char *zValue;
dbda8d6ce9 2007-07-21       drh:   char *zName = 0;
dbda8d6ce9 2007-07-21       drh:   int showBytes = 0;
dbda8d6ce9 2007-07-21       drh:   char *azArg[50];
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   zBoundry = get_line_from_string(&z, &len);
dbda8d6ce9 2007-07-21       drh:   if( zBoundry==0 ) return;
dbda8d6ce9 2007-07-21       drh:   while( (zLine = get_line_from_string(&z, &len))!=0 ){
dbda8d6ce9 2007-07-21       drh:     if( zLine[0]==0 ){
dbda8d6ce9 2007-07-21       drh:       int nContent = 0;
dbda8d6ce9 2007-07-21       drh:       zValue = get_bounded_content(&z, &len, zBoundry, &nContent);
dbda8d6ce9 2007-07-21       drh:       if( zName && zValue && islower(zName[0]) ){
dbda8d6ce9 2007-07-21       drh:         cgi_set_parameter_nocopy(zName, zValue);
dbda8d6ce9 2007-07-21       drh:         if( showBytes ){
dbda8d6ce9 2007-07-21       drh:           cgi_set_parameter_nocopy(mprintf("%s:bytes", zName),
dbda8d6ce9 2007-07-21       drh:                mprintf("%d",nContent));
dbda8d6ce9 2007-07-21       drh:         }
dbda8d6ce9 2007-07-21       drh:       }
dbda8d6ce9 2007-07-21       drh:       zName = 0;
dbda8d6ce9 2007-07-21       drh:       showBytes = 0;
dbda8d6ce9 2007-07-21       drh:     }else{
dbda8d6ce9 2007-07-21       drh:       nArg = tokenize_line(zLine, sizeof(azArg)/sizeof(azArg[0]), azArg);
dbda8d6ce9 2007-07-21       drh:       for(i=0; i<nArg; i++){
dbda8d6ce9 2007-07-21       drh:         int c = tolower(azArg[i][0]);
dbda8d6ce9 2007-07-21       drh:         if( c=='c' && stricmp(azArg[i],"content-disposition:")==0 ){
dbda8d6ce9 2007-07-21       drh:           i++;
dbda8d6ce9 2007-07-21       drh:         }else if( c=='n' && stricmp(azArg[i],"name=")==0 ){
dbda8d6ce9 2007-07-21       drh:           zName = azArg[++i];
dbda8d6ce9 2007-07-21       drh:         }else if( c=='f' && stricmp(azArg[i],"filename=")==0 ){
dbda8d6ce9 2007-07-21       drh:           char *z = azArg[++i];
dbda8d6ce9 2007-07-21       drh:           if( zName && z && islower(zName[0]) ){
dbda8d6ce9 2007-07-21       drh:             cgi_set_parameter_nocopy(mprintf("%s:filename",zName), z);
dbda8d6ce9 2007-07-21       drh:           }
dbda8d6ce9 2007-07-21       drh:           showBytes = 1;
dbda8d6ce9 2007-07-21       drh:         }else if( c=='c' && stricmp(azArg[i],"content-type:")==0 ){
dbda8d6ce9 2007-07-21       drh:           char *z = azArg[++i];
dbda8d6ce9 2007-07-21       drh:           if( zName && z && islower(zName[0]) ){
dbda8d6ce9 2007-07-21       drh:             cgi_set_parameter_nocopy(mprintf("%s:mimetype",zName), z);
dbda8d6ce9 2007-07-21       drh:           }
dbda8d6ce9 2007-07-21       drh:         }
dbda8d6ce9 2007-07-21       drh:       }
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Initialize the query parameter database.  Information is pulled from
dbda8d6ce9 2007-07-21       drh: ** the QUERY_STRING environment variable (if it exists), from standard
dbda8d6ce9 2007-07-21       drh: ** input if there is POST data, and from HTTP_COOKIE.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_init(void){
dbda8d6ce9 2007-07-21       drh:   char *z;
dbda8d6ce9 2007-07-21       drh:   const char *zType;
dbda8d6ce9 2007-07-21       drh:   int len;
dbda8d6ce9 2007-07-21       drh:   z = (char*)P("QUERY_STRING");
dbda8d6ce9 2007-07-21       drh:   if( z ){
dbda8d6ce9 2007-07-21       drh:     z = mprintf("%s",z);
dbda8d6ce9 2007-07-21       drh:     add_param_list(z, '&');
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   len = atoi(PD("CONTENT_LENGTH", "0"));
dbda8d6ce9 2007-07-21       drh:   g.zContentType = zType = P("CONTENT_TYPE");
dbda8d6ce9 2007-07-21       drh:   if( len>0 && zType ){
dbda8d6ce9 2007-07-21       drh:     blob_zero(&g.cgiIn);
dbda8d6ce9 2007-07-21       drh:     if( strcmp(zType,"application/x-www-form-urlencoded")==0
dbda8d6ce9 2007-07-21       drh:          || strncmp(zType,"multipart/form-data",19)==0 ){
dbda8d6ce9 2007-07-21       drh:       z = malloc( len+1 );
dbda8d6ce9 2007-07-21       drh:       if( z==0 ) exit(1);
dbda8d6ce9 2007-07-21       drh:       len = fread(z, 1, len, stdin);
dbda8d6ce9 2007-07-21       drh:       z[len] = 0;
dbda8d6ce9 2007-07-21       drh:       if( zType[0]=='a' ){
dbda8d6ce9 2007-07-21       drh:         add_param_list(z, '&');
dbda8d6ce9 2007-07-21       drh:       }else{
dbda8d6ce9 2007-07-21       drh:         process_multipart_form_data(z, len);
dbda8d6ce9 2007-07-21       drh:       }
dbda8d6ce9 2007-07-21       drh:     }else if( strcmp(zType, "application/x-fossil")==0 ){
dbda8d6ce9 2007-07-21       drh:       blob_read_from_channel(&g.cgiIn, stdin, len);
dbda8d6ce9 2007-07-21       drh:       blob_uncompress(&g.cgiIn, &g.cgiIn);
dbda8d6ce9 2007-07-21       drh:     }else if( strcmp(zType, "application/x-fossil-debug")==0 ){
dbda8d6ce9 2007-07-21       drh:       blob_read_from_channel(&g.cgiIn, stdin, len);
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   z = (char*)P("HTTP_COOKIE");
dbda8d6ce9 2007-07-21       drh:   if( z ){
dbda8d6ce9 2007-07-21       drh:     z = mprintf("%s",z);
dbda8d6ce9 2007-07-21       drh:     add_param_list(z, ';');
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** This is the comparison function used to sort the aParamQP[] array of
dbda8d6ce9 2007-07-21       drh: ** query parameters and cookies.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: static int qparam_compare(const void *a, const void *b){
dbda8d6ce9 2007-07-21       drh:   struct QParam *pA = (struct QParam*)a;
dbda8d6ce9 2007-07-21       drh:   struct QParam *pB = (struct QParam*)b;
dbda8d6ce9 2007-07-21       drh:   int c;
dbda8d6ce9 2007-07-21       drh:   c = strcmp(pA->zName, pB->zName);
dbda8d6ce9 2007-07-21       drh:   if( c==0 ){
dbda8d6ce9 2007-07-21       drh:     c = pA->seq - pB->seq;
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   return c;
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Return the value of a query parameter or cookie whose name is zName.
dbda8d6ce9 2007-07-21       drh: ** If there is no query parameter or cookie named zName and the first
dbda8d6ce9 2007-07-21       drh: ** character of zName is uppercase, then check to see if there is an
dbda8d6ce9 2007-07-21       drh: ** environment variable by that name and return it if there is.  As
dbda8d6ce9 2007-07-21       drh: ** a last resort when nothing else matches, return zDefault.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: const char *cgi_parameter(const char *zName, const char *zDefault){
dbda8d6ce9 2007-07-21       drh:   int lo, hi, mid, c;
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   /* The sortQP flag is set whenever a new query parameter is inserted.
dbda8d6ce9 2007-07-21       drh:   ** It indicates that we need to resort the query parameters.
dbda8d6ce9 2007-07-21       drh:   */
dbda8d6ce9 2007-07-21       drh:   if( sortQP ){
dbda8d6ce9 2007-07-21       drh:     int i, j;
dbda8d6ce9 2007-07-21       drh:     qsort(aParamQP, nUsedQP, sizeof(aParamQP[0]), qparam_compare);
dbda8d6ce9 2007-07-21       drh:     sortQP = 0;
dbda8d6ce9 2007-07-21       drh:     /* After sorting, remove duplicate parameters.  The secondary sort
dbda8d6ce9 2007-07-21       drh:     ** key is aParamQP[].seq and we keep the first entry.  That means
dbda8d6ce9 2007-07-21       drh:     ** with duplicate calls to cgi_set_parameter() the second and
dbda8d6ce9 2007-07-21       drh:     ** subsequent calls are effectively no-ops. */
dbda8d6ce9 2007-07-21       drh:     for(i=j=1; i<nUsedQP; i++){
dbda8d6ce9 2007-07-21       drh:       if( strcmp(aParamQP[i].zName,aParamQP[i-1].zName)==0 ){
dbda8d6ce9 2007-07-21       drh:         continue;
dbda8d6ce9 2007-07-21       drh:       }
dbda8d6ce9 2007-07-21       drh:       if( j<i ){
dbda8d6ce9 2007-07-21       drh:         memcpy(&aParamQP[j], &aParamQP[i], sizeof(aParamQP[j]));
dbda8d6ce9 2007-07-21       drh:       }
dbda8d6ce9 2007-07-21       drh:       j++;
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:     nUsedQP = j;
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   /* Do a binary search for a matching query parameter */
dbda8d6ce9 2007-07-21       drh:   lo = 0;
dbda8d6ce9 2007-07-21       drh:   hi = nUsedQP-1;
dbda8d6ce9 2007-07-21       drh:   while( lo<=hi ){
dbda8d6ce9 2007-07-21       drh:     mid = (lo+hi)/2;
dbda8d6ce9 2007-07-21       drh:     c = strcmp(aParamQP[mid].zName, zName);
dbda8d6ce9 2007-07-21       drh:     if( c==0 ){
dbda8d6ce9 2007-07-21       drh:       CGIDEBUG(("mem-match [%s] = [%s]\n", zName, aParamQP[mid].zValue));
dbda8d6ce9 2007-07-21       drh:       return aParamQP[mid].zValue;
dbda8d6ce9 2007-07-21       drh:     }else if( c>0 ){
dbda8d6ce9 2007-07-21       drh:       hi = mid-1;
dbda8d6ce9 2007-07-21       drh:     }else{
dbda8d6ce9 2007-07-21       drh:       lo = mid+1;
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   /* If no match is found and the name begins with an upper-case
dbda8d6ce9 2007-07-21       drh:   ** letter, then check to see if there is an environment variable
dbda8d6ce9 2007-07-21       drh:   ** with the given name.
dbda8d6ce9 2007-07-21       drh:   */
dbda8d6ce9 2007-07-21       drh:   if( isupper(zName[0]) ){
dbda8d6ce9 2007-07-21       drh:     const char *zValue = getenv(zName);
dbda8d6ce9 2007-07-21       drh:     if( zValue ){
dbda8d6ce9 2007-07-21       drh:       cgi_set_parameter_nocopy(zName, zValue);
dbda8d6ce9 2007-07-21       drh:       CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue));
dbda8d6ce9 2007-07-21       drh:       return zValue;
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   CGIDEBUG(("no-match [%s]\n", zName));
dbda8d6ce9 2007-07-21       drh:   return zDefault;
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Print CGI debugging messages.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_debug(const char *zFormat, ...){
dbda8d6ce9 2007-07-21       drh:   va_list ap;
dbda8d6ce9 2007-07-21       drh:   if( g.fDebug ){
dbda8d6ce9 2007-07-21       drh:     va_start(ap, zFormat);
dbda8d6ce9 2007-07-21       drh:     vfprintf(g.fDebug, zFormat, ap);
dbda8d6ce9 2007-07-21       drh:     va_end(ap);
dbda8d6ce9 2007-07-21       drh:     fflush(g.fDebug);
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Return true if any of the query parameters in the argument
dbda8d6ce9 2007-07-21       drh: ** list are defined.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: int cgi_any(const char *z, ...){
dbda8d6ce9 2007-07-21       drh:   va_list ap;
dbda8d6ce9 2007-07-21       drh:   char *z2;
dbda8d6ce9 2007-07-21       drh:   if( cgi_parameter(z,0)!=0 ) return 1;
dbda8d6ce9 2007-07-21       drh:   va_start(ap, z);
dbda8d6ce9 2007-07-21       drh:   while( (z2 = va_arg(ap, char*))!=0 ){
dbda8d6ce9 2007-07-21       drh:     if( cgi_parameter(z2,0)!=0 ) return 1;
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   va_end(ap);
dbda8d6ce9 2007-07-21       drh:   return 0;
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Return true if all of the query parameters in the argument list
dbda8d6ce9 2007-07-21       drh: ** are defined.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: int cgi_all(const char *z, ...){
dbda8d6ce9 2007-07-21       drh:   va_list ap;
dbda8d6ce9 2007-07-21       drh:   char *z2;
dbda8d6ce9 2007-07-21       drh:   if( cgi_parameter(z,0)==0 ) return 0;
dbda8d6ce9 2007-07-21       drh:   va_start(ap, z);
dbda8d6ce9 2007-07-21       drh:   while( (z2 = va_arg(ap, char*))==0 ){
dbda8d6ce9 2007-07-21       drh:     if( cgi_parameter(z2,0)==0 ) return 0;
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   va_end(ap);
dbda8d6ce9 2007-07-21       drh:   return 1;
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Print all query parameters on standard output.  Format the
dbda8d6ce9 2007-07-21       drh: ** parameters as HTML.  This is used for testing and debugging.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_print_all(void){
dbda8d6ce9 2007-07-21       drh:   int i;
dbda8d6ce9 2007-07-21       drh:   cgi_parameter("","");  /* Force the parameters into sorted order */
dbda8d6ce9 2007-07-21       drh:   for(i=0; i<nUsedQP; i++){
dbda8d6ce9 2007-07-21       drh:     cgi_printf("%s = %s  <br />\n",
dbda8d6ce9 2007-07-21       drh:        htmlize(aParamQP[i].zName, -1), htmlize(aParamQP[i].zValue, -1));
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Write HTML text for an option menu to standard output.  zParam
dbda8d6ce9 2007-07-21       drh: ** is the query parameter that the option menu sets.  zDflt is the
dbda8d6ce9 2007-07-21       drh: ** initial value of the option menu.  Addition arguments are name/value
dbda8d6ce9 2007-07-21       drh: ** pairs that define values on the menu.  The list is terminated with
dbda8d6ce9 2007-07-21       drh: ** a single NULL argument.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_optionmenu(int in, const char *zP, const char *zD, ...){
dbda8d6ce9 2007-07-21       drh:   va_list ap;
dbda8d6ce9 2007-07-21       drh:   char *zName, *zVal;
dbda8d6ce9 2007-07-21       drh:   int dfltSeen = 0;
dbda8d6ce9 2007-07-21       drh:   cgi_printf("%*s<select size=1 name=\"%s\">\n", in, "", zP);
dbda8d6ce9 2007-07-21       drh:   va_start(ap, zD);
dbda8d6ce9 2007-07-21       drh:   while( (zName = va_arg(ap, char*))!=0 && (zVal = va_arg(ap, char*))!=0 ){
dbda8d6ce9 2007-07-21       drh:     if( strcmp(zVal,zD)==0 ){ dfltSeen = 1; break; }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   va_end(ap);
dbda8d6ce9 2007-07-21       drh:   if( !dfltSeen ){
dbda8d6ce9 2007-07-21       drh:     if( zD[0] ){
dbda8d6ce9 2007-07-21       drh:       cgi_printf("%*s<option value=\"%h\" selected>%h</option>\n",
dbda8d6ce9 2007-07-21       drh:         in+2, "", zD, zD);
dbda8d6ce9 2007-07-21       drh:     }else{
dbda8d6ce9 2007-07-21       drh:       cgi_printf("%*s<option value=\"\" selected>&nbsp;</option>\n", in+2, "");
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   va_start(ap, zD);
dbda8d6ce9 2007-07-21       drh:   while( (zName = va_arg(ap, char*))!=0 && (zVal = va_arg(ap, char*))!=0 ){
dbda8d6ce9 2007-07-21       drh:     if( zName[0] ){
dbda8d6ce9 2007-07-21       drh:       cgi_printf("%*s<option value=\"%h\"%s>%h</option>\n",
dbda8d6ce9 2007-07-21       drh:         in+2, "",
dbda8d6ce9 2007-07-21       drh:         zVal,
dbda8d6ce9 2007-07-21       drh:         strcmp(zVal, zD) ? "" : " selected",
dbda8d6ce9 2007-07-21       drh:         zName
dbda8d6ce9 2007-07-21       drh:       );
dbda8d6ce9 2007-07-21       drh:     }else{
dbda8d6ce9 2007-07-21       drh:       cgi_printf("%*s<option value=\"\"%s>&nbsp;</option>\n",
dbda8d6ce9 2007-07-21       drh:         in+2, "",
dbda8d6ce9 2007-07-21       drh:         strcmp(zVal, zD) ? "" : " selected"
dbda8d6ce9 2007-07-21       drh:       );
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   va_end(ap);
dbda8d6ce9 2007-07-21       drh:   cgi_printf("%*s</select>\n", in, "");
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** This routine works a lot like cgi_optionmenu() except that the list of
dbda8d6ce9 2007-07-21       drh: ** values is contained in an array.  Also, the values are just values, not
dbda8d6ce9 2007-07-21       drh: ** name/value pairs as in cgi_optionmenu.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_v_optionmenu(
dbda8d6ce9 2007-07-21       drh:   int in,              /* Indent by this amount */
dbda8d6ce9 2007-07-21       drh:   const char *zP,      /* The query parameter name */
dbda8d6ce9 2007-07-21       drh:   const char *zD,      /* Default value */
dbda8d6ce9 2007-07-21       drh:   const char **az      /* NULL-terminated list of allowed values */
dbda8d6ce9 2007-07-21       drh: ){
dbda8d6ce9 2007-07-21       drh:   const char *zVal;
dbda8d6ce9 2007-07-21       drh:   int i;
dbda8d6ce9 2007-07-21       drh:   cgi_printf("%*s<select size=1 name=\"%s\">\n", in, "", zP);
dbda8d6ce9 2007-07-21       drh:   for(i=0; az[i]; i++){
dbda8d6ce9 2007-07-21       drh:     if( strcmp(az[i],zD)==0 ) break;
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   if( az[i]==0 ){
dbda8d6ce9 2007-07-21       drh:     if( zD[0]==0 ){
dbda8d6ce9 2007-07-21       drh:       cgi_printf("%*s<option value=\"\" selected>&nbsp;</option>\n",
dbda8d6ce9 2007-07-21       drh:        in+2, "");
dbda8d6ce9 2007-07-21       drh:     }else{
dbda8d6ce9 2007-07-21       drh:       cgi_printf("%*s<option value=\"%h\" selected>%h</option>\n",
dbda8d6ce9 2007-07-21       drh:        in+2, "", zD, zD);
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   while( (zVal = *(az++))!=0  ){
dbda8d6ce9 2007-07-21       drh:     if( zVal[0] ){
dbda8d6ce9 2007-07-21       drh:       cgi_printf("%*s<option value=\"%h\"%s>%h</option>\n",
dbda8d6ce9 2007-07-21       drh:         in+2, "",
dbda8d6ce9 2007-07-21       drh:         zVal,
dbda8d6ce9 2007-07-21       drh:         strcmp(zVal, zD) ? "" : " selected",
dbda8d6ce9 2007-07-21       drh:         zVal
dbda8d6ce9 2007-07-21       drh:       );
dbda8d6ce9 2007-07-21       drh:     }else{
dbda8d6ce9 2007-07-21       drh:       cgi_printf("%*s<option value=\"\"%s>&nbsp;</option>\n",
dbda8d6ce9 2007-07-21       drh:         in+2, "",
dbda8d6ce9 2007-07-21       drh:         strcmp(zVal, zD) ? "" : " selected"
dbda8d6ce9 2007-07-21       drh:       );
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   cgi_printf("%*s</select>\n", in, "");
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** This routine works a lot like cgi_v_optionmenu() except that the list
dbda8d6ce9 2007-07-21       drh: ** is a list of pairs.  The first element of each pair is the value used
dbda8d6ce9 2007-07-21       drh: ** internally and the second element is the value displayed to the user.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_v_optionmenu2(
dbda8d6ce9 2007-07-21       drh:   int in,              /* Indent by this amount */
dbda8d6ce9 2007-07-21       drh:   const char *zP,      /* The query parameter name */
dbda8d6ce9 2007-07-21       drh:   const char *zD,      /* Default value */
dbda8d6ce9 2007-07-21       drh:   const char **az      /* NULL-terminated list of allowed values */
dbda8d6ce9 2007-07-21       drh: ){
dbda8d6ce9 2007-07-21       drh:   const char *zVal;
dbda8d6ce9 2007-07-21       drh:   int i;
dbda8d6ce9 2007-07-21       drh:   cgi_printf("%*s<select size=1 name=\"%s\">\n", in, "", zP);
dbda8d6ce9 2007-07-21       drh:   for(i=0; az[i]; i+=2){
dbda8d6ce9 2007-07-21       drh:     if( strcmp(az[i],zD)==0 ) break;
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   if( az[i]==0 ){
dbda8d6ce9 2007-07-21       drh:     if( zD[0]==0 ){
dbda8d6ce9 2007-07-21       drh:       cgi_printf("%*s<option value=\"\" selected>&nbsp;</option>\n",
dbda8d6ce9 2007-07-21       drh:        in+2, "");
dbda8d6ce9 2007-07-21       drh:     }else{
dbda8d6ce9 2007-07-21       drh:       cgi_printf("%*s<option value=\"%h\" selected>%h</option>\n",
dbda8d6ce9 2007-07-21       drh:        in+2, "", zD, zD);
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   while( (zVal = *(az++))!=0  ){
dbda8d6ce9 2007-07-21       drh:     const char *zName = *(az++);
dbda8d6ce9 2007-07-21       drh:     if( zName[0] ){
dbda8d6ce9 2007-07-21       drh:       cgi_printf("%*s<option value=\"%h\"%s>%h</option>\n",
dbda8d6ce9 2007-07-21       drh:         in+2, "",
dbda8d6ce9 2007-07-21       drh:         zVal,
dbda8d6ce9 2007-07-21       drh:         strcmp(zVal, zD) ? "" : " selected",
dbda8d6ce9 2007-07-21       drh:         zName
dbda8d6ce9 2007-07-21       drh:       );
dbda8d6ce9 2007-07-21       drh:     }else{
dbda8d6ce9 2007-07-21       drh:       cgi_printf("%*s<option value=\"%h\"%s>&nbsp;</option>\n",
dbda8d6ce9 2007-07-21       drh:         in+2, "",
dbda8d6ce9 2007-07-21       drh:         zVal,
dbda8d6ce9 2007-07-21       drh:         strcmp(zVal, zD) ? "" : " selected"
dbda8d6ce9 2007-07-21       drh:       );
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   cgi_printf("%*s</select>\n", in, "");
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** This function implements the callback from vxprintf.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** This routine sends nNewChar characters of text in zNewText to
dbda8d6ce9 2007-07-21       drh: ** CGI reply content buffer.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: static void sout(void *NotUsed, const char *zNewText, int nNewChar){
dbda8d6ce9 2007-07-21       drh:   cgi_append_content(zNewText, nNewChar);
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** This routine works like "printf" except that it has the
dbda8d6ce9 2007-07-21       drh: ** extra formatting capabilities such as %h and %t.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_printf(const char *zFormat, ...){
dbda8d6ce9 2007-07-21       drh:   va_list ap;
dbda8d6ce9 2007-07-21       drh:   va_start(ap,zFormat);
dbda8d6ce9 2007-07-21       drh:   vxprintf(sout,0,zFormat,ap);
dbda8d6ce9 2007-07-21       drh:   va_end(ap);
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** This routine works like "vprintf" except that it has the
dbda8d6ce9 2007-07-21       drh: ** extra formatting capabilities such as %h and %t.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_vprintf(const char *zFormat, va_list ap){
dbda8d6ce9 2007-07-21       drh:   vxprintf(sout,0,zFormat,ap);
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Send a reply indicating that the HTTP request was malformed
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: static void malformed_request(void){
dbda8d6ce9 2007-07-21       drh:   cgi_set_status(501, "Not Implemented");
dbda8d6ce9 2007-07-21       drh:   cgi_printf(
dbda8d6ce9 2007-07-21       drh:     "<html><body>Unrecognized HTTP Request</body></html>\n"
dbda8d6ce9 2007-07-21       drh:   );
dbda8d6ce9 2007-07-21       drh:   cgi_reply();
dbda8d6ce9 2007-07-21       drh:   exit(0);
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Panic and die while processing a webpage.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_panic(const char *zFormat, ...){
dbda8d6ce9 2007-07-21       drh:   va_list ap;
dbda8d6ce9 2007-07-21       drh:   cgi_reset_content();
dbda8d6ce9 2007-07-21       drh:   cgi_set_status(500, "Internal Server Error");
dbda8d6ce9 2007-07-21       drh:   cgi_printf(
dbda8d6ce9 2007-07-21       drh:     "<html><body><h1>Internal Server Error</h1>\n"
dbda8d6ce9 2007-07-21       drh:     "<plaintext>"
dbda8d6ce9 2007-07-21       drh:   );
dbda8d6ce9 2007-07-21       drh:   va_start(ap, zFormat);
dbda8d6ce9 2007-07-21       drh:   vxprintf(sout,0,zFormat,ap);
dbda8d6ce9 2007-07-21       drh:   va_end(ap);
dbda8d6ce9 2007-07-21       drh:   cgi_reply();
dbda8d6ce9 2007-07-21       drh:   exit(1);
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Remove the first space-delimited token from a string and return
dbda8d6ce9 2007-07-21       drh: ** a pointer to it.  Add a NULL to the string to terminate the token.
dbda8d6ce9 2007-07-21       drh: ** Make *zLeftOver point to the start of the next token.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: static char *extract_token(char *zInput, char **zLeftOver){
dbda8d6ce9 2007-07-21       drh:   char *zResult = 0;
dbda8d6ce9 2007-07-21       drh:   if( zInput==0 ){
dbda8d6ce9 2007-07-21       drh:     if( zLeftOver ) *zLeftOver = 0;
dbda8d6ce9 2007-07-21       drh:     return 0;
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   while( isspace(*zInput) ){ zInput++; }
dbda8d6ce9 2007-07-21       drh:   zResult = zInput;
dbda8d6ce9 2007-07-21       drh:   while( *zInput && !isspace(*zInput) ){ zInput++; }
dbda8d6ce9 2007-07-21       drh:   if( *zInput ){
dbda8d6ce9 2007-07-21       drh:     *zInput = 0;
dbda8d6ce9 2007-07-21       drh:     zInput++;
dbda8d6ce9 2007-07-21       drh:     while( isspace(*zInput) ){ zInput++; }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   if( zLeftOver ){ *zLeftOver = zInput; }
dbda8d6ce9 2007-07-21       drh:   return zResult;
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** This routine handles a single HTTP request which is coming in on
dbda8d6ce9 2007-07-21       drh: ** standard input and which replies on standard output.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** The HTTP request is read from standard input and is used to initialize
dbda8d6ce9 2007-07-21       drh: ** environment variables as per CGI.  The cgi_init() routine to complete
dbda8d6ce9 2007-07-21       drh: ** the setup.  Once all the setup is finished, this procedure returns
dbda8d6ce9 2007-07-21       drh: ** and subsequent code handles the actual generation of the webpage.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_handle_http_request(void){
dbda8d6ce9 2007-07-21       drh:   char *z, *zToken;
dbda8d6ce9 2007-07-21       drh:   int i;
dbda8d6ce9 2007-07-21       drh:   struct sockaddr_in remoteName;
dbda8d6ce9 2007-07-21       drh:   size_t size = sizeof(struct sockaddr_in);
dbda8d6ce9 2007-07-21       drh:   char zLine[2000];     /* A single line of input. */
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   fullHttpReply = 1;
dbda8d6ce9 2007-07-21       drh:   if( fgets(zLine, sizeof(zLine), stdin)==0 ){
dbda8d6ce9 2007-07-21       drh:     malformed_request();
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   zToken = extract_token(zLine, &z);
dbda8d6ce9 2007-07-21       drh:   if( zToken==0 ){
dbda8d6ce9 2007-07-21       drh:     malformed_request();
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   if( strcmp(zToken,"GET")!=0 && strcmp(zToken,"POST")!=0
dbda8d6ce9 2007-07-21       drh:       && strcmp(zToken,"HEAD")!=0 ){
dbda8d6ce9 2007-07-21       drh:     malformed_request();
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   cgi_setenv("GATEWAY_INTERFACE","CGI/1.0");
dbda8d6ce9 2007-07-21       drh:   cgi_setenv("REQUEST_METHOD",zToken);
dbda8d6ce9 2007-07-21       drh:   zToken = extract_token(z, &z);
dbda8d6ce9 2007-07-21       drh:   if( zToken==0 ){
dbda8d6ce9 2007-07-21       drh:     malformed_request();
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   cgi_setenv("REQUEST_URI", zToken);
dbda8d6ce9 2007-07-21       drh:   for(i=0; zToken[i] && zToken[i]!='?'; i++){}
dbda8d6ce9 2007-07-21       drh:   if( zToken[i] ) zToken[i++] = 0;
dbda8d6ce9 2007-07-21       drh:   cgi_setenv("PATH_INFO", zToken);
dbda8d6ce9 2007-07-21       drh:   cgi_setenv("QUERY_STRING", &zToken[i]);
e63a9fd9d0 2007-09-25       jnc:   if( getpeername(fileno(stdin), (struct sockaddr*)&remoteName, (socklen_t*)&size)>=0 ){
36edf3fd5c 2007-08-01       dan:     char *zIpAddr = inet_ntoa(remoteName.sin_addr);
36edf3fd5c 2007-08-01       dan:     cgi_setenv("REMOTE_ADDR", zIpAddr);
36edf3fd5c 2007-08-01       dan: 
36edf3fd5c 2007-08-01       dan:     /* Set the Global.zIpAddr variable to the server we are talking to.
36edf3fd5c 2007-08-01       dan:     ** This is used to populate the ipaddr column of the rcvfrom table,
36edf3fd5c 2007-08-01       dan:     ** if any files are received from the connected client.
36edf3fd5c 2007-08-01       dan:     */
36edf3fd5c 2007-08-01       dan:     g.zIpAddr = mprintf("%s", zIpAddr);
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   /* Get all the optional fields that follow the first line.
dbda8d6ce9 2007-07-21       drh:   */
dbda8d6ce9 2007-07-21       drh:   while( fgets(zLine,sizeof(zLine),stdin) ){
dbda8d6ce9 2007-07-21       drh:     char *zFieldName;
dbda8d6ce9 2007-07-21       drh:     char *zVal;
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:     zFieldName = extract_token(zLine,&zVal);
dbda8d6ce9 2007-07-21       drh:     if( zFieldName==0 || *zFieldName==0 ) break;
dbda8d6ce9 2007-07-21       drh:     while( isspace(*zVal) ){ zVal++; }
dbda8d6ce9 2007-07-21       drh:     i = strlen(zVal);
dbda8d6ce9 2007-07-21       drh:     while( i>0 && isspace(zVal[i-1]) ){ i--; }
dbda8d6ce9 2007-07-21       drh:     zVal[i] = 0;
dbda8d6ce9 2007-07-21       drh:     for(i=0; zFieldName[i]; i++){ zFieldName[i] = tolower(zFieldName[i]); }
dbda8d6ce9 2007-07-21       drh:     if( strcmp(zFieldName,"user-agent:")==0 ){
dbda8d6ce9 2007-07-21       drh:       cgi_setenv("HTTP_USER_AGENT", zVal);
dbda8d6ce9 2007-07-21       drh:     }else if( strcmp(zFieldName,"content-length:")==0 ){
dbda8d6ce9 2007-07-21       drh:       cgi_setenv("CONTENT_LENGTH", zVal);
dbda8d6ce9 2007-07-21       drh:     }else if( strcmp(zFieldName,"referer:")==0 ){
dbda8d6ce9 2007-07-21       drh:       cgi_setenv("HTTP_REFERER", zVal);
dbda8d6ce9 2007-07-21       drh:     }else if( strcmp(zFieldName,"host:")==0 ){
dbda8d6ce9 2007-07-21       drh:       cgi_setenv("HTTP_HOST", zVal);
dbda8d6ce9 2007-07-21       drh:     }else if( strcmp(zFieldName,"content-type:")==0 ){
dbda8d6ce9 2007-07-21       drh:       cgi_setenv("CONTENT_TYPE", zVal);
dbda8d6ce9 2007-07-21       drh:     }else if( strcmp(zFieldName,"cookie:")==0 ){
dbda8d6ce9 2007-07-21       drh:       cgi_setenv("HTTP_COOKIE", zVal);
dbda8d6ce9 2007-07-21       drh:     }else if( strcmp(zFieldName,"if-none-match:")==0 ){
dbda8d6ce9 2007-07-21       drh:       cgi_setenv("HTTP_IF_NONE_MATCH", zVal);
dbda8d6ce9 2007-07-21       drh:     }else if( strcmp(zFieldName,"if-modified-since:")==0 ){
dbda8d6ce9 2007-07-21       drh:       cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal);
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   cgi_init();
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Maximum number of child processes that we can have running
dbda8d6ce9 2007-07-21       drh: ** at one time before we start slowing things down.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: #define MAX_PARALLEL 2
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Implement an HTTP server daemon listening on port iPort.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** As new connections arrive, fork a child and let child return
dbda8d6ce9 2007-07-21       drh: ** out of this procedure call.  The child will handle the request.
dbda8d6ce9 2007-07-21       drh: ** The parent never returns from this procedure.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_http_server(int iPort){
83c876b447 2007-09-21 anonymous: #ifdef __MINGW32__
83c876b447 2007-09-21 anonymous:   fprintf(stderr,"server not yet available in windows version of fossil\n");
83c876b447 2007-09-21 anonymous:   exit(1);
83c876b447 2007-09-21 anonymous: #else
dbda8d6ce9 2007-07-21       drh:   int listener;                /* The server socket */
dbda8d6ce9 2007-07-21       drh:   int connection;              /* A socket for each individual connection */
dbda8d6ce9 2007-07-21       drh:   fd_set readfds;              /* Set of file descriptors for select() */
dbda8d6ce9 2007-07-21       drh:   size_t lenaddr;              /* Length of the inaddr structure */
dbda8d6ce9 2007-07-21       drh:   int child;                   /* PID of the child process */
dbda8d6ce9 2007-07-21       drh:   int nchildren = 0;           /* Number of child processes */
dbda8d6ce9 2007-07-21       drh:   struct timeval delay;        /* How long to wait inside select() */
dbda8d6ce9 2007-07-21       drh:   struct sockaddr_in inaddr;   /* The socket address */
dbda8d6ce9 2007-07-21       drh:   int opt = 1;                 /* setsockopt flag */
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   memset(&inaddr, 0, sizeof(inaddr));
dbda8d6ce9 2007-07-21       drh:   inaddr.sin_family = AF_INET;
dbda8d6ce9 2007-07-21       drh:   inaddr.sin_addr.s_addr = INADDR_ANY;
dbda8d6ce9 2007-07-21       drh:   inaddr.sin_port = htons(iPort);
dbda8d6ce9 2007-07-21       drh:   listener = socket(AF_INET, SOCK_STREAM, 0);
dbda8d6ce9 2007-07-21       drh:   if( listener<0 ){
dbda8d6ce9 2007-07-21       drh:     fprintf(stderr,"Can't create a socket\n");
dbda8d6ce9 2007-07-21       drh:     exit(1);
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   /* if we can't terminate nicely, at least allow the socket to be reused */
dbda8d6ce9 2007-07-21       drh:   setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   if( bind(listener, (struct sockaddr*)&inaddr, sizeof(inaddr))<0 ){
dbda8d6ce9 2007-07-21       drh:     fprintf(stderr,"Can't bind to port %d\n", iPort);
dbda8d6ce9 2007-07-21       drh:     exit(1);
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   listen(listener,10);
dbda8d6ce9 2007-07-21       drh:   while( 1 ){
dbda8d6ce9 2007-07-21       drh:     if( nchildren>MAX_PARALLEL ){
dbda8d6ce9 2007-07-21       drh:       /* Slow down if connections are arriving too fast */
dbda8d6ce9 2007-07-21       drh:       sleep( nchildren-MAX_PARALLEL );
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:     delay.tv_sec = 60;
dbda8d6ce9 2007-07-21       drh:     delay.tv_usec = 0;
dbda8d6ce9 2007-07-21       drh:     FD_ZERO(&readfds);
dbda8d6ce9 2007-07-21       drh:     FD_SET( listener, &readfds);
dbda8d6ce9 2007-07-21       drh:     if( select( listener+1, &readfds, 0, 0, &delay) ){
dbda8d6ce9 2007-07-21       drh:       lenaddr = sizeof(inaddr);
e63a9fd9d0 2007-09-25       jnc:       connection = accept(listener, (struct sockaddr*)&inaddr, (socklen_t*) &lenaddr);
dbda8d6ce9 2007-07-21       drh:       if( connection>=0 ){
dbda8d6ce9 2007-07-21       drh:         child = fork();
dbda8d6ce9 2007-07-21       drh:         if( child!=0 ){
dbda8d6ce9 2007-07-21       drh:           if( child>0 ) nchildren++;
dbda8d6ce9 2007-07-21       drh:           close(connection);
dbda8d6ce9 2007-07-21       drh:         }else{
dbda8d6ce9 2007-07-21       drh:           close(0);
dbda8d6ce9 2007-07-21       drh:           dup(connection);
dbda8d6ce9 2007-07-21       drh:           close(1);
dbda8d6ce9 2007-07-21       drh:           dup(connection);
dbda8d6ce9 2007-07-21       drh:           if( !g.fHttpTrace ){
dbda8d6ce9 2007-07-21       drh:             close(2);
dbda8d6ce9 2007-07-21       drh:             dup(connection);
dbda8d6ce9 2007-07-21       drh:           }
dbda8d6ce9 2007-07-21       drh:           close(connection);
dbda8d6ce9 2007-07-21       drh:           return;
dbda8d6ce9 2007-07-21       drh:         }
dbda8d6ce9 2007-07-21       drh:       }
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:     /* Bury dead children */
dbda8d6ce9 2007-07-21       drh:     while( waitpid(0, 0, WNOHANG)>0 ){
dbda8d6ce9 2007-07-21       drh:       nchildren--;
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   /* NOT REACHED */
dbda8d6ce9 2007-07-21       drh:   exit(1);
83c876b447 2007-09-21 anonymous: #endif
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Name of days and months.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: static const char *azDays[] =
dbda8d6ce9 2007-07-21       drh:     {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 0};
dbda8d6ce9 2007-07-21       drh: static const char *azMonths[] =
dbda8d6ce9 2007-07-21       drh:     {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
dbda8d6ce9 2007-07-21       drh:      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0};
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Returns an RFC822-formatted time string suitable for HTTP headers, among
dbda8d6ce9 2007-07-21       drh: ** other things.
dbda8d6ce9 2007-07-21       drh: ** Returned timezone is always GMT as required by HTTP/1.1 specification.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** See http://www.faqs.org/rfcs/rfc822.html, section 5
dbda8d6ce9 2007-07-21       drh: ** and http://www.faqs.org/rfcs/rfc2616.html, section 3.3.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: char *cgi_rfc822_datestamp(time_t now){
dbda8d6ce9 2007-07-21       drh:   struct tm *pTm;
dbda8d6ce9 2007-07-21       drh:   pTm = gmtime(&now);
dbda8d6ce9 2007-07-21       drh:   if( pTm==0 ) return "";
dbda8d6ce9 2007-07-21       drh:   return mprintf("%s, %d %s %02d %02d:%02d:%02d GMT",
dbda8d6ce9 2007-07-21       drh:                  azDays[pTm->tm_wday], pTm->tm_mday, azMonths[pTm->tm_mon],
dbda8d6ce9 2007-07-21       drh:                  pTm->tm_year+1900, pTm->tm_hour, pTm->tm_min, pTm->tm_sec);
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Parse an RFC822-formatted timestamp as we'd expect from HTTP and return
dbda8d6ce9 2007-07-21       drh: ** a Unix epoch time. <= zero is returned on failure.
dbda8d6ce9 2007-07-21       drh: **
dbda8d6ce9 2007-07-21       drh: ** Note that this won't handle all the _allowed_ HTTP formats, just the
dbda8d6ce9 2007-07-21       drh: ** most popular one (the one generated by cgi_rfc822_datestamp(), actually).
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: time_t cgi_rfc822_parsedate(const char *zDate){
dbda8d6ce9 2007-07-21       drh:   struct tm t;
dbda8d6ce9 2007-07-21       drh:   char zIgnore[16];
dbda8d6ce9 2007-07-21       drh:   char zMonth[16];
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   memset(&t, 0, sizeof(t));
dbda8d6ce9 2007-07-21       drh:   if( 7==sscanf(zDate, "%12[A-Za-z,] %d %12[A-Za-z] %d %d:%d:%d", zIgnore,
dbda8d6ce9 2007-07-21       drh:                        &t.tm_mday, zMonth, &t.tm_year, &t.tm_hour, &t.tm_min,
dbda8d6ce9 2007-07-21       drh:                        &t.tm_sec)){
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:     if( t.tm_year > 1900 ) t.tm_year -= 1900;
dbda8d6ce9 2007-07-21       drh:     for(t.tm_mon=0; azMonths[t.tm_mon]; t.tm_mon++){
dbda8d6ce9 2007-07-21       drh:       if( !strncasecmp( azMonths[t.tm_mon], zMonth, 3 )){
dbda8d6ce9 2007-07-21       drh:         return mkgmtime(&t);
dbda8d6ce9 2007-07-21       drh:       }
dbda8d6ce9 2007-07-21       drh:     }
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh:   return 0;
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Convert a struct tm* that represents a moment in UTC into the number
dbda8d6ce9 2007-07-21       drh: ** of seconds in 1970, UTC.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: time_t mkgmtime(struct tm *p){
dbda8d6ce9 2007-07-21       drh:   time_t t;
dbda8d6ce9 2007-07-21       drh:   int nDay;
dbda8d6ce9 2007-07-21       drh:   int isLeapYr;
dbda8d6ce9 2007-07-21       drh:   /* Days in each month:       31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 */
dbda8d6ce9 2007-07-21       drh:   static int priorDays[]   = {  0, 31, 59, 90,120,151,181,212,243,273,304,334 };
dbda8d6ce9 2007-07-21       drh:   if( p->tm_mon<0 ){
dbda8d6ce9 2007-07-21       drh:     int nYear = (11 - p->tm_mon)/12;
dbda8d6ce9 2007-07-21       drh:     p->tm_year -= nYear;
dbda8d6ce9 2007-07-21       drh:     p->tm_mon += nYear*12;
dbda8d6ce9 2007-07-21       drh:   }else if( p->tm_mon>11 ){
dbda8d6ce9 2007-07-21       drh:     p->tm_year += p->tm_mon/12;
dbda8d6ce9 2007-07-21       drh:     p->tm_mon %= 12;
dbda8d6ce9 2007-07-21       drh:   }
dbda8d6ce9 2007-07-21       drh:   isLeapYr = p->tm_year%4==0 && (p->tm_year%100!=0 || (p->tm_year+300)%400==0);
dbda8d6ce9 2007-07-21       drh:   p->tm_yday = priorDays[p->tm_mon] + p->tm_mday - 1;
dbda8d6ce9 2007-07-21       drh:   if( isLeapYr && p->tm_mon>1 ) p->tm_yday++;
dbda8d6ce9 2007-07-21       drh:   nDay = (p->tm_year-70)*365 + (p->tm_year-69)/4 -p->tm_year/100 +
dbda8d6ce9 2007-07-21       drh:          (p->tm_year+300)/400 + p->tm_yday;
dbda8d6ce9 2007-07-21       drh:   t = ((nDay*24 + p->tm_hour)*60 + p->tm_min)*60 + p->tm_sec;
dbda8d6ce9 2007-07-21       drh:   return t;
dbda8d6ce9 2007-07-21       drh: }
dbda8d6ce9 2007-07-21       drh: 
dbda8d6ce9 2007-07-21       drh: /*
dbda8d6ce9 2007-07-21       drh: ** Check the objectTime against the If-Modified-Since request header. If the
dbda8d6ce9 2007-07-21       drh: ** object time isn't any newer than the header, we immediately send back
dbda8d6ce9 2007-07-21       drh: ** a 304 reply and exit.
dbda8d6ce9 2007-07-21       drh: */
dbda8d6ce9 2007-07-21       drh: void cgi_modified_since(time_t objectTime){
dbda8d6ce9 2007-07-21       drh:   const char *zIf = P("HTTP_IF_MODIFIED_SINCE");
dbda8d6ce9 2007-07-21       drh:   if( zIf==0 ) return;
dbda8d6ce9 2007-07-21       drh:   if( objectTime > cgi_rfc822_parsedate(zIf) ) return;
dbda8d6ce9 2007-07-21       drh:   cgi_set_status(304,"Not Modified");
dbda8d6ce9 2007-07-21       drh:   cgi_reset_content();
dbda8d6ce9 2007-07-21       drh:   cgi_reply();
dbda8d6ce9 2007-07-21       drh:   exit(0);
dbda8d6ce9 2007-07-21       drh: }