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" dbda8d6ce9 2007-07-21 drh: #include <sys/socket.h> dbda8d6ce9 2007-07-21 drh: #include <netinet/in.h> dbda8d6ce9 2007-07-21 drh: #include <arpa/inet.h> dbda8d6ce9 2007-07-21 drh: #include <time.h> dbda8d6ce9 2007-07-21 drh: #include <sys/times.h> dbda8d6ce9 2007-07-21 drh: #include <sys/time.h> dbda8d6ce9 2007-07-21 drh: #include <sys/wait.h> dbda8d6ce9 2007-07-21 drh: #include <stdio.h> dbda8d6ce9 2007-07-21 drh: #include <stdlib.h> dbda8d6ce9 2007-07-21 drh: #include <sys/select.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: /* 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); 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: ){ dbda8d6ce9 2007-07-21 drh: if( zPath==0 ) zPath = "/"; 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=='/' ){ dbda8d6ce9 2007-07-21 drh: cgi_panic("invalid redirect URL: %s", zURL); dbda8d6ce9 2007-07-21 drh: } dbda8d6ce9 2007-07-21 drh: zLocation = mprintf("Location: %s/%s\r\n", g.zBaseURL, zURL); 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); 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: */ dbda8d6ce9 2007-07-21 drh: static 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)); 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> </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> </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> </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> </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> </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> </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]); dbda8d6ce9 2007-07-21 drh: if( getpeername(fileno(stdin), (struct sockaddr*)&remoteName, &size)>=0 ){ dbda8d6ce9 2007-07-21 drh: cgi_setenv("REMOTE_ADDR", inet_ntoa(remoteName.sin_addr)); 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){ 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); dbda8d6ce9 2007-07-21 drh: connection = accept(listener, (struct sockaddr*)&inaddr, &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); 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: }