/* ** 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 module codes the main() procedure that runs first when the ** program is invoked. */ #include "VERSION.h" #include "config.h" #if defined(_WIN32) # include # include # define isatty(h) _isatty(h) # define GETPID (int)GetCurrentProcessId #endif #include "main.h" #include #include #include #include #include #include /* atexit() */ #include #if !defined(_WIN32) # include /* errno global */ # include # include # define GETPID getpid #endif #ifdef FOSSIL_ENABLE_SSL # include "openssl/crypto.h" #endif #if INTERFACE #ifdef FOSSIL_ENABLE_TCL # include "tcl.h" #endif #ifdef FOSSIL_ENABLE_JSON # include "cson_amalgamation.h" /* JSON API. */ # include "json_detail.h" #endif #ifdef HAVE_BACKTRACE # include #endif /* ** Default length of a timeout for serving an HTTP request. Changable ** using the "--timeout N" command-line option or via "timeout: N" in the ** CGI script. */ #ifndef FOSSIL_DEFAULT_TIMEOUT # define FOSSIL_DEFAULT_TIMEOUT 600 /* 10 minutes */ #endif /* ** Maximum number of auxiliary parameters on reports */ #define MX_AUX 5 /* ** Holds flags for fossil user permissions. */ struct FossilUserPerms { char Setup; /* s: use Setup screens on web interface */ char Admin; /* a: administrative permission */ char Password; /* p: change password */ char Query; /* q: create new reports */ char Write; /* i: xfer inbound. check-in */ char Read; /* o: xfer outbound. check-out */ char Hyperlink; /* h: enable the display of hyperlinks */ char Clone; /* g: clone */ char RdWiki; /* j: view wiki via web */ char NewWiki; /* f: create new wiki via web */ char ApndWiki; /* m: append to wiki via web */ char WrWiki; /* k: edit wiki via web */ char ModWiki; /* l: approve and publish wiki content (Moderator) */ char RdTkt; /* r: view tickets via web */ char NewTkt; /* n: create new tickets */ char ApndTkt; /* c: append to tickets via the web */ char WrTkt; /* w: make changes to tickets via web */ char ModTkt; /* q: approve and publish ticket changes (Moderator) */ char Attach; /* b: add attachments */ char TktFmt; /* t: create new ticket report formats */ char RdAddr; /* e: read email addresses or other private data */ char Zip; /* z: download zipped artifact via /zip URL */ char Private; /* x: can send and receive private content */ char WrUnver; /* y: can push unversioned content */ char RdForum; /* 2: Read forum posts */ char WrForum; /* 3: Create new forum posts */ char WrTForum; /* 4: Post to forums not subject to moderation */ char ModForum; /* 5: Moderate (approve or reject) forum posts */ char AdminForum; /* 6: Grant capability 4 to other users */ char EmailAlert; /* 7: Sign up for email notifications */ char Announce; /* A: Send announcements */ char Chat; /* C: read or write the chatroom */ char Debug; /* D: show extra Fossil debugging features */ /* These last two are included to block infinite recursion */ char XReader; /* u: Inherit all privileges of "reader" */ char XDeveloper; /* v: Inherit all privileges of "developer" */ }; #ifdef FOSSIL_ENABLE_TCL /* ** All Tcl related context information is in this structure. This structure ** definition has been copied from and should be kept in sync with the one in ** "th_tcl.c". */ struct TclContext { int argc; /* Number of original (expanded) arguments. */ char **argv; /* Full copy of the original (expanded) arguments. */ void *hLibrary; /* The Tcl library module handle. */ void *xFindExecutable; /* See tcl_FindExecutableProc in th_tcl.c. */ void *xCreateInterp; /* See tcl_CreateInterpProc in th_tcl.c. */ void *xDeleteInterp; /* See tcl_DeleteInterpProc in th_tcl.c. */ void *xFinalize; /* See tcl_FinalizeProc in th_tcl.c. */ Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */ int useObjProc; /* Non-zero if an objProc can be called directly. */ int useTip285; /* Non-zero if TIP #285 is available. */ char *setup; /* The optional Tcl setup script. */ void *xPreEval; /* Optional, called before Tcl_Eval*(). */ void *pPreContext; /* Optional, provided to xPreEval(). */ void *xPostEval; /* Optional, called after Tcl_Eval*(). */ void *pPostContext; /* Optional, provided to xPostEval(). */ }; #endif struct Global { int argc; char **argv; /* Command-line arguments to the program */ char *nameOfExe; /* Full path of executable. */ const char *zErrlog; /* Log errors to this file, if not NULL */ const char *zPhase; /* Phase of operation, for use by the error log ** and for deriving $canonical_page TH1 variable */ int isConst; /* True if the output is unchanging & cacheable */ const char *zVfsName; /* The VFS to use for database connections */ sqlite3 *db; /* The connection to the databases */ sqlite3 *dbConfig; /* Separate connection for global_config table */ char *zAuxSchema; /* Main repository aux-schema */ int dbIgnoreErrors; /* Ignore database errors if true */ char *zConfigDbName; /* Path of the config database. NULL if not open */ sqlite3_int64 now; /* Seconds since 1970 */ int repositoryOpen; /* True if the main repository database is open */ unsigned iRepoDataVers; /* Initial data version for repository database */ char *zRepositoryOption; /* Most recent cached repository option value */ char *zRepositoryName; /* Name of the repository database file */ char *zLocalDbName; /* Name of the local database file */ char *zOpenRevision; /* Check-in version to use during database open */ const char *zCmdName; /* Name of the Fossil command currently running */ int localOpen; /* True if the local database is open */ char *zLocalRoot; /* The directory holding the local database */ int minPrefix; /* Number of digits needed for a distinct hash */ int eHashPolicy; /* Current hash policy. One of HPOLICY_* */ int fSqlTrace; /* True if --sqltrace flag is present */ int fSqlStats; /* True if --sqltrace or --sqlstats are present */ int fSqlPrint; /* True if --sqlprint flag is present */ int fCgiTrace; /* True if --cgitrace is enabled */ int fQuiet; /* True if -quiet flag is present */ int fJail; /* True if running with a chroot jail */ int fHttpTrace; /* Trace outbound HTTP requests */ int fAnyTrace; /* Any kind of tracing */ int fAllowACME; /* Deliver files from .well-known */ char *zHttpAuth; /* HTTP Authorization user:pass information */ int fSystemTrace; /* Trace calls to fossil_system(), --systemtrace */ int fSshTrace; /* Trace the SSH setup traffic */ int fSshClient; /* HTTP client flags for SSH client */ int fNoHttpCompress; /* Do not compress HTTP traffic (for debugging) */ char *zSshCmd; /* SSH command string */ const char *zHttpCmd; /* External program to do HTTP requests */ int fNoSync; /* Do not do an autosync ever. --nosync */ int fIPv4; /* Use only IPv4, not IPv6. --ipv4 */ char *zPath; /* Name of webpage being served (may be NULL) */ char *zExtra; /* Extra path information past the webpage name */ char *zBaseURL; /* Full text of the URL being served */ char *zHttpsURL; /* zBaseURL translated to https: */ char *zTop; /* Parent directory of zPath */ int nExtraURL; /* Extra bytes added to SCRIPT_NAME */ const char *zExtRoot; /* Document root for the /ext sub-website */ const char *zContentType; /* The content type of the input HTTP request */ int iErrPriority; /* Priority of current error message */ char *zErrMsg; /* Text of an error message */ int sslNotAvailable; /* SSL is not available. Do not redirect to https: */ Blob cgiIn; /* Input to an xfer www method */ int cgiOutput; /* 0: command-line 1: CGI. 2: after CGI */ int xferPanic; /* Write error messages in XFER protocol */ int fullHttpReply; /* True for full HTTP reply. False for CGI reply */ Th_Interp *interp; /* The TH1 interpreter */ char *th1Setup; /* The TH1 post-creation setup script, if any */ int th1Flags; /* The TH1 integration state flags */ FILE *httpIn; /* Accept HTTP input from here */ FILE *httpOut; /* Send HTTP output here */ int httpUseSSL; /* True to use an SSL codec for HTTP traffic */ void *httpSSLConn; /* The SSL connection */ int xlinkClusterOnly; /* Set when cloning. Only process clusters */ int fTimeFormat; /* 1 for UTC. 2 for localtime. 0 not yet selected */ int *aCommitFile; /* Array of files to be committed */ int markPrivate; /* All new artifacts are private if true */ char *ckinLockFail; /* Check-in lock failure received from server */ int clockSkewSeen; /* True if clocks on client and server out of sync */ int wikiFlags; /* Wiki conversion flags applied to %W */ char isHTTP; /* True if server/CGI modes, else assume CLI. */ char jsHref; /* If true, set href= using javascript, not HTML */ Blob httpHeader; /* Complete text of the HTTP request header */ UrlData url; /* Information about current URL */ const char *zLogin; /* Login name. NULL or "" if not logged in. */ const char *zCkoutAlias; /* doc/ uses this branch as an alias for "ckout" */ const char *zMainMenuFile; /* --mainmenu FILE from server/ui/cgi */ const char *zSSLIdentity; /* Value of --ssl-identity option, filename of ** SSL client identity */ #if defined(_WIN32) && USE_SEE const char *zPidKey; /* Saved value of the --usepidkey option. Only * applicable when using SEE on Windows. */ #endif int useLocalauth; /* No login required if from 127.0.0.1 */ int noPswd; /* Logged in without password (on 127.0.0.1) */ int userUid; /* Integer user id */ int isHuman; /* True if access by a human, not a spider or bot */ int comFmtFlags; /* Zero or more "COMMENT_PRINT_*" bit flags, should be ** accessed through get_comment_format(). */ /* Information used to populate the RCVFROM table */ int rcvid; /* The rcvid. 0 if not yet defined. */ char *zIpAddr; /* The remote IP address */ char *zNonce; /* The nonce used for login */ /* permissions available to current user */ struct FossilUserPerms perm; /* permissions available to current user or to "anonymous". ** This is the logical union of perm permissions above with ** the value that perm would take if g.zLogin were "anonymous". */ struct FossilUserPerms anon; #ifdef FOSSIL_ENABLE_TCL /* all Tcl related context necessary for integration */ struct TclContext tcl; #endif /* For defense against Cross-site Request Forgery attacks */ char zCsrfToken[12]; /* Value of the anti-CSRF token */ int okCsrf; /* Anti-CSRF token is present and valid */ int parseCnt[10]; /* Counts of artifacts parsed */ FILE *fDebug; /* Write debug information here, if the file exists */ #ifdef FOSSIL_ENABLE_TH1_HOOKS int fNoThHook; /* Disable all TH1 command/webpage hooks */ #endif int thTrace; /* True to enable TH1 debugging output */ Blob thLog; /* Text of the TH1 debugging output */ int isHome; /* True if rendering the "home" page */ /* Storage for the aux() and/or option() SQL function arguments */ int nAux; /* Number of distinct aux() or option() values */ const char *azAuxName[MX_AUX]; /* Name of each aux() or option() value */ char *azAuxParam[MX_AUX]; /* Param of each aux() or option() value */ const char *azAuxVal[MX_AUX]; /* Value of each aux() or option() value */ const char **azAuxOpt[MX_AUX]; /* Options of each option() value */ int anAuxCols[MX_AUX]; /* Number of columns for option() values */ int allowSymlinks; /* Cached "allow-symlinks" option */ int mainTimerId; /* Set to fossil_timer_start() */ int nPendingRequest; /* # of HTTP requests in "fossil server" */ int nRequest; /* Total # of HTTP request */ int bAvoidDeltaManifests; /* Avoid using delta manifests if true */ #ifdef FOSSIL_ENABLE_JSON struct FossilJsonBits { int isJsonMode; /* True if running in JSON mode, else false. This changes how errors are reported. In JSON mode we try to always output JSON-form error responses and always (in CGI mode) exit() with code 0 to avoid an HTTP 500 error. */ int preserveRc; /* Do not convert error codes into 0. * This is primarily intended for use * by the test suite. */ int resultCode; /* used for passing back specific codes ** from /json callbacks. */ int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */ cson_output_opt outOpt; /* formatting options for JSON mode. */ cson_value *authToken; /* authentication token */ const char *jsonp; /* Name of JSONP function wrapper. */ unsigned char dispatchDepth /* Tells JSON command dispatching which argument we are currently working on. For this purpose, arg#0 is the "json" path/CLI arg. */; struct { /* "garbage collector" */ cson_value *v; cson_array *a; } gc; struct { /* JSON POST data. */ cson_value *v; cson_array *a; int offset; /* Tells us which PATH_INFO/CLI args part holds the "json" command, so that we can account for sub-repos and path prefixes. This is handled differently for CLI and CGI modes. */ const char *commandStr /*"command" request param.*/; } cmd; struct { /* JSON POST data. */ cson_value *v; cson_object *o; } post; struct { /* GET/COOKIE params in JSON mode. */ cson_value *v; cson_object *o; } param; struct { cson_value *v; cson_object *o; } reqPayload; /* request payload object (if any) */ cson_array *warnings; /* response warnings */ int timerId; /* fetched from fossil_timer_start() */ } json; #endif /* FOSSIL_ENABLE_JSON */ int ftntsIssues[4]; /* Counts for misref, strayed, joined, overnested */ int diffCnt[3]; /* Counts for DIFF_NUMSTAT: files, ins, del */ }; /* ** Macro for debugging: */ #define CGIDEBUG(X) if( g.fDebug ) cgi_debug X #endif Global g; /* ** atexit() handler which frees up "some" of the resources ** used by fossil. */ static void fossil_atexit(void) { static int once = 0; if( once++ ) return; /* Ensure that this routine only runs once */ #if USE_SEE /* ** Zero, unlock, and free the saved database encryption key now. */ db_unsave_encryption_key(); #endif #if defined(_WIN32) || (defined(__BIONIC__) && !defined(FOSSIL_HAVE_GETPASS)) /* ** Free the secure getpass() buffer now. */ freepass(); #endif #if defined(_WIN32) && !defined(_WIN64) && defined(FOSSIL_ENABLE_TCL) && \ defined(USE_TCL_STUBS) /* ** If Tcl is compiled on Windows using the latest MinGW, Fossil can crash ** when exiting while a stubs-enabled Tcl is still loaded. This is due to ** a bug in MinGW, see: ** ** http://comments.gmane.org/gmane.comp.gnu.mingw.user/41724 ** ** The workaround is to manually unload the loaded Tcl library prior to ** exiting the process. This issue does not impact 64-bit Windows. */ unloadTcl(g.interp, &g.tcl); #endif #ifdef FOSSIL_ENABLE_JSON cson_value_free(g.json.gc.v); memset(&g.json, 0, sizeof(g.json)); #endif free(g.zErrMsg); if(g.db){ db_close(0); } manifest_clear_cache(); content_clear_cache(1); rebuild_clear_cache(); /* ** FIXME: The next two lines cannot always be enabled; however, they ** are very useful for tracking down TH1 memory leaks. */ if( fossil_getenv("TH1_DELETE_INTERP")!=0 ){ if( g.interp ){ Th_DeleteInterp(g.interp); g.interp = 0; } #if defined(TH_MEMDEBUG) if( Th_GetOutstandingMalloc()!=0 ){ fossil_print("Th_GetOutstandingMalloc() => %d\n", Th_GetOutstandingMalloc()); } assert( Th_GetOutstandingMalloc()==0 ); #endif } } /* ** Convert all arguments from mbcs (or unicode) to UTF-8. Then ** search g.argv for arguments "--args FILENAME". If found, then ** (1) remove the two arguments from g.argv ** (2) Read the file FILENAME ** (3) Use the contents of FILE to replace the two removed arguments: ** (a) Ignore blank lines in the file ** (b) Each non-empty line of the file is an argument, except ** (c) If the line begins with "-" and contains a space, it is broken ** into two arguments at the space. */ void expand_args_option(int argc, void *argv){ Blob file = empty_blob; /* Content of the file */ Blob line = empty_blob; /* One line of the file */ unsigned int nLine; /* Number of lines in the file*/ unsigned int i, j, k; /* Loop counters */ int n; /* Number of bytes in one line */ unsigned int nArg; /* Number of new arguments */ char *z; /* General use string pointer */ char **newArgv; /* New expanded g.argv under construction */ const char *zFileName; /* input file name */ FILE *inFile; /* input FILE */ g.argc = argc; g.argv = argv; sqlite3_initialize(); #if defined(_WIN32) && defined(BROKEN_MINGW_CMDLINE) for(i=0; i=g.argc-1 ) return; zFileName = g.argv[i+1]; if( strcmp(zFileName,"-")==0 ){ inFile = stdin; }else if( !file_isfile(zFileName, ExtFILE) ){ fossil_fatal("Not an ordinary file: \"%s\"", zFileName); }else{ inFile = fossil_fopen(zFileName,"rb"); if( inFile==0 ){ fossil_fatal("Cannot open -args file [%s]", zFileName); } } blob_read_from_channel(&file, inFile, -1); if(stdin != inFile){ fclose(inFile); } inFile = NULL; blob_to_utf8_no_bom(&file, 1); z = blob_str(&file); for(k=0, nLine=1; z[k]; k++) if( z[k]=='\n' ) nLine++; if( nLine>100000000 ) fossil_fatal("too many command-line arguments"); nArg = g.argc + nLine*2; newArgv = fossil_malloc( sizeof(char*)*nArg ); for(j=0; j0 && (n = blob_line(&file, &line))>0 ){ /* Reminder: ^^^ nLine check avoids that embedded NUL bytes in the ** --args file causes nLine to be less than blob_line() will end ** up reporting, as such a miscount leads to an illegal memory ** write. See forum post ** https://fossil-scm.org/forum/forumpost/7b34eecc1b8c for ** details */ if( n<1 ){ /* Reminder: corner-case: a line with 1 byte and no newline. */ continue; } z = blob_buffer(&line); if('\n'==z[n-1]){ z[n-1] = 0; } if((n>1) && ('\r'==z[n-2])){ if(n==2) continue /*empty line*/; z[n-2] = 0; } if(!z[0]) continue; if( j>=nArg ){ fossil_fatal("malformed command-line arguments"); } newArgv[j++] = z; if( z[0]=='-' ){ for(k=1; z[k] && !fossil_isspace(z[k]); k++){} if( z[k] ){ z[k] = 0; k++; if( z[k] ) newArgv[j++] = &z[k]; } } } i += 2; while( i=2 ) break; if( fd<0 ) x = errno; }while( nTry++ < 2 ); if( fd<2 ){ g.cgiOutput = 1; g.httpOut = stdout; g.fullHttpReply = !g.isHTTP; fossil_panic("file descriptor 2 is not open. (fd=%d, errno=%d)", fd, x); } } #endif g.zCmdName = zCmdName; rc = dispatch_name_search(zCmdName, CMDFLAG_COMMAND|CMDFLAG_PREFIX, &pCmd); if( rc==1 && g.argc==2 && file_is_repository(g.argv[1]) ){ /* If the command-line is "fossil ABC" and "ABC" is no a valid command, ** but "ABC" is the name of a repository file, make the command be ** "fossil ui ABC" instead. */ char **zNewArgv = fossil_malloc( sizeof(char*)*4 ); zNewArgv[0] = g.argv[0]; zNewArgv[1] = "ui"; zNewArgv[2] = g.argv[1]; zNewArgv[3] = 0; g.argc = 3; g.argv = zNewArgv; g.zCmdName = zCmdName = "ui"; rc = dispatch_name_search(zCmdName, CMDFLAG_COMMAND|CMDFLAG_PREFIX, &pCmd); } if( rc==1 ){ #ifdef FOSSIL_ENABLE_TH1_HOOKS if( !g.isHTTP && !g.fNoThHook ){ rc = Th_CommandHook(zCmdName, 0); }else{ rc = TH_OK; } if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ if( rc==TH_OK || rc==TH_RETURN ){ #endif fossil_fatal("%s: unknown command: %s\n" "%s: use \"help\" for more information", g.argv[0], zCmdName, g.argv[0]); #ifdef FOSSIL_ENABLE_TH1_HOOKS } if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ Th_CommandNotify(zCmdName, 0); } } fossil_exit(0); #endif }else if( rc==2 ){ Blob couldbe; blob_init(&couldbe,0,0); dispatch_matching_names(zCmdName, CMDFLAG_COMMAND, &couldbe); fossil_print("%s: ambiguous command prefix: %s\n" "%s: could be any of:%s\n" "%s: use \"help\" for more information\n", g.argv[0], zCmdName, g.argv[0], blob_str(&couldbe), g.argv[0]); fossil_exit(1); } #ifdef FOSSIL_ENABLE_JSON else if( rc==0 && strcmp("json",pCmd->zName)==0 ){ g.json.isJsonMode = 1; }else{ assert(!g.json.isJsonMode && "JSON-mode misconfiguration."); } #endif atexit( fossil_atexit ); #ifdef FOSSIL_ENABLE_TH1_HOOKS /* ** The TH1 return codes from the hook will be handled as follows: ** ** TH_OK: The xFunc() and the TH1 notification will both be executed. ** ** TH_ERROR: The xFunc() will be skipped, the TH1 notification will be ** skipped. If the xFunc() is being hooked, the error message ** will be emitted. ** ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped. ** ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be ** skipped. ** ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be ** executed. */ if( !g.isHTTP && !g.fNoThHook ){ rc = Th_CommandHook(pCmd->zName, pCmd->eCmdFlags); }else{ rc = TH_OK; } if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ if( rc==TH_OK || rc==TH_RETURN ){ #endif g.zPhase = pCmd->zName; pCmd->xFunc(); g.zPhase = "shutdown"; #ifdef FOSSIL_ENABLE_TH1_HOOKS } if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ Th_CommandNotify(pCmd->zName, pCmd->eCmdFlags); } } #endif fossil_exit(0); /*NOT_REACHED*/ return 0; } /* ** Print a usage comment and quit */ void usage(const char *zFormat){ fossil_fatal("Usage: %s %s %s", g.argv[0], g.argv[1], zFormat); } /* ** Remove n elements from g.argv beginning with the i-th element. */ static void remove_from_argv(int i, int n){ int j; for(j=i+n; j= g.argc ) break; z = g.argv[i]; if( z[0]!='-' ) continue; z++; if( z[0]=='-' ){ if( z[1]==0 ){ /* Stop processing at "--" without consuming it. verify_all_options() will consume this flag. */ break; } z++; } if( strncmp(z,zLong,nLong)==0 ){ if( hasArg && z[nLong]=='=' ){ zReturn = &z[nLong+1]; remove_from_argv(i, 1); break; }else if( z[nLong]==0 ){ zReturn = g.argv[i+hasArg]; remove_from_argv(i, 1+hasArg); break; } }else if( fossil_strcmp(z,zShort)==0 ){ zReturn = g.argv[i+hasArg]; remove_from_argv(i, 1+hasArg); break; } } return zReturn; } /* Return true if zOption exists in the command-line arguments, ** but do not remove it from the list or otherwise process it. */ int has_option(const char *zOption){ int i; int n = (int)strlen(zOption); for(i=1; i @ %h(blob_str(&versionInfo)) @ style_finish_page(); } /* ** Set the g.zBaseURL value to the full URL for the toplevel of ** the fossil tree. Set g.zTop to g.zBaseURL without the ** leading "http://" and the host and port. ** ** The g.zBaseURL is normally set based on HTTP_HOST and SCRIPT_NAME ** environment variables. However, if zAltBase is not NULL then it ** is the argument to the --baseurl option command-line option and ** g.zBaseURL and g.zTop is set from that instead. */ void set_base_url(const char *zAltBase){ int i; const char *zHost; const char *zMode; const char *zCur; if( g.zBaseURL!=0 ) return; if( zAltBase ){ int i, n, c; g.zTop = g.zBaseURL = mprintf("%s", zAltBase); i = (int)strlen(g.zBaseURL); while( i>3 && g.zBaseURL[i-1]=='/' ){ i--; } g.zBaseURL[i] = 0; if( strncmp(g.zTop, "http://", 7)==0 ){ /* it is HTTP, replace prefix with HTTPS. */ g.zHttpsURL = mprintf("https://%s", &g.zTop[7]); }else if( strncmp(g.zTop, "https://", 8)==0 ){ /* it is already HTTPS, use it. */ g.zHttpsURL = mprintf("%s", g.zTop); }else{ fossil_fatal("argument to --baseurl should be 'http://host/path'" " or 'https://host/path'"); } for(i=n=0; (c = g.zTop[i])!=0; i++){ if( c=='/' ){ n++; if( n==3 ){ g.zTop += i; break; } } } if( n==2 ) g.zTop = ""; if( g.zTop==g.zBaseURL ){ fossil_fatal("argument to --baseurl should be 'http://host/path'" " or 'https://host/path'"); } if( g.zTop[1]==0 ) g.zTop++; }else{ char *z; zHost = PD("HTTP_HOST",""); z = fossil_strdup(zHost); for(i=0; z[i]; i++){ if( z[i]<='Z' && z[i]>='A' ) z[i] += 'a' - 'A'; } if( i>3 && z[i-1]=='0' && z[i-2]=='8' && z[i-3]==':' ) i -= 3; if( i && z[i-1]=='.' ) i--; z[i] = 0; zMode = PD("HTTPS","off"); zCur = PD("SCRIPT_NAME","/"); i = strlen(zCur); while( i>0 && zCur[i-1]=='/' ) i--; if( fossil_stricmp(zMode,"on")==0 ){ g.zBaseURL = mprintf("https://%s%.*s", z, i, zCur); g.zTop = &g.zBaseURL[8+strlen(z)]; g.zHttpsURL = g.zBaseURL; }else{ g.zBaseURL = mprintf("http://%s%.*s", z, i, zCur); g.zTop = &g.zBaseURL[7+strlen(z)]; g.zHttpsURL = mprintf("https://%s%.*s", z, i, zCur); } fossil_free(z); } /* Try to record the base URL as a CONFIG table entry with a name ** of the form: "baseurl:BASE". This keeps a record of how the ** the repository is used as a server, to help in answering questions ** like "where is the CGI script that references this repository?" ** ** This is just a logging hint. So don't worry if it cannot be done. ** Don't try this if the repository database is not writable, for ** example. ** ** If g.useLocalauth is set, that (probably) means that we are running ** "fossil ui" and there is no point in logging those cases either. */ if( db_is_writeable("repository") && !g.useLocalauth ){ int nBase = (int)strlen(g.zBaseURL); char *zBase = g.zBaseURL; if( g.nExtraURL>0 && g.nExtraURL1){ cookie_render(); } cgi_redirectf("%R%s", db_get("index-page", "/index")); } /* ** If running as root, chroot to the directory containing the ** repository zRepo and then drop root privileges. Return the ** new repository name. ** ** zRepo might be a directory itself. In that case chroot into ** the directory zRepo. ** ** Assume the user-id and group-id of the repository, or if zRepo ** is a directory, of that directory. ** ** The noJail flag means that the chroot jail is not entered. But ** privileges are still lowered to that of the user-id and group-id ** of the repository file. */ char *enter_chroot_jail(char *zRepo, int noJail){ #if !defined(_WIN32) if( getuid()==0 ){ int i; struct stat sStat; Blob dir; char *zDir; if( g.db!=0 ){ db_close(1); } file_canonical_name(zRepo, &dir, 0); zDir = blob_str(&dir); if( !noJail ){ if( file_isdir(zDir, ExtFILE)==1 ){ if( file_chdir(zDir, 1) ){ fossil_panic("unable to chroot into %s", zDir); } g.fJail = 1; zRepo = "/"; }else{ for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){} if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo); if( i>0 ){ zDir[i] = 0; if( file_chdir(zDir, 1) ){ fossil_fatal("unable to chroot into %s", zDir); } zDir[i] = '/'; } zRepo = &zDir[i]; } } if( stat(zRepo, &sStat)!=0 ){ fossil_fatal("cannot stat() repository: %s", zRepo); } i = setgid(sStat.st_gid); i = i || setuid(sStat.st_uid); if(i){ fossil_fatal("setgid/uid() failed with errno %d", errno); } if( g.db==0 && file_isfile(zRepo, ExtFILE) ){ db_open_repository(zRepo); } } #endif return zRepo; } /* ** Called whenever a crash is encountered while processing a webpage. */ void sigsegv_handler(int x){ #if HAVE_BACKTRACE void *array[20]; size_t size; char **strings; size_t i; Blob out; size = backtrace(array, sizeof(array)/sizeof(array[0])); strings = backtrace_symbols(array, size); blob_init(&out, 0, 0); blob_appendf(&out, "Segfault during %s", g.zPhase); for(i=0; i @ @ @

