/* ** Copyright (c) 2006 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file began as a set of C functions and procedures used intepret ** CGI environment variables for Fossil web pages that were invoked by ** CGI. That's where the file name comes from. But over the years it ** has grown to incorporate lots of related functionality, including: ** ** * Interpreting CGI environment variables when Fossil is run as ** CGI (the original purpose). ** ** * Interpreting HTTP requests received directly or via an SSH tunnel. ** ** * Interpreting SCGI requests ** ** * Generating appropriate replies to CGI, SCGI, and HTTP requests. ** ** * Listening for incoming HTTP requests and dispatching them. ** (Used by "fossil ui" and "fossil server", for example). ** ** So, even though the name of this file implies that it only deals with ** CGI, in fact, the code in this file is used to interpret webpage requests ** received by a variety of means, and to generate well-formatted replies ** to those requests. ** ** The code in this file abstracts the web-request so that downstream ** modules that generate the body of the reply (based on the requested page) ** do not need to know if the request is coming from CGI, direct HTTP, ** SCGI, or some other means. ** ** This module gathers information about web page request into a key/value ** store. Keys and values come from: ** ** * Query parameters ** * POST parameter ** * Cookies ** * Environment variables ** ** The parameters are accessed using cgi_parameter() and similar functions ** or their convenience macros P() and similar. ** ** Environment variable parameters are set as if the request were coming ** in over CGI even if the request arrived via SCGI or direct HTTP. Thus ** the downstream modules that are trying to interpret the request do not ** need to know the request protocol - they can just request the values ** of environment variables and everything will always work. ** ** This file contains routines used by Fossil when it is acting as a ** CGI client. For the code used by Fossil when it is acting as a ** CGI server (for the /ext webpage) see the "extcgi.c" source file. */ #include "config.h" #ifdef _WIN32 # if !defined(_WIN32_WINNT) # define _WIN32_WINNT 0x0501 # endif # include # include #else # include # include # include # include # include # include # include #endif #ifdef __EMX__ typedef int socklen_t; #endif #include #include #include #include #include #include "cgi.h" #include "cygsup.h" #if INTERFACE /* ** Shortcuts for cgi_parameter. P("x") returns the value of query parameter ** or cookie "x", or NULL if there is no such parameter or cookie. PD("x","y") ** does the same except "y" is returned in place of NULL if there is not match. */ #define P(x) cgi_parameter((x),0) #define PD(x,y) cgi_parameter((x),(y)) #define PT(x) cgi_parameter_trimmed((x),0) #define PDT(x,y) cgi_parameter_trimmed((x),(y)) #define PB(x) cgi_parameter_boolean(x) #define PCK(x) cgi_parameter_checked(x,1) #define PIF(x,y) cgi_parameter_checked(x,y) /* ** Shortcut for the cgi_printf() routine. Instead of using the ** ** @ ... ** ** notation provided by the translate.c utility, you can also ** optionally use: ** ** CX(...) */ #define CX cgi_printf /* ** Destinations for output text. */ #define CGI_HEADER 0 #define CGI_BODY 1 /* ** Flags for SSH HTTP clients */ #define CGI_SSH_CLIENT 0x0001 /* Client is SSH */ #define CGI_SSH_COMPAT 0x0002 /* Compat for old SSH transport */ #define CGI_SSH_FOSSIL 0x0004 /* Use new Fossil SSH transport */ #endif /* INTERFACE */ /* ** The reply content is generated in two pieces: the header and the body. ** These pieces are generated separately because they are not necessarily ** produced in order. Parts of the header might be built after all or ** part of the body. The header and body are accumulated in separate ** Blob structures then output sequentially once everything has been ** built. ** ** Do not confuse the content header with the HTTP header. The content header ** is generated by downstream code. The HTTP header is generated by the ** cgi_reply() routine below. ** ** The content header and contenty body are *approximately* the ** element and the elements for HTML replies. However this is only ** approximate. The content header also includes parts of that ** show the banner and menu bar at the top of each page. Also note that ** not all replies are HTML, but there can still be separate header and ** body sections of the content. ** ** The cgi_destination() interface switches between the buffers. */ static Blob cgiContent[2] = { BLOB_INITIALIZER, BLOB_INITIALIZER }; static Blob *pContent = &cgiContent[0]; /* ** Set the destination buffer into which to accumulate CGI content. */ void cgi_destination(int dest){ switch( dest ){ case CGI_HEADER: { pContent = &cgiContent[0]; break; } case CGI_BODY: { pContent = &cgiContent[1]; break; } default: { cgi_panic("bad destination"); } } } /* ** Check to see if the content header or body contains the zNeedle string. ** Return true if it does and false if it does not. */ int cgi_header_contains(const char *zNeedle){ return strstr(blob_str(&cgiContent[0]), zNeedle)!=0; } int cgi_body_contains(const char *zNeedle){ return strstr(blob_str(&cgiContent[1]), zNeedle)!=0; } /* ** Append new reply content to what already exists. */ void cgi_append_content(const char *zData, int nAmt){ blob_append(pContent, zData, nAmt); } /* ** Reset both reply content buffers to be empty. */ void cgi_reset_content(void){ blob_reset(&cgiContent[0]); blob_reset(&cgiContent[1]); } /* ** Return a pointer to Blob that is currently accumulating reply content. */ Blob *cgi_output_blob(void){ return pContent; } /* ** Return the content header as a text string */ const char *cgi_header(void){ return blob_str(&cgiContent[0]); } /* ** Combine the header and body content all into the header buffer. ** In other words, append the body content to the end of the header ** content. */ static void cgi_combine_header_and_body(void){ int size = blob_size(&cgiContent[1]); if( size>0 ){ blob_append(&cgiContent[0], blob_buffer(&cgiContent[1]), size); blob_reset(&cgiContent[1]); } } /* ** Return a pointer to the combined header+body content. */ char *cgi_extract_content(void){ cgi_combine_header_and_body(); return blob_buffer(&cgiContent[0]); } /* ** Additional information used to form the HTTP reply */ static const char *zContentType = "text/html"; /* Content type of the reply */ static const char *zReplyStatus = "OK"; /* Reply status description */ static int iReplyStatus = 200; /* Reply status code */ static Blob extraHeader = BLOB_INITIALIZER; /* Extra header text */ static int rangeStart = 0; /* Start of Range: */ static int rangeEnd = 0; /* End of Range: plus 1 */ /* ** Set the reply content type. ** ** The reply content type defaults to "text/html". It only needs to be ** changed (by calling this routine) in the exceptional case where some ** other content type is being returned. */ void cgi_set_content_type(const char *zType){ zContentType = fossil_strdup(zType); } /* ** Erase any existing reply content. Replace is with a pNewContent. ** ** This routine erases pNewContent. In other words, it move pNewContent ** into the content buffer. */ void cgi_set_content(Blob *pNewContent){ cgi_reset_content(); cgi_destination(CGI_HEADER); cgiContent[0] = *pNewContent; blob_zero(pNewContent); } /* ** Set the reply status code */ void cgi_set_status(int iStat, const char *zStat){ zReplyStatus = fossil_strdup(zStat); iReplyStatus = iStat; } /* ** Append text to the content header buffer. */ void cgi_append_header(const char *zLine){ blob_append(&extraHeader, zLine, -1); } void cgi_printf_header(const char *zLine, ...){ va_list ap; va_start(ap, zLine); blob_vappendf(&extraHeader, zLine, ap); va_end(ap); } /* ** Set a cookie by queuing up the appropriate HTTP header output. If ** !g.isHTTP, this is a no-op. ** ** Zero lifetime implies a session cookie. A negative one expires ** the cookie immediately. */ void cgi_set_cookie( const char *zName, /* Name of the cookie */ const char *zValue, /* Value of the cookie. Automatically escaped */ const char *zPath, /* Path cookie applies to. NULL means "/" */ int lifetime /* Expiration of the cookie in seconds from now */ ){ char const *zSecure = ""; if(!g.isHTTP) return /* e.g. JSON CLI mode, where g.zTop is not set */; else if( zPath==0 ){ zPath = g.zTop; if( zPath[0]==0 ) zPath = "/"; } if( g.zBaseURL!=0 && strncmp(g.zBaseURL, "https:", 6)==0 ){ zSecure = " secure;"; } if( lifetime!=0 ){ blob_appendf(&extraHeader, "Set-Cookie: %s=%t; Path=%s; max-age=%d; HttpOnly; " "%s Version=1\r\n", zName, lifetime>0 ? zValue : "null", zPath, lifetime, zSecure); }else{ blob_appendf(&extraHeader, "Set-Cookie: %s=%t; Path=%s; HttpOnly; " "%s Version=1\r\n", zName, zValue, zPath, zSecure); } } /* ** Return true if the response should be sent with Content-Encoding: gzip. */ static int is_gzippable(void){ if( g.fNoHttpCompress ) return 0; if( strstr(PD("HTTP_ACCEPT_ENCODING", ""), "gzip")==0 ) return 0; return strncmp(zContentType, "text/", 5)==0 || sqlite3_strglob("application/*xml", zContentType)==0 || sqlite3_strglob("application/*javascript", zContentType)==0; } /* ** The following routines read or write content from/to the wire for ** an HTTP request. Depending on settings the content might be coming ** from or going to a socket, or a file, or it might come from or go ** to an SSL decoder/encoder. */ /* ** Works like fgets(): ** ** Read a single line of input into s[]. Ensure that s[] is zero-terminated. ** The s[] buffer is size bytes and so at most size-1 bytes will be read. ** ** Return a pointer to s[] on success, or NULL at end-of-input. */ static char *cgi_fgets(char *s, int size){ if( !g.httpUseSSL ){ return fgets(s, size, g.httpIn); } #ifdef FOSSIL_ENABLE_SSL return ssl_gets(g.httpSSLConn, s, size); #else fossil_fatal("SSL not available"); #endif } /* Works like fread(): ** ** Read as many as bytes of content as we can, up to a maximum of nmemb ** bytes. Return the number of bytes read. Return 0 if there is no ** further input or if an I/O error occurs. */ size_t cgi_fread(void *ptr, size_t nmemb){ if( !g.httpUseSSL ){ return fread(ptr, 1, nmemb, g.httpIn); } #ifdef FOSSIL_ENABLE_SSL return ssl_read_server(g.httpSSLConn, ptr, nmemb, 1); #else fossil_fatal("SSL not available"); #endif } /* Works like feof(): ** ** Return true if end-of-input has been reached. */ int cgi_feof(void){ if( !g.httpUseSSL ){ return feof(g.httpIn); } #ifdef FOSSIL_ENABLE_SSL return ssl_eof(g.httpSSLConn); #else return 1; #endif } /* Works like fwrite(): ** ** Try to output nmemb bytes of content. Return the number of ** bytes actually written. */ static size_t cgi_fwrite(void *ptr, size_t nmemb){ if( !g.httpUseSSL ){ return fwrite(ptr, 1, nmemb, g.httpOut); } #ifdef FOSSIL_ENABLE_SSL return ssl_write_server(g.httpSSLConn, ptr, nmemb); #else fossil_fatal("SSL not available"); #endif } /* Works like fflush(): ** ** Make sure I/O has completed. */ static void cgi_fflush(void){ if( !g.httpUseSSL ){ fflush(g.httpOut); } } /* ** Generate the reply to a web request. The output might be an ** full HTTP response, or a CGI response, depending on how things have ** be set up. ** ** The reply consists of a response header (an HTTP or CGI response header) ** followed by the concatenation of the content header and content body. */ void cgi_reply(void){ Blob hdr = BLOB_INITIALIZER; int total_size; if( iReplyStatus<=0 ){ iReplyStatus = 200; zReplyStatus = "OK"; } if( g.fullHttpReply ){ if( rangeEnd>0 && iReplyStatus==200 && fossil_strcmp(P("REQUEST_METHOD"),"GET")==0 ){ iReplyStatus = 206; zReplyStatus = "Partial Content"; } blob_appendf(&hdr, "HTTP/1.0 %d %s\r\n", iReplyStatus, zReplyStatus); blob_appendf(&hdr, "Date: %s\r\n", cgi_rfc822_datestamp(time(0))); blob_appendf(&hdr, "Connection: close\r\n"); blob_appendf(&hdr, "X-UA-Compatible: IE=edge\r\n"); }else{ assert( rangeEnd==0 ); blob_appendf(&hdr, "Status: %d %s\r\n", iReplyStatus, zReplyStatus); } if( etag_tag()[0]!=0 ){ blob_appendf(&hdr, "ETag: %s\r\n", etag_tag()); blob_appendf(&hdr, "Cache-Control: max-age=%d\r\n", etag_maxage()); if( etag_mtime()>0 ){ blob_appendf(&hdr, "Last-Modified: %s\r\n", cgi_rfc822_datestamp(etag_mtime())); } }else if( g.isConst ){ /* isConst means that the reply is guaranteed to be invariant, even ** after configuration changes and/or Fossil binary recompiles. */ blob_appendf(&hdr, "Cache-Control: max-age=315360000, immutable\r\n"); }else{ blob_appendf(&hdr, "Cache-control: no-cache\r\n"); } if( blob_size(&extraHeader)>0 ){ blob_appendf(&hdr, "%s", blob_buffer(&extraHeader)); } /* Add headers to turn on useful security options in browsers. */ blob_appendf(&hdr, "X-Frame-Options: SAMEORIGIN\r\n"); /* This stops fossil pages appearing in frames or iframes, preventing ** click-jacking attacks on supporting browsers. ** ** Other good headers would be ** Strict-Transport-Security: max-age=62208000 ** if we're using https. However, this would break sites which serve different ** content on http and https protocols. Also, ** X-Content-Security-Policy: allow 'self' ** would help mitigate some XSS and data injection attacks, but will break ** deliberate inclusion of external resources, such as JavaScript syntax ** highlighter scripts. ** ** These headers are probably best added by the web server hosting fossil as ** a CGI script. */ /* Content intended for logged in users should only be cached in ** the browser, not some shared location. */ if( iReplyStatus!=304 ) { blob_appendf(&hdr, "Content-Type: %s; charset=utf-8\r\n", zContentType); if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){ cgi_combine_header_and_body(); blob_compress(&cgiContent[0], &cgiContent[0]); } if( is_gzippable() && iReplyStatus!=206 ){ int i; gzip_begin(0); for( i=0; i<2; i++ ){ int size = blob_size(&cgiContent[i]); if( size>0 ) gzip_step(blob_buffer(&cgiContent[i]), size); blob_reset(&cgiContent[i]); } gzip_finish(&cgiContent[0]); blob_appendf(&hdr, "Content-Encoding: gzip\r\n"); blob_appendf(&hdr, "Vary: Accept-Encoding\r\n"); } total_size = blob_size(&cgiContent[0]) + blob_size(&cgiContent[1]); if( iReplyStatus==206 ){ blob_appendf(&hdr, "Content-Range: bytes %d-%d/%d\r\n", rangeStart, rangeEnd-1, total_size); total_size = rangeEnd - rangeStart; } blob_appendf(&hdr, "Content-Length: %d\r\n", total_size); }else{ total_size = 0; } blob_appendf(&hdr, "\r\n"); cgi_fwrite(blob_buffer(&hdr), blob_size(&hdr)); blob_reset(&hdr); if( total_size>0 && iReplyStatus!=304 && fossil_strcmp(P("REQUEST_METHOD"),"HEAD")!=0 ){ int i, size; for(i=0; i<2; i++){ size = blob_size(&cgiContent[i]); if( size<=rangeStart ){ rangeStart -= size; }else{ int n = size - rangeStart; if( n>total_size ){ n = total_size; } cgi_fwrite(blob_buffer(&cgiContent[i])+rangeStart, n); rangeStart = 0; total_size -= n; } } } cgi_fflush(); CGIDEBUG(("-------- END cgi ---------\n")); /* After the webpage has been sent, do any useful background ** processing. */ g.cgiOutput = 2; if( g.db!=0 && iReplyStatus==200 ){ backoffice_check_if_needed(); } } /* ** Generate an HTTP or CGI redirect response that causes a redirect ** to the URL given in the argument. ** ** The URL must be relative to the base of the fossil server. */ NORETURN void cgi_redirect_with_status( const char *zURL, int iStat, const char *zStat ){ char *zLocation; CGIDEBUG(("redirect to %s\n", zURL)); if( strncmp(zURL,"http:",5)==0 || strncmp(zURL,"https:",6)==0 ){ zLocation = mprintf("Location: %s\r\n", zURL); }else if( *zURL=='/' ){ int n1 = (int)strlen(g.zBaseURL); int n2 = (int)strlen(g.zTop); if( g.zBaseURL[n1-1]=='/' ) zURL++; zLocation = mprintf("Location: %.*s%s\r\n", n1-n2, g.zBaseURL, zURL); }else{ zLocation = mprintf("Location: %s/%s\r\n", g.zBaseURL, zURL); } cgi_append_header(zLocation); cgi_reset_content(); cgi_printf("\n

