/* ** 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) #define P_NoBot(x) cgi_parameter_nosql((x),0) #define PD_NoBot(x,y) cgi_parameter_nosql((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 && fossil_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; /* Maintenance note: this oddball structure is intended to make ** adding new mimetypes to this list less of a performance hit than ** doing a strcmp/glob over a growing set of compressible types. */ switch(zContentType ? *zContentType : 0){ case (int)'a': if(0==fossil_strncmp("application/",zContentType,12)){ const char * z = &zContentType[12]; switch(*z){ case (int)'j': return fossil_strcmp("javascript", z)==0 || fossil_strcmp("json", z)==0; case (int)'w': return fossil_strcmp("wasm", z)==0; case (int)'x': return fossil_strcmp("x-tcl", z)==0 || fossil_strcmp("x-tar", z)==0; default: return sqlite3_strglob("*xml", z)==0; } } break; case (int)'i': return fossil_strcmp(zContentType, "image/svg+xml")==0 || fossil_strcmp(zContentType, "image/vnd.microsoft.icon")==0; case (int)'t': return fossil_strncmp(zContentType, "text/", 5)==0; } return 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); } } /* ** Given a Content-Type value, returns a string suitable for appending ** to the Content-Type header for adding (or not) the "; charset=..." ** part. It returns an empty string for most types or if zContentType ** is NULL. ** ** See forum post f60dece061c364d1 for the discussions which lead to ** this. Previously we always appended the charset, but WASM loaders ** are pedantic and refuse to load any responses which have a ** charset. Also, adding a charset is not strictly appropriate for ** most types (and not required for many others which may ostensibly ** benefit from one, as detailed in that forum post). */ static const char * content_type_charset(const char *zContentType){ if(0==fossil_strncmp(zContentType,"text/",5)){ return "; charset=utf-8"; } return ""; } /* ** 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%s\r\n", zContentType, content_type_charset(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( fossil_strncmp(zURL,"http:",5)==0 || fossil_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; } /* ** Renders the "begone, spider" page and exits. */ static void cgi_begone_spider(void){ Blob content = empty_blob; cgi_set_content(&content); style_set_current_feature("test"); style_header("Malicious Query Detected"); @

Begone, Fiend!

@

This page was generated because Fossil believes it has @ detected an SQL injection attack. If you believe you are seeing @ this in error, contact the developers on the Fossil-SCM Forum. Type @ "fossil-scm forum" into any search engine to locate the Fossil-SCM Forum. style_finish_page(); cgi_set_status(404,"Robot Attack Detected"); cgi_reply(); exit(0); } /* ** If looks_like_sql_injection() returns true for the given string, calls ** cgi_begone_spider() and does not return, else this function has no ** side effects. The range of checks performed by this function may ** be extended in the future. ** ** Checks are omitted for any logged-in user. ** ** This is NOT a defense against SQL injection. Fossil should easily be ** proof against SQL injection without this routine. Rather, this is an ** attempt to avoid denial-of-service caused by persistent spiders that hammer ** the server with dozens or hundreds of SQL injection attempts per second ** against pages (such as /vdiff) that are expensive to compute. In other ** words, this is an effort to reduce the CPU load imposed by malicious ** spiders. It is not an effect defense against SQL injection vulnerabilities. */ void cgi_value_spider_check(const char *zTxt){ if( g.zLogin==0 && looks_like_sql_injection(zTxt) ){ cgi_begone_spider(); } } /* ** A variant of cgi_parameter() with the same semantics except that if ** cgi_parameter(zName,zDefault) returns a value other than zDefault ** then it passes that value to cgi_value_spider_check(). */ const char *cgi_parameter_nosql(const char *zName, const char *zDefault){ const char *zTxt = cgi_parameter(zName, zDefault); if( zTxt!=zDefault ){ cgi_value_spider_check(zTxt); } return zTxt; } /* ** 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

Bad Request: %s

\n", zMsg ); cgi_reply(); fossil_exit(0); } /* ** Panic and die while processing a webpage. */ NORETURN void cgi_panic(const char *zFormat, ...){ va_list ap; cgi_reset_content(); #ifdef FOSSIL_ENABLE_JSON if( g.json.isJsonMode ){ char * zMsg; va_start(ap, zFormat); zMsg = vmprintf(zFormat,ap); va_end(ap); json_err( FSL_JSON_E_PANIC, zMsg, 1 ); free(zMsg); fossil_exit( g.isHTTP ? 0 : 1 ); }else #endif /* FOSSIL_ENABLE_JSON */ { cgi_set_status(500, "Internal Server Error"); cgi_printf( "

Internal Server Error

\n" "" ); va_start(ap, zFormat); vxprintf(pContent,zFormat,ap); va_end(ap); cgi_reply(); fossil_exit(1); } } /* z[] is the value of an X-FORWARDED-FOR: line in an HTTP header. ** Return a pointer to a string containing the real IP address, or a ** NULL pointer to stick with the IP address previously computed and ** loaded into g.zIpAddr. */ static const char *cgi_accept_forwarded_for(const char *z){ int i; if( !cgi_is_loopback(g.zIpAddr) ){ /* Only accept X-FORWARDED-FOR if input coming from the local machine */ return 0; } i = strlen(z)-1; while( i>=0 && z[i]!=',' && !fossil_isspace(z[i]) ) i--; return &z[++i]; } /* ** Remove the first space-delimited token from a string and return ** a pointer to it. Add a NULL to the string to terminate the token. ** Make *zLeftOver point to the start of the next token. */ static char *extract_token(char *zInput, char **zLeftOver){ char *zResult = 0; if( zInput==0 ){ if( zLeftOver ) *zLeftOver = 0; return 0; } while( fossil_isspace(*zInput) ){ zInput++; } zResult = zInput; while( *zInput && !fossil_isspace(*zInput) ){ zInput++; } if( *zInput ){ *zInput = 0; zInput++; while( fossil_isspace(*zInput) ){ zInput++; } } if( zLeftOver ){ *zLeftOver = zInput; } return zResult; } /* ** Determine the IP address on the other side of a connection. ** Return a pointer to a string. Or return 0 if unable. ** ** The string is held in a static buffer that is overwritten on ** each call. */ char *cgi_remote_ip(int fd){ #if 0 static char zIp[100]; struct sockaddr_in6 addr; socklen_t sz = sizeof(addr); if( getpeername(fd, &addr, &sz) ) return 0; zIp[0] = 0; if( inet_ntop(AF_INET6, &addr, zIp, sizeof(zIp))==0 ){ return 0; } return zIp; #else struct sockaddr_in remoteName; socklen_t size = sizeof(struct sockaddr_in); if( getpeername(fd, (struct sockaddr*)&remoteName, &size) ) return 0; return inet_ntoa(remoteName.sin_addr); #endif } /* ** This routine handles a single HTTP request which is coming in on ** g.httpIn and which replies on g.httpOut ** ** The HTTP request is read from g.httpIn and is used to initialize ** entries in the cgi_parameter() hash, as if those entries were ** environment variables. A call to cgi_init() completes ** the setup. Once all the setup is finished, this procedure returns ** and subsequent code handles the actual generation of the webpage. */ void cgi_handle_http_request(const char *zIpAddr){ char *z, *zToken; int i; const char *zScheme = "http"; char zLine[2000]; /* A single line of input. */ g.fullHttpReply = 1; if( cgi_fgets(zLine, sizeof(zLine))==0 ){ malformed_request("missing HTTP header"); } blob_append(&g.httpHeader, zLine, -1); cgi_trace(zLine); zToken = extract_token(zLine, &z); if( zToken==0 ){ malformed_request("malformed HTTP header"); } if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0 && fossil_strcmp(zToken,"HEAD")!=0 ){ malformed_request("unsupported HTTP method"); } cgi_setenv("GATEWAY_INTERFACE","CGI/1.0"); cgi_setenv("REQUEST_METHOD",zToken); zToken = extract_token(z, &z); if( zToken==0 ){ malformed_request("malformed URL in HTTP header"); } cgi_setenv("REQUEST_URI", zToken); cgi_setenv("SCRIPT_NAME", ""); for(i=0; zToken[i] && zToken[i]!='?'; i++){} if( zToken[i] ) zToken[i++] = 0; cgi_setenv("PATH_INFO", zToken); cgi_setenv("QUERY_STRING", &zToken[i]); if( zIpAddr==0 ){ zIpAddr = cgi_remote_ip(fileno(g.httpIn)); } if( zIpAddr ){ cgi_setenv("REMOTE_ADDR", zIpAddr); g.zIpAddr = fossil_strdup(zIpAddr); } /* Get all the optional fields that follow the first line. */ while( cgi_fgets(zLine,sizeof(zLine)) ){ char *zFieldName; char *zVal; cgi_trace(zLine); blob_append(&g.httpHeader, zLine, -1); zFieldName = extract_token(zLine,&zVal); if( zFieldName==0 || *zFieldName==0 ) break; while( fossil_isspace(*zVal) ){ zVal++; } i = strlen(zVal); while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; } zVal[i] = 0; for(i=0; zFieldName[i]; i++){ zFieldName[i] = fossil_tolower(zFieldName[i]); } if( fossil_strcmp(zFieldName,"accept-encoding:")==0 ){ cgi_setenv("HTTP_ACCEPT_ENCODING", zVal); }else if( fossil_strcmp(zFieldName,"content-length:")==0 ){ cgi_setenv("CONTENT_LENGTH", zVal); }else if( fossil_strcmp(zFieldName,"content-type:")==0 ){ cgi_setenv("CONTENT_TYPE", zVal); }else if( fossil_strcmp(zFieldName,"cookie:")==0 ){ cgi_setenv("HTTP_COOKIE", zVal); }else if( fossil_strcmp(zFieldName,"https:")==0 ){ cgi_setenv("HTTPS", zVal); zScheme = "https"; }else if( fossil_strcmp(zFieldName,"host:")==0 ){ char *z; cgi_setenv("HTTP_HOST", zVal); z = strchr(zVal, ':'); if( z ) z[0] = 0; cgi_setenv("SERVER_NAME", zVal); }else if( fossil_strcmp(zFieldName,"if-none-match:")==0 ){ cgi_setenv("HTTP_IF_NONE_MATCH", zVal); }else if( fossil_strcmp(zFieldName,"if-modified-since:")==0 ){ cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal); }else if( fossil_strcmp(zFieldName,"referer:")==0 ){ cgi_setenv("HTTP_REFERER", zVal); }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){ cgi_setenv("HTTP_USER_AGENT", zVal); }else if( fossil_strcmp(zFieldName,"authorization:")==0 ){ cgi_setenv("HTTP_AUTHORIZATION", zVal); }else if( fossil_strcmp(zFieldName,"x-forwarded-for:")==0 ){ const char *zIpAddr = cgi_accept_forwarded_for(zVal); if( zIpAddr!=0 ){ g.zIpAddr = fossil_strdup(zIpAddr); cgi_replace_parameter("REMOTE_ADDR", g.zIpAddr); } }else if( fossil_strcmp(zFieldName,"range:")==0 ){ int x1 = 0; int x2 = 0; if( sscanf(zVal,"bytes=%d-%d",&x1,&x2)==2 && x1>=0 && x1<=x2 ){ rangeStart = x1; rangeEnd = x2+1; } } } cgi_setenv("REQUEST_SCHEME",zScheme); cgi_init(); cgi_trace(0); } /* ** This routine handles a single HTTP request from an SSH client which is ** coming in on g.httpIn and which replies on g.httpOut ** ** Once all the setup is finished, this procedure returns ** and subsequent code handles the actual generation of the webpage. ** ** It is called in a loop so some variables will need to be replaced */ void cgi_handle_ssh_http_request(const char *zIpAddr){ static int nCycles = 0; static char *zCmd = 0; char *z, *zToken; const char *zType = 0; int i, content_length = 0; char zLine[2000]; /* A single line of input. */ assert( !g.httpUseSSL ); #ifdef FOSSIL_ENABLE_JSON if( nCycles==0 ){ json_bootstrap_early(); } #endif if( zIpAddr ){ if( nCycles==0 ){ cgi_setenv("REMOTE_ADDR", zIpAddr); g.zIpAddr = fossil_strdup(zIpAddr); } }else{ fossil_fatal("missing SSH IP address"); } if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ malformed_request("missing HTTP header"); } cgi_trace(zLine); zToken = extract_token(zLine, &z); if( zToken==0 ){ malformed_request("malformed HTTP header"); } if( fossil_strcmp(zToken, "echo")==0 ){ /* start looking for probes to complete transport_open */ zCmd = cgi_handle_ssh_probes(zLine, sizeof(zLine), z, zToken); if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ malformed_request("missing HTTP header"); } cgi_trace(zLine); zToken = extract_token(zLine, &z); if( zToken==0 ){ malformed_request("malformed HTTP header"); } }else if( zToken && strlen(zToken)==0 && zCmd ){ /* transport_flip request and continued transport_open */ cgi_handle_ssh_transport(zCmd); if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ malformed_request("missing HTTP header"); } cgi_trace(zLine); zToken = extract_token(zLine, &z); if( zToken==0 ){ malformed_request("malformed HTTP header"); } } if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0 && fossil_strcmp(zToken,"HEAD")!=0 ){ malformed_request("unsupported HTTP method"); } if( nCycles==0 ){ cgi_setenv("GATEWAY_INTERFACE","CGI/1.0"); cgi_setenv("REQUEST_METHOD",zToken); } zToken = extract_token(z, &z); if( zToken==0 ){ malformed_request("malformed URL in HTTP header"); } if( nCycles==0 ){ cgi_setenv("REQUEST_URI", zToken); cgi_setenv("SCRIPT_NAME", ""); } for(i=0; zToken[i] && zToken[i]!='?'; i++){} if( zToken[i] ) zToken[i++] = 0; if( nCycles==0 ){ cgi_setenv("PATH_INFO", zToken); }else{ cgi_replace_parameter("PATH_INFO", fossil_strdup(zToken)); } /* Get all the optional fields that follow the first line. */ while( fgets(zLine,sizeof(zLine),g.httpIn) ){ char *zFieldName; char *zVal; cgi_trace(zLine); zFieldName = extract_token(zLine,&zVal); if( zFieldName==0 || *zFieldName==0 ) break; while( fossil_isspace(*zVal) ){ zVal++; } i = strlen(zVal); while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; } zVal[i] = 0; for(i=0; zFieldName[i]; i++){ zFieldName[i] = fossil_tolower(zFieldName[i]); } if( fossil_strcmp(zFieldName,"content-length:")==0 ){ content_length = atoi(zVal); }else if( fossil_strcmp(zFieldName,"content-type:")==0 ){ g.zContentType = zType = fossil_strdup(zVal); }else if( fossil_strcmp(zFieldName,"host:")==0 ){ if( nCycles==0 ){ cgi_setenv("HTTP_HOST", zVal); } }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){ if( nCycles==0 ){ cgi_setenv("HTTP_USER_AGENT", zVal); } }else if( fossil_strcmp(zFieldName,"x-fossil-transport:")==0 ){ if( fossil_strnicmp(zVal, "ssh", 3)==0 ){ if( nCycles==0 ){ g.fSshClient |= CGI_SSH_FOSSIL; g.fullHttpReply = 0; } } } } if( nCycles==0 ){ if( ! ( g.fSshClient & CGI_SSH_FOSSIL ) ){ /* did not find new fossil ssh transport */ g.fSshClient &= ~CGI_SSH_CLIENT; g.fullHttpReply = 1; cgi_replace_parameter("REMOTE_ADDR", "127.0.0.1"); } } cgi_reset_content(); cgi_destination(CGI_BODY); if( content_length>0 && zType ){ blob_zero(&g.cgiIn); if( fossil_strcmp(zType, "application/x-fossil")==0 ){ blob_read_from_channel(&g.cgiIn, g.httpIn, content_length); blob_uncompress(&g.cgiIn, &g.cgiIn); }else if( fossil_strcmp(zType, "application/x-fossil-debug")==0 ){ blob_read_from_channel(&g.cgiIn, g.httpIn, content_length); }else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){ blob_read_from_channel(&g.cgiIn, g.httpIn, content_length); } } cgi_trace(0); nCycles++; } /* ** This routine handles the old fossil SSH probes */ char *cgi_handle_ssh_probes(char *zLine, int zSize, char *z, char *zToken){ /* Start looking for probes */ assert( !g.httpUseSSL ); while( fossil_strcmp(zToken, "echo")==0 ){ zToken = extract_token(z, &z); if( zToken==0 ){ malformed_request("malformed probe"); } if( fossil_strncmp(zToken, "test", 4)==0 || fossil_strncmp(zToken, "probe-", 6)==0 ){ fprintf(g.httpOut, "%s\n", zToken); fflush(g.httpOut); }else{ malformed_request("malformed probe"); } if( fgets(zLine, zSize, g.httpIn)==0 ){ malformed_request("malformed probe"); } cgi_trace(zLine); zToken = extract_token(zLine, &z); if( zToken==0 ){ malformed_request("malformed probe"); } } /* Got all probes now first transport_open is completed ** so return the command that was requested */ g.fSshClient |= CGI_SSH_COMPAT; return fossil_strdup(zToken); } /* ** This routine handles the old fossil SSH transport_flip ** and transport_open communications if detected. */ void cgi_handle_ssh_transport(const char *zCmd){ char *z, *zToken; char zLine[2000]; /* A single line of input. */ assert( !g.httpUseSSL ); /* look for second newline of transport_flip */ if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ malformed_request("incorrect transport_flip"); } cgi_trace(zLine); zToken = extract_token(zLine, &z); if( zToken && strlen(zToken)==0 ){ /* look for path to fossil */ if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ if( zCmd==0 ){ malformed_request("missing fossil command"); }else{ /* no new command so exit */ fossil_exit(0); } } cgi_trace(zLine); zToken = extract_token(zLine, &z); if( zToken==0 ){ malformed_request("malformed fossil command"); } /* see if we've seen the command */ if( zCmd && zCmd[0] && fossil_strcmp(zToken, zCmd)==0 ){ return; }else{ malformed_request("transport_open failed"); } }else{ malformed_request("transport_flip failed"); } } /* ** This routine handles a single SCGI request which is coming in on ** g.httpIn and which replies on g.httpOut ** ** The SCGI request is read from g.httpIn and is used to initialize ** entries in the cgi_parameter() hash, as if those entries were ** environment variables. A call to cgi_init() completes ** the setup. Once all the setup is finished, this procedure returns ** and subsequent code handles the actual generation of the webpage. */ void cgi_handle_scgi_request(void){ char *zHdr; char *zToFree; int nHdr = 0; int nRead; int c, n, m; assert( !g.httpUseSSL ); while( (c = fgetc(g.httpIn))!=EOF && fossil_isdigit((char)c) ){ nHdr = nHdr*10 + (char)c - '0'; } if( nHdr<16 ) malformed_request("SCGI header too short"); zToFree = zHdr = fossil_malloc(nHdr); nRead = (int)fread(zHdr, 1, nHdr, g.httpIn); if( nRead<nHdr ) malformed_request("cannot read entire SCGI header"); nHdr = nRead; while( nHdr ){ for(n=0; n<nHdr && zHdr[n]; n++){} for(m=n+1; m<nHdr && zHdr[m]; m++){} if( m>=nHdr ) malformed_request("SCGI header formatting error"); cgi_set_parameter(zHdr, zHdr+n+1); zHdr += m+1; nHdr -= m+1; } fossil_free(zToFree); fgetc(g.httpIn); /* Read past the "," separating header from content */ cgi_init(); } #if INTERFACE /* ** Bitmap values for the flags parameter to cgi_http_server(). */ #define HTTP_SERVER_LOCALHOST 0x0001 /* Bind to 127.0.0.1 only */ #define HTTP_SERVER_SCGI 0x0002 /* SCGI instead of HTTP */ #define HTTP_SERVER_HAD_REPOSITORY 0x0004 /* Was the repository open? */ #define HTTP_SERVER_HAD_CHECKOUT 0x0008 /* Was a checkout open? */ #define HTTP_SERVER_REPOLIST 0x0010 /* Allow repo listing */ #define HTTP_SERVER_NOFORK 0x0020 /* Do not call fork() */ #endif /* INTERFACE */ /* ** Maximum number of child processes that we can have running ** at one time. Set this to 0 for "no limit". */ #ifndef FOSSIL_MAX_CONNECTIONS # define FOSSIL_MAX_CONNECTIONS 1000 #endif /* ** Implement an HTTP server daemon listening on port iPort. ** ** As new connections arrive, fork a child and let child return ** out of this procedure call. The child will handle the request. ** The parent never returns from this procedure. ** ** Return 0 to each child as it runs. If unable to establish a ** listening socket, return non-zero. */ int cgi_http_server( int mnPort, int mxPort, /* Range of TCP ports to try */ const char *zBrowser, /* Run this browser, if not NULL */ const char *zIpAddr, /* Bind to this IP address, if not null */ int flags /* HTTP_SERVER_* flags */ ){ #if defined(_WIN32) /* Use win32_http_server() instead */ fossil_exit(1); #else int listener = -1; /* The server socket */ int connection; /* A socket for each individual connection */ int nRequest = 0; /* Number of requests handled so far */ fd_set readfds; /* Set of file descriptors for select() */ socklen_t lenaddr; /* Length of the inaddr structure */ int child; /* PID of the child process */ int nchildren = 0; /* Number of child processes */ struct timeval delay; /* How long to wait inside select() */ struct sockaddr_in inaddr; /* The socket address */ int opt = 1; /* setsockopt flag */ int iPort = mnPort; while( iPort<=mxPort ){ memset(&inaddr, 0, sizeof(inaddr)); inaddr.sin_family = AF_INET; if( zIpAddr ){ inaddr.sin_addr.s_addr = inet_addr(zIpAddr); if( inaddr.sin_addr.s_addr == (-1) ){ fossil_fatal("not a valid IP address: %s", zIpAddr); } }else if( flags & HTTP_SERVER_LOCALHOST ){ inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); }else{ inaddr.sin_addr.s_addr = htonl(INADDR_ANY); } inaddr.sin_port = htons(iPort); listener = socket(AF_INET, SOCK_STREAM, 0); if( listener<0 ){ iPort++; continue; } /* if we can't terminate nicely, at least allow the socket to be reused */ setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); if( bind(listener, (struct sockaddr*)&inaddr, sizeof(inaddr))<0 ){ close(listener); iPort++; continue; } break; } if( iPort>mxPort ){ if( mnPort==mxPort ){ fossil_fatal("unable to open listening socket on port %d", mnPort); }else{ fossil_fatal("unable to open listening socket on any" " port in the range %d..%d", mnPort, mxPort); } } if( iPort>mxPort ) return 1; listen(listener,10); fossil_print("Listening for %s requests on TCP port %d\n", (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" : g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP", iPort); fflush(stdout); if( zBrowser ){ assert( strstr(zBrowser,"%d")!=0 ); zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort); #if defined(__CYGWIN__) /* On Cygwin, we can do better than "echo" */ if( fossil_strncmp(zBrowser, "echo ", 5)==0 ){ wchar_t *wUrl = fossil_utf8_to_unicode(zBrowser+5); wUrl[wcslen(wUrl)-2] = 0; /* Strip terminating " &" */ if( (size_t)ShellExecuteW(0, L"open", wUrl, 0, 0, 1)<33 ){ fossil_warning("cannot start browser\n"); } }else #endif if( fossil_system(zBrowser)<0 ){ fossil_warning("cannot start browser: %s\n", zBrowser); } } while( 1 ){ #if FOSSIL_MAX_CONNECTIONS>0 while( nchildren>=FOSSIL_MAX_CONNECTIONS ){ if( wait(0)>=0 ) nchildren--; } #endif delay.tv_sec = 0; delay.tv_usec = 100000; FD_ZERO(&readfds); assert( listener>=0 ); FD_SET( listener, &readfds); select( listener+1, &readfds, 0, 0, &delay); if( FD_ISSET(listener, &readfds) ){ lenaddr = sizeof(inaddr); connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr); if( connection>=0 ){ if( flags & HTTP_SERVER_NOFORK ){ child = 0; }else{ child = fork(); } if( child!=0 ){ if( child>0 ){ nchildren++; nRequest++; } close(connection); }else{ int nErr = 0, fd; close(0); fd = dup(connection); if( fd!=0 ) nErr++; close(1); fd = dup(connection); if( fd!=1 ) nErr++; if( 0 && !g.fAnyTrace ){ close(2); fd = dup(connection); if( fd!=2 ) nErr++; } close(connection); g.nPendingRequest = nchildren+1; g.nRequest = nRequest+1; return nErr; } } } /* Bury dead children */ if( nchildren ){ while(1){ int iStatus = 0; pid_t x = waitpid(-1, &iStatus, WNOHANG); if( x<=0 ) break; if( WIFSIGNALED(iStatus) && g.fAnyTrace ){ fprintf(stderr, "/***** Child %d exited on signal %d (%s) *****/\n", x, WTERMSIG(iStatus), strsignal(WTERMSIG(iStatus))); } nchildren--; } } } /* NOT REACHED */ fossil_exit(1); #endif /* NOT REACHED */ return 0; } /* ** Name of days and months. */ static const char *const azDays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 0}; static const char *const azMonths[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0}; /* ** Returns an RFC822-formatted time string suitable for HTTP headers. ** The timezone is always GMT. The value returned is always a ** string obtained from mprintf() and must be freed using fossil_free() ** to avoid a memory leak. ** ** See http://www.faqs.org/rfcs/rfc822.html, section 5 ** and http://www.faqs.org/rfcs/rfc2616.html, section 3.3. */ char *cgi_rfc822_datestamp(time_t now){ struct tm *pTm; pTm = gmtime(&now); if( pTm==0 ){ return mprintf(""); }else{ return mprintf("%s, %d %s %02d %02d:%02d:%02d +0000", azDays[pTm->tm_wday], pTm->tm_mday, azMonths[pTm->tm_mon], pTm->tm_year+1900, pTm->tm_hour, pTm->tm_min, pTm->tm_sec); } } /* ** Returns an ISO8601-formatted time string suitable for debugging ** purposes. ** ** The value returned is always a string obtained from mprintf() and must ** be freed using fossil_free() to avoid a memory leak. */ char *cgi_iso8601_datestamp(void){ struct tm *pTm; time_t now = time(0); pTm = gmtime(&now); if( pTm==0 ){ return mprintf(""); }else{ return mprintf("%04d-%02d-%02d %02d:%02d:%02d", pTm->tm_year+1900, pTm->tm_mon+1, pTm->tm_mday, pTm->tm_hour, pTm->tm_min, pTm->tm_sec); } } /* ** COMMAND: test-date ** ** Show the current date and time in both RFC822 and ISO8601. */ void test_date(void){ fossil_print("%z = ", cgi_iso8601_datestamp()); fossil_print("%z\n", cgi_rfc822_datestamp(time(0))); } /* ** Parse an RFC822-formatted timestamp as we'd expect from HTTP and return ** a Unix epoch time. <= zero is returned on failure. ** ** Note that this won't handle all the _allowed_ HTTP formats, just the ** most popular one (the one generated by cgi_rfc822_datestamp(), actually). */ time_t cgi_rfc822_parsedate(const char *zDate){ int mday, mon, year, yday, hour, min, sec; char zIgnore[4]; char zMonth[4]; static const char *const azMonths[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0}; if( 7==sscanf(zDate, "%3[A-Za-z], %d %3[A-Za-z] %d %d:%d:%d", zIgnore, &mday, zMonth, &year, &hour, &min, &sec)){ if( year > 1900 ) year -= 1900; for(mon=0; azMonths[mon]; mon++){ if( !fossil_strncmp( azMonths[mon], zMonth, 3 )){ int nDay; int isLeapYr; static int priorDays[] = { 0, 31, 59, 90,120,151,181,212,243,273,304,334 }; if( mon<0 ){ int nYear = (11 - mon)/12; year -= nYear; mon += nYear*12; }else if( mon>11 ){ year += mon/12; mon %= 12; } isLeapYr = year%4==0 && (year%100!=0 || (year+300)%400==0); yday = priorDays[mon] + mday - 1; if( isLeapYr && mon>1 ) yday++; nDay = (year-70)*365 + (year-69)/4 - year/100 + (year+300)/400 + yday; return ((time_t)(nDay*24 + hour)*60 + min)*60 + sec; } } } return 0; } /* ** Check the objectTime against the If-Modified-Since request header. If the ** object time isn't any newer than the header, we immediately send back ** a 304 reply and exit. */ void cgi_modified_since(time_t objectTime){ const char *zIf = P("HTTP_IF_MODIFIED_SINCE"); if( zIf==0 ) return; if( objectTime > cgi_rfc822_parsedate(zIf) ) return; cgi_set_status(304,"Not Modified"); cgi_reset_content(); cgi_reply(); fossil_exit(0); } /* ** Check to see if the remote client is SSH and return ** its IP or return default */ const char *cgi_ssh_remote_addr(const char *zDefault){ char *zIndex; const char *zSshConn = fossil_getenv("SSH_CONNECTION"); if( zSshConn && zSshConn[0] ){ char *zSshClient = fossil_strdup(zSshConn); if( (zIndex = strchr(zSshClient,' '))!=0 ){ zSshClient[zIndex-zSshClient] = '\0'; return zSshClient; } } return zDefault; } /* ** Return true if information is coming from the loopback network. */ int cgi_is_loopback(const char *zIpAddr){ return fossil_strcmp(zIpAddr, "127.0.0.1")==0 || fossil_strcmp(zIpAddr, "::ffff:127.0.0.1")==0 || fossil_strcmp(zIpAddr, "::1")==0; } /* ** Return true if the HTTP request is likely to be from a small-screen ** mobile device. ** ** The returned value is a guess. Use it only for setting up defaults. */ int cgi_from_mobile(void){ const char *zAgent = P("HTTP_USER_AGENT"); if( zAgent==0 ) return 0; if( sqlite3_strglob("*iPad*", zAgent)==0 ) return 0; return sqlite3_strlike("%mobile%", zAgent, 0)==0; }