Not Found

@ cgi_set_status(404, "Not Found"); cgi_reply(); } /* ** Preconditions: ** ** * Environment variables are set up according to the CGI standard. ** ** If the repository is known, it has already been opened. If unknown, ** then g.zRepositoryName holds the directory that contains the repository ** and the actual repository is taken from the first element of PATH_INFO. ** ** Process the webpage specified by the PATH_INFO or REQUEST_URI ** environment variable. ** ** If the repository is not known, then a search is done through the ** file hierarchy rooted at g.zRepositoryName for a suitable repository ** with a name of $prefix.fossil, where $prefix is any prefix of PATH_INFO. ** Or, if an ordinary file named $prefix is found, and $prefix matches ** pFileGlob and $prefix does not match "*.fossil*" and the mimetype of ** $prefix can be determined from its suffix, then the file $prefix is ** returned as static text. ** ** If no suitable webpage is found, try to redirect to zNotFound. */ static void process_one_web_page( const char *zNotFound, /* Redirect here on a 404 if not NULL */ Glob *pFileGlob, /* Deliver static files matching */ int allowRepoList /* Send repo list for "/" URL */ ){ const char *zPathInfo = PD("PATH_INFO", ""); char *zPath = NULL; int i; const CmdOrPage *pCmd = 0; const char *zBase = g.zRepositoryName; g.zPhase = "process_one_web_page"; #if !defined(_WIN32) signal(SIGSEGV, sigsegv_handler); #endif /* Handle universal query parameters */ if( PB("utc") ){ g.fTimeFormat = 1; }else if( PB("localtime") ){ g.fTimeFormat = 2; } #ifdef FOSSIL_ENABLE_JSON /* ** Ensure that JSON mode is set up if we're visiting /json, to allow ** us to customize some following behaviour (error handling and only ** process JSON-mode POST data if we're actually in a /json ** page). This is normally set up before this routine is called, but ** it looks like the ssh_request_loop() approach to dispatching ** might bypass that. */ if( g.json.isJsonMode==0 && json_request_is_json_api(zPathInfo)!=0 ){ g.json.isJsonMode = 1; json_bootstrap_early(); } #endif /* If the repository has not been opened already, then find the ** repository based on the first element of PATH_INFO and open it. */ if( !g.repositoryOpen ){ char *zRepo; /* Candidate repository name */ char *zToFree = 0; /* Malloced memory that needs to be freed */ const char *zCleanRepo; /* zRepo with surplus leading "/" removed */ const char *zOldScript = PD("SCRIPT_NAME", ""); /* Original SCRIPT_NAME */ char *zNewScript; /* Revised SCRIPT_NAME after processing */ int j, k; /* Loop variables */ i64 szFile; /* File size of the candidate repository */ i = zPathInfo[0]!=0; if( fossil_strcmp(g.zRepositoryName, "/")==0 ){ zBase++; #if defined(_WIN32) || defined(__CYGWIN__) if( sqlite3_strglob("/[a-zA-Z]:/*", zPathInfo)==0 ) i = 4; #endif } while( 1 ){ size_t nBase = strlen(zBase); while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; } /* The candidate repository name is some prefix of the PATH_INFO ** with ".fossil" appended */ zRepo = zToFree = mprintf("%s%.*s.fossil",zBase,i,zPathInfo); if( g.fHttpTrace ){ @ fprintf(stderr, "# looking for repository named \"%s\"\n", zRepo); } /* For safety -- to prevent an attacker from accessing arbitrary disk ** files by sending a maliciously crafted request URI to a public ** server -- make sure the repository basename contains no ** characters other than alphanumerics, "/", "_", "-", and ".", and ** that "-" never occurs immediately after a "/" and that "." is always ** surrounded by two alphanumerics. Any character that does not ** satisfy these constraints is converted into "_". */ szFile = 0; for(j=nBase+1, k=0; zRepo[j] && k fprintf(stderr, "# unsafe pathname rejected: %s\n", zRepo); } break; } /* Check to see if a file name zRepo exists. If a file named zRepo ** does not exist, szFile will become -1. If the file does exist, ** then szFile will become zero (for an empty file) or positive. ** Special case: Assume any file with a basename of ".fossil" does ** not exist. */ zCleanRepo = file_cleanup_fullpath(zRepo); if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){ szFile = file_size(zCleanRepo, ExtFILE); if( g.fHttpTrace ){ char zBuf[24]; sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", szFile); @ fprintf(stderr, "# file_size(%s) = %s\n", zCleanRepo, zBuf); } } /* If no file named by zRepo exists, remove the added ".fossil" suffix ** and check to see if there is a file or directory with the same ** name as the raw PATH_INFO text. */ if( szFile<0 && i>0 ){ const char *zMimetype; assert( fossil_strcmp(&zRepo[j], ".fossil")==0 ); zRepo[j] = 0; /* Remove the ".fossil" suffix */ /* The PATH_INFO prefix seen so far is a valid directory. ** Continue the loop with the next element of the PATH_INFO */ if( zPathInfo[i]=='/' && file_isdir(zCleanRepo, ExtFILE)==1 ){ fossil_free(zToFree); i++; continue; } /* If zRepo is the name of an ordinary file that matches the ** "--file GLOB" pattern, then the CGI reply is the text of ** of the file. ** ** For safety, do not allow any file whose name contains ".fossil" ** to be returned this way, to prevent complete repositories from ** being delivered accidently. This is not intended to be a ** general-purpose web server. The "--file GLOB" mechanism is ** designed to allow the delivery of a few static images or HTML ** pages. */ if( pFileGlob!=0 && file_isfile(zCleanRepo, ExtFILE) && glob_match(pFileGlob, file_cleanup_fullpath(zRepo+nBase)) && sqlite3_strglob("*.fossil*",zRepo)!=0 && (zMimetype = mimetype_from_name(zRepo))!=0 && strcmp(zMimetype, "application/x-fossil-artifact")!=0 ){ Blob content; blob_read_from_file(&content, file_cleanup_fullpath(zRepo), ExtFILE); cgi_set_content_type(zMimetype); cgi_set_content(&content); cgi_reply(); return; } /* In support of the ACME protocol, files under the .well-known/ ** directory is always accepted. */ if( g.fAllowACME && strncmp(&zRepo[nBase],"/.well-known/",12)==0 && file_isfile(zCleanRepo, ExtFILE) ){ Blob content; blob_read_from_file(&content, file_cleanup_fullpath(zRepo), ExtFILE); cgi_set_content_type(mimetype_from_name(zRepo)); cgi_set_content(&content); cgi_reply(); return; } zRepo[j] = '.'; } /* If we reach this point, it means that the search of the PATH_INFO ** string is finished. Either zRepo contains the name of the ** repository to be used, or else no repository could be found and ** some kind of error response is required. */ if( szFile<1024 ){ set_base_url(0); if( (zPathInfo[0]==0 || strcmp(zPathInfo,"/")==0) && allowRepoList && repo_list_page() ){ /* Will return a list of repositories */ }else if( zNotFound ){ cgi_redirect(zNotFound); }else{ fossil_not_found_page(); } return; } break; } /* Add the repository name (without the ".fossil" suffix) to the end ** of SCRIPT_NAME and g.zTop and g.zBaseURL and remove the repository ** name from the beginning of PATH_INFO. */ zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo); if( g.zTop ) g.zTop = mprintf("%R%.*s", i, zPathInfo); if( g.zBaseURL ) g.zBaseURL = mprintf("%s%.*s", g.zBaseURL, i, zPathInfo); cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]); zPathInfo += i; cgi_replace_parameter("SCRIPT_NAME", zNewScript); db_open_repository(file_cleanup_fullpath(zRepo)); if( g.fHttpTrace ){ @ @ @ fprintf(stderr, "# repository: [%s]\n" "# translated PATH_INFO = [%s]\n" "# translated SCRIPT_NAME = [%s]\n", zRepo, zPathInfo, zNewScript); if( g.zTop ){ @ fprintf(stderr, "# translated g.zTop = [%s]\n", g.zTop); } if( g.zBaseURL ){ @ fprintf(stderr, "# translated g.zBaseURL = [%s]\n", g.zBaseURL); } } } /* At this point, the appropriate repository database file will have ** been opened. */ /* ** Check to see if the first term of PATH_INFO specifies an ** alternative skin. This will be the case if the first term of ** PATH_INFO begins with "draftN/" where N is an integer between 1 ** and 9. If so, activate the skin associated with that draft. */ if( zPathInfo && strncmp(zPathInfo,"/draft",6)==0 && zPathInfo[6]>='1' && zPathInfo[6]<='9' && (zPathInfo[7]=='/' || zPathInfo[7]==0) ){ int iSkin = zPathInfo[6] - '0'; char *zNewScript; if( db_int(0,"SELECT count(*) FROM config WHERE name GLOB 'draft%d-*'", iSkin)<5 ){ fossil_not_found_page(); fossil_exit(0); } skin_use_draft(iSkin); zNewScript = mprintf("%T/draft%d", P("SCRIPT_NAME"), iSkin); if( g.zTop ) g.zTop = mprintf("%R/draft%d", iSkin); if( g.zBaseURL ) g.zBaseURL = mprintf("%s/draft%d", g.zBaseURL, iSkin); zPathInfo += 7; g.nExtraURL += 7; cgi_replace_parameter("PATH_INFO", zPathInfo); cgi_replace_parameter("SCRIPT_NAME", zNewScript); etag_cancel(); } /* If the content type is application/x-fossil or ** application/x-fossil-debug, then a sync/push/pull/clone is ** desired, so default the PATH_INFO to /xfer */ if( g.zContentType && strncmp(g.zContentType, "application/x-fossil", 20)==0 ){ /* Special case: If the content mimetype shows that it is "fossil sync" ** payload, then pretend that the PATH_INFO is /xfer so that we always ** invoke the sync page. */ zPathInfo = "/xfer"; } /* Use the first element of PATH_INFO as the page name ** and deliver the appropriate page back to the user. */ set_base_url(0); if( fossil_redirect_to_https_if_needed(2) ) return; if( zPathInfo==0 || zPathInfo[0]==0 || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){ /* Second special case: If the PATH_INFO is blank, issue a redirect to ** the home page identified by the "index-page" setting in the repository ** CONFIG table, to "/index" if there no "index-page" setting. */ #ifdef FOSSIL_ENABLE_JSON if(g.json.isJsonMode){ json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1); fossil_exit(0); } #endif fossil_redirect_home() /*does not return*/; }else{ zPath = mprintf("%s", zPathInfo); } /* Make g.zPath point to the first element of the path. Make ** g.zExtra point to everything past that point. */ while(1){ g.zPath = &zPath[1]; for(i=1; zPath[i] && zPath[i]!='/'; i++){} if( zPath[i]=='/' ){ zPath[i] = 0; g.zExtra = &zPath[i+1]; }else{ g.zExtra = 0; } break; } if( g.zExtra ){ /* CGI parameters get this treatment elsewhere, but places like getfile ** will use g.zExtra directly. ** Reminder: the login mechanism uses 'name' differently, and may ** eventually have a problem/collision with this. ** ** Disabled by stephan when running in JSON mode because this ** particular parameter name is very common and i have had no end ** of grief with this handling. The JSON API never relies on the ** handling below, and by disabling it in JSON mode I can remove ** lots of special-case handling in several JSON handlers. */ #ifdef FOSSIL_ENABLE_JSON if(g.json.isJsonMode==0){ #endif dehttpize(g.zExtra); cgi_set_parameter_nocopy("name", g.zExtra, 1); #ifdef FOSSIL_ENABLE_JSON } #endif } /* Locate the method specified by the path and execute the function ** that implements that method. */ if( dispatch_name_search(g.zPath-1, CMDFLAG_WEBPAGE, &pCmd) && dispatch_alias(g.zPath-1, &pCmd) ){ #ifdef FOSSIL_ENABLE_JSON if(g.json.isJsonMode!=0){ json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,0); }else #endif { #ifdef FOSSIL_ENABLE_TH1_HOOKS int rc; if( !g.fNoThHook ){ rc = Th_WebpageHook(g.zPath, 0); }else{ rc = TH_OK; } if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ if( rc==TH_OK || rc==TH_RETURN ){ #endif cgi_set_status(404,"Not Found"); @

Not Found

@

Page not found: %h(g.zPath)

#ifdef FOSSIL_ENABLE_TH1_HOOKS } if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ Th_WebpageNotify(g.zPath, 0); } } #endif } }else if( pCmd->xFunc!=page_xfer && db_schema_is_outofdate() ){ #ifdef FOSSIL_ENABLE_JSON if(g.json.isJsonMode!=0){ json_err(FSL_JSON_E_DB_NEEDS_REBUILD,NULL,0); }else #endif { @

Server Configuration Error

@

The database schema on the server is out-of-date. Please ask @ the administrator to run fossil rebuild.

} }else{ if(0==(CMDFLAG_LDAVG_EXEMPT & pCmd->eCmdFlags)){ load_control(); } #ifdef FOSSIL_ENABLE_JSON { static int jsonOnce = 0; if( jsonOnce==0 && g.json.isJsonMode!=0 ){ assert(json_is_bootstrapped_early()); json_bootstrap_late(); jsonOnce = 1; } } #endif if( (pCmd->eCmdFlags & CMDFLAG_RAWCONTENT)==0 ){ cgi_decode_post_parameters(); } if( g.fCgiTrace ){ fossil_trace("######## Calling %s #########\n", pCmd->zName); cgi_print_all(1, 1); } #ifdef FOSSIL_ENABLE_TH1_HOOKS { /* ** The TH1 return codes from the hook will be handled as follows: ** ** TH_OK: The xFunc() and the TH1 notification will both be executed. ** ** TH_ERROR: The xFunc() will be skipped, the TH1 notification will be ** skipped. If the xFunc() is being hooked, the error message ** will be emitted. ** ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped. ** ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be ** skipped. ** ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be ** executed. */ int rc; if( !g.fNoThHook ){ rc = Th_WebpageHook(pCmd->zName+1, pCmd->eCmdFlags); }else{ rc = TH_OK; } if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ if( rc==TH_OK || rc==TH_RETURN ){ #endif g.zPhase = pCmd->zName; pCmd->xFunc(); #ifdef FOSSIL_ENABLE_TH1_HOOKS } if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ Th_WebpageNotify(pCmd->zName+1, pCmd->eCmdFlags); } } } #endif } /* Return the result. */ g.zPhase = "web-page reply"; cgi_reply(); } /* If the CGI program contains one or more lines of the form ** ** redirect: repository-filename http://hostname/path/%s ** ** then control jumps here. Search each repository for an artifact ID ** or ticket ID that matches the "name" query parameter. If there is ** no "name" query parameter, use PATH_INFO instead. If a match is ** found, redirect to the corresponding URL. Substitute "%s" in the ** URL with the value of the name query parameter before the redirect. ** ** If there is a line of the form: ** ** redirect: * URL ** ** Then a redirect is made to URL if no match is found. If URL contains ** "%s" then substitute the "name" query parameter. If REPO is "*" and ** URL does not contains "%s" and does not contain "?" then append ** PATH_INFO and QUERY_STRING to the URL prior to the redirect. ** ** If no matches are found and if there is no "*" entry, then generate ** a primitive error message. ** ** USE CASES: ** ** (1) Suppose you have two related projects projA and projB. You can ** use this feature to set up an /info page that covers both ** projects. ** ** redirect: /fossils/projA.fossil /proj-a/info/%s ** redirect: /fossils/projB.fossil /proj-b/info/%s ** ** Then visits to the /info/HASH page will redirect to the ** first project that contains that hash. ** ** (2) Use the "*" form for to redirect legacy URLs. On the Fossil ** website we have an CGI at http://fossil.com/index.html (note ** ".com" instead of ".org") that looks like this: ** ** #!/usr/bin/fossil ** redirect: * https://fossil-scm.org/home ** ** Thus requests to the .com website redirect to the .org website. */ static void redirect_web_page(int nRedirect, char **azRedirect){ int i; /* Loop counter */ const char *zNotFound = 0; /* Not found URL */ const char *zName = P("name"); set_base_url(0); if( zName==0 ){ zName = P("PATH_INFO"); if( zName && zName[0]=='/' ) zName++; } if( zName ){ for(i=0; i @ No Such Object @ @

No such object: %h(zName)

@ cgi_reply(); } } /* ** COMMAND: cgi* ** ** Usage: %fossil ?cgi? FILE ** ** This command causes Fossil to generate reply to a CGI request. ** ** The FILE argument is the name of a control file that provides Fossil ** with important information such as where to find its repository. In ** a typical CGI deployment, FILE is the name of the CGI script and will ** typically look something like this: ** ** #!/usr/bin/fossil ** repository: /home/somebody/project.db ** ** The command name, "cgi", may be omitted if the GATEWAY_INTERFACE ** environment variable is set to "CGI", which should always be the ** case for CGI scripts run by a webserver. Fossil ignores any lines ** that begin with "#". ** ** The following control lines are recognized: ** ** repository: PATH Name of the Fossil repository ** ** directory: PATH Name of a directory containing many Fossil ** repositories whose names all end with ".fossil". ** There should only be one of "repository:" ** or "directory:" ** ** notfound: URL When in "directory:" mode, redirect to ** URL if no suitable repository is found. ** ** repolist When in "directory:" mode, display a page ** showing a list of available repositories if ** the URL is "/". ** ** localauth Grant administrator privileges to connections ** from 127.0.0.1 or ::1. ** ** skin: LABEL Use the built-in skin called LABEL rather than ** the default. If there are no skins called LABEL ** then this line is a no-op. ** ** files: GLOBLIST GLOBLIST is a comma-separated list of GLOB ** patterns that specify files that can be ** returned verbatim. This feature allows Fossil ** to act as a web server returning static ** content. ** ** setenv: NAME VALUE Set environment variable NAME to VALUE. Or ** if VALUE is omitted, unset NAME. ** ** HOME: PATH Shorthand for "setenv: HOME PATH" ** ** cgi-debug: FILE Causing debugging information to be written ** into FILE. ** ** errorlog: FILE Warnings, errors, and panics written to FILE. ** ** timeout: SECONDS Do not run for longer than SECONDS. The default ** timeout is FOSSIL_DEFAULT_TIMEOUT (600) seconds. ** ** extroot: DIR Directory that is the root of the sub-CGI tree ** on the /ext page. ** ** redirect: REPO URL Extract the "name" query parameter and search ** REPO for a check-in or ticket that matches the ** value of "name", then redirect to URL. There ** can be multiple "redirect:" lines that are ** processed in order. If the REPO is "*", then ** an unconditional redirect to URL is taken. ** ** jsmode: VALUE Specifies the delivery mode for JavaScript ** files. See the help text for the --jsmode ** flag of the http command. ** ** mainmenu: FILE Override the mainmenu config setting with the ** contents of the given file. ** ** Most CGI files contain only a "repository:" line. It is uncommon to ** use any other option. ** ** See also: [[http]], [[server]], [[winsrv]] */ void cmd_cgi(void){ const char *zFile; const char *zNotFound = 0; char **azRedirect = 0; /* List of repositories to redirect to */ int nRedirect = 0; /* Number of entries in azRedirect */ Glob *pFileGlob = 0; /* Pattern for files */ int allowRepoList = 0; /* Allow lists of repository files */ Blob config, line, key, value, value2; /* Initialize the CGI environment. */ g.httpOut = stdout; g.httpIn = stdin; fossil_binary_mode(g.httpOut); fossil_binary_mode(g.httpIn); g.cgiOutput = 1; fossil_set_timeout(FOSSIL_DEFAULT_TIMEOUT); /* Find the name of the CGI control file */ if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){ zFile = g.argv[2]; }else if( g.argc>=2 ){ zFile = g.argv[1]; }else{ cgi_panic("No CGI control file specified"); } /* Read and parse the CGI control file. */ blob_read_from_file(&config, zFile, ExtFILE); while( blob_line(&config, &line) ){ if( !blob_token(&line, &key) ) continue; if( blob_buffer(&key)[0]=='#' ) continue; if( blob_eq(&key, "repository:") && blob_tail(&line, &value) ){ /* repository: FILENAME ** ** The name of the Fossil repository to be served via CGI. Most ** fossil CGI scripts have a single non-comment line that contains ** this one entry. */ blob_trim(&value); db_open_repository(blob_str(&value)); blob_reset(&value); continue; } if( blob_eq(&key, "directory:") && blob_token(&line, &value) ){ /* directory: DIRECTORY ** ** If repository: is omitted, then terms of the PATH_INFO cgi parameter ** are appended to DIRECTORY looking for a repository (whose name ends ** in ".fossil") or a file in "files:". */ db_close(1); g.zRepositoryName = mprintf("%s", blob_str(&value)); blob_reset(&value); continue; } if( blob_eq(&key, "notfound:") && blob_token(&line, &value) ){ /* notfound: URL ** ** If using directory: and no suitable repository or file is found, ** then redirect to URL. */ zNotFound = mprintf("%s", blob_str(&value)); blob_reset(&value); continue; } if( blob_eq(&key, "localauth") ){ /* localauth ** ** Grant "administrator" privileges to users connecting with HTTP ** from IP address 127.0.0.1. Do not bother checking credentials. */ g.useLocalauth = 1; continue; } if( blob_eq(&key, "repolist") ){ /* repolist ** ** If using "directory:" and the URL is "/" then generate a page ** showing a list of available repositories. */ allowRepoList = 1; continue; } if( blob_eq(&key, "redirect:") && blob_token(&line, &value) && blob_token(&line, &value2) ){ /* See the header comment on the redirect_web_page() function ** above for details. */ nRedirect++; azRedirect = fossil_realloc(azRedirect, 2*nRedirect*sizeof(char*)); azRedirect[nRedirect*2-2] = mprintf("%s", blob_str(&value)); azRedirect[nRedirect*2-1] = mprintf("%s", blob_str(&value2)); blob_reset(&value); blob_reset(&value2); continue; } if( blob_eq(&key, "files:") && blob_token(&line, &value) ){ /* files: GLOBLIST ** ** GLOBLIST is a comma-separated list of filename globs. For ** example: *.html,*.css,*.js ** ** If the repository: line is omitted and then PATH_INFO is searched ** for files that match any of these GLOBs and if any such file is ** found it is returned verbatim. This feature allows "fossil server" ** to function as a primitive web-server delivering arbitrary content. */ pFileGlob = glob_create(blob_str(&value)); blob_reset(&value); continue; } if( blob_eq(&key, "setenv:") && blob_token(&line, &value) ){ /* setenv: NAME VALUE ** setenv: NAME ** ** Sets environment variable NAME to VALUE. If VALUE is omitted, then ** the environment variable is unset. */ blob_token(&line,&value2); fossil_setenv(blob_str(&value), blob_str(&value2)); blob_reset(&value); blob_reset(&value2); continue; } if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){ /* errorlog: FILENAME ** ** Causes messages from warnings, errors, and panics to be appended ** to FILENAME. */ g.zErrlog = mprintf("%s", blob_str(&value)); blob_reset(&value); continue; } if( blob_eq(&key, "extroot:") && blob_token(&line, &value) ){ /* extroot: DIRECTORY ** ** Enables the /ext webpage to use sub-cgi rooted at DIRECTORY */ g.zExtRoot = mprintf("%s", blob_str(&value)); blob_reset(&value); continue; } if( blob_eq(&key, "timeout:") && blob_token(&line, &value) ){ /* timeout: SECONDS ** ** Set an alarm() that kills the process after SECONDS. The ** default value is FOSSIL_DEFAULT_TIMEOUT (600) seconds. */ fossil_set_timeout(atoi(blob_str(&value))); continue; } if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){ /* HOME: VALUE ** ** Set CGI parameter "HOME" to VALUE. This is legacy. Use ** setenv: instead. */ cgi_setenv("HOME", blob_str(&value)); blob_reset(&value); continue; } if( blob_eq(&key, "skin:") && blob_token(&line, &value) ){ /* skin: LABEL ** ** Use one of the built-in skins defined by LABEL. LABEL is the ** name of the subdirectory under the skins/ directory that holds ** the elements of the built-in skin. If LABEL does not match, ** this directive is a silent no-op. */ fossil_free(skin_use_alternative(blob_str(&value), 1)); blob_reset(&value); continue; } if( blob_eq(&key, "jsmode:") && blob_token(&line, &value) ){ /* jsmode: MODE ** ** Change how JavaScript resources are delivered with each HTML ** page. MODE is "inline" to put all JS inline, or "separate" to ** cause each JS file to be requested using a separate HTTP request, ** or "bundled" to have all JS files to be fetched with a single ** auxiliary HTTP request. Noting, however, that "single" might ** actually mean more than one, depending on the script-timing ** requirements of any given page. */ builtin_set_js_delivery_mode(blob_str(&value),0); blob_reset(&value); continue; } if( blob_eq(&key, "mainmenu:") && blob_token(&line, &value) ){ /* mainmenu: FILENAME ** ** Use the contents of FILENAME as the value of the site's ** "mainmenu" setting, overriding the contents (for this ** request) of the db-side setting or the hard-coded default. */ g.zMainMenuFile = mprintf("%s", blob_str(&value)); blob_reset(&value); continue; } if( blob_eq(&key, "cgi-debug:") && blob_token(&line, &value) ){ /* cgi-debug: FILENAME ** ** Causes output from cgi_debug() and CGIDEBUG(()) calls to go ** into FILENAME. Useful for debugging CGI configuration problems. */ char *zNow = cgi_iso8601_datestamp(); cgi_load_environment(); g.fDebug = fossil_fopen(blob_str(&value), "ab"); blob_reset(&value); cgi_debug("-------- BEGIN cgi at %s --------\n", zNow); fossil_free(zNow); cgi_print_all(1,2); continue; } } blob_reset(&config); if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){ cgi_panic("Unable to find or open the project repository"); } cgi_init(); if( nRedirect ){ redirect_web_page(nRedirect, azRedirect); }else{ process_one_web_page(zNotFound, pFileGlob, allowRepoList); } } /* ** If g.argv[arg] exists then it is either the name of a repository ** that will be used by a server, or else it is a directory that ** contains multiple repositories that can be served. If g.argv[arg] ** is a directory, the repositories it contains must be named ** "*.fossil". If g.argv[arg] does not exist, then we must be within ** an open check-out and the repository to serve is the repository of ** that check-out. ** ** Open the repository to be served if it is known. If g.argv[arg] is ** a directory full of repositories, then set g.zRepositoryName to ** the name of that directory and the specific repository will be ** opened later by process_one_web_page() based on the content of ** the PATH_INFO variable. ** ** If the fCreate flag is set, then create the repository if it ** does not already exist. Always use "auto" hash-policy in this case. */ static void find_server_repository(int arg, int fCreate){ if( g.argc<=arg ){ db_must_be_within_tree(); }else{ const char *zRepo = g.argv[arg]; int isDir = file_isdir(zRepo, ExtFILE); if( isDir==1 ){ g.zRepositoryName = mprintf("%s", zRepo); file_simplify_name(g.zRepositoryName, -1, 0); }else{ if( isDir==0 && fCreate ){ const char *zPassword; db_create_repository(zRepo); db_open_repository(zRepo); db_begin_transaction(); g.eHashPolicy = HPOLICY_SHA3; db_set_int("hash-policy", HPOLICY_SHA3, 0); db_initial_setup(0, "now", g.zLogin); db_end_transaction(0); fossil_print("project-id: %s\n", db_get("project-code", 0)); fossil_print("server-id: %s\n", db_get("server-code", 0)); zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin); fossil_print("admin-user: %s (initial password is \"%s\")\n", g.zLogin, zPassword); cache_initialize(); g.zLogin = 0; g.userUid = 0; }else{ db_open_repository(zRepo); } } } } #if defined(_WIN32) && USE_SEE /* ** This function attempts to parse a string value in the following ** format: ** ** "%lu:%p:%u" ** ** There are three parts, which must be delimited by colons. The ** first part is an unsigned long integer in base-10 (decimal) format. ** The second part is a numerical representation of a native pointer, ** in the appropriate implementation defined format. The third part ** is an unsigned integer in base-10 (decimal) format. ** ** If the specified value cannot be parsed, for any reason, a fatal ** error will be raised and the process will be terminated. */ void parse_pid_key_value( const char *zPidKey, /* The value to be parsed. */ DWORD *pProcessId, /* The extracted process identifier. */ LPVOID *ppAddress, /* The extracted pointer value. */ SIZE_T *pnSize /* The extracted size value. */ ){ unsigned int nSize = 0; if( sscanf(zPidKey, "%lu:%p:%u", pProcessId, ppAddress, &nSize)==3 ){ *pnSize = (SIZE_T)nSize; }else{ fossil_fatal("failed to parse pid key"); } } #endif /* ** WEBPAGE: test-pid ** ** Return the process identifier of the running Fossil server instance. ** ** Query parameters: ** ** usepidkey When present and available, also return the ** address and size, within this server process, ** of the saved database encryption key. This ** is only supported when using SEE on Windows. */ void test_pid_page(void){ login_check_credentials(); if( !g.perm.Setup ){ login_needed(0); return; } #if defined(_WIN32) && USE_SEE if( P("usepidkey")!=0 ){ if( g.zPidKey ){ @ %s(g.zPidKey) return; }else{ const char *zSavedKey = db_get_saved_encryption_key(); size_t savedKeySize = db_get_saved_encryption_key_size(); if( zSavedKey!=0 && savedKeySize>0 ){ @ %lu(GetCurrentProcessId()):%p(zSavedKey):%u(savedKeySize) return; } } } #endif @ %d(GETPID()) } /* ** Check for options to "fossil server" or "fossil ui" that imply that ** SSL should be used, and initialize the SSL decoder. */ static void decode_ssl_options(void){ #if FOSSIL_ENABLE_SSL const char *zCertFile = 0; const char *zKeyFile = 0; zCertFile = find_option("cert",0,1); zKeyFile = find_option("pkey",0,1); if( zCertFile ){ g.httpUseSSL = 1; ssl_init_server(zCertFile, zKeyFile); }else if( zKeyFile ){ fossil_fatal("--pkey without a corresponding --cert"); } #endif } /* ** COMMAND: http* ** ** Usage: %fossil http ?REPOSITORY? ?OPTIONS? ** ** Handle a single HTTP request appearing on stdin. The resulting webpage ** is delivered on stdout. This method is used to launch an HTTP request ** handler from inetd, for example. The REPOSITORY argument is the name of ** the repository. ** ** If REPOSITORY is a directory that contains one or more repositories, ** either directly in REPOSITORY itself or in subdirectories, and ** with names of the form "*.fossil" then a prefix of the URL pathname ** selects from among the various repositories. If the pathname does ** not select a valid repository and the --notfound option is available, ** then the server redirects (HTTP code 302) to the URL of --notfound. ** When REPOSITORY is a directory, the pathname must contain only ** alphanumerics, "_", "/", "-" and "." and no "-" may occur after a "/" ** and every "." must be surrounded on both sides by alphanumerics or else ** a 404 error is returned. Static content files in the directory are ** returned if they match comma-separate GLOB pattern specified by --files ** and do not match "*.fossil*" and have a well-known suffix. ** ** Options: ** --acme Deliver files from the ".well-known" subdirectory ** --baseurl URL base URL (useful with reverse proxies) ** --cert FILE Use TLS (HTTPS) encryption with the certificate (the ** fullchain.pem) taken from FILE. ** --chroot DIR Use directory for chroot instead of repository path. ** --ckout-alias N Treat URIs of the form /doc/N/... as if they were ** /doc/ckout/... ** --extroot DIR Document root for the /ext extension mechanism ** --files GLOB Comma-separate glob patterns for static file to serve ** --host NAME DNS Hostname of the server ** --https The HTTP request originated from https but has already ** been decoded by a reverse proxy. Hence, URLs created ** by Fossil should use "https:" rather than "http:". ** --in FILE Take input from FILE instead of standard input ** --ipaddr ADDR Assume the request comes from the given IP address ** --jsmode MODE Determine how JavaScript is delivered with pages. ** Mode can be one of: ** inline All JavaScript is inserted inline at ** one or more points in the HTML file. ** separate Separate HTTP requests are made for ** each JavaScript file. ** bundled Groups JavaScript files into one or ** more bundled requests which ** concatenate scripts together. ** Depending on the needs of any given page, inline ** and bundled modes might result in a single ** amalgamated script or several, but both approaches ** result in fewer HTTP requests than the separate mode. ** --localauth Connections from localhost are given "setup" ** privileges without having to log in. ** --mainmenu FILE Override the mainmenu config setting with the contents ** of the given file. ** --nocompress Do not compress HTTP replies ** --nodelay Omit backoffice processing if it would delay ** process exit ** --nojail Drop root privilege but do not enter the chroot jail ** --nossl Do not do http: to https: redirects, regardless of ** the redirect-to-https setting. ** --notfound URL Use URL as the "HTTP 404, object not found" page. ** --out FILE Write the HTTP reply to FILE instead of to ** standard output ** --pkey FILE Read the private key used for TLS from FILE. ** --repolist If REPOSITORY is directory, URL "/" lists all repos ** --scgi Interpret input as SCGI rather than HTTP ** --skin LABEL Use override skin LABEL ** --th-trace Trace TH1 execution (for debugging purposes) ** --usepidkey Use saved encryption key from parent process. This is ** only necessary when using SEE on Windows. ** ** See also: [[cgi]], [[server]], [[winsrv]] */ void cmd_http(void){ const char *zIpAddr = 0; const char *zNotFound; const char *zHost; const char *zAltBase; const char *zFileGlob; const char *zInFile; const char *zOutFile; const char *zChRoot; int useSCGI; int noJail; int allowRepoList; Th_InitTraceLog(); builtin_set_js_delivery_mode(find_option("jsmode",0,1),0); /* The winhttp module passes the --files option as --files-urlenc with ** the argument being URL encoded, to avoid wildcard expansion in the ** shell. This option is for internal use and is undocumented. */ zFileGlob = find_option("files-urlenc",0,1); if( zFileGlob ){ char *z = mprintf("%s", zFileGlob); dehttpize(z); zFileGlob = z; }else{ zFileGlob = find_option("files",0,1); } skin_override(); zNotFound = find_option("notfound", 0, 1); zChRoot = find_option("chroot",0,1); noJail = find_option("nojail",0,0)!=0; allowRepoList = find_option("repolist",0,0)!=0; g.useLocalauth = find_option("localauth", 0, 0)!=0; g.sslNotAvailable = find_option("nossl", 0, 0)!=0; g.fNoHttpCompress = find_option("nocompress",0,0)!=0; g.zExtRoot = find_option("extroot",0,1); g.zCkoutAlias = find_option("ckout-alias",0,1); zInFile = find_option("in",0,1); if( zInFile ){ backoffice_disable(); g.httpIn = fossil_fopen(zInFile, "rb"); if( g.httpIn==0 ) fossil_fatal("cannot open \"%s\" for reading", zInFile); }else{ g.httpIn = stdin; #if defined(_WIN32) _setmode(_fileno(stdin), _O_BINARY); #endif } zOutFile = find_option("out",0,1); if( zOutFile ){ g.httpOut = fossil_fopen(zOutFile, "wb"); if( g.httpOut==0 ) fossil_fatal("cannot open \"%s\" for writing", zOutFile); }else{ g.httpOut = stdout; #if defined(_WIN32) _setmode(_fileno(stdout), _O_BINARY); #endif } zIpAddr = find_option("ipaddr",0,1); useSCGI = find_option("scgi", 0, 0)!=0; zAltBase = find_option("baseurl", 0, 1); if( find_option("nodelay",0,0)!=0 ) backoffice_no_delay(); if( zAltBase ) set_base_url(zAltBase); if( find_option("https",0,0)!=0 ){ zIpAddr = fossil_getenv("REMOTE_HOST"); /* From stunnel */ cgi_replace_parameter("HTTPS","on"); } zHost = find_option("host", 0, 1); if( zHost ) cgi_replace_parameter("HTTP_HOST",zHost); g.zMainMenuFile = find_option("mainmenu",0,1); if( g.zMainMenuFile!=0 && file_size(g.zMainMenuFile,ExtFILE)<0 ){ fossil_fatal("Cannot read --mainmenu file %s", g.zMainMenuFile); } decode_ssl_options(); if( find_option("acme",0,0)!=0 ) g.fAllowACME = 1; /* We should be done with options.. */ verify_all_options(); if( g.httpUseSSL ){ if( useSCGI ){ fossil_fatal("SSL not (yet) supported for SCGI"); } if( g.fSshClient & CGI_SSH_CLIENT ){ fossil_fatal("SSL not compatible with SSH"); } if( zInFile || zOutFile ){ fossil_fatal("SSL usable only on a socket"); } cgi_replace_parameter("HTTPS","on"); } if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?"); g.cgiOutput = 1; g.fullHttpReply = 1; find_server_repository(2, 0); if( zIpAddr==0 ){ zIpAddr = cgi_ssh_remote_addr(0); if( zIpAddr && zIpAddr[0] ){ g.fSshClient |= CGI_SSH_CLIENT; } } if( zChRoot ){ enter_chroot_jail((char*)zChRoot, noJail); }else{ g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); } if( useSCGI ){ cgi_handle_scgi_request(); }else if( g.fSshClient & CGI_SSH_CLIENT ){ ssh_request_loop(zIpAddr, glob_create(zFileGlob)); }else{ #if FOSSIL_ENABLE_SSL if( g.httpUseSSL ){ g.httpSSLConn = ssl_new_server(0); } #endif cgi_handle_http_request(zIpAddr); } process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList); #if FOSSIL_ENABLE_SSL if( g.httpUseSSL && g.httpSSLConn ){ ssl_close_server(g.httpSSLConn); g.httpSSLConn = 0; } #endif /* FOSSIL_ENABLE_SSL */ } /* ** Process all requests in a single SSH connection if possible. */ void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){ blob_zero(&g.cgiIn); do{ cgi_handle_ssh_http_request(zIpAddr); process_one_web_page(0, FileGlob, 0); blob_reset(&g.cgiIn); } while ( g.fSshClient & CGI_SSH_FOSSIL || g.fSshClient & CGI_SSH_COMPAT ); } /* ** COMMAND: test-http ** ** Works like the [[http]] command but gives setup permission to all users, ** or whatever permission is described by "--usercap CAP". ** ** This command can used for interactive debugging of web pages. For ** example, one can put a simple HTTP request in a file like this: ** ** echo 'GET /timeline' >request.txt ** ** Then run (in a debugger) a command like this: ** ** fossil test-http --debug 0 ){ /* If REPOSITORY arg is the root of a checkout, ** chdir to that checkout so that the current version ** gets highlighted in the timeline by default. */ const char * zDir = g.argv[2]; if(dir_has_ckout_db(zDir)){ if(0!=file_chdir(zDir, 0)){ fossil_fatal("Cannot chdir to %s", zDir); } findServerArg = 99; fCreate = 0; g.argv[2] = 0; --g.argc; } } if( isUiCmd && 3==g.argc && (zRemote = (char*)file_skip_userhost(g.argv[2]))!=0 ){ /* The REPOSITORY argument has a USER@HOST: or HOST: prefix */ const char *zRepoTail = file_skip_userhost(g.argv[2]); unsigned x; int n; sqlite3_randomness(2,&x); zPort = mprintf("%d", 8100+(x%32000)); n = (int)(zRepoTail - g.argv[2]) - 1; zRemote = mprintf("%.*s", n, g.argv[2]); g.argv[2] = (char*)zRepoTail; } if( isUiCmd ){ flags |= HTTP_SERVER_LOCALHOST|HTTP_SERVER_REPOLIST; g.useLocalauth = 1; allowRepoList = 1; } if( !zRemote ){ find_server_repository(findServerArg, fCreate); } if( zInitPage==0 ){ if( isUiCmd && g.localOpen ){ zInitPage = "timeline?c=current"; }else{ zInitPage = ""; } } if( zPort ){ if( strchr(zPort,':') ){ int i; for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){} if( i>0 ){ if( zPort[0]=='[' && zPort[i-1]==']' ){ zIpAddr = mprintf("%.*s", i-2, zPort+1); }else{ zIpAddr = mprintf("%.*s", i, zPort); } zPort += i+1; } } iPort = mxPort = atoi(zPort); }else{ iPort = db_get_int("http-port", 8080); mxPort = iPort+100; } if( isUiCmd && !fNoBrowser ){ char *zBrowserArg; const char *zProtocol = g.httpUseSSL ? "https" : "http"; if( zRemote ) db_open_config(0,0); zBrowser = fossil_web_browser(); if( zIpAddr==0 ){ zBrowserArg = mprintf("%s://localhost:%%d/%s", zProtocol, zInitPage); }else if( strchr(zIpAddr,':') ){ zBrowserArg = mprintf("%s://[%s]:%%d/%s", zProtocol, zIpAddr, zInitPage); }else{ zBrowserArg = mprintf("%s://%s:%%d/%s", zProtocol, zIpAddr, zInitPage); } zBrowserCmd = mprintf("%s %!$ &", zBrowser, zBrowserArg); fossil_free(zBrowserArg); } if( zRemote ){ /* If a USER@HOST:REPO argument is supplied, then use SSH to run ** "fossil ui --nobrowser" on the remote system and to set up a ** tunnel from the local machine to the remote. */ FILE *sshIn; Blob ssh; char zLine[1000]; blob_init(&ssh, 0, 0); transport_ssh_command(&ssh); db_close_config(); if( zFossilCmd==0 ) zFossilCmd = "fossil"; blob_appendf(&ssh, " -t -L 127.0.0.1:%d:127.0.0.1:%d %!$" " %$ ui --nobrowser --localauth --port %d", iPort, iPort, zRemote, zFossilCmd, iPort); if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound); if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob); if( g.zCkoutAlias ) blob_appendf(&ssh, " --ckout-alias %!$",g.zCkoutAlias); if( g.zExtRoot ) blob_appendf(&ssh, " --extroot %$", g.zExtRoot); if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use()); if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode); if( fCreate ) blob_appendf(&ssh, " --create"); blob_appendf(&ssh, " %$", g.argv[2]); fossil_print("%s\n", blob_str(&ssh)); sshIn = popen(blob_str(&ssh), "r"); if( sshIn==0 ){ fossil_fatal("unable to %s", blob_str(&ssh)); } while( fgets(zLine, sizeof(zLine), sshIn) ){ fputs(zLine, stdout); fflush(stdout); if( zBrowserCmd && sqlite3_strglob("*Listening for HTTP*",zLine)==0 ){ char *zCmd = mprintf(zBrowserCmd/*works-like:"%d"*/,iPort); fossil_system(zCmd); fossil_free(zCmd); fossil_free(zBrowserCmd); zBrowserCmd = 0; } } pclose(sshIn); fossil_free(zBrowserCmd); return; } if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY; if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT; db_close(1); /* Start up an HTTP server */ fossil_setenv("SERVER_SOFTWARE", "fossil version " RELEASE_VERSION " " MANIFEST_VERSION " " MANIFEST_DATE); #if !defined(_WIN32) /* Unix implementation */ if( cgi_http_server(iPort, mxPort, zBrowserCmd, zIpAddr, flags) ){ fossil_fatal("unable to listen on TCP socket %d", iPort); } /* For the parent process, the cgi_http_server() command above never ** returns (except in the case of an error). Instead, for each incoming ** client connection, a child process is created, file descriptors 0 ** and 1 are bound to that connection, and the child returns. ** ** So, when control reaches this point, we are running as a ** child process, the HTTP or SCGI request is pending on file ** descriptor 0 and the reply should be written to file descriptor 1. */ if( zTimeout ){ fossil_set_timeout(atoi(zTimeout)); }else{ fossil_set_timeout(FOSSIL_DEFAULT_TIMEOUT); } g.httpIn = stdin; g.httpOut = stdout; signal(SIGSEGV, sigsegv_handler); signal(SIGPIPE, sigpipe_handler); if( g.fAnyTrace ){ fprintf(stderr, "/***** Subprocess %d *****/\n", getpid()); } g.cgiOutput = 1; find_server_repository(2, 0); if( fossil_strcmp(g.zRepositoryName,"/")==0 ){ allowRepoList = 1; }else{ if( zChRoot ){ enter_chroot_jail((char*)zChRoot, noJail); }else{ g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); } } if( flags & HTTP_SERVER_SCGI ){ cgi_handle_scgi_request(); }else if( g.httpUseSSL ){ #if FOSSIL_ENABLE_SSL g.httpSSLConn = ssl_new_server(0); #endif cgi_handle_http_request(0); }else{ cgi_handle_http_request(0); } process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList); if( g.fAnyTrace ){ fprintf(stderr, "/***** Webpage finished in subprocess %d *****/\n", getpid()); } #if FOSSIL_ENABLE_SSL if( g.httpUseSSL && g.httpSSLConn ){ ssl_close_server(g.httpSSLConn); g.httpSSLConn = 0; } #endif /* FOSSIL_ENABLE_SSL */ #else /* WIN32 */ /* Win32 implementation */ if( allowRepoList ){ flags |= HTTP_SERVER_REPOLIST; } if( win32_http_service(iPort, zAltBase, zNotFound, zFileGlob, flags) ){ win32_http_server(iPort, mxPort, zBrowserCmd, zStopperFile, zAltBase, zNotFound, zFileGlob, zIpAddr, flags); } #endif } /* ** COMMAND: test-echo ** ** Usage: %fossil test-echo [--hex] ARGS... ** ** Echo all command-line arguments (enclosed in [...]) to the screen so that ** wildcard expansion behavior of the host shell can be investigated. ** ** With the --hex option, show the output as hexadecimal. This can be used ** to verify the fossil_path_to_utf8() routine on Windows and Mac. */ void test_echo_cmd(void){ int i, j; if( find_option("hex",0,0)==0 ){ fossil_print("g.nameOfExe = [%s]\n", g.nameOfExe); for(i=0; i4 ){ @

