1 /*
2 ** Copyright (c) 2006 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
6 ** known as the "2-Clause License" or "FreeBSD License".)
7 **
8 ** This program is distributed in the hope that it will be useful,
9 ** but without any warranty; without even the implied warranty of
10 ** merchantability or fitness for a particular purpose.
11 **
12 ** Author contact information:
13 ** drh@hwaci.com
14 ** http://www.hwaci.com/drh/
15 **
16 *******************************************************************************
17 **
18 ** This module codes the main() procedure that runs first when the
19 ** program is invoked.
20 */
21 #include "VERSION.h"
22 #include "config.h"
23 #include "main.h"
24 #include <string.h>
25 #include <time.h>
26 #include <fcntl.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <stdlib.h> /* atexit() */
30 #if defined(_WIN32)
31 # include <windows.h>
32 #else
33 # include <errno.h> /* errno global */
34 #endif
35 #ifdef FOSSIL_ENABLE_SSL
36 # include "openssl/crypto.h"
37 #endif
38 #if defined(FOSSIL_ENABLE_MINIZ)
39 # define MINIZ_HEADER_FILE_ONLY
40 # include "miniz.c"
41 #else
42 # include <zlib.h>
43 #endif
44 #if INTERFACE
45 #ifdef FOSSIL_ENABLE_TCL
46 # include "tcl.h"
47 #endif
48 #ifdef FOSSIL_ENABLE_JSON
49 # include "cson_amalgamation.h" /* JSON API. */
50 # include "json_detail.h"
51 #endif
52
53 /*
54 ** Number of elements in an array
55 */
56 #define count(X) (sizeof(X)/sizeof(X[0]))
57
58 /*
59 ** Size of a UUID in characters
60 */
61 #define UUID_SIZE 40
62
63 /*
64 ** Maximum number of auxiliary parameters on reports
65 */
66 #define MX_AUX 5
67
68 /*
69 ** Holds flags for fossil user permissions.
70 */
71 struct FossilUserPerms {
72 char Setup; /* s: use Setup screens on web interface */
73 char Admin; /* a: administrative permission */
74 char Delete; /* d: delete wiki or tickets */
75 char Password; /* p: change password */
76 char Query; /* q: create new reports */
77 char Write; /* i: xfer inbound. check-in */
78 char Read; /* o: xfer outbound. check-out */
79 char Hyperlink; /* h: enable the display of hyperlinks */
80 char Clone; /* g: clone */
81 char RdWiki; /* j: view wiki via web */
82 char NewWiki; /* f: create new wiki via web */
83 char ApndWiki; /* m: append to wiki via web */
84 char WrWiki; /* k: edit wiki via web */
85 char ModWiki; /* l: approve and publish wiki content (Moderator) */
86 char RdTkt; /* r: view tickets via web */
87 char NewTkt; /* n: create new tickets */
88 char ApndTkt; /* c: append to tickets via the web */
89 char WrTkt; /* w: make changes to tickets via web */
90 char ModTkt; /* q: approve and publish ticket changes (Moderator) */
91 char Attach; /* b: add attachments */
92 char TktFmt; /* t: create new ticket report formats */
93 char RdAddr; /* e: read email addresses or other private data */
94 char Zip; /* z: download zipped artifact via /zip URL */
95 char Private; /* x: can send and receive private content */
96 };
97
98 #ifdef FOSSIL_ENABLE_TCL
99 /*
100 ** All Tcl related context information is in this structure. This structure
101 ** definition has been copied from and should be kept in sync with the one in
102 ** "th_tcl.c".
103 */
104 struct TclContext {
105 int argc; /* Number of original (expanded) arguments. */
106 char **argv; /* Full copy of the original (expanded) arguments. */
107 void *hLibrary; /* The Tcl library module handle. */
108 void *xFindExecutable; /* See tcl_FindExecutableProc in th_tcl.c. */
109 void *xCreateInterp; /* See tcl_CreateInterpProc in th_tcl.c. */
110 void *xDeleteInterp; /* See tcl_DeleteInterpProc in th_tcl.c. */
111 void *xFinalize; /* See tcl_FinalizeProc in th_tcl.c. */
112 Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */
113 int useObjProc; /* Non-zero if an objProc can be called directly. */
114 int useTip285; /* Non-zero if TIP #285 is available. */
115 char *setup; /* The optional Tcl setup script. */
116 void *xPreEval; /* Optional, called before Tcl_Eval*(). */
117 void *pPreContext; /* Optional, provided to xPreEval(). */
118 void *xPostEval; /* Optional, called after Tcl_Eval*(). */
119 void *pPostContext; /* Optional, provided to xPostEval(). */
120 };
121 #endif
122
123 struct Global {
124 int argc; char **argv; /* Command-line arguments to the program */
125 char *nameOfExe; /* Full path of executable. */
126 const char *zErrlog; /* Log errors to this file, if not NULL */
127 int isConst; /* True if the output is unchanging & cacheable */
128 const char *zVfsName; /* The VFS to use for database connections */
129 sqlite3 *db; /* The connection to the databases */
130 sqlite3 *dbConfig; /* Separate connection for global_config table */
131 char *zAuxSchema; /* Main repository aux-schema */
132 int useAttach; /* True if global_config is attached to repository */
133 const char *zConfigDbName;/* Path of the config database. NULL if not open */
134 sqlite3_int64 now; /* Seconds since 1970 */
135 int repositoryOpen; /* True if the main repository database is open */
136 char *zRepositoryOption; /* Most recent cached repository option value */
137 char *zRepositoryName; /* Name of the repository database */
138 char *zLocalDbName; /* Name of the local database */
139 const char *zMainDbType;/* "configdb", "localdb", or "repository" */
140 const char *zConfigDbType; /* "configdb", "localdb", or "repository" */
141 char *zOpenRevision; /* Check-in version to use during database open */
142 int localOpen; /* True if the local database is open */
143 char *zLocalRoot; /* The directory holding the local database */
144 int minPrefix; /* Number of digits needed for a distinct UUID */
145 int fSqlTrace; /* True if --sqltrace flag is present */
146 int fSqlStats; /* True if --sqltrace or --sqlstats are present */
147 int fSqlPrint; /* True if -sqlprint flag is present */
148 int fQuiet; /* True if -quiet flag is present */
149 int fHttpTrace; /* Trace outbound HTTP requests */
150 int fAnyTrace; /* Any kind of tracing */
151 char *zHttpAuth; /* HTTP Authorization user:pass information */
152 int fSystemTrace; /* Trace calls to fossil_system(), --systemtrace */
153 int fSshTrace; /* Trace the SSH setup traffic */
154 int fSshClient; /* HTTP client flags for SSH client */
155 char *zSshCmd; /* SSH command string */
156 int fNoSync; /* Do not do an autosync ever. --nosync */
157 int fIPv4; /* Use only IPv4, not IPv6. --ipv4 */
158 char *zPath; /* Name of webpage being served */
159 char *zExtra; /* Extra path information past the webpage name */
160 char *zBaseURL; /* Full text of the URL being served */
161 char *zHttpsURL; /* zBaseURL translated to https: */
162 char *zTop; /* Parent directory of zPath */
163 const char *zContentType; /* The content type of the input HTTP request */
164 int iErrPriority; /* Priority of current error message */
165 char *zErrMsg; /* Text of an error message */
166 int sslNotAvailable; /* SSL is not available. Do not redirect to https: */
167 Blob cgiIn; /* Input to an xfer www method */
168 int cgiOutput; /* Write error and status messages to CGI */
169 int xferPanic; /* Write error messages in XFER protocol */
170 int fullHttpReply; /* True for full HTTP reply. False for CGI reply */
171 Th_Interp *interp; /* The TH1 interpreter */
172 char *th1Setup; /* The TH1 post-creation setup script, if any */
173 int th1Flags; /* The TH1 integration state flags */
174 FILE *httpIn; /* Accept HTTP input from here */
175 FILE *httpOut; /* Send HTTP output here */
176 int xlinkClusterOnly; /* Set when cloning. Only process clusters */
177 int fTimeFormat; /* 1 for UTC. 2 for localtime. 0 not yet selected */
178 int *aCommitFile; /* Array of files to be committed */
179 int markPrivate; /* All new artifacts are private if true */
180 int clockSkewSeen; /* True if clocks on client and server out of sync */
181 int wikiFlags; /* Wiki conversion flags applied to %W */
182 char isHTTP; /* True if server/CGI modes, else assume CLI. */
183 char javascriptHyperlink; /* If true, set href= using script, not HTML */
184 Blob httpHeader; /* Complete text of the HTTP request header */
185 UrlData url; /* Information about current URL */
186 const char *zLogin; /* Login name. NULL or "" if not logged in. */
187 const char *zSSLIdentity; /* Value of --ssl-identity option, filename of
188 ** SSL client identity */
189 int useLocalauth; /* No login required if from 127.0.0.1 */
190 int noPswd; /* Logged in without password (on 127.0.0.1) */
191 int userUid; /* Integer user id */
192 int isHuman; /* True if access by a human, not a spider or bot */
193 int comFmtFlags; /* Zero or more "COMMENT_PRINT_*" bit flags */
194
195 /* Information used to populate the RCVFROM table */
196 int rcvid; /* The rcvid. 0 if not yet defined. */
197 char *zIpAddr; /* The remote IP address */
198 char *zNonce; /* The nonce used for login */
199
200 /* permissions available to current user */
201 struct FossilUserPerms perm;
202
203 /* permissions available to current user or to "anonymous".
204 ** This is the logical union of perm permissions above with
205 ** the value that perm would take if g.zLogin were "anonymous". */
206 struct FossilUserPerms anon;
207
208 #ifdef FOSSIL_ENABLE_TCL
209 /* all Tcl related context necessary for integration */
210 struct TclContext tcl;
211 #endif
212
213 /* For defense against Cross-site Request Forgery attacks */
214 char zCsrfToken[12]; /* Value of the anti-CSRF token */
215 int okCsrf; /* Anti-CSRF token is present and valid */
216
217 int parseCnt[10]; /* Counts of artifacts parsed */
218 FILE *fDebug; /* Write debug information here, if the file exists */
219 #ifdef FOSSIL_ENABLE_TH1_HOOKS
220 int fNoThHook; /* Disable all TH1 command/webpage hooks */
221 #endif
222 int thTrace; /* True to enable TH1 debugging output */
223 Blob thLog; /* Text of the TH1 debugging output */
224
225 int isHome; /* True if rendering the "home" page */
226
227 /* Storage for the aux() and/or option() SQL function arguments */
228 int nAux; /* Number of distinct aux() or option() values */
229 const char *azAuxName[MX_AUX]; /* Name of each aux() or option() value */
230 char *azAuxParam[MX_AUX]; /* Param of each aux() or option() value */
231 const char *azAuxVal[MX_AUX]; /* Value of each aux() or option() value */
232 const char **azAuxOpt[MX_AUX]; /* Options of each option() value */
233 int anAuxCols[MX_AUX]; /* Number of columns for option() values */
234
235 int allowSymlinks; /* Cached "allow-symlinks" option */
236
237 int mainTimerId; /* Set to fossil_timer_start() */
238 #ifdef FOSSIL_ENABLE_JSON
239 struct FossilJsonBits {
240 int isJsonMode; /* True if running in JSON mode, else
241 false. This changes how errors are
242 reported. In JSON mode we try to
243 always output JSON-form error
244 responses and always exit() with
245 code 0 to avoid an HTTP 500 error.
246 */
247 int resultCode; /* used for passing back specific codes
248 ** from /json callbacks. */
249 int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
250 cson_output_opt outOpt; /* formatting options for JSON mode. */
251 cson_value *authToken; /* authentication token */
252 const char *jsonp; /* Name of JSONP function wrapper. */
253 unsigned char dispatchDepth /* Tells JSON command dispatching
254 which argument we are currently
255 working on. For this purpose, arg#0
256 is the "json" path/CLI arg.
257 */;
258 struct { /* "garbage collector" */
259 cson_value *v;
260 cson_array *a;
261 } gc;
262 struct { /* JSON POST data. */
263 cson_value *v;
264 cson_array *a;
265 int offset; /* Tells us which PATH_INFO/CLI args
266 part holds the "json" command, so
267 that we can account for sub-repos
268 and path prefixes. This is handled
269 differently for CLI and CGI modes.
270 */
271 const char *commandStr /*"command" request param.*/;
272 } cmd;
273 struct { /* JSON POST data. */
274 cson_value *v;
275 cson_object *o;
276 } post;
277 struct { /* GET/COOKIE params in JSON mode. */
278 cson_value *v;
279 cson_object *o;
280 } param;
281 struct {
282 cson_value *v;
283 cson_object *o;
284 } reqPayload; /* request payload object (if any) */
285 cson_array *warnings; /* response warnings */
286 int timerId; /* fetched from fossil_timer_start() */
287 } json;
288 #endif /* FOSSIL_ENABLE_JSON */
289 };
290
291 /*
292 ** Macro for debugging:
293 */
294 #define CGIDEBUG(X) if( g.fDebug ) cgi_debug X
295
296 #endif
297
298 Global g;
299
300 /*
301 ** The table of web pages supported by this application is generated
302 ** automatically by the "mkindex" program and written into a file
303 ** named "page_index.h". We include that file here to get access
304 ** to the table.
305 */
306 #include "page_index.h"
307
308 /*
309 ** Search for a function whose name matches zName. Write a pointer to
310 ** that function into *pxFunc and return 0. If no match is found,
311 ** return 1. If the command is ambiguous return 2;
312 **
313 ** The NameMap structure and the tables we are searching against are
314 ** defined in the page_index.h header file which is automatically
315 ** generated by mkindex.c program.
316 */
317 static int name_search(
318 const char *zName, /* The name we are looking for */
319 const NameMap *aMap, /* Search in this array */
320 int nMap, /* Number of slots in aMap[] */
321 int iBegin, /* Lower bound on the array search */
322 int *pIndex /* OUT: The index in aMap[] of the match */
323 ){
324 int upr, lwr, cnt, m, i;
325 int n = strlen(zName);
326 lwr = iBegin;
327 upr = nMap-1;
328 while( lwr<=upr ){
329 int mid, c;
330 mid = (upr+lwr)/2;
331 c = fossil_strcmp(zName, aMap[mid].zName);
332 if( c==0 ){
333 *pIndex = mid;
334 return 0;
335 }else if( c<0 ){
336 upr = mid - 1;
337 }else{
338 lwr = mid + 1;
339 }
340 }
341 for(m=cnt=0, i=upr-2; cnt<2 && i<=upr+3 && i<nMap; i++){
342 if( i<iBegin ) continue;
343 if( strncmp(zName, aMap[i].zName, n)==0 ){
344 m = i;
345 cnt++;
346 }
347 }
348 if( cnt==1 ){
349 *pIndex = m;
350 return 0;
351 }
352 return 1+(cnt>1);
353 }
354
355 /*
356 ** atexit() handler which frees up "some" of the resources
357 ** used by fossil.
358 */
359 static void fossil_atexit(void) {
360 #if defined(_WIN32) && !defined(_WIN64) && defined(FOSSIL_ENABLE_TCL) && \
361 defined(USE_TCL_STUBS)
362 /*
363 ** If Tcl is compiled on Windows using the latest MinGW, Fossil can crash
364 ** when exiting while a stubs-enabled Tcl is still loaded. This is due to
365 ** a bug in MinGW, see:
366 **
367 ** http://comments.gmane.org/gmane.comp.gnu.mingw.user/41724
368 **
369 ** The workaround is to manually unload the loaded Tcl library prior to
370 ** exiting the process. This issue does not impact 64-bit Windows.
371 */
372 unloadTcl(g.interp, &g.tcl);
373 #endif
374 #ifdef FOSSIL_ENABLE_JSON
375 cson_value_free(g.json.gc.v);
376 memset(&g.json, 0, sizeof(g.json));
377 #endif
378 free(g.zErrMsg);
379 if(g.db){
380 db_close(0);
381 }
382 /*
383 ** FIXME: The next two lines cannot always be enabled; however, they
384 ** are very useful for tracking down TH1 memory leaks.
385 */
386 if( fossil_getenv("TH1_DELETE_INTERP")!=0 ){
387 if( g.interp ){
388 Th_DeleteInterp(g.interp); g.interp = 0;
389 }
390 assert( Th_GetOutstandingMalloc()==0 );
391 }
392 }
393
394 /*
395 ** Convert all arguments from mbcs (or unicode) to UTF-8. Then
396 ** search g.argv for arguments "--args FILENAME". If found, then
397 ** (1) remove the two arguments from g.argv
398 ** (2) Read the file FILENAME
399 ** (3) Use the contents of FILE to replace the two removed arguments:
400 ** (a) Ignore blank lines in the file
401 ** (b) Each non-empty line of the file is an argument, except
402 ** (c) If the line begins with "-" and contains a space, it is broken
403 ** into two arguments at the space.
404 */
405 static void expand_args_option(int argc, void *argv){
406 Blob file = empty_blob; /* Content of the file */
407 Blob line = empty_blob; /* One line of the file */
408 unsigned int nLine; /* Number of lines in the file*/
409 unsigned int i, j, k; /* Loop counters */
410 int n; /* Number of bytes in one line */
411 char *z; /* General use string pointer */
412 char **newArgv; /* New expanded g.argv under construction */
413 const char *zFileName; /* input file name */
414 FILE *inFile; /* input FILE */
415 #if defined(_WIN32)
416 wchar_t buf[MAX_PATH];
417 #endif
418
419 g.argc = argc;
420 g.argv = argv;
421 sqlite3_initialize();
422 #if defined(_WIN32) && defined(BROKEN_MINGW_CMDLINE)
423 for(i=0; i<g.argc; i++) g.argv[i] = fossil_mbcs_to_utf8(g.argv[i]);
424 #else
425 for(i=0; i<g.argc; i++) g.argv[i] = fossil_path_to_utf8(g.argv[i]);
426 #endif
427 #if defined(_WIN32)
428 GetModuleFileNameW(NULL, buf, MAX_PATH);
429 g.nameOfExe = fossil_path_to_utf8(buf);
430 #else
431 g.nameOfExe = g.argv[0];
432 #endif
433 for(i=1; i<g.argc-1; i++){
434 z = g.argv[i];
435 if( z[0]!='-' ) continue;
436 z++;
437 if( z[0]=='-' ) z++;
438 if( z[0]==0 ) return; /* Stop searching at "--" */
439 if( fossil_strcmp(z, "args")==0 ) break;
440 }
441 if( i>=g.argc-1 ) return;
442
443 zFileName = g.argv[i+1];
444 inFile = (0==strcmp("-",zFileName))
445 ? stdin
446 : fossil_fopen(zFileName,"rb");
447 if(!inFile){
448 fossil_fatal("Cannot open -args file [%s]", zFileName);
449 }else{
450 blob_read_from_channel(&file, inFile, -1);
451 if(stdin != inFile){
452 fclose(inFile);
453 }
454 inFile = NULL;
455 }
456 blob_to_utf8_no_bom(&file, 1);
457 z = blob_str(&file);
458 for(k=0, nLine=1; z[k]; k++) if( z[k]=='\n' ) nLine++;
459 newArgv = fossil_malloc( sizeof(char*)*(g.argc + nLine*2) );
460 for(j=0; j<i; j++) newArgv[j] = g.argv[j];
461
462 blob_rewind(&file);
463 while( (n = blob_line(&file, &line))>0 ){
464 if( n<1 ) continue
465 /**
466 ** Reminder: corner-case: a line with 1 byte and no newline.
467 */;
468 z = blob_buffer(&line);
469 if('\n'==z[n-1]){
470 z[n-1] = 0;
471 }
472
473 if((n>1) && ('\r'==z[n-2])){
474 if(n==2) continue /*empty line*/;
475 z[n-2] = 0;
476 }
477 if(!z[0]) continue;
478 newArgv[j++] = z;
479 if( z[0]=='-' ){
480 for(k=1; z[k] && !fossil_isspace(z[k]); k++){}
481 if( z[k] ){
482 z[k] = 0;
483 k++;
484 if( z[k] ) newArgv[j++] = &z[k];
485 }
486 }
487 }
488 i += 2;
489 while( i<g.argc ) newArgv[j++] = g.argv[i++];
490 newArgv[j] = 0;
491 g.argc = j;
492 g.argv = newArgv;
493 }
494
495 #ifdef FOSSIL_ENABLE_TCL
496 /*
497 ** Make a deep copy of the provided argument array and return it.
498 */
499 static char **copy_args(int argc, char **argv){
500 char **zNewArgv;
501 int i;
502 zNewArgv = fossil_malloc( sizeof(char*)*(argc+1) );
503 memset(zNewArgv, 0, sizeof(char*)*(argc+1));
504 for(i=0; i<argc; i++){
505 zNewArgv[i] = fossil_strdup(argv[i]);
506 }
507 return zNewArgv;
508 }
509 #endif
510
511 /*
512 ** Returns a name for a SQLite return code.
513 */
514 static const char *fossil_sqlite_return_code_name(int rc){
515 static char zCode[30];
516 switch( rc & 0xff ){
517 case SQLITE_OK: return "SQLITE_OK";
518 case SQLITE_ERROR: return "SQLITE_ERROR";
519 case SQLITE_INTERNAL: return "SQLITE_INTERNAL";
520 case SQLITE_PERM: return "SQLITE_PERM";
521 case SQLITE_ABORT: return "SQLITE_ABORT";
522 case SQLITE_BUSY: return "SQLITE_BUSY";
523 case SQLITE_LOCKED: return "SQLITE_LOCKED";
524 case SQLITE_NOMEM: return "SQLITE_NOMEM";
525 case SQLITE_READONLY: return "SQLITE_READONLY";
526 case SQLITE_INTERRUPT: return "SQLITE_INTERRUPT";
527 case SQLITE_IOERR: return "SQLITE_IOERR";
528 case SQLITE_CORRUPT: return "SQLITE_CORRUPT";
529 case SQLITE_NOTFOUND: return "SQLITE_NOTFOUND";
530 case SQLITE_FULL: return "SQLITE_FULL";
531 case SQLITE_CANTOPEN: return "SQLITE_CANTOPEN";
532 case SQLITE_PROTOCOL: return "SQLITE_PROTOCOL";
533 case SQLITE_EMPTY: return "SQLITE_EMPTY";
534 case SQLITE_SCHEMA: return "SQLITE_SCHEMA";
535 case SQLITE_TOOBIG: return "SQLITE_TOOBIG";
536 case SQLITE_CONSTRAINT: return "SQLITE_CONSTRAINT";
537 case SQLITE_MISMATCH: return "SQLITE_MISMATCH";
538 case SQLITE_MISUSE: return "SQLITE_MISUSE";
539 case SQLITE_NOLFS: return "SQLITE_NOLFS";
540 case SQLITE_AUTH: return "SQLITE_AUTH";
541 case SQLITE_FORMAT: return "SQLITE_FORMAT";
542 case SQLITE_RANGE: return "SQLITE_RANGE";
543 case SQLITE_NOTADB: return "SQLITE_NOTADB";
544 case SQLITE_NOTICE: return "SQLITE_NOTICE";
545 case SQLITE_WARNING: return "SQLITE_WARNING";
546 case SQLITE_ROW: return "SQLITE_ROW";
547 case SQLITE_DONE: return "SQLITE_DONE";
548 default: {
549 sqlite3_snprintf(sizeof(zCode), zCode, "SQLite return code %d", rc);
550 }
551 }
552 return zCode;
553 }
554
555 /* Error logs from SQLite */
556 static void fossil_sqlite_log(void *notUsed, int iCode, const char *zErrmsg){
557 #ifdef __APPLE__
558 /* Disable the file alias warning on apple products because Time Machine
559 ** creates lots of aliases and the warning alarms people. */
560 if( iCode==SQLITE_WARNING ) return;
561 #endif
562 if( iCode==SQLITE_SCHEMA ) return;
563 fossil_warning("%s: %s", fossil_sqlite_return_code_name(iCode), zErrmsg);
564 }
565
566 /*
567 ** This function attempts to find command line options known to contain
568 ** bitwise flags and initializes the associated global variables. After
569 ** this function executes, all global variables (i.e. in the "g" struct)
570 ** containing option-settable bitwise flag fields must be initialized.
571 */
572 static void fossil_init_flags_from_options(void){
573 const char *zValue = find_option("comfmtflags", 0, 1);
574 if( zValue ){
575 g.comFmtFlags = atoi(zValue);
576 }else{
577 g.comFmtFlags = COMMENT_PRINT_DEFAULT;
578 }
579 }
580
581 /*
582 ** This procedure runs first.
583 */
584 #if defined(_WIN32) && !defined(BROKEN_MINGW_CMDLINE)
585 int _dowildcard = -1; /* This turns on command-line globbing in MinGW-w64 */
586 int wmain(int argc, wchar_t **argv)
587 #else
588 #if defined(_WIN32)
589 int _CRT_glob = 0x0001; /* See MinGW bug #2062 */
590 #endif
591 int main(int argc, char **argv)
592 #endif
593 {
594 const char *zCmdName = "unknown";
595 int idx;
596 int rc;
597 if( sqlite3_libversion_number()<3010000 ){
598 fossil_fatal("Unsuitable SQLite version %s, must be at least 3.10.0",
599 sqlite3_libversion());
600 }
601 sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
602 sqlite3_config(SQLITE_CONFIG_LOG, fossil_sqlite_log, 0);
603 memset(&g, 0, sizeof(g));
604 g.now = time(0);
605 g.httpHeader = empty_blob;
606 #ifdef FOSSIL_ENABLE_JSON
607 #if defined(NDEBUG)
608 g.json.errorDetailParanoia = 2 /* FIXME: make configurable
609 One problem we have here is that this
610 code is needed before the db is opened,
611 so we can't sql for it.*/;
612 #else
613 g.json.errorDetailParanoia = 0;
614 #endif
615 g.json.outOpt = cson_output_opt_empty;
616 g.json.outOpt.addNewline = 1;
617 g.json.outOpt.indentation = 1 /* in CGI/server mode this can be configured */;
618 #endif /* FOSSIL_ENABLE_JSON */
619 expand_args_option(argc, argv);
620 #ifdef FOSSIL_ENABLE_TCL
621 memset(&g.tcl, 0, sizeof(TclContext));
622 g.tcl.argc = g.argc;
623 g.tcl.argv = copy_args(g.argc, g.argv); /* save full arguments */
624 #endif
625 g.mainTimerId = fossil_timer_start();
626 capture_case_sensitive_option();
627 g.zVfsName = find_option("vfs",0,1);
628 if( g.zVfsName==0 ){
629 g.zVfsName = fossil_getenv("FOSSIL_VFS");
630 }
631 if( g.zVfsName ){
632 sqlite3_vfs *pVfs = sqlite3_vfs_find(g.zVfsName);
633 if( pVfs ){
634 sqlite3_vfs_register(pVfs, 1);
635 }else{
636 fossil_fatal("no such VFS: \"%s\"", g.zVfsName);
637 }
638 }
639 if( fossil_getenv("GATEWAY_INTERFACE")!=0 && !find_option("nocgi", 0, 0)){
640 zCmdName = "cgi";
641 g.isHTTP = 1;
642 }else if( g.argc<2 ){
643 fossil_print(
644 "Usage: %s COMMAND ...\n"
645 " or: %s help -- for a list of common commands\n"
646 " or: %s help COMMAND -- for help with the named command\n",
647 g.argv[0], g.argv[0], g.argv[0]);
648 fossil_print(
649 "\nCommands and filenames may be passed on to fossil from a file\n"
650 "by using:\n"
651 "\n %s --args FILENAME ...\n",
652 g.argv[0]
653 );
654 fossil_print(
655 "\nEach line of the file is assumed to be a filename unless it starts\n"
656 "with '-' and contains a space, in which case it is assumed to be\n"
657 "another flag and is treated as such. --args FILENAME may be used\n"
658 "in conjunction with any other flags.\n");
659 fossil_exit(1);
660 }else{
661 const char *zChdir = find_option("chdir",0,1);
662 g.isHTTP = 0;
663 g.rcvid = 0;
664 g.fQuiet = find_option("quiet", 0, 0)!=0;
665 g.fSqlTrace = find_option("sqltrace", 0, 0)!=0;
666 g.fSqlStats = find_option("sqlstats", 0, 0)!=0;
667 g.fSystemTrace = find_option("systemtrace", 0, 0)!=0;
668 g.fSshTrace = find_option("sshtrace", 0, 0)!=0;
669 g.fSshClient = 0;
670 g.zSshCmd = 0;
671 if( g.fSqlTrace ) g.fSqlStats = 1;
672 g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
673 #ifdef FOSSIL_ENABLE_TH1_HOOKS
674 g.fNoThHook = find_option("no-th-hook", 0, 0)!=0;
675 #endif
676 g.fAnyTrace = g.fSqlTrace|g.fSystemTrace|g.fSshTrace|g.fHttpTrace;
677 g.zHttpAuth = 0;
678 g.zLogin = find_option("user", "U", 1);
679 g.zSSLIdentity = find_option("ssl-identity", 0, 1);
680 g.zErrlog = find_option("errorlog", 0, 1);
681 fossil_init_flags_from_options();
682 if( find_option("utc",0,0) ) g.fTimeFormat = 1;
683 if( find_option("localtime",0,0) ) g.fTimeFormat = 2;
684 if( zChdir && file_chdir(zChdir, 0) ){
685 fossil_fatal("unable to change directories to %s", zChdir);
686 }
687 if( find_option("help",0,0)!=0 ){
688 /* If --help is found anywhere on the command line, translate the command
689 * to "fossil help cmdname" where "cmdname" is the first argument that
690 * does not begin with a "-" character. If all arguments start with "-",
691 * translate to "fossil help argv[1] argv[2]...". */
692 int i, nNewArgc;
693 char **zNewArgv = fossil_malloc( sizeof(char*)*(g.argc+2) );
694 zNewArgv[0] = g.argv[0];
695 zNewArgv[1] = "help";
696 for(i=1; i<g.argc; i++){
697 if( g.argv[i][0]!='-' ){
698 nNewArgc = 3;
699 zNewArgv[2] = g.argv[i];
700 zNewArgv[3] = 0;
701 break;
702 }
703 }
704 if( i==g.argc ){
705 for(i=1; i<g.argc; i++) zNewArgv[i+1] = g.argv[i];
706 nNewArgc = g.argc+1;
707 zNewArgv[i+1] = 0;
708 }
709 g.argc = nNewArgc;
710 g.argv = zNewArgv;
711 }
712 zCmdName = g.argv[1];
713 }
714 #ifndef _WIN32
715 /* There is a bug in stunnel4 in which it sometimes starts up client
716 ** processes without first opening file descriptor 2 (standard error).
717 ** If this happens, and a subsequent open() of a database returns file
718 ** descriptor 2, and then an assert() fires and writes on fd 2, that
719 ** can corrupt the data file. To avoid this problem, make sure open()
720 ** will never return file descriptor 2 or less. */
721 if( !is_valid_fd(2) ){
722 int nTry = 0;
723 int fd = 0;
724 int x = 0;
725 do{
726 fd = open("/dev/null",O_WRONLY);
727 if( fd>=2 ) break;
728 if( fd<0 ) x = errno;
729 }while( nTry++ < 2 );
730 if( fd<2 ){
731 g.cgiOutput = 1;
732 g.httpOut = stdout;
733 g.fullHttpReply = !g.isHTTP;
734 fossil_fatal("file descriptor 2 is not open. (fd=%d, errno=%d)",
735 fd, x);
736 }
737 }
738 #endif
739 rc = name_search(zCmdName, aCommand, count(aCommand), FOSSIL_FIRST_CMD, &idx);
740 if( rc==1 ){
741 #ifdef FOSSIL_ENABLE_TH1_HOOKS
742 if( !g.isHTTP && !g.fNoThHook ){
743 rc = Th_CommandHook(zCmdName, 0);
744 }else{
745 rc = TH_OK;
746 }
747 if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
748 if( rc==TH_OK || rc==TH_RETURN ){
749 #endif
750 fossil_fatal("%s: unknown command: %s\n"
751 "%s: use \"help\" for more information\n",
752 g.argv[0], zCmdName, g.argv[0]);
753 #ifdef FOSSIL_ENABLE_TH1_HOOKS
754 }
755 if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
756 Th_CommandNotify(zCmdName, 0);
757 }
758 }
759 fossil_exit(0);
760 #endif
761 }else if( rc==2 ){
762 int i, n;
763 Blob couldbe;
764 blob_zero(&couldbe);
765 n = strlen(zCmdName);
766 for(i=0; i<count(aCommand); i++){
767 if( memcmp(zCmdName, aCommand[i].zName, n)==0 ){
768 blob_appendf(&couldbe, " %s", aCommand[i].zName);
769 }
770 }
771 fossil_print("%s: ambiguous command prefix: %s\n"
772 "%s: could be any of:%s\n"
773 "%s: use \"help\" for more information\n",
774 g.argv[0], zCmdName, g.argv[0], blob_str(&couldbe), g.argv[0]);
775 fossil_exit(1);
776 }
777 atexit( fossil_atexit );
778 #ifdef FOSSIL_ENABLE_TH1_HOOKS
779 /*
780 ** The TH1 return codes from the hook will be handled as follows:
781 **
782 ** TH_OK: The xFunc() and the TH1 notification will both be executed.
783 **
784 ** TH_ERROR: The xFunc() will be executed, the TH1 notification will be
785 ** skipped. If the xFunc() is being hooked, the error message
786 ** will be emitted.
787 **
788 ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped.
789 **
790 ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be
791 ** skipped.
792 **
793 ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be
794 ** executed.
795 */
796 if( !g.isHTTP && !g.fNoThHook ){
797 rc = Th_CommandHook(aCommand[idx].zName, aCommand[idx].cmdFlags);
798 }else{
799 rc = TH_OK;
800 }
801 if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
802 if( rc==TH_OK || rc==TH_RETURN ){
803 #endif
804 aCommand[idx].xFunc();
805 #ifdef FOSSIL_ENABLE_TH1_HOOKS
806 }
807 if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
808 Th_CommandNotify(aCommand[idx].zName, aCommand[idx].cmdFlags);
809 }
810 }
811 #endif
812 fossil_exit(0);
813 /*NOT_REACHED*/
814 return 0;
815 }
816
817 /*
818 ** Print a usage comment and quit
819 */
820 void usage(const char *zFormat){
821 fossil_fatal("Usage: %s %s %s", g.argv[0], g.argv[1], zFormat);
822 }
823
824 /*
825 ** Remove n elements from g.argv beginning with the i-th element.
826 */
827 static void remove_from_argv(int i, int n){
828 int j;
829 for(j=i+n; j<g.argc; i++, j++){
830 g.argv[i] = g.argv[j];
831 }
832 g.argc = i;
833 }
834
835
836 /*
837 ** Look for a command-line option. If present, return a pointer.
838 ** Return NULL if missing.
839 **
840 ** hasArg==0 means the option is a flag. It is either present or not.
841 ** hasArg==1 means the option has an argument. Return a pointer to the
842 ** argument.
843 */
844 const char *find_option(const char *zLong, const char *zShort, int hasArg){
845 int i;
846 int nLong;
847 const char *zReturn = 0;
848 assert( hasArg==0 || hasArg==1 );
849 nLong = strlen(zLong);
850 for(i=1; i<g.argc; i++){
851 char *z;
852 if( i+hasArg >= g.argc ) break;
853 z = g.argv[i];
854 if( z[0]!='-' ) continue;
855 z++;
856 if( z[0]=='-' ){
857 if( z[1]==0 ){
858 remove_from_argv(i, 1);
859 break;
860 }
861 z++;
862 }
863 if( strncmp(z,zLong,nLong)==0 ){
864 if( hasArg && z[nLong]=='=' ){
865 zReturn = &z[nLong+1];
866 remove_from_argv(i, 1);
867 break;
868 }else if( z[nLong]==0 ){
869 zReturn = g.argv[i+hasArg];
870 remove_from_argv(i, 1+hasArg);
871 break;
872 }
873 }else if( fossil_strcmp(z,zShort)==0 ){
874 zReturn = g.argv[i+hasArg];
875 remove_from_argv(i, 1+hasArg);
876 break;
877 }
878 }
879 return zReturn;
880 }
881
882 /*
883 ** Look for multiple occurrences of a command-line option with the
884 ** corresponding argument.
885 **
886 ** Return a malloc allocated array of pointers to the arguments.
887 **
888 ** pnUsedArgs is used to store the number of matched arguments.
889 **
890 ** Caller is responsible to free allocated memory.
891 */
892 const char **find_repeatable_option(
893 const char *zLong,
894 const char *zShort,
895 int *pnUsedArgs
896 ){
897 const char *zOption;
898 const char **pzArgs = 0;
899 int nAllocArgs = 0;
900 int nUsedArgs = 0;
901
902 while( (zOption = find_option(zLong, zShort, 1))!=0 ){
903 if( pzArgs==0 && nAllocArgs==0 ){
904 nAllocArgs = 1;
905 pzArgs = fossil_malloc( nAllocArgs*sizeof(pzArgs[0]) );
906 }else if( nAllocArgs<=nUsedArgs ){
907 nAllocArgs = nAllocArgs*2;
908 pzArgs = fossil_realloc( (void *)pzArgs, nAllocArgs*sizeof(pzArgs[0]) );
909 }
910 pzArgs[nUsedArgs++] = zOption;
911 }
912 *pnUsedArgs = nUsedArgs;
913 return pzArgs;
914 }
915
916 /*
917 ** Look for a repository command-line option. If present, [re-]cache it in
918 ** the global state and return the new pointer, freeing any previous value.
919 ** If absent and there is no cached value, return NULL.
920 */
921 const char *find_repository_option(){
922 const char *zRepository = find_option("repository", "R", 1);
923 if( zRepository ){
924 if( g.zRepositoryOption ) fossil_free(g.zRepositoryOption);
925 g.zRepositoryOption = mprintf("%s", zRepository);
926 }
927 return g.zRepositoryOption;
928 }
929
930 /*
931 ** Verify that there are no unprocessed command-line options. If
932 ** Any remaining command-line argument begins with "-" print
933 ** an error message and quit.
934 */
935 void verify_all_options(void){
936 int i;
937 for(i=1; i<g.argc; i++){
938 if( g.argv[i][0]=='-' ){
939 fossil_fatal(
940 "unrecognized command-line option, or missing argument: %s",
941 g.argv[i]);
942 }
943 }
944 }
945
946 /*
947 ** Print a list of words in multiple columns.
948 */
949 static void multi_column_list(const char **azWord, int nWord){
950 int i, j, len;
951 int mxLen = 0;
952 int nCol;
953 int nRow;
954 for(i=0; i<nWord; i++){
955 len = strlen(azWord[i]);
956 if( len>mxLen ) mxLen = len;
957 }
958 nCol = 80/(mxLen+2);
959 if( nCol==0 ) nCol = 1;
960 nRow = (nWord + nCol - 1)/nCol;
961 for(i=0; i<nRow; i++){
962 const char *zSpacer = "";
963 for(j=i; j<nWord; j+=nRow){
964 fossil_print("%s%-*s", zSpacer, mxLen, azWord[j]);
965 zSpacer = " ";
966 }
967 fossil_print("\n");
968 }
969 }
970
971 /*
972 ** List of commands starting with zPrefix, or all commands if zPrefix is NULL.
973 */
974 static void command_list(const char *zPrefix, int cmdMask){
975 int i, nCmd;
976 int nPrefix = zPrefix ? strlen(zPrefix) : 0;
977 const char *aCmd[count(aCommand)];
978 for(i=nCmd=0; i<count(aCommand); i++){
979 const char *z = aCommand[i].zName;
980 if( (aCommand[i].cmdFlags & cmdMask)==0 ) continue;
981 if( zPrefix && memcmp(zPrefix, z, nPrefix)!=0 ) continue;
982 aCmd[nCmd++] = aCommand[i].zName;
983 }
984 multi_column_list(aCmd, nCmd);
985 }
986
987 /*
988 ** COMMAND: test-list-webpage
989 **
990 ** List all web pages
991 */
992 void cmd_test_webpage_list(void){
993 int i, nCmd;
994 const char *aCmd[count(aCommand)];
995 for(i=nCmd=0; i<count(aCommand); i++){
996 if(0x08 & aCommand[i].cmdFlags){
997 aCmd[nCmd++] = aWebpage[i].zName;
998 }
999 }
1000 assert(nCmd && "page list is empty?");
1001 multi_column_list(aCmd, nCmd);
1002 }
1003
1004
1005
1006 /*
1007 ** This function returns a human readable version string.
1008 */
1009 const char *get_version(){
1010 static const char version[] = RELEASE_VERSION " " MANIFEST_VERSION " "
1011 MANIFEST_DATE " UTC";
1012 return version;
1013 }
1014
1015 /*
1016 ** This function populates a blob with version information. It is used by
1017 ** the "version" command and "test-version" web page. It assumes the blob
1018 ** passed to it is uninitialized; otherwise, it will leak memory.
1019 */
1020 static void get_version_blob(
1021 Blob *pOut, /* Write the manifest here */
1022 int bVerbose /* Non-zero for full information. */
1023 ){
1024 #if defined(FOSSIL_ENABLE_TCL)
1025 int rc;
1026 const char *zRc;
1027 #endif
1028 blob_zero(pOut);
1029 blob_appendf(pOut, "This is fossil version %s\n", get_version());
1030 if( !bVerbose ) return;
1031 blob_appendf(pOut, "Compiled on %s %s using %s (%d-bit)\n",
1032 __DATE__, __TIME__, COMPILER_NAME, sizeof(void*)*8);
1033 blob_appendf(pOut, "SQLite %s %.30s\n", sqlite3_libversion(),
1034 sqlite3_sourceid());
1035 blob_appendf(pOut, "Schema version %s\n", AUX_SCHEMA_MAX);
1036 #if defined(FOSSIL_ENABLE_MINIZ)
1037 blob_appendf(pOut, "miniz %s, loaded %s\n", MZ_VERSION, mz_version());
1038 #else
1039 blob_appendf(pOut, "zlib %s, loaded %s\n", ZLIB_VERSION, zlibVersion());
1040 #endif
1041 #if defined(FOSSIL_ENABLE_SSL)
1042 blob_appendf(pOut, "SSL (%s)\n", SSLeay_version(SSLEAY_VERSION));
1043 #endif
1044 #if defined(FOSSIL_ENABLE_LEGACY_MV_RM)
1045 blob_append(pOut, "LEGACY_MV_RM\n", -1);
1046 #endif
1047 #if defined(FOSSIL_ENABLE_EXEC_REL_PATHS)
1048 blob_append(pOut, "EXEC_REL_PATHS\n", -1);
1049 #endif
1050 #if defined(FOSSIL_ENABLE_TH1_DOCS)
1051 blob_append(pOut, "TH1_DOCS\n", -1);
1052 #endif
1053 #if defined(FOSSIL_ENABLE_TH1_HOOKS)
1054 blob_append(pOut, "TH1_HOOKS\n", -1);
1055 #endif
1056 #if defined(FOSSIL_ENABLE_TCL)
1057 Th_FossilInit(TH_INIT_DEFAULT | TH_INIT_FORCE_TCL);
1058 rc = Th_Eval(g.interp, 0, "tclInvoke info patchlevel", -1);
1059 zRc = Th_ReturnCodeName(rc, 0);
1060 blob_appendf(pOut, "TCL (Tcl %s, loaded %s: %s)\n",
1061 TCL_PATCH_LEVEL, zRc, Th_GetResult(g.interp, 0)
1062 );
1063 #endif
1064 #if defined(USE_TCL_STUBS)
1065 blob_append(pOut, "USE_TCL_STUBS\n", -1);
1066 #endif
1067 #if defined(FOSSIL_ENABLE_TCL_STUBS)
1068 blob_append(pOut, "TCL_STUBS\n", -1);
1069 #endif
1070 #if defined(FOSSIL_ENABLE_TCL_PRIVATE_STUBS)
1071 blob_append(pOut, "TCL_PRIVATE_STUBS\n", -1);
1072 #endif
1073 #if defined(FOSSIL_ENABLE_JSON)
1074 blob_appendf(pOut, "JSON (API %s)\n", FOSSIL_JSON_API_VERSION);
1075 #endif
1076 #if defined(BROKEN_MINGW_CMDLINE)
1077 blob_append(pOut, "MBCS_COMMAND_LINE\n", -1);
1078 #else
1079 blob_append(pOut, "UNICODE_COMMAND_LINE\n", -1);
1080 #endif
1081 #if defined(FOSSIL_DYNAMIC_BUILD)
1082 blob_append(pOut, "DYNAMIC_BUILD\n", -1);
1083 #else
1084 blob_append(pOut, "STATIC_BUILD\n", -1);
1085 #endif
1086 }
1087
1088 /*
1089 ** This function returns the user-agent string for Fossil, for
1090 ** use in HTTP(S) requests.
1091 */
1092 const char *get_user_agent(){
1093 static const char version[] = "Fossil/" RELEASE_VERSION " (" MANIFEST_DATE
1094 " " MANIFEST_VERSION ")";
1095 return version;
1096 }
1097
1098
1099 /*
1100 ** COMMAND: version
1101 **
1102 ** Usage: %fossil version ?-verbose|-v?
1103 **
1104 ** Print the source code version number for the fossil executable.
1105 ** If the verbose option is specified, additional details will
1106 ** be output about what optional features this binary was compiled
1107 ** with
1108 */
1109 void version_cmd(void){
1110 Blob versionInfo;
1111 int verboseFlag = find_option("verbose","v",0)!=0;
1112
1113 /* We should be done with options.. */
1114 verify_all_options();
1115 get_version_blob(&versionInfo, verboseFlag);
1116 fossil_print("%s", blob_str(&versionInfo));
1117 }
1118
1119
1120 /*
1121 ** WEBPAGE: test-version
1122 **
1123 ** Show the version information for Fossil.
1124 **
1125 ** Query parameters:
1126 **
1127 ** verbose Show all available details.
1128 */
1129 void test_version_page(void){
1130 Blob versionInfo;
1131 int verboseFlag;
1132
1133 login_check_credentials();
1134 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1135 verboseFlag = P("verbose")!=0;
1136 style_header("Version Information");
1137 get_version_blob(&versionInfo, verboseFlag);
1138 @ <blockquote><pre>
1139 @ %h(blob_str(&versionInfo))
1140 @ </pre></blockquote>
1141 style_footer();
1142 }
1143
1144
1145 /*
1146 ** COMMAND: help
1147 **
1148 ** Usage: %fossil help COMMAND
1149 ** or: %fossil COMMAND --help
1150 **
1151 ** Display information on how to use COMMAND. To display a list of
1152 ** available commands use one of:
1153 **
1154 ** %fossil help Show common commands
1155 ** %fossil help -a|--all Show both common and auxiliary commands
1156 ** %fossil help -t|--test Show test commands only
1157 ** %fossil help -x|--aux Show auxiliary commands only
1158 ** %fossil help -w|--www Show list of WWW pages
1159 */
1160 void help_cmd(void){
1161 int rc, idx, isPage = 0;
1162 const char *z;
1163 const char *zCmdOrPage;
1164 const char *zCmdOrPagePlural;
1165 if( g.argc<3 ){
1166 z = g.argv[0];
1167 fossil_print(
1168 "Usage: %s help COMMAND\n"
1169 "Common COMMANDs: (use \"%s help -a|--all\" for a complete list)\n",
1170 z, z);
1171 command_list(0, CMDFLAG_1ST_TIER);
1172 version_cmd();
1173 return;
1174 }
1175 if( find_option("all","a",0) ){
1176 command_list(0, CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER);
1177 return;
1178 }
1179 else if( find_option("www","w",0) ){
1180 command_list(0, CMDFLAG_WEBPAGE);
1181 return;
1182 }
1183 else if( find_option("aux","x",0) ){
1184 command_list(0, CMDFLAG_2ND_TIER);
1185 return;
1186 }
1187 else if( find_option("test","t",0) ){
1188 command_list(0, CMDFLAG_TEST);
1189 return;
1190 }
1191 isPage = ('/' == *g.argv[2]) ? 1 : 0;
1192 if(isPage){
1193 zCmdOrPage = "page";
1194 zCmdOrPagePlural = "pages";
1195 }else{
1196 zCmdOrPage = "command";
1197 zCmdOrPagePlural = "commands";
1198 }
1199 rc = name_search(g.argv[2], aCommand, count(aCommand), 0, &idx);
1200 if( rc==1 ){
1201 fossil_print("unknown %s: %s\nAvailable %s:\n",
1202 zCmdOrPage, g.argv[2], zCmdOrPagePlural);
1203 command_list(0, isPage ? CMDFLAG_WEBPAGE : (0xff & ~CMDFLAG_WEBPAGE));
1204 fossil_exit(1);
1205 }else if( rc==2 ){
1206 fossil_print("ambiguous %s prefix: %s\nMatching %s:\n",
1207 zCmdOrPage, g.argv[2], zCmdOrPagePlural);
1208 command_list(g.argv[2], 0xff);
1209 fossil_exit(1);
1210 }
1211 z = aCmdHelp[idx].zText;
1212 if( z==0 ){
1213 fossil_fatal("no help available for the %s %s",
1214 aCommand[idx].zName, zCmdOrPage);
1215 }
1216 while( *z ){
1217 if( *z=='%' && strncmp(z, "%fossil", 7)==0 ){
1218 fossil_print("%s", g.argv[0]);
1219 z += 7;
1220 }else{
1221 putchar(*z);
1222 z++;
1223 }
1224 }
1225 putchar('\n');
1226 }
1227
1228 /*
1229 ** WEBPAGE: help
1230 ** URL: /help?name=CMD
1231 **
1232 ** Show the built-in help text for CMD. CMD can be a command-line interface
1233 ** command or a page name from the web interface.
1234 */
1235 void help_page(void){
1236 const char *zCmd = P("cmd");
1237
1238 if( zCmd==0 ) zCmd = P("name");
1239 style_header("Command-line Help");
1240 if( zCmd ){
1241 int rc, idx;
1242 char *z, *s, *d;
1243 const char *zCmdOrPage = ('/'==*zCmd) ? "page" : "command";
1244 style_submenu_element("Command-List", "Command-List", "%s/help", g.zTop);
1245 @ <h1>The "%s(zCmd)" %s(zCmdOrPage):</h1>
1246 rc = name_search(zCmd, aCommand, count(aCommand), 0, &idx);
1247 if( rc==1 ){
1248 @ unknown command: %s(zCmd)
1249 }else if( rc==2 ){
1250 @ ambiguous command prefix: %s(zCmd)
1251 }else{
1252 z = (char*)aCmdHelp[idx].zText;
1253 if( z==0 ){
1254 @ no help available for the %s(aCommand[idx].zName) command
1255 }else{
1256 z=s=d=mprintf("%s",z);
1257 while( *s ){
1258 if( *s=='%' && strncmp(s, "%fossil", 7)==0 ){
1259 s++;
1260 }else{
1261 *d++ = *s++;
1262 }
1263 }
1264 *d = 0;
1265 @ <blockquote><pre>
1266 @ %h(z)
1267 @ </pre></blockquote>
1268 fossil_free(z);
1269 }
1270 }
1271 }else{
1272 int i, j, n;
1273
1274 @ <h1>Available commands:</h1>
1275 @ <table border="0"><tr>
1276 for(i=j=0; i<count(aCommand); i++){
1277 const char *z = aCommand[i].zName;
1278 if( '/'==*z || strncmp(z,"test",4)==0 ) continue;
1279 j++;
1280 }
1281 n = (j+6)/7;
1282 for(i=j=0; i<count(aCommand); i++){
1283 const char *z = aCommand[i].zName;
1284 if( '/'==*z || strncmp(z,"test",4)==0 ) continue;
1285 if( j==0 ){
1286 @ <td valign="top"><ul>
1287 }
1288 @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
1289 j++;
1290 if( j>=n ){
1291 @ </ul></td>
1292 j = 0;
1293 }
1294 }
1295 if( j>0 ){
1296 @ </ul></td>
1297 }
1298 @ </tr></table>
1299
1300 @ <h1>Available web UI pages:</h1>
1301 @ <table border="0"><tr>
1302 for(i=j=0; i<count(aCommand); i++){
1303 const char *z = aCommand[i].zName;
1304 if( '/'!=*z ) continue;
1305 j++;
1306 }
1307 n = (j+4)/5;
1308 for(i=j=0; i<count(aCommand); i++){
1309 const char *z = aCommand[i].zName;
1310 if( '/'!=*z ) continue;
1311 if( j==0 ){
1312 @ <td valign="top"><ul>
1313 }
1314 if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){
1315 @ <li><a href="%R/help?cmd=%s(z)">%s(z+1)</a></li>
1316 }else{
1317 @ <li>%s(z+1)</li>
1318 }
1319 j++;
1320 if( j>=n ){
1321 @ </ul></td>
1322 j = 0;
1323 }
1324 }
1325 if( j>0 ){
1326 @ </ul></td>
1327 }
1328 @ </tr></table>
1329
1330 @ <h1>Unsupported commands:</h1>
1331 @ <table border="0"><tr>
1332 for(i=j=0; i<count(aCommand); i++){
1333 const char *z = aCommand[i].zName;
1334 if( strncmp(z,"test",4)!=0 ) continue;
1335 j++;
1336 }
1337 n = (j+3)/4;
1338 for(i=j=0; i<count(aCommand); i++){
1339 const char *z = aCommand[i].zName;
1340 if( strncmp(z,"test",4)!=0 ) continue;
1341 if( j==0 ){
1342 @ <td valign="top"><ul>
1343 }
1344 if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){
1345 @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
1346 }else{
1347 @ <li>%s(z)</li>
1348 }
1349 j++;
1350 if( j>=n ){
1351 @ </ul></td>
1352 j = 0;
1353 }
1354 }
1355 if( j>0 ){
1356 @ </ul></td>
1357 }
1358 @ </tr></table>
1359
1360 }
1361 style_footer();
1362 }
1363
1364 /*
1365 ** WEBPAGE: test-all-help
1366 **
1367 ** Show all help text on a single page. Useful for proof-reading.
1368 */
1369 void test_all_help_page(void){
1370 int i;
1371 style_header("Testpage: All Help Text");
1372 for(i=0; i<count(aCommand); i++){
1373 if( memcmp(aCommand[i].zName, "test", 4)==0 ) continue;
1374 @ <h2>%s(aCommand[i].zName):</h2>
1375 @ <blockquote><pre>
1376 @ %h(aCmdHelp[i].zText)
1377 @ </pre></blockquote>
1378 }
1379 style_footer();
1380 }
1381
1382 /*
1383 ** Set the g.zBaseURL value to the full URL for the toplevel of
1384 ** the fossil tree. Set g.zTop to g.zBaseURL without the
1385 ** leading "http://" and the host and port.
1386 **
1387 ** The g.zBaseURL is normally set based on HTTP_HOST and SCRIPT_NAME
1388 ** environment variables. However, if zAltBase is not NULL then it
1389 ** is the argument to the --baseurl option command-line option and
1390 ** g.zBaseURL and g.zTop is set from that instead.
1391 */
1392 static void set_base_url(const char *zAltBase){
1393 int i;
1394 const char *zHost;
1395 const char *zMode;
1396 const char *zCur;
1397
1398 if( g.zBaseURL!=0 ) return;
1399 if( zAltBase ){
1400 int i, n, c;
1401 g.zTop = g.zBaseURL = mprintf("%s", zAltBase);
1402 if( strncmp(g.zTop, "http://", 7)==0 ){
1403 /* it is HTTP, replace prefix with HTTPS. */
1404 g.zHttpsURL = mprintf("https://%s", &g.zTop[7]);
1405 }else if( strncmp(g.zTop, "https://", 8)==0 ){
1406 /* it is already HTTPS, use it. */
1407 g.zHttpsURL = mprintf("%s", g.zTop);
1408 }else{
1409 fossil_fatal("argument to --baseurl should be 'http://host/path'"
1410 " or 'https://host/path'");
1411 }
1412 for(i=n=0; (c = g.zTop[i])!=0; i++){
1413 if( c=='/' ){
1414 n++;
1415 if( n==3 ){
1416 g.zTop += i;
1417 break;
1418 }
1419 }
1420 }
1421 if( g.zTop==g.zBaseURL ){
1422 fossil_fatal("argument to --baseurl should be 'http://host/path'"
1423 " or 'https://host/path'");
1424 }
1425 if( g.zTop[1]==0 ) g.zTop++;
1426 }else{
1427 zHost = PD("HTTP_HOST","");
1428 zMode = PD("HTTPS","off");
1429 zCur = PD("SCRIPT_NAME","/");
1430 i = strlen(zCur);
1431 while( i>0 && zCur[i-1]=='/' ) i--;
1432 if( fossil_stricmp(zMode,"on")==0 ){
1433 g.zBaseURL = mprintf("https://%s%.*s", zHost, i, zCur);
1434 g.zTop = &g.zBaseURL[8+strlen(zHost)];
1435 g.zHttpsURL = g.zBaseURL;
1436 }else{
1437 g.zBaseURL = mprintf("http://%s%.*s", zHost, i, zCur);
1438 g.zTop = &g.zBaseURL[7+strlen(zHost)];
1439 g.zHttpsURL = mprintf("https://%s%.*s", zHost, i, zCur);
1440 }
1441 }
1442 if( db_is_writeable("repository") ){
1443 if( !db_exists("SELECT 1 FROM config WHERE name='baseurl:%q'", g.zBaseURL)){
1444 db_multi_exec("INSERT INTO config(name,value,mtime)"
1445 "VALUES('baseurl:%q',1,now())", g.zBaseURL);
1446 }else{
1447 db_optional_sql("repository",
1448 "REPLACE INTO config(name,value,mtime)"
1449 "VALUES('baseurl:%q',1,now())", g.zBaseURL
1450 );
1451 }
1452 }
1453 }
1454
1455 /*
1456 ** Send an HTTP redirect back to the designated Index Page.
1457 */
1458 NORETURN void fossil_redirect_home(void){
1459 cgi_redirectf("%s%s", g.zTop, db_get("index-page", "/index"));
1460 }
1461
1462 /*
1463 ** If running as root, chroot to the directory containing the
1464 ** repository zRepo and then drop root privileges. Return the
1465 ** new repository name.
1466 **
1467 ** zRepo might be a directory itself. In that case chroot into
1468 ** the directory zRepo.
1469 **
1470 ** Assume the user-id and group-id of the repository, or if zRepo
1471 ** is a directory, of that directory.
1472 **
1473 ** The noJail flag means that the chroot jail is not entered. But
1474 ** privileges are still lowered to that of the user-id and group-id
1475 ** of the repository file.
1476 */
1477 static char *enter_chroot_jail(char *zRepo, int noJail){
1478 #if !defined(_WIN32)
1479 if( getuid()==0 ){
1480 int i;
1481 struct stat sStat;
1482 Blob dir;
1483 char *zDir;
1484 if( g.db!=0 ){
1485 db_close(1);
1486 }
1487
1488 file_canonical_name(zRepo, &dir, 0);
1489 zDir = blob_str(&dir);
1490 if( !noJail ){
1491 if( file_isdir(zDir)==1 ){
1492 if( file_chdir(zDir, 1) ){
1493 fossil_fatal("unable to chroot into %s", zDir);
1494 }
1495 zRepo = "/";
1496 }else{
1497 for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
1498 if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo);
1499 if( i>0 ){
1500 zDir[i] = 0;
1501 if( file_chdir(zDir, 1) ){
1502 fossil_fatal("unable to chroot into %s", zDir);
1503 }
1504 zDir[i] = '/';
1505 }
1506 zRepo = &zDir[i];
1507 }
1508 }
1509 if( stat(zRepo, &sStat)!=0 ){
1510 fossil_fatal("cannot stat() repository: %s", zRepo);
1511 }
1512 i = setgid(sStat.st_gid);
1513 i = i || setuid(sStat.st_uid);
1514 if(i){
1515 fossil_fatal("setgid/uid() failed with errno %d", errno);
1516 }
1517 if( g.db==0 && file_isfile(zRepo) ){
1518 db_open_repository(zRepo);
1519 }
1520 }
1521 #endif
1522 return zRepo;
1523 }
1524
1525 /*
1526 ** Generate a web-page that lists all repositories located under the
1527 ** g.zRepositoryName directory and return non-zero.
1528 **
1529 ** Or, if no repositories can be located beneath g.zRepositoryName,
1530 ** return 0.
1531 */
1532 static int repo_list_page(void){
1533 Blob base;
1534 int n = 0;
1535
1536 assert( g.db==0 );
1537 blob_init(&base, g.zRepositoryName, -1);
1538 sqlite3_open(":memory:", &g.db);
1539 db_multi_exec("CREATE TABLE sfile(x TEXT);");
1540 db_multi_exec("CREATE TABLE vfile(pathname);");
1541 vfile_scan(&base, blob_size(&base), 0, 0, 0);
1542 db_multi_exec("DELETE FROM sfile WHERE x NOT GLOB '*[^/].fossil'");
1543 n = db_int(0, "SELECT count(*) FROM sfile");
1544 if( n>0 ){
1545 Stmt q;
1546 @ <html>
1547 @ <head>
1548 @ <title>Repository List</title>
1549 @ </head>
1550 @ <body>
1551 @ <h1>Available Repositories:</h1>
1552 @ <ol>
1553 db_prepare(&q, "SELECT x, substr(x,-7,-100000)||'/home'"
1554 " FROM sfile ORDER BY x COLLATE nocase;");
1555 while( db_step(&q)==SQLITE_ROW ){
1556 const char *zName = db_column_text(&q, 0);
1557 const char *zUrl = db_column_text(&q, 1);
1558 @ <li><a href="%h(zUrl)" target="_blank">%h(zName)</a></li>
1559 }
1560 @ </ol>
1561 @ </body>
1562 @ </html>
1563 cgi_reply();
1564 }
1565 sqlite3_close(g.db);
1566 g.db = 0;
1567 return n;
1568 }
1569
1570 /*
1571 ** Preconditions:
1572 **
1573 ** * Environment variables are set up according to the CGI standard.
1574 **
1575 ** If the repository is known, it has already been opened. If unknown,
1576 ** then g.zRepositoryName holds the directory that contains the repository
1577 ** and the actual repository is taken from the first element of PATH_INFO.
1578 **
1579 ** Process the webpage specified by the PATH_INFO or REQUEST_URI
1580 ** environment variable.
1581 **
1582 ** If the repository is not known, then a search is done through the
1583 ** file hierarchy rooted at g.zRepositoryName for a suitable repository
1584 ** with a name of $prefix.fossil, where $prefix is any prefix of PATH_INFO.
1585 ** Or, if an ordinary file named $prefix is found, and $prefix matches
1586 ** pFileGlob and $prefix does not match "*.fossil*" and the mimetype of
1587 ** $prefix can be determined from its suffix, then the file $prefix is
1588 ** returned as static text.
1589 **
1590 ** If no suitable webpage is found, try to redirect to zNotFound.
1591 */
1592 static void process_one_web_page(
1593 const char *zNotFound, /* Redirect here on a 404 if not NULL */
1594 Glob *pFileGlob, /* Deliver static files matching */
1595 int allowRepoList /* Send repo list for "/" URL */
1596 ){
1597 const char *zPathInfo;
1598 char *zPath = NULL;
1599 int idx;
1600 int i;
1601
1602 /* Handle universal query parameters */
1603 if( PB("utc") ){
1604 g.fTimeFormat = 1;
1605 }else if( PB("localtime") ){
1606 g.fTimeFormat = 2;
1607 }
1608
1609 /* If the repository has not been opened already, then find the
1610 ** repository based on the first element of PATH_INFO and open it.
1611 */
1612 zPathInfo = PD("PATH_INFO","");
1613 if( !g.repositoryOpen ){
1614 char *zRepo, *zToFree;
1615 const char *zOldScript = PD("SCRIPT_NAME", "");
1616 char *zNewScript;
1617 int j, k;
1618 i64 szFile;
1619
1620 i = zPathInfo[0]!=0;
1621 while( 1 ){
1622 while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; }
1623 zRepo = zToFree = mprintf("%s%.*s.fossil",g.zRepositoryName,i,zPathInfo);
1624
1625 /* To avoid mischief, make sure the repository basename contains no
1626 ** characters other than alphanumerics, "/", "_", "-", and ".", and
1627 ** that "-" never occurs immediately after a "/" and that "." is always
1628 ** surrounded by two alphanumerics. Any character that does not
1629 ** satisfy these constraints is converted into "_".
1630 */
1631 szFile = 0;
1632 for(j=strlen(g.zRepositoryName)+1, k=0; zRepo[j] && k<i-1; j++, k++){
1633 char c = zRepo[j];
1634 if( fossil_isalnum(c) ) continue;
1635 if( c=='/' ) continue;
1636 if( c=='_' ) continue;
1637 if( c=='-' && zRepo[j-1]!='/' ) continue;
1638 if( c=='.' && fossil_isalnum(zRepo[j-1]) && fossil_isalnum(zRepo[j+1])){
1639 continue;
1640 }
1641 szFile = 1;
1642 break;
1643 }
1644 if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){
1645 if( zRepo[0]=='/' && zRepo[1]=='/' ){ zRepo++; j--; }
1646 szFile = file_size(zRepo);
1647 /* this should only be set from the --baseurl option, not CGI */
1648 if( g.zBaseURL && g.zBaseURL[0]!=0 && g.zTop && g.zTop[0]!=0 &&
1649 file_isdir(g.zRepositoryName)==1 ){
1650 g.zBaseURL = mprintf("%s%.*s", g.zBaseURL, i, zPathInfo);
1651 g.zTop = mprintf("%s%.*s", g.zTop, i, zPathInfo);
1652 }
1653 }
1654 if( szFile<0 && i>0 ){
1655 const char *zMimetype;
1656 assert( fossil_strcmp(&zRepo[j], ".fossil")==0 );
1657 zRepo[j] = 0;
1658 if( zPathInfo[i]=='/' && file_isdir(zRepo)==1 ){
1659 fossil_free(zToFree);
1660 i++;
1661 continue;
1662 }
1663 if( pFileGlob!=0
1664 && file_isfile(zRepo)
1665 && glob_match(pFileGlob, zRepo)
1666 && sqlite3_strglob("*.fossil*",zRepo)!=0
1667 && (zMimetype = mimetype_from_name(zRepo))!=0
1668 && strcmp(zMimetype, "application/x-fossil-artifact")!=0
1669 ){
1670 Blob content;
1671 blob_read_from_file(&content, zRepo);
1672 cgi_set_content_type(zMimetype);
1673 cgi_set_content(&content);
1674 cgi_reply();
1675 return;
1676 }
1677 zRepo[j] = '.';
1678 }
1679
1680 if( szFile<1024 ){
1681 set_base_url(0);
1682 if( strcmp(zPathInfo,"/")==0
1683 && allowRepoList
1684 && repo_list_page() ){
1685 /* Will return a list of repositories */
1686 }else if( zNotFound ){
1687 cgi_redirect(zNotFound);
1688 }else{
1689 #ifdef FOSSIL_ENABLE_JSON
1690 if(g.json.isJsonMode){
1691 json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
1692 return;
1693 }
1694 #endif
1695 @ <h1>Not Found</h1>
1696 cgi_set_status(404, "not found");
1697 cgi_reply();
1698 }
1699 return;
1700 }
1701 break;
1702 }
1703 zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo);
1704 cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]);
1705 zPathInfo += i;
1706 cgi_replace_parameter("SCRIPT_NAME", zNewScript);
1707 db_open_repository(zRepo);
1708 if( g.fHttpTrace ){
1709 fprintf(stderr,
1710 "# repository: [%s]\n"
1711 "# new PATH_INFO = [%s]\n"
1712 "# new SCRIPT_NAME = [%s]\n",
1713 zRepo, zPathInfo, zNewScript);
1714 }
1715 }
1716
1717 /* Find the page that the user has requested, construct and deliver that
1718 ** page.
1719 */
1720 if( g.zContentType &&
1721 strncmp(g.zContentType, "application/x-fossil", 20)==0 ){
1722 zPathInfo = "/xfer";
1723 }
1724 set_base_url(0);
1725 if( zPathInfo==0 || zPathInfo[0]==0
1726 || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
1727 #ifdef FOSSIL_ENABLE_JSON
1728 if(g.json.isJsonMode){
1729 json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
1730 fossil_exit(0);
1731 }
1732 #endif
1733 fossil_redirect_home() /*does not return*/;
1734 }else{
1735 zPath = mprintf("%s", zPathInfo);
1736 }
1737
1738 /* Make g.zPath point to the first element of the path. Make
1739 ** g.zExtra point to everything past that point.
1740 */
1741 while(1){
1742 char *zAltRepo = 0;
1743 g.zPath = &zPath[1];
1744 for(i=1; zPath[i] && zPath[i]!='/'; i++){}
1745 if( zPath[i]=='/' ){
1746 zPath[i] = 0;
1747 g.zExtra = &zPath[i+1];
1748
1749 /* Look for sub-repositories. A sub-repository is another repository
1750 ** that accepts the login credentials of the current repository. A
1751 ** subrepository is identified by a CONFIG table entry "subrepo:NAME"
1752 ** where NAME is the first component of the path. The value of the
1753 ** the CONFIG entries is the string "USER:FILENAME" where USER is the
1754 ** USER name to log in as in the subrepository and FILENAME is the
1755 ** repository filename.
1756 */
1757 zAltRepo = db_text(0, "SELECT value FROM config WHERE name='subrepo:%q'",
1758 g.zPath);
1759 if( zAltRepo ){
1760 int nHost;
1761 int jj;
1762 char *zUser = zAltRepo;
1763 login_check_credentials();
1764 for(jj=0; zAltRepo[jj] && zAltRepo[jj]!=':'; jj++){}
1765 if( zAltRepo[jj]==':' ){
1766 zAltRepo[jj] = 0;
1767 zAltRepo += jj+1;
1768 }else{
1769 zUser = "nobody";
1770 }
1771 if( g.zLogin==0 || g.zLogin[0]==0 ) zUser = "nobody";
1772 if( zAltRepo[0]!='/' ){
1773 zAltRepo = mprintf("%s/../%s", g.zRepositoryName, zAltRepo);
1774 file_simplify_name(zAltRepo, -1, 0);
1775 }
1776 db_close(1);
1777 db_open_repository(zAltRepo);
1778 login_as_user(zUser);
1779 g.perm.Password = 0;
1780 zPath += i;
1781 nHost = g.zTop - g.zBaseURL;
1782 g.zBaseURL = mprintf("%z/%s", g.zBaseURL, g.zPath);
1783 g.zTop = g.zBaseURL + nHost;
1784 continue;
1785 }
1786 }else{
1787 g.zExtra = 0;
1788 }
1789 break;
1790 }
1791 #ifdef FOSSIL_ENABLE_JSON
1792 /*
1793 ** Workaround to allow us to customize some following behaviour for
1794 ** JSON mode. The problem is, we don't always know if we're in JSON
1795 ** mode at this point (namely, for GET mode we don't know but POST
1796 ** we do), so we snoop g.zPath and cheat a bit.
1797 */
1798 if( !g.json.isJsonMode && g.zPath && (0==strncmp("json",g.zPath,4)) ){
1799 g.json.isJsonMode = 1;
1800 }
1801 #endif
1802 if( g.zExtra ){
1803 /* CGI parameters get this treatment elsewhere, but places like getfile
1804 ** will use g.zExtra directly.
1805 ** Reminder: the login mechanism uses 'name' differently, and may
1806 ** eventually have a problem/collision with this.
1807 **
1808 ** Disabled by stephan when running in JSON mode because this
1809 ** particular parameter name is very common and i have had no end
1810 ** of grief with this handling. The JSON API never relies on the
1811 ** handling below, and by disabling it in JSON mode I can remove
1812 ** lots of special-case handling in several JSON handlers.
1813 */
1814 #ifdef FOSSIL_ENABLE_JSON
1815 if(!g.json.isJsonMode){
1816 #endif
1817 dehttpize(g.zExtra);
1818 cgi_set_parameter_nocopy("name", g.zExtra, 1);
1819 #ifdef FOSSIL_ENABLE_JSON
1820 }
1821 #endif
1822 }
1823
1824 /* Locate the method specified by the path and execute the function
1825 ** that implements that method.
1826 */
1827 if( name_search(g.zPath, aWebpage, count(aWebpage), 0, &idx) ){
1828 #ifdef FOSSIL_ENABLE_JSON
1829 if(g.json.isJsonMode){
1830 json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,0);
1831 }else
1832 #endif
1833 {
1834 #ifdef FOSSIL_ENABLE_TH1_HOOKS
1835 int rc;
1836 if( !g.fNoThHook ){
1837 rc = Th_WebpageHook(g.zPath, 0);
1838 }else{
1839 rc = TH_OK;
1840 }
1841 if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
1842 if( rc==TH_OK || rc==TH_RETURN ){
1843 #endif
1844 cgi_set_status(404,"Not Found");
1845 @ <h1>Not Found</h1>
1846 @ <p>Page not found: %h(g.zPath)</p>
1847 #ifdef FOSSIL_ENABLE_TH1_HOOKS
1848 }
1849 if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
1850 Th_WebpageNotify(g.zPath, 0);
1851 }
1852 }
1853 #endif
1854 }
1855 }else if( aWebpage[idx].xFunc!=page_xfer && db_schema_is_outofdate() ){
1856 #ifdef FOSSIL_ENABLE_JSON
1857 if(g.json.isJsonMode){
1858 json_err(FSL_JSON_E_DB_NEEDS_REBUILD,NULL,0);
1859 }else
1860 #endif
1861 {
1862 @ <h1>Server Configuration Error</h1>
1863 @ <p>The database schema on the server is out-of-date. Please ask
1864 @ the administrator to run <b>fossil rebuild</b>.</p>
1865 }
1866 }else{
1867 #ifdef FOSSIL_ENABLE_TH1_HOOKS
1868 /*
1869 ** The TH1 return codes from the hook will be handled as follows:
1870 **
1871 ** TH_OK: The xFunc() and the TH1 notification will both be executed.
1872 **
1873 ** TH_ERROR: The xFunc() will be executed, the TH1 notification will be
1874 ** skipped. If the xFunc() is being hooked, the error message
1875 ** will be emitted.
1876 **
1877 ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped.
1878 **
1879 ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be
1880 ** skipped.
1881 **
1882 ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be
1883 ** executed.
1884 */
1885 int rc;
1886 if( !g.fNoThHook ){
1887 rc = Th_WebpageHook(aWebpage[idx].zName, aWebpage[idx].cmdFlags);
1888 }else{
1889 rc = TH_OK;
1890 }
1891 if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
1892 if( rc==TH_OK || rc==TH_RETURN ){
1893 #endif
1894 aWebpage[idx].xFunc();
1895 #ifdef FOSSIL_ENABLE_TH1_HOOKS
1896 }
1897 if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
1898 Th_WebpageNotify(aWebpage[idx].zName, aWebpage[idx].cmdFlags);
1899 }
1900 }
1901 #endif
1902 }
1903
1904 /* Return the result.
1905 */
1906 cgi_reply();
1907 }
1908
1909 /* If the CGI program contains one or more lines of the form
1910 **
1911 ** redirect: repository-filename http://hostname/path/%s
1912 **
1913 ** then control jumps here. Search each repository for an artifact ID
1914 ** or ticket ID that matches the "name" CGI parameter and for the
1915 ** first match, redirect to the corresponding URL with the "name" CGI
1916 ** parameter inserted. Paint an error page if no match is found.
1917 **
1918 ** If there is a line of the form:
1919 **
1920 ** redirect: * URL
1921 **
1922 ** Then a redirect is made to URL if no match is found. Otherwise a
1923 ** very primitive error message is returned.
1924 */
1925 static void redirect_web_page(int nRedirect, char **azRedirect){
1926 int i; /* Loop counter */
1927 const char *zNotFound = 0; /* Not found URL */
1928 const char *zName = P("name");
1929 set_base_url(0);
1930 if( zName==0 ){
1931 zName = P("SCRIPT_NAME");
1932 if( zName && zName[0]=='/' ) zName++;
1933 }
1934 if( zName && validate16(zName, strlen(zName)) ){
1935 for(i=0; i<nRedirect; i++){
1936 if( fossil_strcmp(azRedirect[i*2],"*")==0 ){
1937 zNotFound = azRedirect[i*2+1];
1938 continue;
1939 }
1940 db_open_repository(azRedirect[i*2]);
1941 if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'", zName) ||
1942 db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){
1943 cgi_redirectf(azRedirect[i*2+1] /*works-like:"%s"*/, zName);
1944 return;
1945 }
1946 db_close(1);
1947 }
1948 }
1949 if( zNotFound ){
1950 cgi_redirectf(zNotFound /*works-like:"%s"*/, zName);
1951 }else{
1952 @ <html>
1953 @ <head><title>No Such Object</title></head>
1954 @ <body>
1955 @ <p>No such object: <b>%h(zName)</b></p>
1956 @ </body>
1957 cgi_reply();
1958 }
1959 }
1960
1961 /*
1962 ** COMMAND: cgi*
1963 **
1964 ** Usage: %fossil ?cgi? SCRIPT
1965 **
1966 ** The SCRIPT argument is the name of a file that is the CGI script
1967 ** that is being run. The command name, "cgi", may be omitted if
1968 ** the GATEWAY_INTERFACE environment variable is set to "CGI" (which
1969 ** should always be the case for CGI scripts run by a webserver.) The
1970 ** SCRIPT file should look something like this:
1971 **
1972 ** #!/usr/bin/fossil
1973 ** repository: /home/somebody/project.db
1974 **
1975 ** The second line defines the name of the repository. After locating
1976 ** the repository, fossil will generate a webpage on stdout based on
1977 ** the values of standard CGI environment variables.
1978 **
1979 ** See also: http, server, winsrv
1980 */
1981 void cmd_cgi(void){
1982 const char *zFile;
1983 const char *zNotFound = 0;
1984 char **azRedirect = 0; /* List of repositories to redirect to */
1985 int nRedirect = 0; /* Number of entries in azRedirect */
1986 Glob *pFileGlob = 0; /* Pattern for files */
1987 int allowRepoList = 0; /* Allow lists of repository files */
1988 Blob config, line, key, value, value2;
1989 if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){
1990 zFile = g.argv[2];
1991 }else{
1992 zFile = g.argv[1];
1993 }
1994 g.httpOut = stdout;
1995 g.httpIn = stdin;
1996 fossil_binary_mode(g.httpOut);
1997 fossil_binary_mode(g.httpIn);
1998 g.cgiOutput = 1;
1999 blob_read_from_file(&config, zFile);
2000 while( blob_line(&config, &line) ){
2001 if( !blob_token(&line, &key) ) continue;
2002 if( blob_buffer(&key)[0]=='#' ) continue;
2003 if( blob_eq(&key, "repository:") && blob_tail(&line, &value) ){
2004 /* repository: FILENAME
2005 **
2006 ** The name of the Fossil repository to be served via CGI. Most
2007 ** fossil CGI scripts have a single non-comment line that contains
2008 ** this one entry.
2009 */
2010 blob_trim(&value);
2011 db_open_repository(blob_str(&value));
2012 blob_reset(&value);
2013 continue;
2014 }
2015 if( blob_eq(&key, "directory:") && blob_token(&line, &value) ){
2016 /* directory: DIRECTORY
2017 **
2018 ** If repository: is omitted, then terms of the PATH_INFO cgi parameter
2019 ** are appended to DIRECTORY looking for a repository (whose name ends
2020 ** in ".fossil") or a file in "files:".
2021 */
2022 db_close(1);
2023 g.zRepositoryName = mprintf("%s", blob_str(&value));
2024 blob_reset(&value);
2025 continue;
2026 }
2027 if( blob_eq(&key, "notfound:") && blob_token(&line, &value) ){
2028 /* notfound: URL
2029 **
2030 ** If using directory: and no suitable repository or file is found,
2031 ** then redirect to URL.
2032 */
2033 zNotFound = mprintf("%s", blob_str(&value));
2034 blob_reset(&value);
2035 continue;
2036 }
2037 if( blob_eq(&key, "localauth") ){
2038 /* localauth
2039 **
2040 ** Grant "administrator" privileges to users connecting with HTTP
2041 ** from IP address 127.0.0.1. Do not bother checking credentials.
2042 */
2043 g.useLocalauth = 1;
2044 continue;
2045 }
2046 if( blob_eq(&key, "repolist") ){
2047 /* repolist
2048 **
2049 ** If using "directory:" and the URL is "/" then generate a page
2050 ** showing a list of available repositories.
2051 */
2052 allowRepoList = 1;
2053 continue;
2054 }
2055 if( blob_eq(&key, "redirect:") && blob_token(&line, &value)
2056 && blob_token(&line, &value2) ){
2057 /* See the header comment on the redirect_web_page() function
2058 ** above for details. */
2059 nRedirect++;
2060 azRedirect = fossil_realloc(azRedirect, 2*nRedirect*sizeof(char*));
2061 azRedirect[nRedirect*2-2] = mprintf("%s", blob_str(&value));
2062 azRedirect[nRedirect*2-1] = mprintf("%s", blob_str(&value2));
2063 blob_reset(&value);
2064 blob_reset(&value2);
2065 continue;
2066 }
2067 if( blob_eq(&key, "files:") && blob_token(&line, &value) ){
2068 /* files: GLOBLIST
2069 **
2070 ** GLOBLIST is a comma-separated list of filename globs. For
2071 ** example: *.html,*.css,*.js
2072 **
2073 ** If the repository: line is omitted and then PATH_INFO is searched
2074 ** for files that match any of these GLOBs and if any such file is
2075 ** found it is returned verbatim. This feature allows "fossil server"
2076 ** to function as a primitive web-server delivering arbitrary content.
2077 */
2078 pFileGlob = glob_create(blob_str(&value));
2079 blob_reset(&value);
2080 continue;
2081 }
2082 if( blob_eq(&key, "setenv:") && blob_token(&line, &value)
2083 && blob_token(&line, &value2) ){
2084 /* setenv: NAME VALUE
2085 **
2086 ** Sets environment variable NAME to VALUE
2087 */
2088 fossil_setenv(blob_str(&value), blob_str(&value2));
2089 blob_reset(&value);
2090 blob_reset(&value2);
2091 continue;
2092 }
2093 if( blob_eq(&key, "debug:") && blob_token(&line, &value) ){
2094 /* debug: FILENAME
2095 **
2096 ** Causes output from cgi_debug() and CGIDEBUG(()) calls to go
2097 ** into FILENAME.
2098 */
2099 g.fDebug = fossil_fopen(blob_str(&value), "ab");
2100 blob_reset(&value);
2101 continue;
2102 }
2103 if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){
2104 /* errorlog: FILENAME
2105 **
2106 ** Causes messages from warnings, errors, and panics to be appended
2107 ** to FILENAME.
2108 */
2109 g.zErrlog = mprintf("%s", blob_str(&value));
2110 blob_reset(&value);
2111 continue;
2112 }
2113 if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){
2114 /* HOME: VALUE
2115 **
2116 ** Set CGI parameter "HOME" to VALUE. This is legacy. Use
2117 ** setenv: instead.
2118 */
2119 cgi_setenv("HOME", blob_str(&value));
2120 blob_reset(&value);
2121 continue;
2122 }
2123 if( blob_eq(&key, "skin:") && blob_token(&line, &value) ){
2124 /* skin: LABEL
2125 **
2126 ** Use one of the built-in skins defined by LABEL. LABEL is the
2127 ** name of the subdirectory under the skins/ directory that holds
2128 ** the elements of the built-in skin. If LABEL does not match,
2129 ** this directive is a silent no-op.
2130 */
2131 skin_use_alternative(blob_str(&value));
2132 blob_reset(&value);
2133 continue;
2134 }
2135 }
2136 blob_reset(&config);
2137 if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){
2138 cgi_panic("Unable to find or open the project repository");
2139 }
2140 cgi_init();
2141 if( nRedirect ){
2142 redirect_web_page(nRedirect, azRedirect);
2143 }else{
2144 process_one_web_page(zNotFound, pFileGlob, allowRepoList);
2145 }
2146 }
2147
2148 /*
2149 ** If g.argv[arg] exists then it is either the name of a repository
2150 ** that will be used by a server, or else it is a directory that
2151 ** contains multiple repositories that can be served. If g.argv[arg]
2152 ** is a directory, the repositories it contains must be named
2153 ** "*.fossil". If g.argv[arg] does not exist, then we must be within
2154 ** an open check-out and the repository serve is the repository of
2155 ** that check-out.
2156 **
2157 ** Open the repository to be served if it is known. If g.argv[arg] is
2158 ** a directory full of repositories, then set g.zRepositoryName to
2159 ** the name of that directory and the specific repository will be
2160 ** opened later by process_one_web_page() based on the content of
2161 ** the PATH_INFO variable.
2162 **
2163 ** If the fCreate flag is set, then create the repository if it
2164 ** does not already exist.
2165 */
2166 static void find_server_repository(int arg, int fCreate){
2167 if( g.argc<=arg ){
2168 db_must_be_within_tree();
2169 }else{
2170 const char *zRepo = g.argv[arg];
2171 int isDir = file_isdir(zRepo);
2172 if( isDir==1 ){
2173 g.zRepositoryName = mprintf("%s", zRepo);
2174 file_simplify_name(g.zRepositoryName, -1, 0);
2175 }else{
2176 if( isDir==0 && fCreate ){
2177 const char *zPassword;
2178 db_create_repository(zRepo);
2179 db_open_repository(zRepo);
2180 db_begin_transaction();
2181 db_initial_setup(0, "now", g.zLogin);
2182 db_end_transaction(0);
2183 fossil_print("project-id: %s\n", db_get("project-code", 0));
2184 fossil_print("server-id: %s\n", db_get("server-code", 0));
2185 zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
2186 fossil_print("admin-user: %s (initial password is \"%s\")\n",
2187 g.zLogin, zPassword);
2188 cache_initialize();
2189 g.zLogin = 0;
2190 g.userUid = 0;
2191 }else{
2192 db_open_repository(zRepo);
2193 }
2194 }
2195 }
2196 }
2197
2198 /*
2199 ** undocumented format:
2200 **
2201 ** fossil http INFILE OUTFILE IPADDR ?REPOSITORY?
2202 **
2203 ** The argv==6 form (with no options) is used by the win32 server only.
2204 **
2205 ** COMMAND: http*
2206 **
2207 ** Usage: %fossil http ?REPOSITORY? ?OPTIONS?
2208 **
2209 ** Handle a single HTTP request appearing on stdin. The resulting webpage
2210 ** is delivered on stdout. This method is used to launch an HTTP request
2211 ** handler from inetd, for example. The argument is the name of the
2212 ** repository.
2213 **
2214 ** If REPOSITORY is a directory that contains one or more repositories,
2215 ** either directly in REPOSITORY itself or in subdirectories, and
2216 ** with names of the form "*.fossil" then a prefix of the URL pathname
2217 ** selects from among the various repositories. If the pathname does
2218 ** not select a valid repository and the --notfound option is available,
2219 ** then the server redirects (HTTP code 302) to the URL of --notfound.
2220 ** When REPOSITORY is a directory, the pathname must contain only
2221 ** alphanumerics, "_", "/", "-" and "." and no "-" may occur after a "/"
2222 ** and every "." must be surrounded on both sides by alphanumerics or else
2223 ** a 404 error is returned. Static content files in the directory are
2224 ** returned if they match comma-separate GLOB pattern specified by --files
2225 ** and do not match "*.fossil*" and have a well-known suffix.
2226 **
2227 ** The --host option can be used to specify the hostname for the server.
2228 ** The --https option indicates that the request came from HTTPS rather
2229 ** than HTTP. If --nossl is given, then SSL connections will not be available,
2230 ** thus also no redirecting from http: to https: will take place.
2231 **
2232 ** If the --localauth option is given, then automatic login is performed
2233 ** for requests coming from localhost, if the "localauth" setting is not
2234 ** enabled.
2235 **
2236 ** Options:
2237 ** --baseurl URL base URL (useful with reverse proxies)
2238 ** --files GLOB comma-separate glob patterns for static file to serve
2239 ** --localauth enable automatic login for local connections
2240 ** --host NAME specify hostname of the server
2241 ** --https signal a request coming in via https
2242 ** --nojail drop root privilege but do not enter the chroot jail
2243 ** --nossl signal that no SSL connections are available
2244 ** --notfound URL use URL as "HTTP 404, object not found" page.
2245 ** --repolist If REPOSITORY is directory, URL "/" lists all repos
2246 ** --scgi Interpret input as SCGI rather than HTTP
2247 ** --skin LABEL Use override skin LABEL
2248 **
2249 ** See also: cgi, server, winsrv
2250 */
2251 void cmd_http(void){
2252 const char *zIpAddr = 0;
2253 const char *zNotFound;
2254 const char *zHost;
2255 const char *zAltBase;
2256 const char *zFileGlob;
2257 int useSCGI;
2258 int noJail;
2259 int allowRepoList;
2260
2261 /* The winhttp module passes the --files option as --files-urlenc with
2262 ** the argument being URL encoded, to avoid wildcard expansion in the
2263 ** shell. This option is for internal use and is undocumented.
2264 */
2265 zFileGlob = find_option("files-urlenc",0,1);
2266 if( zFileGlob ){
2267 char *z = mprintf("%s", zFileGlob);
2268 dehttpize(z);
2269 zFileGlob = z;
2270 }else{
2271 zFileGlob = find_option("files",0,1);
2272 }
2273 skin_override();
2274 zNotFound = find_option("notfound", 0, 1);
2275 noJail = find_option("nojail",0,0)!=0;
2276 allowRepoList = find_option("repolist",0,0)!=0;
2277 g.useLocalauth = find_option("localauth", 0, 0)!=0;
2278 g.sslNotAvailable = find_option("nossl", 0, 0)!=0;
2279 useSCGI = find_option("scgi", 0, 0)!=0;
2280 zAltBase = find_option("baseurl", 0, 1);
2281 if( zAltBase ) set_base_url(zAltBase);
2282 if( find_option("https",0,0)!=0 ){
2283 zIpAddr = fossil_getenv("REMOTE_HOST"); /* From stunnel */
2284 cgi_replace_parameter("HTTPS","on");
2285 }
2286 zHost = find_option("host", 0, 1);
2287 if( zHost ) cgi_replace_parameter("HTTP_HOST",zHost);
2288
2289 /* We should be done with options.. */
2290 verify_all_options();
2291
2292 if( g.argc!=2 && g.argc!=3 && g.argc!=5 && g.argc!=6 ){
2293 fossil_fatal("no repository specified");
2294 }
2295 g.cgiOutput = 1;
2296 g.fullHttpReply = 1;
2297 if( g.argc>=5 ){
2298 g.httpIn = fossil_fopen(g.argv[2], "rb");
2299 g.httpOut = fossil_fopen(g.argv[3], "wb");
2300 zIpAddr = g.argv[4];
2301 find_server_repository(5, 0);
2302 }else{
2303 g.httpIn = stdin;
2304 g.httpOut = stdout;
2305 find_server_repository(2, 0);
2306 }
2307 if( zIpAddr==0 ){
2308 zIpAddr = cgi_ssh_remote_addr(0);
2309 if( zIpAddr && zIpAddr[0] ){
2310 g.fSshClient |= CGI_SSH_CLIENT;
2311 }
2312 }
2313 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
2314 if( useSCGI ){
2315 cgi_handle_scgi_request();
2316 }else if( g.fSshClient & CGI_SSH_CLIENT ){
2317 ssh_request_loop(zIpAddr, glob_create(zFileGlob));
2318 }else{
2319 cgi_handle_http_request(zIpAddr);
2320 }
2321 process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList);
2322 }
2323
2324 /*
2325 ** Process all requests in a single SSH connection if possible.
2326 */
2327 void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){
2328 blob_zero(&g.cgiIn);
2329 do{
2330 cgi_handle_ssh_http_request(zIpAddr);
2331 process_one_web_page(0, FileGlob, 0);
2332 blob_reset(&g.cgiIn);
2333 } while ( g.fSshClient & CGI_SSH_FOSSIL ||
2334 g.fSshClient & CGI_SSH_COMPAT );
2335 }
2336
2337 /*
2338 ** Note that the following command is used by ssh:// processing.
2339 **
2340 ** COMMAND: test-http
2341 **
2342 ** Works like the http command but gives setup permission to all users.
2343 **
2344 ** Options:
2345 ** --th-trace trace TH1 execution (for debugging purposes)
2346 **
2347 */
2348 void cmd_test_http(void){
2349 const char *zIpAddr; /* IP address of remote client */
2350
2351 Th_InitTraceLog();
2352 login_set_capabilities("sx", 0);
2353 g.useLocalauth = 1;
2354 g.httpIn = stdin;
2355 g.httpOut = stdout;
2356 find_server_repository(2, 0);
2357 g.cgiOutput = 1;
2358 g.fullHttpReply = 1;
2359 zIpAddr = cgi_ssh_remote_addr(0);
2360 if( zIpAddr && zIpAddr[0] ){
2361 g.fSshClient |= CGI_SSH_CLIENT;
2362 ssh_request_loop(zIpAddr, 0);
2363 }else{
2364 cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
2365 cgi_handle_http_request(0);
2366 process_one_web_page(0, 0, 0);
2367 }
2368 }
2369
2370 #if !defined(_WIN32)
2371 #if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__)
2372 /*
2373 ** Search for an executable on the PATH environment variable.
2374 ** Return true (1) if found and false (0) if not found.
2375 */
2376 static int binaryOnPath(const char *zBinary){
2377 const char *zPath = fossil_getenv("PATH");
2378 char *zFull;
2379 int i;
2380 int bExists;
2381 while( zPath && zPath[0] ){
2382 while( zPath[0]==':' ) zPath++;
2383 for(i=0; zPath[i] && zPath[i]!=':'; i++){}
2384 zFull = mprintf("%.*s/%s", i, zPath, zBinary);
2385 bExists = file_access(zFull, X_OK);
2386 fossil_free(zFull);
2387 if( bExists==0 ) return 1;
2388 zPath += i;
2389 }
2390 return 0;
2391 }
2392 #endif
2393 #endif
2394
2395 /*
2396 ** COMMAND: server*
2397 ** COMMAND: ui
2398 **
2399 ** Usage: %fossil server ?OPTIONS? ?REPOSITORY?
2400 ** Or: %fossil ui ?OPTIONS? ?REPOSITORY?
2401 **
2402 ** Open a socket and begin listening and responding to HTTP requests on
2403 ** TCP port 8080, or on any other TCP port defined by the -P or
2404 ** --port option. The optional argument is the name of the repository.
2405 ** The repository argument may be omitted if the working directory is
2406 ** within an open checkout.
2407 **
2408 ** The "ui" command automatically starts a web browser after initializing
2409 ** the web server. The "ui" command also binds to 127.0.0.1 and so will
2410 ** only process HTTP traffic from the local machine.
2411 **
2412 ** The REPOSITORY can be a directory (aka folder) that contains one or
2413 ** more repositories with names ending in ".fossil". In this case, a
2414 ** prefix of the URL pathname is used to search the directory for an
2415 ** appropriate repository. To thwart mischief, the pathname in the URL must
2416 ** contain only alphanumerics, "_", "/", "-", and ".", and no "-" may
2417 ** occur after "/", and every "." must be surrounded on both sides by
2418 ** alphanumerics. Any pathname that does not satisfy these constraints
2419 ** results in a 404 error. Files in REPOSITORY that match the comma-separated
2420 ** list of glob patterns given by --files and that have known suffixes
2421 ** such as ".txt" or ".html" or ".jpeg" and do not match the pattern
2422 ** "*.fossil*" will be served as static content. With the "ui" command,
2423 ** the REPOSITORY can only be a directory if the --notfound option is
2424 ** also present.
2425 **
2426 ** By default, the "ui" command provides full administrative access without
2427 ** having to log in. This can be disabled by turning off the "localauth"
2428 ** setting. Automatic login for the "server" command is available if the
2429 ** --localauth option is present and the "localauth" setting is off and the
2430 ** connection is from localhost. The "ui" command also enables --repolist
2431 ** by default.
2432 **
2433 ** Options:
2434 ** --baseurl URL Use URL as the base (useful for reverse proxies)
2435 ** --create Create a new REPOSITORY if it does not already exist
2436 ** --page PAGE Start "ui" on PAGE. ex: --page "timeline?y=ci"
2437 ** --files GLOBLIST Comma-separated list of glob patterns for static files
2438 ** --localauth enable automatic login for requests from localhost
2439 ** --localhost listen on 127.0.0.1 only (always true for "ui")
2440 ** --https signal a request coming in via https
2441 ** --nojail Drop root privileges but do not enter the chroot jail
2442 ** --nossl signal that no SSL connections are available
2443 ** --notfound URL Redirect
2444 ** -P|--port TCPPORT listen to request on port TCPPORT
2445 ** --th-trace trace TH1 execution (for debugging purposes)
2446 ** --repolist If REPOSITORY is dir, URL "/" lists repos.
2447 ** --scgi Accept SCGI rather than HTTP
2448 ** --skin LABEL Use override skin LABEL
2449
2450 **
2451 ** See also: cgi, http, winsrv
2452 */
2453 void cmd_webserver(void){
2454 int iPort, mxPort; /* Range of TCP ports allowed */
2455 const char *zPort; /* Value of the --port option */
2456 const char *zBrowser; /* Name of web browser program */
2457 char *zBrowserCmd = 0; /* Command to launch the web browser */
2458 int isUiCmd; /* True if command is "ui", not "server' */
2459 const char *zNotFound; /* The --notfound option or NULL */
2460 int flags = 0; /* Server flags */
2461 #if !defined(_WIN32)
2462 int noJail; /* Do not enter the chroot jail */
2463 #endif
2464 int allowRepoList; /* List repositories on URL "/" */
2465 const char *zAltBase; /* Argument to the --baseurl option */
2466 const char *zFileGlob; /* Static content must match this */
2467 char *zIpAddr = 0; /* Bind to this IP address */
2468 int fCreate = 0; /* The --create flag */
2469 const char *zInitPage = 0; /* Start on this page. --page option */
2470
2471 #if defined(_WIN32)
2472 const char *zStopperFile; /* Name of file used to terminate server */
2473 zStopperFile = find_option("stopper", 0, 1);
2474 #endif
2475
2476 zFileGlob = find_option("files-urlenc",0,1);
2477 if( zFileGlob ){
2478 char *z = mprintf("%s", zFileGlob);
2479 dehttpize(z);
2480 zFileGlob = z;
2481 }else{
2482 zFileGlob = find_option("files",0,1);
2483 }
2484 skin_override();
2485 #if !defined(_WIN32)
2486 noJail = find_option("nojail",0,0)!=0;
2487 #endif
2488 g.useLocalauth = find_option("localauth", 0, 0)!=0;
2489 Th_InitTraceLog();
2490 zPort = find_option("port", "P", 1);
2491 isUiCmd = g.argv[1][0]=='u';
2492 if( isUiCmd ){
2493 zInitPage = find_option("page", 0, 1);
2494 }
2495 if( zInitPage==0 ) zInitPage = "";
2496 zNotFound = find_option("notfound", 0, 1);
2497 allowRepoList = find_option("repolist",0,0)!=0;
2498 zAltBase = find_option("baseurl", 0, 1);
2499 fCreate = find_option("create",0,0)!=0;
2500 if( find_option("scgi", 0, 0)!=0 ) flags |= HTTP_SERVER_SCGI;
2501 if( zAltBase ){
2502 set_base_url(zAltBase);
2503 }
2504 g.sslNotAvailable = find_option("nossl", 0, 0)!=0;
2505 if( find_option("https",0,0)!=0 ){
2506 cgi_replace_parameter("HTTPS","on");
2507 }else{
2508 /* without --https, defaults to not available. */
2509 g.sslNotAvailable = 1;
2510 }
2511 if( find_option("localhost", 0, 0)!=0 ){
2512 flags |= HTTP_SERVER_LOCALHOST;
2513 }
2514
2515 /* We should be done with options.. */
2516 verify_all_options();
2517
2518 if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
2519 if( isUiCmd ){
2520 flags |= HTTP_SERVER_LOCALHOST|HTTP_SERVER_REPOLIST;
2521 g.useLocalauth = 1;
2522 allowRepoList = 1;
2523 }
2524 find_server_repository(2, fCreate);
2525 if( zPort ){
2526 int i;
2527 for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){}
2528 if( i>0 ){
2529 zIpAddr = mprintf("%.*s", i, zPort);
2530 zPort += i+1;
2531 }
2532 iPort = mxPort = atoi(zPort);
2533 }else{
2534 iPort = db_get_int("http-port", 8080);
2535 mxPort = iPort+100;
2536 }
2537 #if !defined(_WIN32)
2538 /* Unix implementation */
2539 if( isUiCmd ){
2540 #if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__)
2541 zBrowser = db_get("web-browser", 0);
2542 if( zBrowser==0 ){
2543 static const char *const azBrowserProg[] =
2544 { "xdg-open", "gnome-open", "firefox", "google-chrome" };
2545 int i;
2546 zBrowser = "echo";
2547 for(i=0; i<sizeof(azBrowserProg)/sizeof(azBrowserProg[0]); i++){
2548 if( binaryOnPath(azBrowserProg[i]) ){
2549 zBrowser = azBrowserProg[i];
2550 break;
2551 }
2552 }
2553 }
2554 #else
2555 zBrowser = db_get("web-browser", "open");
2556 #endif
2557 if( zIpAddr ){
2558 zBrowserCmd = mprintf("%s http://%s:%%d/%s &",
2559 zBrowser, zIpAddr, zInitPage);
2560 }else{
2561 zBrowserCmd = mprintf("%s http://localhost:%%d/%s &",
2562 zBrowser, zInitPage);
2563 }
2564 }
2565 if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY;
2566 if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT;
2567 db_close(1);
2568 if( cgi_http_server(iPort, mxPort, zBrowserCmd, zIpAddr, flags) ){
2569 fossil_fatal("unable to listen on TCP socket %d", iPort);
2570 }
2571 g.httpIn = stdin;
2572 g.httpOut = stdout;
2573 if( g.fHttpTrace || g.fSqlTrace ){
2574 fprintf(stderr, "====== SERVER pid %d =======\n", getpid());
2575 }
2576 g.cgiOutput = 1;
2577 find_server_repository(2, 0);
2578 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
2579 if( flags & HTTP_SERVER_SCGI ){
2580 cgi_handle_scgi_request();
2581 }else{
2582 cgi_handle_http_request(0);
2583 }
2584 process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList);
2585 #else
2586 /* Win32 implementation */
2587 if( isUiCmd ){
2588 zBrowser = db_get("web-browser", "start");
2589 if( zIpAddr ){
2590 zBrowserCmd = mprintf("%s http://%s:%%d/%s &",
2591 zBrowser, zIpAddr, zInitPage);
2592 }else{
2593 zBrowserCmd = mprintf("%s http://localhost:%%d/%s &",
2594 zBrowser, zInitPage);
2595 }
2596 }
2597 if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY;
2598 if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT;
2599 db_close(1);
2600 if( allowRepoList ){
2601 flags |= HTTP_SERVER_REPOLIST;
2602 }
2603 if( win32_http_service(iPort, zNotFound, zFileGlob, flags) ){
2604 win32_http_server(iPort, mxPort, zBrowserCmd,
2605 zStopperFile, zNotFound, zFileGlob, zIpAddr, flags);
2606 }
2607 #endif
2608 }
2609
2610 /*
2611 ** COMMAND: test-echo
2612 **
2613 ** Usage: %fossil test-echo [--hex] ARGS...
2614 **
2615 ** Echo all command-line arguments (enclosed in [...]) to the screen so that
2616 ** wildcard expansion behavior of the host shell can be investigated.
2617 **
2618 ** With the --hex option, show the output as hexadecimal. This can be used
2619 ** to verify the fossil_path_to_utf8() routine on Windows and Mac.
2620 */
2621 void test_echo_cmd(void){
2622 int i, j;
2623 if( find_option("hex",0,0)==0 ){
2624 fossil_print("g.nameOfExe = [%s]\n", g.nameOfExe);
2625 for(i=0; i<g.argc; i++){
2626 fossil_print("argv[%d] = [%s]\n", i, g.argv[i]);
2627 }
2628 }else{
2629 unsigned char *z, c;
2630 for(i=0; i<g.argc; i++){
2631 fossil_print("argv[%d] = [", i);
2632 z = (unsigned char*)g.argv[i];
2633 for(j=0; (c = z[j])!=0; j++){
2634 fossil_print("%02x", c);
2635 }
2636 fossil_print("]\n");
2637 }
2638 }
2639 }