Redirect to %h

\n\n", zLocation); cgi_set_status(iStat, zStat); free(zLocation); cgi_reply(); fossil_exit(0); } NORETURN void cgi_redirect(const char *zURL){ cgi_redirect_with_status(zURL, 302, "Moved Temporarily"); } NORETURN void cgi_redirect_with_method(const char *zURL){ cgi_redirect_with_status(zURL, 307, "Temporary Redirect"); } NORETURN void cgi_redirectf(const char *zFormat, ...){ va_list ap; va_start(ap, zFormat); cgi_redirect(vmprintf(zFormat, ap)); va_end(ap); } /* ** Add a "Content-disposition: attachment; filename=%s" header to the reply. */ void cgi_content_disposition_filename(const char *zFilename){ char *z; int i, n; /* 0123456789 123456789 123456789 123456789 123456*/ z = mprintf("Content-Disposition: attachment; filename=\"%s\";\r\n", file_tail(zFilename)); n = (int)strlen(z); for(i=43; i1000 ){ /* Prevent a DOS service attack against the framework */ fossil_fatal("Too many query parameters"); } aParamQP = fossil_realloc( aParamQP, nAllocQP*sizeof(aParamQP[0]) ); } aParamQP[nUsedQP].zName = zName; aParamQP[nUsedQP].zValue = zValue; if( g.fHttpTrace ){ fprintf(stderr, "# cgi: %s = [%s]\n", zName, zValue); } aParamQP[nUsedQP].seq = seqQP++; aParamQP[nUsedQP].isQP = isQP; aParamQP[nUsedQP].cTag = 0; nUsedQP++; sortQP = 1; } /* ** Add another query parameter or cookie to the parameter set. ** zName is the name of the query parameter or cookie and zValue ** is its fully decoded value. zName will be modified to be an ** all lowercase string. ** ** zName and zValue are not copied and must not change or be ** deallocated after this routine returns. This routine changes ** all ASCII alphabetic characters in zName to lower case. The ** caller must not change them back. */ void cgi_set_parameter_nocopy_tolower( char *zName, const char *zValue, int isQP ){ int i; for(i=0; zName[i]; i++){ zName[i] = fossil_tolower(zName[i]); } cgi_set_parameter_nocopy(zName, zValue, isQP); } /* ** Add another query parameter or cookie to the parameter set. ** zName is the name of the query parameter or cookie and zValue ** is its fully decoded value. ** ** Copies are made of both the zName and zValue parameters. */ void cgi_set_parameter(const char *zName, const char *zValue){ cgi_set_parameter_nocopy(fossil_strdup(zName),fossil_strdup(zValue), 0); } void cgi_set_query_parameter(const char *zName, const char *zValue){ cgi_set_parameter_nocopy(fossil_strdup(zName),fossil_strdup(zValue), 1); } /* ** Replace a parameter with a new value. */ void cgi_replace_parameter(const char *zName, const char *zValue){ int i; for(i=0; i0 && z[i-1]=='\r' ){ z[i-1] = 0; }else{ z[i] = 0; } i++; break; } } *pz = &z[i]; *pLen -= i; return z; } /* ** The input *pz points to content that is terminated by a "\r\n" ** followed by the boundary marker zBoundary. An extra "--" may or ** may not be appended to the boundary marker. There are *pLen characters ** in *pz. ** ** This routine adds a "\000" to the end of the content (overwriting ** the "\r\n") and returns a pointer to the content. The *pz input ** is adjusted to point to the first line following the boundary. ** The length of the content is stored in *pnContent. */ static char *get_bounded_content( char **pz, /* Content taken from here */ int *pLen, /* Number of bytes of data in (*pz)[] */ char *zBoundary, /* Boundary text marking the end of content */ int *pnContent /* Write the size of the content here */ ){ char *z = *pz; int len = *pLen; int i; int nBoundary = strlen(zBoundary); *pnContent = len; for(i=0; i0 && z[i-1]=='\r' ) i--; z[i] = 0; *pnContent = i; i += nBoundary; break; } } *pz = &z[i]; get_line_from_string(pz, pLen); return z; } /* ** Tokenize a line of text into as many as nArg tokens. Make ** azArg[] point to the start of each token. ** ** Tokens consist of space or semi-colon delimited words or ** strings inside double-quotes. Example: ** ** content-disposition: form-data; name="fn"; filename="index.html" ** ** The line above is tokenized as follows: ** ** azArg[0] = "content-disposition:" ** azArg[1] = "form-data" ** azArg[2] = "name=" ** azArg[3] = "fn" ** azArg[4] = "filename=" ** azArg[5] = "index.html" ** azArg[6] = 0; ** ** '\000' characters are inserted in z[] at the end of each token. ** This routine returns the total number of tokens on the line, 6 ** in the example above. */ static int tokenize_line(char *z, int mxArg, char **azArg){ int i = 0; while( *z ){ while( fossil_isspace(*z) || *z==';' ){ z++; } if( *z=='"' && z[1] ){ *z = 0; z++; if( ii && zScriptName[i]!=0 ){ /* If SCRIPT_NAME is not a prefix of REQUEST_URI, truncate it so ** that it is. See https://fossil-scm.org/forum/forumpost/049e8650ed */ char *zNew = fossil_strndup(zScriptName, i); cgi_replace_parameter("SCRIPT_NAME", zNew); } } #ifdef FOSSIL_ENABLE_JSON if(noJson==0 && json_request_is_json_api(zPathInfo)){ /* We need to change some following behaviour depending on whether ** we are operating in JSON mode or not. We cannot, however, be ** certain whether we should/need to be in JSON mode until the ** PATH_INFO is set up. */ g.json.isJsonMode = 1; json_bootstrap_early(); }else{ assert(!g.json.isJsonMode && "Internal misconfiguration of g.json.isJsonMode"); } #endif z = (char*)P("HTTP_COOKIE"); if( z ){ z = fossil_strdup(z); add_param_list(z, ';'); z = (char*)cookie_value("skin",0); if(z){ skin_use_alternative(z, 2); } } cgi_setup_query_string(); z = (char*)P("REMOTE_ADDR"); if( z ){ g.zIpAddr = fossil_strdup(z); } len = atoi(PD("CONTENT_LENGTH", "0")); zType = P("CONTENT_TYPE"); zSemi = zType ? strchr(zType, ';') : 0; if( zSemi ){ g.zContentType = fossil_strndup(zType, (int)(zSemi-zType)); zType = g.zContentType; }else{ g.zContentType = zType; } blob_zero(&g.cgiIn); if( len>0 && zType ){ if( blob_read_from_cgi(&g.cgiIn, len)zName, pB->zName); if( c==0 ){ c = pA->seq - pB->seq; } return c; } /* ** Return the value of a query parameter or cookie whose name is zName. ** If there is no query parameter or cookie named zName and the first ** character of zName is uppercase, then check to see if there is an ** environment variable by that name and return it if there is. As ** a last resort when nothing else matches, return zDefault. */ const char *cgi_parameter(const char *zName, const char *zDefault){ int lo, hi, mid, c; /* The sortQP flag is set whenever a new query parameter is inserted. ** It indicates that we need to resort the query parameters. */ if( sortQP ){ int i, j; qsort(aParamQP, nUsedQP, sizeof(aParamQP[0]), qparam_compare); sortQP = 0; /* After sorting, remove duplicate parameters. The secondary sort ** key is aParamQP[].seq and we keep the first entry. That means ** with duplicate calls to cgi_set_parameter() the second and ** subsequent calls are effectively no-ops. */ for(i=j=1; i0 ){ hi = mid-1; }else{ lo = mid+1; } } /* If no match is found and the name begins with an upper-case ** letter, then check to see if there is an environment variable ** with the given name. */ if( fossil_isupper(zName[0]) ){ const char *zValue = fossil_getenv(zName); if( zValue ){ cgi_set_parameter_nocopy(zName, zValue, 0); CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue)); return zValue; } } CGIDEBUG(("no-match [%s]\n", zName)); return zDefault; } /* ** Return the value of the first defined query parameter or cookie whose ** name appears in the list of arguments. Or if no parameter is found, ** return NULL. */ const char *cgi_coalesce(const char *zName, ...){ va_list ap; const char *z; const char *zX; if( zName==0 ) return 0; z = cgi_parameter(zName, 0); va_start(ap, zName); while( z==0 && (zX = va_arg(ap,const char*))!=0 ){ z = cgi_parameter(zX, 0); } va_end(ap); return z; } /* ** Return the value of a CGI parameter with leading and trailing ** spaces removed and with internal \r\n changed to just \n */ char *cgi_parameter_trimmed(const char *zName, const char *zDefault){ const char *zIn; char *zOut, c; int i, j; zIn = cgi_parameter(zName, 0); if( zIn==0 ) zIn = zDefault; if( zIn==0 ) return 0; while( fossil_isspace(zIn[0]) ) zIn++; zOut = fossil_strdup(zIn); for(i=j=0; (c = zOut[i])!=0; i++){ if( c=='\r' && zOut[i+1]=='\n' ) continue; zOut[j++] = c; } zOut[j] = 0; while( j>0 && fossil_isspace(zOut[j-1]) ) zOut[--j] = 0; return zOut; } /* ** Return true if the CGI parameter zName exists and is not equal to 0, ** or "no" or "off". */ int cgi_parameter_boolean(const char *zName){ const char *zIn = cgi_parameter(zName, 0); if( zIn==0 ) return 0; return zIn[0]==0 || is_truth(zIn); } /* ** Return either an empty string "" or the string "checked" depending ** on whether or not parameter zName has value iValue. If parameter ** zName does not exist, that is assumed to be the same as value 0. ** ** This routine implements the PCK(x) and PIF(x,y) macros. The PIF(x,y) ** macro generateds " checked" if the value of parameter x equals integer y. ** PCK(x) is the same as PIF(x,1). These macros are used to generate ** the "checked" attribute on checkbox and radio controls of forms. */ const char *cgi_parameter_checked(const char *zName, int iValue){ const char *zIn = cgi_parameter(zName,0); int x; if( zIn==0 ){ x = 0; }else if( !fossil_isdigit(zIn[0]) ){ x = is_truth(zIn); }else{ x = atoi(zIn); } return x==iValue ? "checked" : ""; } /* ** Return the name of the i-th CGI parameter. Return NULL if there ** are fewer than i registered CGI parameters. */ const char *cgi_parameter_name(int i){ if( i>=0 && i\n", zName, aParamQP[i].zValue); break; } case 1: { fossil_trace("%s = %s\n", zName, aParamQP[i].zValue); break; } case 2: { cgi_debug("%s = %s\n", zName, aParamQP[i].zValue); break; } } } } /* ** Put information about the N-th parameter into arguments. ** Return non-zero on success, and return 0 if there is no N-th parameter. */ int cgi_param_info( int N, const char **pzName, const char **pzValue, int *pbIsQP ){ if( N>=0 && N