Generate a message to the error log @ by clicking on one of the following cases: }else{ @

This is the test page for case=%d(iCase). All possible cases: } for(i=1; i<=8; i++){ @ [%d(i)] } @

@

    @
  1. Call fossil_warning() if( iCase==1 ){ fossil_warning("Test warning message from /test-warning"); } @
  2. Call db_begin_transaction() if( iCase==2 ){ db_begin_transaction(); } @
  3. Call db_end_transaction() if( iCase==3 ){ db_end_transaction(0); } @
  4. warning during SQL if( iCase==4 ){ Stmt q; db_prepare(&q, "SELECT uuid FROM blob LIMIT 5"); db_step(&q); sqlite3_log(SQLITE_ERROR, "Test warning message during SQL"); db_finalize(&q); } @
  5. simulate segfault handling if( iCase==5 ){ sigsegv_handler(0); } @
  6. call webpage_assert(0) if( iCase==6 ){ webpage_assert( 5==7 ); } @
  7. call webpage_error()" if( iCase==7 ){ cgi_reset_content(); webpage_error("Case 7 from /test-warning"); } @
  8. simulated timeout" if( iCase==8 ){ fossil_set_timeout(1); cgi_reset_content(); sqlite3_sleep(1100); } @
@

End of test

style_finish_page(); }