Fossil SCM

fossil-scm / src / main.c
Blame History Raw 3916 lines
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
** [email protected]
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
#if defined(_WIN32)
24
# include <windows.h>
25
# include <io.h>
26
# define GETPID (int)GetCurrentProcessId
27
#endif
28
29
/* BUGBUG: This (PID_T) does not work inside of INTERFACE block. */
30
#if USE_SEE
31
#if defined(_WIN32)
32
typedef DWORD PID_T;
33
#else
34
typedef pid_t PID_T;
35
#endif
36
#endif
37
38
#include "main.h"
39
#include <string.h>
40
#include <time.h>
41
#include <fcntl.h>
42
#include <sys/types.h>
43
#include <sys/stat.h>
44
#include <stdlib.h> /* atexit() */
45
#include <zlib.h>
46
#if !defined(_WIN32)
47
# include <errno.h> /* errno global */
48
# include <unistd.h>
49
# include <signal.h>
50
# define GETPID getpid
51
#endif
52
#ifdef FOSSIL_ENABLE_SSL
53
# include "openssl/crypto.h"
54
#endif
55
#if INTERFACE
56
#ifdef FOSSIL_ENABLE_TCL
57
# include "tcl.h"
58
#endif
59
#ifdef FOSSIL_ENABLE_JSON
60
# include "cson_amalgamation.h" /* JSON API. */
61
# include "json_detail.h"
62
#endif
63
#ifdef HAVE_BACKTRACE
64
# include <execinfo.h>
65
#endif
66
67
/*
68
** Default length of a timeout for serving an HTTP request. Changeable
69
** using the "--timeout N" command-line option or via "timeout: N" in the
70
** CGI script.
71
*/
72
#ifndef FOSSIL_DEFAULT_TIMEOUT
73
# define FOSSIL_DEFAULT_TIMEOUT 600 /* 10 minutes */
74
#endif
75
76
/*
77
** Maximum number of auxiliary parameters on reports
78
*/
79
#define MX_AUX 5
80
81
/*
82
** Holds flags for fossil user permissions.
83
*/
84
struct FossilUserPerms {
85
char Setup; /* s: use Setup screens on web interface */
86
char Admin; /* a: administrative permission */
87
char Password; /* p: change password */
88
char Write; /* i: xfer inbound. check-in */
89
char Read; /* o: xfer outbound. check-out */
90
char Hyperlink; /* h: enable the display of hyperlinks */
91
char Clone; /* g: clone */
92
char RdWiki; /* j: view wiki via web */
93
char NewWiki; /* f: create new wiki via web */
94
char ApndWiki; /* m: append to wiki via web */
95
char WrWiki; /* k: edit wiki via web */
96
char ModWiki; /* l: approve and publish wiki content (Moderator) */
97
char RdTkt; /* r: view tickets via web */
98
char NewTkt; /* n: create new tickets */
99
char ApndTkt; /* c: append to tickets via the web */
100
char WrTkt; /* w: make changes to tickets via web */
101
char ModTkt; /* q: approve and publish ticket changes (Moderator) */
102
char Attach; /* b: add attachments */
103
char TktFmt; /* t: create new ticket report formats */
104
char RdAddr; /* e: read email addresses or other private data */
105
char Zip; /* z: download zipped artifact via /zip URL */
106
char Private; /* x: can send and receive private content */
107
char WrUnver; /* y: can push unversioned content */
108
char RdForum; /* 2: Read forum posts */
109
char WrForum; /* 3: Create new forum posts */
110
char WrTForum; /* 4: Post to forums not subject to moderation */
111
char ModForum; /* 5: Moderate (approve or reject) forum posts */
112
char AdminForum; /* 6: Grant capability 4 to other users */
113
char EmailAlert; /* 7: Sign up for email notifications */
114
char Announce; /* A: Send announcements */
115
char Chat; /* C: read or write the chatroom */
116
char Debug; /* D: show extra Fossil debugging features */
117
/* These last two are included to block infinite recursion */
118
char XReader; /* u: Inherit all privileges of "reader" */
119
char XDeveloper; /* v: Inherit all privileges of "developer" */
120
};
121
122
#ifdef FOSSIL_ENABLE_TCL
123
/*
124
** All Tcl related context information is in this structure. This structure
125
** definition has been copied from and should be kept in sync with the one in
126
** "th_tcl.c".
127
*/
128
struct TclContext {
129
int argc; /* Number of original (expanded) arguments. */
130
char **argv; /* Full copy of the original (expanded) arguments. */
131
void *hLibrary; /* The Tcl library module handle. */
132
void *xFindExecutable; /* See tcl_FindExecutableProc in th_tcl.c. */
133
#if TCL_MAJOR_VERSION>=9
134
void *xZipfsAppHook; /* See TclZipfsAppHookProc in th_tcl.c. */
135
#endif
136
void *xCreateInterp; /* See tcl_CreateInterpProc in th_tcl.c. */
137
void *xDeleteInterp; /* See tcl_DeleteInterpProc in th_tcl.c. */
138
void *xFinalize; /* See tcl_FinalizeProc in th_tcl.c. */
139
Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */
140
int useObjProc; /* Non-zero if an objProc can be called directly. */
141
int useTip285; /* Non-zero if TIP #285 is available. */
142
char *setup; /* The optional Tcl setup script. */
143
void *xPreEval; /* Optional, called before Tcl_Eval*(). */
144
void *pPreContext; /* Optional, provided to xPreEval(). */
145
void *xPostEval; /* Optional, called after Tcl_Eval*(). */
146
void *pPostContext; /* Optional, provided to xPostEval(). */
147
};
148
#endif
149
150
struct Global {
151
int argc; char **argv; /* Command-line arguments to the program */
152
char **argvOrig; /* Original g.argv prior to removing options */
153
char *nameOfExe; /* Full path of executable. */
154
const char *zErrlog; /* Log errors to this file, if not NULL */
155
const char *zPhase; /* Phase of operation, for use by the error log
156
** and for deriving $canonical_page TH1 variable */
157
int isConst; /* True if the output is unchanging & cacheable */
158
int iResultCode; /* Process reply code for commands */
159
const char *zVfsName; /* The VFS to use for database connections */
160
sqlite3 *db; /* The connection to the databases */
161
sqlite3 *dbConfig; /* Separate connection for global_config table */
162
char *zAuxSchema; /* Main repository aux-schema */
163
int dbIgnoreErrors; /* Ignore database errors if true */
164
char *zConfigDbName; /* Path of the config database. NULL if not open */
165
sqlite3_int64 now; /* Seconds since 1970 */
166
int repositoryOpen; /* True if the main repository database is open */
167
unsigned iRepoDataVers; /* Initial data version for repository database */
168
char *zRepositoryOption; /* Most recent cached repository option value */
169
char *zRepositoryName; /* Name of the repository database file */
170
char *zLocalDbName; /* Name of the local database file */
171
char *zOpenRevision; /* Check-in version to use during database open */
172
const char *zCmdName; /* Name of the Fossil command currently running */
173
int localOpen; /* True if the local database is open */
174
char *zLocalRoot; /* The directory holding the local database */
175
int minPrefix; /* Number of digits needed for a distinct hash */
176
int eHashPolicy; /* Current hash policy. One of HPOLICY_* */
177
int fSqlTrace; /* True if --sqltrace flag is present */
178
int fSqlStats; /* True if --sqltrace or --sqlstats are present */
179
int fSqlPrint; /* True if --sqlprint flag is present */
180
int fCgiTrace; /* True if --cgitrace is enabled */
181
int fQuiet; /* True if -quiet flag is present */
182
int fJail; /* True if running with a chroot jail */
183
int fHttpTrace; /* Trace outbound HTTP requests */
184
int fAnyTrace; /* Any kind of tracing */
185
int fAllowACME; /* Deliver files from .well-known */
186
char *zHttpAuth; /* HTTP Authorization user:pass information */
187
int fSystemTrace; /* Trace calls to fossil_system(), --systemtrace */
188
int fSshTrace; /* Trace the SSH setup traffic */
189
int fSshClient; /* HTTP client flags for SSH client */
190
int fNoHttpCompress; /* Do not compress HTTP traffic (for debugging) */
191
char *zSshCmd; /* SSH command string */
192
const char *zHttpCmd; /* External program to do HTTP requests */
193
int fNoSync; /* Do not do an autosync ever. --nosync */
194
int eIPvers; /* 0: any 1: ipv4-only 2: ipv6-only */
195
char *zPath; /* Name of webpage being served (may be NULL) */
196
char *zExtra; /* Extra path information past the webpage name */
197
char *zBaseURL; /* Full text of the URL being served */
198
char *zHttpsURL; /* zBaseURL translated to https: */
199
char *zTop; /* Parent directory of zPath */
200
int nExtraURL; /* Extra bytes added to SCRIPT_NAME */
201
const char *zExtRoot; /* Document root for the /ext sub-website */
202
const char *zContentType; /* The content type of the input HTTP request */
203
int iErrPriority; /* Priority of current error message */
204
char *zErrMsg; /* Text of an error message */
205
int sslNotAvailable; /* SSL is not available. Do not redirect to https: */
206
Blob cgiIn; /* Input to an xfer www method */
207
int cgiOutput; /* 0: command-line 1: CGI. 2: after CGI */
208
int xferPanic; /* Write error messages in XFER protocol */
209
int fullHttpReply; /* True for full HTTP reply. False for CGI reply */
210
Th_Interp *interp; /* The TH1 interpreter */
211
char *th1Setup; /* The TH1 post-creation setup script, if any */
212
int th1Flags; /* The TH1 integration state flags */
213
FILE *httpIn; /* Accept HTTP input from here */
214
FILE *httpOut; /* Send HTTP output here */
215
int httpUseSSL; /* True to use an SSL codec for HTTP traffic */
216
void *httpSSLConn; /* The SSL connection */
217
int xlinkClusterOnly; /* Set when cloning. Only process clusters */
218
int fTimeFormat; /* 1 for UTC. 2 for localtime. 0 not yet selected */
219
int *aCommitFile; /* Array of files to be committed */
220
int markPrivate; /* All new artifacts are private if true */
221
char *ckinLockFail; /* Check-in lock failure received from server */
222
int clockSkewSeen; /* True if clocks on client and server out of sync */
223
int wikiFlags; /* Wiki conversion flags applied to %W */
224
char isHTTP; /* True if server/CGI modes, else assume CLI. */
225
char jsHref; /* If true, set href= using javascript, not HTML */
226
Blob httpHeader; /* Complete text of the HTTP request header */
227
UrlData url; /* Information about current URL */
228
const char *zLogin; /* Login name. NULL or "" if not logged in. */
229
const char *zCkoutAlias; /* doc/ uses this branch as an alias for "ckout" */
230
const char *zMainMenuFile; /* --mainmenu FILE from server/ui/cgi */
231
const char *zSSLIdentity; /* Value of --ssl-identity option, filename of
232
** SSL client identity */
233
const char *zCgiFile; /* Name of the CGI file */
234
const char *zReqType; /* Type of request: "HTTP", "CGI", "SCGI" */
235
#if USE_SEE
236
const char *zPidKey; /* Saved value of the --usepidkey option. Only
237
* applicable when using SEE on Windows or Linux. */
238
#endif
239
int useLocalauth; /* No login required if from 127.0.0.1 */
240
int noPswd; /* Logged in without password (on 127.0.0.1) */
241
int userUid; /* Integer user id */
242
int eAuthMethod; /* How the user authenticated to us */
243
# define AUTH_NONE 0 /* Not authenticated */
244
# define AUTH_COOKIE 1 /* Authentication by cookie */
245
# define AUTH_LOCAL 2 /* Uses loopback */
246
# define AUTH_PW 3 /* Authentication by password */
247
# define AUTH_ENV 4 /* Authenticated by REMOTE_USER environment var */
248
# define AUTH_HTTP 5 /* HTTP Basic Authentication */
249
int isRobot; /* True if the client is definitely a robot. False
250
** negatives are common for this flag */
251
int comFmtFlags; /* Zero or more "COMMENT_PRINT_*" bit flags, should be
252
** accessed through get_comment_format(). */
253
const char *zSockName; /* Name of the unix-domain socket file */
254
const char *zSockMode; /* File permissions for unix-domain socket */
255
const char *zSockOwner; /* Owner, or owner:group for unix-domain socket */
256
257
/* Information used to populate the RCVFROM table */
258
int rcvid; /* The rcvid. 0 if not yet defined. */
259
char *zIpAddr; /* The remote IP address */
260
char *zNonce; /* The nonce used for login */
261
262
/* permissions available to current user */
263
struct FossilUserPerms perm;
264
265
/* permissions available to current user or to "anonymous".
266
** This is the logical union of perm permissions above with
267
** the value that perm would take if g.zLogin were "anonymous". */
268
struct FossilUserPerms anon;
269
270
#ifdef FOSSIL_ENABLE_TCL
271
/* all Tcl related context necessary for integration */
272
struct TclContext tcl;
273
#endif
274
275
/* For defense against Cross-site Request Forgery attacks */
276
char zCsrfToken[16]; /* Value of the anti-CSRF token */
277
int okCsrf; /* -1: unsafe
278
** 0: unknown
279
** 1: same origin
280
** 2: same origin + is POST
281
** 3: same origin, POST, valid csrf token */
282
283
int parseCnt[10]; /* Counts of artifacts parsed */
284
FILE *fDebug; /* Write debug information here, if the file exists */
285
#ifdef FOSSIL_ENABLE_TH1_HOOKS
286
int fNoThHook; /* Disable all TH1 command/webpage hooks */
287
#endif
288
int thTrace; /* True to enable TH1 debugging output */
289
Blob thLog; /* Text of the TH1 debugging output */
290
291
int isHome; /* True if rendering the "home" page */
292
293
/* Storage for the aux() and/or option() SQL function arguments */
294
int nAux; /* Number of distinct aux() or option() values */
295
const char *azAuxName[MX_AUX]; /* Name of each aux() or option() value */
296
char *azAuxParam[MX_AUX]; /* Param of each aux() or option() value */
297
const char *azAuxVal[MX_AUX]; /* Value of each aux() or option() value */
298
const char **azAuxOpt[MX_AUX]; /* Options of each option() value */
299
int anAuxCols[MX_AUX]; /* Number of columns for option() values */
300
int allowSymlinks; /* Cached "allow-symlinks" option */
301
int mainTimerId; /* Set to fossil_timer_start() */
302
int nPendingRequest; /* # of HTTP requests in "fossil server" */
303
int nRequest; /* Total # of HTTP request */
304
int bAvoidDeltaManifests; /* Avoid using delta manifests if true */
305
306
/* State for communicating specific details between the inbound HTTP
307
** header parser (cgi.c), xfer.c, and http.c. */
308
struct {
309
char *zLoginCard; /* Inbound "x-f-l-c" Cookie header. */
310
int fLoginCardMode; /* If non-0, emit login cards in outbound
311
** requests as a HTTP cookie instead of as
312
** part of the payload. Gets activated
313
** on-demand based on xfer traffic
314
** contents. Values, for
315
** diagnostic/debugging purposes: 0x01=CLI
316
** --flag, 0x02=cgi_setup_query_string(),
317
** 0x04=page_xfer(),
318
** 0x08=client_sync(). */
319
int remoteVersion; /* Remote fossil version. Used for negotiating
320
** how to handle the login card. */
321
} syncInfo;
322
#ifdef FOSSIL_ENABLE_JSON
323
struct FossilJsonBits {
324
int isJsonMode; /* True if running in JSON mode, else
325
false. This changes how errors are
326
reported. In JSON mode we try to
327
always output JSON-form error
328
responses and always (in CGI mode)
329
exit() with code 0 to avoid an HTTP
330
500 error.
331
*/
332
int preserveRc; /* Do not convert error codes into 0.
333
* This is primarily intended for use
334
* by the test suite. */
335
int resultCode; /* used for passing back specific codes
336
** from /json callbacks. */
337
int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
338
cson_output_opt outOpt; /* formatting options for JSON mode. */
339
cson_value *authToken; /* authentication token */
340
const char *jsonp; /* Name of JSONP function wrapper. */
341
unsigned char dispatchDepth /* Tells JSON command dispatching
342
which argument we are currently
343
working on. For this purpose, arg#0
344
is the "json" path/CLI arg.
345
*/;
346
struct { /* "garbage collector" */
347
cson_value *v;
348
cson_array *a;
349
} gc;
350
struct { /* JSON POST data. */
351
cson_value *v;
352
cson_array *a;
353
int offset; /* Tells us which PATH_INFO/CLI args
354
part holds the "json" command, so
355
that we can account for sub-repos
356
and path prefixes. This is handled
357
differently for CLI and CGI modes.
358
*/
359
const char *commandStr /*"command" request param.*/;
360
} cmd;
361
struct { /* JSON POST data. */
362
cson_value *v;
363
cson_object *o;
364
} post;
365
struct { /* GET/COOKIE params in JSON mode. */
366
cson_value *v;
367
cson_object *o;
368
} param;
369
struct {
370
cson_value *v;
371
cson_object *o;
372
} reqPayload; /* request payload object (if any) */
373
cson_array *warnings; /* response warnings */
374
int timerId; /* fetched from fossil_timer_start() */
375
} json;
376
#endif /* FOSSIL_ENABLE_JSON */
377
int ftntsIssues[4]; /* Counts for misref, strayed, joined, overnested */
378
int diffCnt[3]; /* Counts for DIFF_NUMSTAT: files, ins, del */
379
};
380
381
/*
382
** Macro for debugging:
383
*/
384
#define CGIDEBUG(X) if( g.fDebug ) cgi_debug X
385
386
#endif
387
388
Global g;
389
390
/*
391
** atexit() handler which frees up "some" of the resources
392
** used by fossil.
393
*/
394
static void fossil_atexit(void) {
395
static int once = 0;
396
if( once++ ) return; /* Ensure that this routine only runs once */
397
#if USE_SEE
398
/*
399
** Zero, unlock, and free the saved database encryption key now.
400
*/
401
db_unsave_encryption_key();
402
#endif
403
#if defined(_WIN32) || (defined(__BIONIC__) && !defined(FOSSIL_HAVE_GETPASS))
404
/*
405
** Free the secure getpass() buffer now.
406
*/
407
freepass();
408
#endif
409
#if defined(_WIN32) && !defined(_WIN64) && defined(FOSSIL_ENABLE_TCL) && \
410
defined(USE_TCL_STUBS)
411
/*
412
** If Tcl is compiled on Windows using the latest MinGW, Fossil can crash
413
** when exiting while a stubs-enabled Tcl is still loaded. This is due to
414
** a bug in MinGW, see:
415
**
416
** http://comments.gmane.org/gmane.comp.gnu.mingw.user/41724
417
**
418
** The workaround is to manually unload the loaded Tcl library prior to
419
** exiting the process. This issue does not impact 64-bit Windows.
420
*/
421
unloadTcl(g.interp, &g.tcl);
422
#endif
423
#ifdef FOSSIL_ENABLE_JSON
424
cson_value_free(g.json.gc.v);
425
memset(&g.json, 0, sizeof(g.json));
426
#endif
427
#if !defined(_WIN32)
428
if( g.zSockName && file_issocket(g.zSockName) ){
429
unlink(g.zSockName);
430
}
431
#endif
432
free(g.zErrMsg);
433
if(g.db){
434
db_close(0);
435
}
436
manifest_clear_cache();
437
content_clear_cache(1);
438
rebuild_clear_cache();
439
/*
440
** FIXME: The next two lines cannot always be enabled; however, they
441
** are very useful for tracking down TH1 memory leaks.
442
*/
443
if( fossil_getenv("TH1_DELETE_INTERP")!=0 ){
444
if( g.interp ){
445
Th_DeleteInterp(g.interp); g.interp = 0;
446
}
447
#if defined(TH_MEMDEBUG)
448
if( Th_GetOutstandingMalloc()!=0 ){
449
fossil_print("Th_GetOutstandingMalloc() => %d\n",
450
Th_GetOutstandingMalloc());
451
}
452
assert( Th_GetOutstandingMalloc()==0 );
453
#endif
454
}
455
}
456
457
458
/*
459
** Compare argv[0] with a list of subcommand and shift argv in order fossil is
460
** invoked with the matching subcommand.
461
*/
462
void process_argv0(void){
463
int i;
464
int nNewArgc = g.argc;
465
/* strip any path element: "/path/to/cmd" -> "cmd" */
466
char *zArg0BaseName = command_basename(g.argv[0]);
467
int nArgcDiff = 0;
468
char **zNewArgv = NULL;
469
470
471
if( fossil_strcmp(zArg0BaseName, "md5sum") == 0
472
|| fossil_strcmp(zArg0BaseName, "pikchr") == 0
473
|| fossil_strcmp(zArg0BaseName, "sha1sum") == 0
474
|| fossil_strcmp(zArg0BaseName, "sha3sum") == 0 ){
475
nNewArgc++;
476
477
}else if( fossil_strcmp(zArg0BaseName, "date") == 0
478
|| fossil_strcmp(zArg0BaseName, "ls") == 0
479
|| fossil_strcmp(zArg0BaseName, "pwd") == 0
480
|| fossil_strcmp(zArg0BaseName, "stty") == 0
481
|| fossil_strcmp(zArg0BaseName, "unzip") == 0
482
|| fossil_strcmp(zArg0BaseName, "which") == 0
483
|| fossil_strcmp(zArg0BaseName, "zip") == 0 ){
484
nNewArgc+=2;
485
486
}else if( fossil_strcmp(zArg0BaseName, "sqlite3") == 0 ){
487
/* with sqlite3 use --no-repository to make it behave like real sqlite3 */
488
nNewArgc+=2;
489
zNewArgv = fossil_malloc( sizeof(char*)*(nNewArgc) );
490
zNewArgv[0] = "fossil";
491
zNewArgv[1] = zArg0BaseName;
492
zNewArgv[2] = "--no-repository";
493
for(i=1; i<g.argc; i++){
494
zNewArgv[i+2] = g.argv[i];
495
}
496
g.argc = nNewArgc;
497
g.argv = zNewArgv;
498
return;
499
}
500
501
nArgcDiff = nNewArgc - g.argc;
502
if( nArgcDiff > 0 ){
503
zNewArgv = fossil_malloc( sizeof(char*)*(nNewArgc) );
504
switch( nArgcDiff ){
505
case 2:
506
/* system subcommand */
507
zNewArgv[1] = "system";
508
509
/* FALLTHROUGH */
510
511
case 1:
512
/*regular subcommand */
513
zNewArgv[0] = "fossil";
514
zNewArgv[nArgcDiff] = zArg0BaseName;
515
for(i=1; i<g.argc; i++){
516
zNewArgv[i+nArgcDiff] = g.argv[i];
517
}
518
g.argc = nNewArgc;
519
g.argv = zNewArgv;
520
}
521
}
522
}
523
524
/*
525
** Convert all arguments from mbcs (or unicode) to UTF-8. Then
526
** search g.argv for arguments "--args FILENAME". If found, then
527
** (1) remove the two arguments from g.argv
528
** (2) Read the file FILENAME
529
** (3) Use the contents of FILE to replace the two removed arguments:
530
** (a) Ignore blank lines in the file
531
** (b) Each non-empty line of the file is an argument, except
532
** (c) If the line begins with "-" and contains a space, it is broken
533
** into two arguments at the space.
534
*/
535
void expand_args_option(int argc, void *argv){
536
Blob file = empty_blob; /* Content of the file */
537
Blob line = empty_blob; /* One line of the file */
538
unsigned int nLine; /* Number of lines in the file*/
539
unsigned int i, j, k; /* Loop counters */
540
int n; /* Number of bytes in one line */
541
unsigned int nArg; /* Number of new arguments */
542
char *z; /* General use string pointer */
543
char **newArgv; /* New expanded g.argv under construction */
544
const char *zFileName; /* input file name */
545
FILE *inFile; /* input FILE */
546
547
g.argc = argc;
548
g.argv = argv;
549
sqlite3_initialize();
550
#if defined(_WIN32) && defined(BROKEN_MINGW_CMDLINE)
551
for(i=0; (int)i<g.argc; i++) g.argv[i] = fossil_mbcs_to_utf8(g.argv[i]);
552
#else
553
for(i=0; (int)i<g.argc; i++) g.argv[i] = fossil_path_to_utf8(g.argv[i]);
554
#endif
555
g.nameOfExe = file_fullexename(g.argv[0]);
556
for(i=1; (int)i<g.argc-1; i++){
557
z = g.argv[i];
558
if( z[0]!='-' ) continue;
559
z++;
560
if( z[0]=='-' ) z++;
561
/* Maintenance reminder: we do not stop at a "--" flag here,
562
** instead delegating that to find_option(). Doing it here
563
** introduces some weird corner cases, as covered in forum thread
564
** 4382bbc66757c39f. e.g. (fossil -U -- --args ...) is handled
565
** differently when we stop at "--" here. */
566
if( fossil_strcmp(z, "args")==0 ) break;
567
}
568
if( (int)i>=g.argc-1 ){
569
g.argvOrig = fossil_malloc( sizeof(char*)*(g.argc+1) );
570
memcpy(g.argvOrig, g.argv, sizeof(g.argv[0])*(g.argc+1));
571
return;
572
}
573
574
zFileName = g.argv[i+1];
575
if( strcmp(zFileName,"-")==0 ){
576
inFile = stdin;
577
}else if( !file_isfile(zFileName, ExtFILE) ){
578
fossil_fatal("Not an ordinary file: \"%s\"", zFileName);
579
}else{
580
inFile = fossil_fopen(zFileName,"rb");
581
if( inFile==0 ){
582
fossil_fatal("Cannot open -args file [%s]", zFileName);
583
}
584
}
585
blob_read_from_channel(&file, inFile, -1);
586
if(stdin != inFile){
587
fclose(inFile);
588
}
589
inFile = NULL;
590
blob_to_utf8_no_bom(&file, 1);
591
z = blob_str(&file);
592
for(k=0, nLine=1; z[k]; k++) if( z[k]=='\n' ) nLine++;
593
if( nLine>100000000 ) fossil_fatal("too many command-line arguments");
594
nArg = g.argc + nLine*2;
595
newArgv = fossil_malloc( sizeof(char*)*nArg*2 + 2);
596
for(j=0; j<i; j++) newArgv[j] = g.argv[j];
597
598
blob_rewind(&file);
599
while( nLine-->0 && (n = blob_line(&file, &line))>0 ){
600
/* Reminder: ^^^ nLine check avoids that embedded NUL bytes in the
601
** --args file causes nLine to be less than blob_line() will end
602
** up reporting, as such a miscount leads to an illegal memory
603
** write. See forum post
604
** https://fossil-scm.org/forum/forumpost/7b34eecc1b8c for
605
** details */
606
if( n<1 ){
607
/* Reminder: corner-case: a line with 1 byte and no newline. */
608
continue;
609
}
610
z = blob_buffer(&line);
611
if('\n'==z[n-1]){
612
z[n-1] = 0;
613
}
614
615
if((n>1) && ('\r'==z[n-2])){
616
if(n==2) continue /*empty line*/;
617
z[n-2] = 0;
618
}
619
if(!z[0]) continue;
620
if( j>=nArg ){
621
fossil_fatal("malformed command-line arguments");
622
}
623
newArgv[j++] = z;
624
if( z[0]=='-' ){
625
for(k=1; z[k] && !fossil_isspace(z[k]); k++){}
626
if( z[k] ){
627
z[k] = 0;
628
k++;
629
if( z[k] ) newArgv[j++] = &z[k];
630
}
631
}
632
}
633
i += 2;
634
while( (int)i<g.argc ) newArgv[j++] = g.argv[i++];
635
newArgv[j] = 0;
636
g.argc = j;
637
g.argv = newArgv;
638
g.argvOrig = &g.argv[j+1];
639
memcpy(g.argvOrig, g.argv, sizeof(g.argv[0])*(j+1));
640
}
641
642
#ifdef FOSSIL_ENABLE_TCL
643
/*
644
** Make a deep copy of the provided argument array and return it.
645
*/
646
static char **copy_args(int argc, char **argv){
647
char **zNewArgv;
648
int i;
649
zNewArgv = fossil_malloc( sizeof(char*)*(argc+1) );
650
memset(zNewArgv, 0, sizeof(char*)*(argc+1));
651
for(i=0; i<argc; i++){
652
zNewArgv[i] = fossil_strdup(argv[i]);
653
}
654
return zNewArgv;
655
}
656
#endif
657
658
/*
659
** Returns a name for a SQLite return code.
660
*/
661
static const char *fossil_sqlite_return_code_name(int rc){
662
static char zCode[30];
663
switch( rc & 0xff ){
664
case SQLITE_OK: return "SQLITE_OK";
665
case SQLITE_ERROR: return "SQLITE_ERROR";
666
case SQLITE_INTERNAL: return "SQLITE_INTERNAL";
667
case SQLITE_PERM: return "SQLITE_PERM";
668
case SQLITE_ABORT: return "SQLITE_ABORT";
669
case SQLITE_BUSY: return "SQLITE_BUSY";
670
case SQLITE_LOCKED: return "SQLITE_LOCKED";
671
case SQLITE_NOMEM: return "SQLITE_NOMEM";
672
case SQLITE_READONLY: return "SQLITE_READONLY";
673
case SQLITE_INTERRUPT: return "SQLITE_INTERRUPT";
674
case SQLITE_IOERR: return "SQLITE_IOERR";
675
case SQLITE_CORRUPT: return "SQLITE_CORRUPT";
676
case SQLITE_NOTFOUND: return "SQLITE_NOTFOUND";
677
case SQLITE_FULL: return "SQLITE_FULL";
678
case SQLITE_CANTOPEN: return "SQLITE_CANTOPEN";
679
case SQLITE_PROTOCOL: return "SQLITE_PROTOCOL";
680
case SQLITE_EMPTY: return "SQLITE_EMPTY";
681
case SQLITE_SCHEMA: return "SQLITE_SCHEMA";
682
case SQLITE_TOOBIG: return "SQLITE_TOOBIG";
683
case SQLITE_CONSTRAINT: return "SQLITE_CONSTRAINT";
684
case SQLITE_MISMATCH: return "SQLITE_MISMATCH";
685
case SQLITE_MISUSE: return "SQLITE_MISUSE";
686
case SQLITE_NOLFS: return "SQLITE_NOLFS";
687
case SQLITE_AUTH: return "SQLITE_AUTH";
688
case SQLITE_FORMAT: return "SQLITE_FORMAT";
689
case SQLITE_RANGE: return "SQLITE_RANGE";
690
case SQLITE_NOTADB: return "SQLITE_NOTADB";
691
case SQLITE_NOTICE: return "SQLITE_NOTICE";
692
case SQLITE_WARNING: return "SQLITE_WARNING";
693
case SQLITE_ROW: return "SQLITE_ROW";
694
case SQLITE_DONE: return "SQLITE_DONE";
695
default: {
696
sqlite3_snprintf(sizeof(zCode), zCode, "SQLite return code %d", rc);
697
}
698
}
699
return zCode;
700
}
701
702
/* Error logs from SQLite */
703
static void fossil_sqlite_log(void *notUsed, int iCode, const char *zErrmsg){
704
sqlite3_stmt *p;
705
Blob msg;
706
#ifdef __APPLE__
707
/* Disable the file alias warning on apple products because Time Machine
708
** creates lots of aliases and the warnings alarm people. */
709
if( iCode==SQLITE_WARNING ) return;
710
#endif
711
#ifndef FOSSIL_DEBUG
712
/* Disable the automatic index warning except in FOSSIL_DEBUG builds. */
713
if( iCode==SQLITE_WARNING_AUTOINDEX ) return;
714
#endif
715
if( iCode==SQLITE_SCHEMA ) return;
716
if( g.dbIgnoreErrors ) return;
717
#ifdef SQLITE_READONLY_DIRECTORY
718
if( iCode==SQLITE_READONLY_DIRECTORY ){
719
zErrmsg = "database is in a read-only directory";
720
}
721
#endif
722
blob_init(&msg, 0, 0);
723
blob_appendf(&msg, "%s(%d): %s",
724
fossil_sqlite_return_code_name(iCode), iCode, zErrmsg);
725
if( g.db ){
726
for(p=sqlite3_next_stmt(g.db, 0); p; p=sqlite3_next_stmt(g.db,p)){
727
const char *zSql;
728
if( !sqlite3_stmt_busy(p) ) continue;
729
zSql = sqlite3_sql(p);
730
if( zSql==0 ) continue;
731
blob_appendf(&msg, "\nSQL: %s", zSql);
732
}
733
}
734
fossil_warning("%s", blob_str(&msg));
735
blob_reset(&msg);
736
}
737
738
/*
739
** Initialize the g.comFmtFlags global variable.
740
**
741
** Global command-line options --comfmtflags or --comment-format can be
742
** used for this. However, those command-line options are undocumented
743
** and deprecated. They are here for backwards compatibility only.
744
*/
745
static void fossil_init_flags_from_options(void){
746
const char *zValue = find_option("comfmtflags", 0, 1);
747
if( zValue==0 ){
748
zValue = find_option("comment-format", 0, 1);
749
}
750
if( zValue ){
751
g.comFmtFlags = atoi(zValue);
752
}else{
753
g.comFmtFlags = COMMENT_PRINT_UNSET; /* Command-line option not found. */
754
}
755
}
756
757
/*
758
** Check to see if the Fossil binary contains an appended repository
759
** file using the appendvfs extension. If so, change command-line arguments
760
** to cause Fossil to launch with "fossil ui" on that repo.
761
*/
762
static int fossilExeHasAppendedRepo(void){
763
extern int deduceDatabaseType(const char*,int);
764
if( 2==deduceDatabaseType(g.nameOfExe,0) ){
765
static char *azAltArgv[] = { 0, "ui", 0, 0 };
766
azAltArgv[0] = g.nameOfExe;
767
azAltArgv[2] = g.nameOfExe;
768
g.argv = azAltArgv;
769
g.argc = 3;
770
return 1;
771
}else{
772
return 0;
773
}
774
}
775
776
/*
777
** This procedure runs first.
778
*/
779
#if defined(FOSSIL_FUZZ)
780
/* Do not include a main() procedure when building for fuzz testing.
781
** libFuzzer will supply main(). */
782
#elif defined(_WIN32) && !defined(BROKEN_MINGW_CMDLINE)
783
int _dowildcard = -1; /* This turns on command-line globbing in MinGW-w64 */
784
int wmain(int argc, wchar_t **argv){ return fossil_main(argc,(char**)argv); }
785
#elif defined(_WIN32)
786
int _CRT_glob = 0x0001; /* See MinGW bug #2062 */
787
int main(int argc, char **argv){ return fossil_main(argc, argv); }
788
#else
789
int main(int argc, char **argv){ return fossil_main(argc, argv); }
790
#endif
791
792
/* All the work of main() is done by a separate procedure "fossil_main()".
793
** We have to break this out, because fossil_main() is sometimes called
794
** separately (by the "shell" command) but we do not want atwait() handlers
795
** being called by separate invocations of fossil_main().
796
*/
797
int fossil_main(int argc, char **argv){
798
const char *zCmdName = "unknown";
799
const CmdOrPage *pCmd = 0;
800
int rc;
801
802
g.zPhase = "init";
803
#if !defined(_WIN32_WCE)
804
if( fossil_getenv("FOSSIL_BREAK") ){
805
if( fossil_isatty(0) && fossil_isatty(2) ){
806
fprintf(stderr,
807
"attach debugger to process %d and press any key to continue.\n",
808
GETPID());
809
(void)fgetc(stdin);
810
}else{
811
#if defined(_WIN32) || defined(WIN32)
812
DebugBreak();
813
#elif defined(SIGTRAP)
814
raise(SIGTRAP);
815
#endif
816
}
817
}
818
#endif
819
820
fossil_printf_selfcheck();
821
fossil_limit_memory(1);
822
823
/* When updating the minimum SQLite version, change the number here,
824
** and also MINIMUM_SQLITE_VERSION value set in ../auto.def. Take
825
** care that both places agree! */
826
if( sqlite3_libversion_number()<3049000
827
|| strncmp(sqlite3_sourceid(),"2025-02-06",10)<0
828
){
829
fossil_panic("Unsuitable SQLite version %s, must be at least 3.49.0",
830
sqlite3_libversion());
831
}
832
833
sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
834
sqlite3_config(SQLITE_CONFIG_LOG, fossil_sqlite_log, 0);
835
memset(&g, 0, sizeof(g));
836
g.now = time(0);
837
g.httpHeader = empty_blob;
838
#ifdef FOSSIL_ENABLE_JSON
839
#if defined(NDEBUG)
840
g.json.errorDetailParanoia = 2 /* FIXME: make configurable
841
One problem we have here is that this
842
code is needed before the db is opened,
843
so we can't sql for it.*/;
844
#else
845
g.json.errorDetailParanoia = 0;
846
#endif
847
g.json.outOpt = cson_output_opt_empty;
848
g.json.outOpt.addNewline = 1;
849
g.json.outOpt.indentation = 1 /* in CGI/server mode this can be configured */;
850
#endif /* FOSSIL_ENABLE_JSON */
851
expand_args_option(argc, argv);
852
process_argv0();
853
#ifdef FOSSIL_ENABLE_TCL
854
memset(&g.tcl, 0, sizeof(TclContext));
855
g.tcl.argc = g.argc;
856
g.tcl.argv = copy_args(g.argc, g.argv); /* save full arguments */
857
#endif
858
g.mainTimerId = fossil_timer_start();
859
capture_case_sensitive_option();
860
g.syncInfo.fLoginCardMode =
861
/* The undocumented/unsupported --login-card-header provides a way
862
** to force use of the feature added by the xfer-login-card branch
863
** in 2025-07, intended for assisting in debugging any related
864
** issues. It can be removed once we reach the level of "implicit
865
** trust" in that feature. */
866
find_option("login-card-header",0,0) ? 0x01 : 0;
867
g.zVfsName = find_option("vfs",0,1);
868
if( g.zVfsName==0 ){
869
g.zVfsName = fossil_getenv("FOSSIL_VFS");
870
}
871
if( g.zVfsName ){
872
sqlite3_vfs *pVfs = sqlite3_vfs_find(g.zVfsName);
873
if( pVfs ){
874
sqlite3_vfs_register(pVfs, 1);
875
}else{
876
fossil_fatal("no such VFS: \"%s\"", g.zVfsName);
877
}
878
}
879
if( !find_option("nocgi", 0, 0) && fossil_getenv("GATEWAY_INTERFACE")!=0){
880
zCmdName = "cgi";
881
g.isHTTP = 1;
882
}else if( g.argc<2 && !fossilExeHasAppendedRepo() ){
883
fossil_print(
884
"Usage: %s COMMAND ...\n"
885
" or: %s help -- for a list of common commands\n"
886
" or: %s help COMMAND -- for help with the named command\n",
887
g.argv[0], g.argv[0], g.argv[0]);
888
fossil_print(
889
"\nCommands and filenames may be passed on to fossil from a file\n"
890
"by using:\n"
891
"\n %s --args FILENAME ...\n",
892
g.argv[0]
893
);
894
fossil_print(
895
"\nEach line of the file is assumed to be a filename unless it starts\n"
896
"with '-' and contains a space, in which case it is assumed to be\n"
897
"another flag and is treated as such. --args FILENAME may be used\n"
898
"in conjunction with any other flags.\n");
899
fossil_exit(1);
900
}else{
901
const char *zChdir = find_option("chdir",0,1);
902
g.isHTTP = 0;
903
g.rcvid = 0;
904
g.fQuiet = find_option("quiet", "q", 0)!=0;
905
g.fSqlTrace = find_option("sqltrace", 0, 0)!=0;
906
g.fSqlStats = find_option("sqlstats", 0, 0)!=0;
907
g.fSystemTrace = find_option("systemtrace", 0, 0)!=0;
908
g.fSshTrace = find_option("sshtrace", 0, 0)!=0;
909
g.fCgiTrace = find_option("cgitrace", 0, 0)!=0;
910
g.fSshClient = 0;
911
g.zSshCmd = 0;
912
if( g.fSqlTrace ) g.fSqlStats = 1;
913
#ifdef FOSSIL_ENABLE_JSON
914
g.json.preserveRc = find_option("json-preserve-rc", 0, 0)!=0;
915
#endif
916
g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
917
#ifdef FOSSIL_ENABLE_TH1_HOOKS
918
g.fNoThHook = find_option("no-th-hook", 0, 0)!=0;
919
#endif
920
g.fAnyTrace = g.fSqlTrace|g.fSystemTrace|g.fSshTrace|
921
g.fHttpTrace|g.fCgiTrace;
922
g.zHttpAuth = 0;
923
g.zLogin = find_option("user", "U", 1);
924
g.zSSLIdentity = find_option("ssl-identity", 0, 1);
925
g.zErrlog = find_option("errorlog", 0, 1);
926
fossil_init_flags_from_options();
927
if( find_option("utc",0,0) ) g.fTimeFormat = 1;
928
if( find_option("localtime",0,0) ) g.fTimeFormat = 2;
929
if( zChdir && file_chdir(zChdir, 0) ){
930
fossil_fatal("unable to change directories to %s", zChdir);
931
}
932
#if USE_SEE
933
db_maybe_handle_saved_encryption_key_for_process(SEE_KEY_READ);
934
#endif
935
if( find_option("help","?",0)!=0 ){
936
/* If --help is found anywhere on the command line, translate the command
937
* to "fossil help cmdname" where "cmdname" is the first argument that
938
* does not begin with a "-" character. If all arguments start with "-",
939
* translate to "fossil help argv[1] argv[2]...". */
940
int i, nNewArgc;
941
char **zNewArgv = fossil_malloc( sizeof(char*)*(g.argc+3) );
942
zNewArgv[0] = g.argv[0];
943
zNewArgv[1] = "help";
944
zNewArgv[2] = "-c";
945
for(i=1; i<g.argc; i++){
946
if( g.argv[i][0]!='-' ){
947
nNewArgc = 4;
948
zNewArgv[3] = g.argv[i];
949
zNewArgv[4] = 0;
950
break;
951
}
952
}
953
if( i==g.argc ){
954
for(i=1; i<g.argc; i++) zNewArgv[i+1] = g.argv[i];
955
nNewArgc = g.argc+1;
956
zNewArgv[i+1] = 0;
957
}
958
g.argc = nNewArgc;
959
g.argv = zNewArgv;
960
#if 0
961
}else if( g.argc==2 && file_is_repository(g.argv[1]) ){
962
char **zNewArgv = fossil_malloc( sizeof(char*)*4 );
963
zNewArgv[0] = g.argv[0];
964
zNewArgv[1] = "ui";
965
zNewArgv[2] = g.argv[1];
966
zNewArgv[3] = 0;
967
g.argc = 3;
968
g.argv = zNewArgv;
969
#endif
970
}
971
zCmdName = g.argv[1];
972
}
973
#ifndef _WIN32
974
/* There is a bug in stunnel4 in which it sometimes starts up client
975
** processes without first opening file descriptor 2 (standard error).
976
** If this happens, and a subsequent open() of a database returns file
977
** descriptor 2, and then an assert() fires and writes on fd 2, that
978
** can corrupt the data file. To avoid this problem, make sure open()
979
** will never return file descriptor 2 or less. */
980
if( !is_valid_fd(2) ){
981
int nTry = 0;
982
int fd = 0;
983
int x = 0;
984
do{
985
fd = open("/dev/null",O_WRONLY);
986
if( fd>=2 ) break;
987
if( fd<0 ) x = errno;
988
}while( nTry++ < 2 );
989
if( fd<2 ){
990
g.cgiOutput = 1;
991
g.httpOut = stdout;
992
g.fullHttpReply = !g.isHTTP;
993
fossil_panic("file descriptor 2 is not open. (fd=%d, errno=%d)",
994
fd, x);
995
}
996
}
997
#endif
998
g.zCmdName = zCmdName;
999
rc = dispatch_name_search(zCmdName, CMDFLAG_COMMAND|CMDFLAG_PREFIX, &pCmd);
1000
if( rc==1 && g.argc==2 && file_is_repository(g.argv[1]) ){
1001
/* If the command-line is "fossil ABC" and "ABC" is no a valid command,
1002
** but "ABC" is the name of a repository file, make the command be
1003
** "fossil ui ABC" instead.
1004
*/
1005
char **zNewArgv = fossil_malloc( sizeof(char*)*4 );
1006
zNewArgv[0] = g.argv[0];
1007
zNewArgv[1] = "ui";
1008
zNewArgv[2] = g.argv[1];
1009
zNewArgv[3] = 0;
1010
g.argc = 3;
1011
g.argv = zNewArgv;
1012
g.zCmdName = zCmdName = "ui";
1013
rc = dispatch_name_search(zCmdName, CMDFLAG_COMMAND|CMDFLAG_PREFIX, &pCmd);
1014
}
1015
if( rc==1 ){
1016
#ifdef FOSSIL_ENABLE_TH1_HOOKS
1017
if( !g.isHTTP && !g.fNoThHook ){
1018
rc = Th_CommandHook(zCmdName, 0);
1019
}else{
1020
rc = TH_OK;
1021
}
1022
if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
1023
if( rc==TH_OK || rc==TH_RETURN ){
1024
#endif
1025
fossil_fatal("%s: unknown command: %s\n"
1026
"%s: use \"help\" for more information",
1027
g.argv[0], zCmdName, g.argv[0]);
1028
#ifdef FOSSIL_ENABLE_TH1_HOOKS
1029
}
1030
if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
1031
Th_CommandNotify(zCmdName, 0);
1032
}
1033
}
1034
fossil_exit(0);
1035
#endif
1036
}else if( rc==2 ){
1037
Blob couldbe;
1038
blob_init(&couldbe,0,0);
1039
dispatch_matching_names(zCmdName, CMDFLAG_COMMAND, &couldbe);
1040
fossil_print("%s: ambiguous command prefix: %s\n"
1041
"%s: could be any of:%s\n"
1042
"%s: use \"help\" for more information\n",
1043
g.argv[0], zCmdName, g.argv[0], blob_str(&couldbe), g.argv[0]);
1044
fossil_exit(1);
1045
}
1046
#ifdef FOSSIL_ENABLE_JSON
1047
else if( rc==0 && strcmp("json",pCmd->zName)==0 ){
1048
g.json.isJsonMode = 1;
1049
}else{
1050
assert(!g.json.isJsonMode && "JSON-mode misconfiguration.");
1051
}
1052
#endif
1053
atexit( fossil_atexit );
1054
#ifdef FOSSIL_ENABLE_TH1_HOOKS
1055
/*
1056
** The TH1 return codes from the hook will be handled as follows:
1057
**
1058
** TH_OK: The xFunc() and the TH1 notification will both be executed.
1059
**
1060
** TH_ERROR: The xFunc() will be skipped, the TH1 notification will be
1061
** skipped. If the xFunc() is being hooked, the error message
1062
** will be emitted.
1063
**
1064
** TH_BREAK: The xFunc() and the TH1 notification will both be skipped.
1065
**
1066
** TH_RETURN: The xFunc() will be executed, the TH1 notification will be
1067
** skipped.
1068
**
1069
** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be
1070
** executed.
1071
*/
1072
if( !g.isHTTP && !g.fNoThHook ){
1073
rc = Th_CommandHook(pCmd->zName, pCmd->eCmdFlags);
1074
}else{
1075
rc = TH_OK;
1076
}
1077
if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
1078
if( rc==TH_OK || rc==TH_RETURN ){
1079
#endif
1080
g.zPhase = pCmd->zName;
1081
pCmd->xFunc();
1082
g.zPhase = "shutdown";
1083
#ifdef FOSSIL_ENABLE_TH1_HOOKS
1084
}
1085
if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
1086
Th_CommandNotify(pCmd->zName, pCmd->eCmdFlags);
1087
}
1088
}
1089
#endif
1090
fossil_exit(g.iResultCode);
1091
/*NOT_REACHED*/
1092
return 0;
1093
}
1094
1095
/*
1096
** Print a usage comment and quit
1097
*/
1098
void usage(const char *zFormat){
1099
fossil_fatal("Usage: %s %s %s", g.argv[0], g.argv[1], zFormat);
1100
}
1101
1102
/*
1103
** Remove n elements from g.argv beginning with the i-th element.
1104
*/
1105
static void remove_from_argv(int i, int n){
1106
memmove(&g.argv[i], &g.argv[i+n], sizeof(g.argv[i])*(g.argc-i-n));
1107
g.argc -= n;
1108
}
1109
1110
1111
/*
1112
** Look for a command-line option. If present, remove it from the
1113
** argument list and return a pointer to either the flag's name (if
1114
** hasArg==0), sans leading - or --, or its value (if hasArg==1).
1115
** Return NULL if the flag is not found.
1116
**
1117
** zLong is the "long" form of the flag and zShort is the
1118
** short/abbreviated form (typically a single letter, but it may be
1119
** longer). zLong must not be NULL, but zShort may be.
1120
**
1121
** hasArg==0 means the option is a flag. It is either present or not.
1122
** hasArg==1 means the option has an argument, in which case a pointer
1123
** to the argument's value is returned. For zLong, a flag value (if
1124
** hasValue==1) may either be in the form (--flag=value) or (--flag
1125
** value). For zShort, only the latter form is accepted.
1126
**
1127
** If a standalone argument of "--" is encountered in the argument
1128
** list while searching for the given flag(s), this routine stops
1129
** searching and NULL is returned.
1130
*/
1131
const char *find_option(const char *zLong, const char *zShort, int hasArg){
1132
int i;
1133
int nLong;
1134
const char *zReturn = 0;
1135
assert( hasArg==0 || hasArg==1 );
1136
nLong = strlen(zLong);
1137
for(i=1; i<g.argc; i++){
1138
char *z;
1139
z = g.argv[i];
1140
if( z[0]!='-' ) continue;
1141
z++;
1142
if( z[0]=='-' ){
1143
if( z[1]==0 ){
1144
/* Stop processing at "--" without consuming it.
1145
verify_all_options() will consume this flag. */
1146
break;
1147
}
1148
z++;
1149
}
1150
if( strncmp(z,zLong,nLong)==0 ){
1151
if( hasArg && z[nLong]=='=' ){
1152
zReturn = &z[nLong+1];
1153
remove_from_argv(i, 1);
1154
break;
1155
}else if( z[nLong]==0 ){
1156
if( i+hasArg >= g.argc ) break;
1157
zReturn = g.argv[i+hasArg];
1158
remove_from_argv(i, 1+hasArg);
1159
break;
1160
}
1161
}else if( fossil_strcmp(z,zShort)==0 ){
1162
if( i+hasArg >= g.argc ) break;
1163
zReturn = g.argv[i+hasArg];
1164
remove_from_argv(i, 1+hasArg);
1165
break;
1166
}
1167
}
1168
return zReturn;
1169
}
1170
1171
/*
1172
** Restore an option previously removed by find_option().
1173
*/
1174
void restore_option(const char *zName, const char *zValue, int hasOpt){
1175
if( zValue==0 && hasOpt ) return;
1176
g.argv[g.argc++] = (char*)zName;
1177
if( hasOpt ) g.argv[g.argc++] = (char*)zValue;
1178
}
1179
1180
/* Return true if zOption exists in the command-line arguments,
1181
** but do not remove it from the list or otherwise process it.
1182
*/
1183
int has_option(const char *zOption){
1184
int i;
1185
int n = (int)strlen(zOption);
1186
for(i=1; i<g.argc; i++){
1187
char *z = g.argv[i];
1188
if( z[0]!='-' ) continue;
1189
z++;
1190
if( z[0]=='-' ){
1191
if( z[1]==0 ){
1192
/* Stop processing at "--" */
1193
break;
1194
}
1195
z++;
1196
}
1197
if( strncmp(z,zOption,n)==0 && (z[n]==0 || z[n]=='=') ) return 1;
1198
}
1199
return 0;
1200
}
1201
1202
/*
1203
** Look for multiple occurrences of a command-line option with the
1204
** corresponding argument.
1205
**
1206
** Return a malloc allocated array of pointers to the arguments.
1207
**
1208
** pnUsedArgs is used to store the number of matched arguments.
1209
**
1210
** Caller is responsible for freeing allocated memory by passing the
1211
** head of the array (not each entry) to fossil_free(). (The
1212
** individual entries have the same lifetime as values returned from
1213
** find_option().)
1214
*/
1215
const char **find_repeatable_option(
1216
const char *zLong,
1217
const char *zShort,
1218
int *pnUsedArgs
1219
){
1220
const char *zOption;
1221
const char **pzArgs = 0;
1222
int nAllocArgs = 0;
1223
int nUsedArgs = 0;
1224
1225
while( (zOption = find_option(zLong, zShort, 1))!=0 ){
1226
if( pzArgs==0 && nAllocArgs==0 ){
1227
nAllocArgs = 1;
1228
pzArgs = fossil_malloc( nAllocArgs*sizeof(pzArgs[0]) );
1229
}else if( nAllocArgs<=nUsedArgs ){
1230
nAllocArgs = nAllocArgs*2;
1231
pzArgs = fossil_realloc( (void *)pzArgs, nAllocArgs*sizeof(pzArgs[0]) );
1232
}
1233
pzArgs[nUsedArgs++] = zOption;
1234
}
1235
*pnUsedArgs = nUsedArgs;
1236
return pzArgs;
1237
}
1238
1239
/*
1240
** Look for a repository command-line option. If present, [re-]cache it in
1241
** the global state and return the new pointer, freeing any previous value.
1242
** If absent and there is no cached value, return NULL.
1243
*/
1244
const char *find_repository_option(){
1245
const char *zRepository = find_option("repository", "R", 1);
1246
if( zRepository ){
1247
if( g.zRepositoryOption ) fossil_free(g.zRepositoryOption);
1248
g.zRepositoryOption = fossil_strdup(zRepository);
1249
}
1250
return g.zRepositoryOption;
1251
}
1252
1253
/*
1254
** Verify that there are no unprocessed command-line options. If
1255
** Any remaining command-line argument begins with "-" print
1256
** an error message and quit.
1257
**
1258
** Exception: if "--" is encountered, it is consumed from the argument
1259
** list and this function immediately returns. The effect is to treat
1260
** all arguments after "--" as non-flags (conventionally used to
1261
** enable passing-in of filenames which start with a dash).
1262
**
1263
** This function must normally only be called one time per app
1264
** invocation. The exception is commands which process their
1265
** arguments, call this to confirm that there are no extraneous flags,
1266
** then modify the arguments list for forwarding to another
1267
** (sub)command (which itself will call this to confirm its own
1268
** arguments).
1269
*/
1270
void verify_all_options(void){
1271
int i;
1272
for(i=1; i<g.argc; i++){
1273
const char * arg = g.argv[i];
1274
if( arg[0]=='-' ){
1275
if( arg[1]=='-' && arg[2]==0 ){
1276
/* Remove "--" from the list and treat all following
1277
** arguments as non-flags. */
1278
remove_from_argv(i, 1);
1279
break;
1280
}else if( arg[1]!=0 ){
1281
fossil_fatal(
1282
"unrecognized command-line option or missing argument: %s",
1283
arg);
1284
}
1285
}
1286
}
1287
}
1288
1289
/*
1290
** This function returns a human readable version string.
1291
*/
1292
const char *get_version(){
1293
static const char version[] = RELEASE_VERSION " " MANIFEST_VERSION " "
1294
MANIFEST_DATE " UTC";
1295
return version;
1296
}
1297
1298
/*
1299
** This function populates a blob with version information. It is used by
1300
** the "version" command and "test-version" web page. It assumes the blob
1301
** passed to it is uninitialized; otherwise, it will leak memory.
1302
*/
1303
void fossil_version_blob(
1304
Blob *pOut, /* Write the manifest here */
1305
int eVerbose /* 0: brief. 1: more text, 2: lots of text */
1306
){
1307
#if defined(FOSSIL_ENABLE_TCL)
1308
int rc;
1309
const char *zRc;
1310
#endif
1311
Stmt q;
1312
size_t pageSize = 0;
1313
blob_zero(pOut);
1314
blob_appendf(pOut, "This is fossil version %s\n", get_version());
1315
if( eVerbose<=0 ) return;
1316
1317
blob_appendf(pOut, "Compiled on %s %s using %s (%d-bit)\n",
1318
__DATE__, __TIME__, COMPILER_NAME, sizeof(void*)*8);
1319
blob_appendf(pOut, "SQLite %s %.30s\n", sqlite3_libversion(),
1320
sqlite3_sourceid());
1321
#if defined(FOSSIL_ENABLE_SSL)
1322
blob_appendf(pOut, "SSL (%s)\n", SSLeay_version(SSLEAY_VERSION));
1323
#endif
1324
blob_appendf(pOut, "zlib %s, loaded %s\n", ZLIB_VERSION, zlibVersion());
1325
#if defined(FOSSIL_HAVE_FUSEFS)
1326
blob_appendf(pOut, "libfuse %s, loaded %s\n", fusefs_inc_version(),
1327
fusefs_lib_version());
1328
#endif
1329
#if defined(FOSSIL_ENABLE_TCL)
1330
Th_FossilInit(TH_INIT_DEFAULT | TH_INIT_FORCE_TCL);
1331
rc = Th_Eval(g.interp, 0, "tclInvoke info patchlevel", -1);
1332
zRc = Th_ReturnCodeName(rc, 0);
1333
blob_appendf(pOut, "TCL (Tcl %s, loaded %s: %s)\n",
1334
TCL_PATCH_LEVEL, zRc, Th_GetResult(g.interp, 0)
1335
);
1336
#endif
1337
if( eVerbose<=1 ) return;
1338
1339
blob_appendf(pOut, "Schema version %s\n", AUX_SCHEMA_MAX);
1340
fossil_get_page_size(&pageSize);
1341
blob_appendf(pOut, "Detected memory page size is %lu bytes\n",
1342
(unsigned long)pageSize);
1343
#if FOSSIL_HARDENED_SHA1
1344
blob_appendf(pOut, "hardened-SHA1 by Marc Stevens and Dan Shumow\n");
1345
#endif
1346
#if defined(FOSSIL_DEBUG)
1347
blob_append(pOut, "FOSSIL_DEBUG\n", -1);
1348
#endif
1349
#if defined(FOSSIL_ENABLE_DELTA_CKSUM_TEST)
1350
blob_append(pOut, "FOSSIL_ENABLE_DELTA_CKSUM_TEST\n", -1);
1351
#endif
1352
blob_append(pOut, "FOSSIL_ENABLE_LEGACY_MV_RM\n", -1);
1353
#if defined(FOSSIL_ENABLE_EXEC_REL_PATHS)
1354
blob_append(pOut, "FOSSIL_ENABLE_EXEC_REL_PATHS\n", -1);
1355
#endif
1356
#if defined(FOSSIL_ENABLE_TH1_DOCS)
1357
blob_append(pOut, "FOSSIL_ENABLE_TH1_DOCS\n", -1);
1358
#endif
1359
#if defined(FOSSIL_ENABLE_TH1_HOOKS)
1360
blob_append(pOut, "FOSSIL_ENABLE_TH1_HOOKS\n", -1);
1361
#endif
1362
#if defined(USE_TCL_STUBS)
1363
blob_append(pOut, "USE_TCL_STUBS\n", -1);
1364
#endif
1365
#if defined(FOSSIL_ENABLE_TCL_STUBS)
1366
blob_append(pOut, "FOSSIL_TCL_STUBS\n", -1);
1367
#endif
1368
#if defined(FOSSIL_ENABLE_TCL_PRIVATE_STUBS)
1369
blob_append(pOut, "FOSSIL_ENABLE_TCL_PRIVATE_STUBS\n", -1);
1370
#endif
1371
#if defined(FOSSIL_ENABLE_JSON)
1372
blob_appendf(pOut, "JSON (API %s)\n", FOSSIL_JSON_API_VERSION);
1373
#endif
1374
blob_append(pOut, "MARKDOWN\n", -1);
1375
#if defined(BROKEN_MINGW_CMDLINE)
1376
blob_append(pOut, "MBCS_COMMAND_LINE\n", -1);
1377
#else
1378
blob_append(pOut, "UNICODE_COMMAND_LINE\n", -1);
1379
#endif
1380
#if defined(FOSSIL_DYNAMIC_BUILD)
1381
blob_append(pOut, "FOSSIL_DYNAMIC_BUILD\n", -1);
1382
#else
1383
blob_append(pOut, "FOSSIL_STATIC_BUILD\n", -1);
1384
#endif
1385
#if defined(HAVE_PLEDGE)
1386
blob_append(pOut, "HAVE_PLEDGE\n", -1);
1387
#endif
1388
#if defined(USE_MMAN_H)
1389
blob_append(pOut, "USE_MMAN_H\n", -1);
1390
#endif
1391
#if defined(USE_SEE)
1392
blob_appendf(pOut, "USE_SEE (%s)\n",
1393
db_have_saved_encryption_key() ? "SET" : "UNSET");
1394
#endif
1395
#if defined(FOSSIL_ALLOW_OUT_OF_ORDER_DATES)
1396
blob_append(pOut, "FOSSIL_ALLOW_OUT_OF_ORDER_DATES\n");
1397
#endif
1398
1399
if( g.db==0 ) sqlite3_open(":memory:", &g.db);
1400
db_prepare(&q,
1401
"pragma compile_options");
1402
while( db_step(&q)==SQLITE_ROW ){
1403
const char *text = db_column_text(&q, 0);
1404
if( strncmp(text, "COMPILER", 8) ){
1405
blob_appendf(pOut, "SQLITE_%s\n", text);
1406
}
1407
}
1408
db_finalize(&q);
1409
}
1410
1411
/*
1412
** This function returns the user-agent string for Fossil, for
1413
** use in HTTP(S) requests.
1414
*/
1415
const char *get_user_agent(){
1416
static const char version[] = "Fossil/" RELEASE_VERSION " (" MANIFEST_DATE
1417
" " MANIFEST_VERSION ")";
1418
return version;
1419
}
1420
1421
1422
/*
1423
** COMMAND: version
1424
**
1425
** Usage: %fossil version ?-v|--verbose?
1426
**
1427
** Print the source code version number for the fossil executable.
1428
** If the verbose option is specified, additional details will
1429
** be output about what optional features this binary was compiled
1430
** with.
1431
**
1432
** Repeat the -v option or use -vv for even more information.
1433
*/
1434
void version_cmd(void){
1435
Blob versionInfo;
1436
int verboseFlag = 0;
1437
1438
while( find_option("verbose","v",0)!=0 ) verboseFlag++;
1439
while( find_option("vv",0,0)!=0 ) verboseFlag += 2;
1440
1441
/* We should be done with options.. */
1442
verify_all_options();
1443
fossil_version_blob(&versionInfo, verboseFlag);
1444
fossil_print("%s", blob_str(&versionInfo));
1445
blob_reset(&versionInfo);
1446
}
1447
1448
1449
/*
1450
** WEBPAGE: version
1451
**
1452
** Show the version information for Fossil.
1453
**
1454
** Query parameters:
1455
**
1456
** verbose Show details
1457
*/
1458
void test_version_page(void){
1459
Blob versionInfo;
1460
int verboseFlag;
1461
1462
login_check_credentials();
1463
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1464
verboseFlag = P("verbose")!=0 ? 2 : 1;
1465
style_header("Version Information");
1466
style_submenu_element("Stat", "stat");
1467
fossil_version_blob(&versionInfo, verboseFlag);
1468
@ <pre>
1469
@ %h(blob_str(&versionInfo))
1470
@ </pre>
1471
style_finish_page();
1472
}
1473
1474
1475
/*
1476
** Set the g.zBaseURL value to the full URL for the top level of
1477
** the fossil tree. Set g.zTop to g.zBaseURL without the
1478
** leading "http://" and the host and port.
1479
**
1480
** The g.zBaseURL is normally set based on HTTP_HOST and SCRIPT_NAME
1481
** environment variables. However, if zAltBase is not NULL then it
1482
** is the argument to the --baseurl option command-line option and
1483
** g.zBaseURL and g.zTop is set from that instead.
1484
*/
1485
void set_base_url(const char *zAltBase){
1486
int i;
1487
const char *zHost;
1488
const char *zMode;
1489
const char *zCur;
1490
1491
if( g.zBaseURL!=0 ) return;
1492
if( zAltBase ){
1493
int i, n, c;
1494
g.zTop = g.zBaseURL = fossil_strdup(zAltBase);
1495
i = (int)strlen(g.zBaseURL);
1496
while( i>3 && g.zBaseURL[i-1]=='/' ){ i--; }
1497
g.zBaseURL[i] = 0;
1498
if( strncmp(g.zTop, "http://", 7)==0 ){
1499
/* it is HTTP, replace prefix with HTTPS. */
1500
g.zHttpsURL = mprintf("https://%s", &g.zTop[7]);
1501
}else if( strncmp(g.zTop, "https://", 8)==0 ){
1502
/* it is already HTTPS, use it. */
1503
g.zHttpsURL = fossil_strdup(g.zTop);
1504
}else{
1505
fossil_fatal("argument to --baseurl should be 'http://host/path'"
1506
" or 'https://host/path'");
1507
}
1508
for(i=n=0; (c = g.zTop[i])!=0; i++){
1509
if( c=='/' ){
1510
n++;
1511
if( n==3 ){
1512
g.zTop += i;
1513
break;
1514
}
1515
}
1516
}
1517
if( n==2 ) g.zTop = "";
1518
if( g.zTop==g.zBaseURL ){
1519
fossil_fatal("argument to --baseurl should be 'http://host/path'"
1520
" or 'https://host/path'");
1521
}
1522
if( g.zTop[1]==0 ) g.zTop++;
1523
}else{
1524
char *z;
1525
zMode = PD("HTTPS","off");
1526
zHost = PD("HTTP_HOST","");
1527
z = fossil_strdup(zHost);
1528
for(i=0; z[i]; i++){
1529
if( z[i]<='Z' && z[i]>='A' ) z[i] += 'a' - 'A';
1530
}
1531
if( fossil_strcmp(zMode,"on")==0 ){
1532
/* Remove trailing ":443" from the HOST, if any */
1533
if( i>4 && z[i-1]=='3' && z[i-2]=='4' && z[i-3]=='4' && z[i-4]==':' ){
1534
i -= 4;
1535
}
1536
}else{
1537
/* Remove trailing ":80" from the HOST */
1538
if( i>3 && z[i-1]=='0' && z[i-2]=='8' && z[i-3]==':' ) i -= 3;
1539
}
1540
if( i && z[i-1]=='.' ) i--;
1541
z[i] = 0;
1542
zCur = PD("SCRIPT_NAME","/");
1543
i = strlen(zCur);
1544
while( i>0 && zCur[i-1]=='/' ) i--;
1545
if( fossil_stricmp(zMode,"on")==0 ){
1546
g.zBaseURL = mprintf("https://%s%.*s", z, i, zCur);
1547
g.zTop = &g.zBaseURL[8+strlen(z)];
1548
g.zHttpsURL = g.zBaseURL;
1549
}else{
1550
g.zBaseURL = mprintf("http://%s%.*s", z, i, zCur);
1551
g.zTop = &g.zBaseURL[7+strlen(z)];
1552
g.zHttpsURL = mprintf("https://%s%.*s", z, i, zCur);
1553
}
1554
fossil_free(z);
1555
}
1556
1557
/* Try to record the base URL as a CONFIG table entry with a name
1558
** of the form: "baseurl:BASE". This keeps a record of how the
1559
** the repository is used as a server, to help in answering questions
1560
** like "where is the CGI script that references this repository?"
1561
**
1562
** This is just a logging hint. So don't worry if it cannot be done.
1563
** Don't try this if the repository database is not writable, for
1564
** example.
1565
**
1566
** If g.useLocalauth is set, that (probably) means that we are running
1567
** "fossil ui" and there is no point in logging those cases either.
1568
*/
1569
if( db_is_writeable("repository") && !g.useLocalauth ){
1570
int nBase = (int)strlen(g.zBaseURL);
1571
char *zBase = g.zBaseURL;
1572
if( g.nExtraURL>0 && g.nExtraURL<nBase-6 ){
1573
zBase = fossil_strndup(g.zBaseURL, nBase - g.nExtraURL);
1574
}
1575
db_unprotect(PROTECT_CONFIG);
1576
if( !db_exists("SELECT 1 FROM config WHERE name='baseurl:%q'", zBase)){
1577
db_multi_exec("INSERT INTO config(name,value,mtime)"
1578
"VALUES('baseurl:%q',1,now())", zBase);
1579
}else{
1580
db_optional_sql("repository",
1581
"REPLACE INTO config(name,value,mtime)"
1582
"VALUES('baseurl:%q',1,now())", zBase
1583
);
1584
}
1585
db_protect_pop();
1586
if( zBase!=g.zBaseURL ) fossil_free(zBase);
1587
}
1588
}
1589
1590
/*
1591
** Send an HTTP redirect back to the designated Index Page.
1592
*/
1593
NORETURN void fossil_redirect_home(void){
1594
/* In order for ?skin=... to work when visiting the site from
1595
** a typical external link, we have to process it here, as
1596
** that parameter gets lost during the redirect. We "could"
1597
** pass the whole query string along instead, but that seems
1598
** unnecessary. */
1599
if(cgi_setup_query_string() & 0x02){
1600
cookie_render();
1601
}
1602
cgi_redirectf("%R%s", db_get("index-page", "/index"));
1603
}
1604
1605
/*
1606
** If running as root, chroot to the directory containing the
1607
** repository zRepo and then drop root privileges. Return the
1608
** new repository name.
1609
**
1610
** zRepo can be a directory. If so and if the repo name was saved
1611
** to g.zRepositoryName before we were called, we canonicalize the
1612
** two paths and check that one is the prefix of the other, else you
1613
** won't be able to open the repo inside the jail. If it all works
1614
** out, we return the "jailed" version of the repo name.
1615
**
1616
** Assume the user-id and group-id of the repository, or if zRepo
1617
** is a directory, of that directory.
1618
**
1619
** The noJail flag means that the chroot jail is not entered. But
1620
** privileges are still lowered to that of the user-id and group-id
1621
** of the repository file.
1622
*/
1623
static char *enter_chroot_jail(const char *zRepo, int noJail){
1624
#if !defined(_WIN32)
1625
if( getuid()==0 ){
1626
int i;
1627
struct stat sStat;
1628
Blob dir;
1629
char *zDir;
1630
size_t nDir;
1631
if( g.db!=0 ){
1632
db_close(1);
1633
}
1634
1635
file_canonical_name(zRepo, &dir, 0);
1636
zDir = blob_str(&dir);
1637
nDir = blob_size(&dir);
1638
if( !noJail ){
1639
if( file_isdir(zDir, ExtFILE)==1 ){
1640
/* Translate the repository name to the new root */
1641
if( g.zRepositoryName ){
1642
Blob repo;
1643
file_canonical_name(g.zRepositoryName, &repo, 0);
1644
zRepo = blob_str(&repo);
1645
if( strncmp(zRepo, zDir, nDir)!=0 ){
1646
fossil_fatal("repo %s not under chroot dir %s", zRepo, zDir);
1647
}
1648
zRepo += nDir;
1649
if( *zRepo == '\0' ) zRepo = "/";
1650
}else {
1651
zRepo = "/";
1652
}
1653
/* If a unix socket is defined, try to translate its name into
1654
** the new root so that it can be delete by atexit(). If unable,
1655
** just zero out the socket name. */
1656
if( g.zSockName ){
1657
if( strncmp(g.zSockName, zDir, nDir)==0
1658
&& g.zSockName[nDir]=='/'
1659
){
1660
g.zSockName += nDir;
1661
}else{
1662
g.zSockName = 0;
1663
}
1664
}
1665
if( file_chdir(zDir, 1) ){
1666
fossil_panic("unable to chroot into %s", zDir);
1667
}
1668
}else{
1669
for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
1670
if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo);
1671
if( i>0 ){
1672
zDir[i] = 0;
1673
if( file_chdir(zDir, 1) ){
1674
fossil_fatal("unable to chroot into %s", zDir);
1675
}
1676
zDir[i] = '/';
1677
}
1678
zRepo = &zDir[i];
1679
}
1680
}
1681
if( stat(zRepo, &sStat)!=0 ){
1682
fossil_fatal("cannot stat() repository: %s", zRepo);
1683
}
1684
i = setgid(sStat.st_gid);
1685
i = i || setuid(sStat.st_uid);
1686
if(i){
1687
fossil_fatal("setgid/uid() failed with errno %d", errno);
1688
}
1689
if( g.db==0 && file_isfile(zRepo, ExtFILE) ){
1690
db_open_repository(zRepo);
1691
}
1692
}
1693
#endif
1694
return (char*)zRepo; /* no longer const: always reassigned from blob_str() */
1695
}
1696
1697
/*
1698
** Called whenever a crash is encountered while processing a webpage.
1699
*/
1700
void sigsegv_handler(int x){
1701
#if HAVE_BACKTRACE
1702
void *array[20];
1703
size_t size;
1704
char **strings;
1705
size_t i;
1706
Blob out;
1707
size = backtrace(array, sizeof(array)/sizeof(array[0]));
1708
strings = backtrace_symbols(array, size);
1709
blob_init(&out, 0, 0);
1710
blob_appendf(&out, "Segfault during %s in fossil %s",
1711
g.zPhase, MANIFEST_VERSION);
1712
for(i=0; i<size; i++){
1713
size_t len;
1714
const char *z = strings[i];
1715
if( i==0 ) blob_appendf(&out, "\nBacktrace:");
1716
len = strlen(strings[i]);
1717
if( z[0]=='[' && z[len-1]==']' ){
1718
blob_appendf(&out, " %.*s", (int)(len-2), &z[1]);
1719
}else{
1720
blob_appendf(&out, " %s", z);
1721
}
1722
}
1723
fossil_panic("%s", blob_str(&out));
1724
#else
1725
fossil_panic("Segfault during %s in fossil %s",
1726
g.zPhase, MANIFEST_VERSION);
1727
#endif
1728
exit(1);
1729
}
1730
1731
/*
1732
** Called if a server gets a SIGPIPE. This often happens when a client
1733
** webbrowser opens a connection but never sends the HTTP request
1734
*/
1735
void sigpipe_handler(int x){
1736
#ifndef _WIN32
1737
if( g.fAnyTrace ){
1738
fprintf(stderr,"/***** sigpipe received by subprocess %d ****\n", getpid());
1739
}
1740
#endif
1741
g.zPhase = "sigpipe shutdown";
1742
db_panic_close();
1743
exit(1);
1744
}
1745
1746
/*
1747
** Return true if it is appropriate to redirect requests to HTTPS.
1748
**
1749
** Redirect to https is appropriate if all of the above are true:
1750
** (1) The redirect-to-https flag has a value of iLevel or greater.
1751
** (2) The current connection is http, not https or ssh
1752
** (3) The sslNotAvailable flag is clear
1753
*/
1754
int fossil_wants_https(int iLevel){
1755
if( g.sslNotAvailable ) return 0;
1756
if( db_get_int("redirect-to-https",0)<iLevel ) return 0;
1757
if( P("HTTPS")!=0 ) return 0;
1758
return 1;
1759
}
1760
1761
/*
1762
** Redirect to the equivalent HTTPS request if the current connection is
1763
** insecure and if the redirect-to-https flag greater than or equal to
1764
** iLevel. iLevel is 1 for /login pages and 2 for every other page.
1765
*/
1766
int fossil_redirect_to_https_if_needed(int iLevel){
1767
if( fossil_wants_https(iLevel) ){
1768
const char *zQS = P("QUERY_STRING");
1769
char *zURL = 0;
1770
if( zQS==0 || zQS[0]==0 ){
1771
zURL = mprintf("%s%T", g.zHttpsURL, P("PATH_INFO"));
1772
}else if( zQS[0]!=0 ){
1773
zURL = mprintf("%s%T?%s", g.zHttpsURL, P("PATH_INFO"), zQS);
1774
}
1775
cgi_redirect_with_status(zURL, 301, "Moved Permanently");
1776
return 1;
1777
}
1778
return 0;
1779
}
1780
1781
/*
1782
** Send a 404 Not Found reply
1783
*/
1784
void fossil_not_found_page(void){
1785
#ifdef FOSSIL_ENABLE_JSON
1786
if(g.json.isJsonMode){
1787
json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
1788
return;
1789
}
1790
#endif
1791
@ <html><head>
1792
@ <meta name="viewport" \
1793
@ content="width=device-width, initial-scale=1.0">
1794
@ </head><body>
1795
@ <h1>Not Found</h1>
1796
@ </body>
1797
cgi_set_status(404, "Not Found");
1798
cgi_reply();
1799
}
1800
1801
/*
1802
** Preconditions:
1803
**
1804
** * Environment variables are set up according to the CGI standard.
1805
**
1806
** If the repository is known, it has already been opened. If unknown,
1807
** then g.zRepositoryName holds the directory that contains the repository
1808
** and the actual repository is taken from the first element of PATH_INFO.
1809
**
1810
** Process the webpage specified by the PATH_INFO or REQUEST_URI
1811
** environment variable.
1812
**
1813
** If the repository is not known, then a search is done through the
1814
** file hierarchy rooted at g.zRepositoryName for a suitable repository
1815
** with a name of $prefix.fossil, where $prefix is any prefix of PATH_INFO.
1816
** Or, if an ordinary file named $prefix is found, and $prefix matches
1817
** pFileGlob and $prefix does not match "*.fossil*" and the mimetype of
1818
** $prefix can be determined from its suffix, then the file $prefix is
1819
** returned as static text.
1820
**
1821
** If no suitable webpage is found, try to redirect to zNotFound.
1822
*/
1823
static void process_one_web_page(
1824
const char *zNotFound, /* Redirect here on a 404 if not NULL */
1825
Glob *pFileGlob, /* Deliver static files matching */
1826
int allowRepoList /* Send repo list for "/" URL */
1827
){
1828
const char *zPathInfo = PD("PATH_INFO", "");
1829
char *zPath = NULL;
1830
int i;
1831
const CmdOrPage *pCmd = 0;
1832
const char *zBase = g.zRepositoryName;
1833
int isReadonly = 0;
1834
1835
g.zPhase = "process_one_web_page";
1836
#if !defined(_WIN32)
1837
signal(SIGSEGV, sigsegv_handler);
1838
#endif
1839
1840
/* Decode %HH escapes in PATHINFO */
1841
if( strchr(zPathInfo,'%') ){
1842
char *z = fossil_strdup(zPathInfo);
1843
dehttpize(z);
1844
zPathInfo = z;
1845
}
1846
1847
/* Handle universal query parameters */
1848
if( PB("utc") ){
1849
g.fTimeFormat = 1;
1850
}else if( PB("localtime") ){
1851
g.fTimeFormat = 2;
1852
}
1853
#ifdef FOSSIL_ENABLE_JSON
1854
/*
1855
** Ensure that JSON mode is set up if we're visiting /json, to allow
1856
** us to customize some following behaviour (error handling and only
1857
** process JSON-mode POST data if we're actually in a /json
1858
** page). This is normally set up before this routine is called, but
1859
** it looks like the ssh_request_loop() approach to dispatching
1860
** might bypass that.
1861
*/
1862
if( g.json.isJsonMode==0 && json_request_is_json_api(zPathInfo)!=0 ){
1863
g.json.isJsonMode = 1;
1864
json_bootstrap_early();
1865
}
1866
#endif
1867
/* If the repository has not been opened already, then find the
1868
** repository based on the first element of PATH_INFO and open it.
1869
*/
1870
if( !g.repositoryOpen ){
1871
char zBuf[24];
1872
const char *zRepoExt = ".fossil";
1873
char *zRepo; /* Candidate repository name */
1874
char *zToFree = 0; /* Malloced memory that needs to be freed */
1875
const char *zCleanRepo; /* zRepo with surplus leading "/" removed */
1876
const char *zOldScript = PD("SCRIPT_NAME", ""); /* Original SCRIPT_NAME */
1877
char *zNewScript; /* Revised SCRIPT_NAME after processing */
1878
int j, k; /* Loop variables */
1879
i64 szFile; /* File size of the candidate repository */
1880
1881
i = zPathInfo[0]!=0;
1882
if( fossil_strcmp(g.zRepositoryName, "/")==0 ){
1883
zBase++;
1884
#if defined(_WIN32) || defined(__CYGWIN__)
1885
if( sqlite3_strglob("/[a-zA-Z]:/*", zPathInfo)==0 ) i = 4;
1886
#endif
1887
}
1888
while( 1 ){
1889
size_t nBase = strlen(zBase);
1890
while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; }
1891
1892
/* The candidate repository name is some prefix of the PATH_INFO
1893
** with ".fossil" appended */
1894
zRepo = zToFree = mprintf("%s%.*s%s",zBase,i,zPathInfo,zRepoExt);
1895
if( g.fHttpTrace ){
1896
@ <!-- Looking for repository named "%h(zRepo)" -->
1897
fprintf(stderr, "# looking for repository named \"%s\"\n", zRepo);
1898
}
1899
1900
1901
/* Restrictions on the URI for security:
1902
**
1903
** 1. Reject characters that are not ASCII alphanumerics,
1904
** "-", "_", ".", "/", or unicode (above ASCII).
1905
** In other words: No ASCII punctuation or control characters
1906
** other than "-", "_", "." and "/".
1907
** 2. Exception to rule 1: Allow /X:/ where X is any ASCII
1908
** alphabetic character at the beginning of the name on windows.
1909
** 3. "-" may not occur immediately after "/"
1910
** 4. "." may not be adjacent to another "." or to "/"
1911
**
1912
** Any character does not satisfy these constraints a Not Found
1913
** error is returned.
1914
*/
1915
szFile = 0;
1916
for(j=nBase+1, k=0; zRepo[j] && k<i-1; j++, k++){
1917
char c = zRepo[j];
1918
if( c>='a' && c<='z' ) continue;
1919
if( c>='A' && c<='Z' ) continue;
1920
if( c>='0' && c<='9' ) continue;
1921
if( (c&0x80)==0x80 ) continue;
1922
#if defined(_WIN32) || defined(__CYGWIN__)
1923
/* Allow names to begin with "/X:/" on windows */
1924
if( c==':' && j==2 && sqlite3_strglob("/[a-zA-Z]:/*", zRepo)==0 ){
1925
continue;
1926
}
1927
#endif
1928
if( c=='/' ) continue;
1929
if( c=='_' ) continue;
1930
if( c=='-' && zRepo[j-1]!='/' ) continue;
1931
if( c=='.'
1932
&& zRepo[j-1]!='.' && zRepo[j-1]!='/'
1933
&& zRepo[j+1]!='.' && zRepo[j+1]!='/'
1934
){
1935
continue;
1936
}
1937
if( c=='.' && g.fAllowACME && j==(int)nBase+1
1938
&& strncmp(&zRepo[j-1],"/.well-known/",12)==0
1939
){
1940
/* We allow .well-known as the top-level directory for ACME */
1941
continue;
1942
}
1943
/* If we reach this point, it means that the request URI contains
1944
** an illegal character or character combination. Provoke a
1945
** "Not Found" error. */
1946
szFile = 1;
1947
if( g.fHttpTrace ){
1948
@ <!-- Unsafe pathname rejected: "%h(zRepo)" -->
1949
fprintf(stderr, "# unsafe pathname rejected: %s\n", zRepo);
1950
}
1951
break;
1952
}
1953
1954
/* Check to see if a file name zRepo exists. If a file named zRepo
1955
** does not exist, szFile will become -1. If the file does exist,
1956
** then szFile will become zero (for an empty file) or positive.
1957
** Special case: Assume any file with a basename of ".fossil" does
1958
** not exist.
1959
*/
1960
zCleanRepo = file_cleanup_fullpath(zRepo);
1961
if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){
1962
szFile = file_size(zCleanRepo, ExtFILE);
1963
if( szFile>0 && !file_isfile(zCleanRepo, ExtFILE) ){
1964
/* Only let szFile be non-negative if zCleanRepo really is a file
1965
** and not a directory or some other filesystem object. */
1966
szFile = -1;
1967
}
1968
if( g.fHttpTrace ){
1969
sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", szFile);
1970
@ <!-- file_size(%h(zCleanRepo)) is %s(zBuf) -->
1971
fprintf(stderr, "# file_size(%s) = %s\n", zCleanRepo, zBuf);
1972
}
1973
}
1974
1975
/* If no file named by zRepo exists, remove the added ".fossil" suffix
1976
** and check to see if there is a file or directory with the same
1977
** name as the raw PATH_INFO text.
1978
*/
1979
if( szFile<0 && i>0 ){
1980
const char *zMimetype;
1981
assert( file_is_repository_extension(&zRepo[j]) );
1982
zRepo[j] = 0; /* Remove the ".fossil" suffix */
1983
1984
/* The PATH_INFO prefix seen so far is a valid directory.
1985
** Continue the loop with the next element of the PATH_INFO */
1986
if( zPathInfo[i]=='/' && file_isdir(zCleanRepo, ExtFILE)==1 ){
1987
fossil_free(zToFree);
1988
i++;
1989
continue;
1990
}
1991
1992
/* If zRepo is the name of an ordinary file that matches the
1993
** "--file GLOB" pattern, then the CGI reply is the text of
1994
** of the file.
1995
**
1996
** For safety, do not allow any file whose name contains ".fossil"
1997
** to be returned this way, to prevent complete repositories from
1998
** being delivered accidently. This is not intended to be a
1999
** general-purpose web server. The "--file GLOB" mechanism is
2000
** designed to allow the delivery of a few static images or HTML
2001
** pages.
2002
*/
2003
if( pFileGlob!=0
2004
&& file_isfile(zCleanRepo, ExtFILE)
2005
&& glob_match(pFileGlob, file_cleanup_fullpath(zRepo+nBase))
2006
&& !file_contains_repository_extension(zRepo)
2007
&& (zMimetype = mimetype_from_name(zRepo))!=0
2008
&& strcmp(zMimetype, "application/x-fossil-artifact")!=0
2009
){
2010
Blob content;
2011
blob_read_from_file(&content, file_cleanup_fullpath(zRepo), ExtFILE);
2012
cgi_set_content_type(zMimetype);
2013
cgi_set_content(&content);
2014
cgi_reply();
2015
return;
2016
}
2017
2018
/* In support of the ACME protocol, files under the .well-known/
2019
** directory is always accepted.
2020
*/
2021
if( g.fAllowACME
2022
&& strncmp(&zRepo[nBase],"/.well-known/",12)==0
2023
&& file_isfile(zCleanRepo, ExtFILE)
2024
){
2025
Blob content;
2026
blob_read_from_file(&content, file_cleanup_fullpath(zRepo), ExtFILE);
2027
cgi_set_content_type(mimetype_from_name(zRepo));
2028
cgi_set_content(&content);
2029
cgi_reply();
2030
return;
2031
}
2032
zRepo[j] = '.';
2033
}
2034
2035
/* If we reach this point, it means that the search of the PATH_INFO
2036
** string is finished. Either zRepo contains the name of the
2037
** repository to be used, or else no repository could be found and
2038
** some kind of error response is required.
2039
*/
2040
if( szFile<1024 ){
2041
#if USE_SEE
2042
if( strcmp(zRepoExt,".fossil")==0 ){
2043
fossil_free(zToFree);
2044
zRepoExt = ".efossil";
2045
continue;
2046
}
2047
#endif
2048
set_base_url(0);
2049
if( (zPathInfo[0]==0 || strcmp(zPathInfo,"/")==0)
2050
&& allowRepoList
2051
&& repo_list_page() ){
2052
/* Will return a list of repositories */
2053
}else if( zNotFound ){
2054
cgi_redirect(zNotFound);
2055
}else{
2056
fossil_not_found_page();
2057
}
2058
return;
2059
}
2060
break;
2061
}
2062
2063
/* Add the repository name (without the ".fossil" suffix) to the end
2064
** of SCRIPT_NAME and g.zTop and g.zBaseURL and remove the repository
2065
** name from the beginning of PATH_INFO.
2066
*/
2067
zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo);
2068
if( g.zTop ) g.zTop = mprintf("%R%.*s", i, zPathInfo);
2069
if( g.zBaseURL ) g.zBaseURL = mprintf("%s%.*s", g.zBaseURL, i, zPathInfo);
2070
cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]);
2071
zPathInfo += i;
2072
cgi_replace_parameter("SCRIPT_NAME", zNewScript);
2073
#if USE_SEE
2074
if( zPathInfo ){
2075
if( g.fHttpTrace ){
2076
sqlite3_snprintf(sizeof(zBuf), zBuf, "%d", i);
2077
@ <!-- see_path_info(%s(zBuf)) is %h(zPathInfo) -->
2078
fprintf(stderr, "# see_path_info(%d) = %s\n", i, zPathInfo);
2079
}
2080
if( strcmp(zPathInfo,"/setseekey")==0
2081
&& strcmp(zRepoExt,".efossil")==0
2082
&& !db_have_saved_encryption_key() ){
2083
db_set_see_key_page();
2084
cgi_reply();
2085
fossil_exit(0);
2086
}
2087
}
2088
#endif
2089
db_open_repository(file_cleanup_fullpath(zRepo));
2090
if( g.fHttpTrace ){
2091
@ <!-- repository: "%h(zRepo)" -->
2092
@ <!-- translated PATH_INFO: "%h(zPathInfo)" -->
2093
@ <!-- translated SCRIPT_NAME: "%h(zNewScript)" -->
2094
fprintf(stderr,
2095
"# repository: [%s]\n"
2096
"# translated PATH_INFO = [%s]\n"
2097
"# translated SCRIPT_NAME = [%s]\n",
2098
zRepo, zPathInfo, zNewScript);
2099
if( g.zTop ){
2100
@ <!-- translated g.zTop: "%h(g.zTop)" -->
2101
fprintf(stderr, "# translated g.zTop = [%s]\n", g.zTop);
2102
}
2103
if( g.zBaseURL ){
2104
@ <!-- translated g.zBaseURL: "%h(g.zBaseURL)" -->
2105
fprintf(stderr, "# translated g.zBaseURL = [%s]\n", g.zBaseURL);
2106
}
2107
}
2108
}
2109
2110
/* At this point, the appropriate repository database file will have
2111
** been opened.
2112
*/
2113
2114
/*
2115
** Check to see if the first term of PATH_INFO specifies an
2116
** alternative skin. This will be the case if the first term of
2117
** PATH_INFO begins with "draftN/" where N is an integer between 1
2118
** and 9. If so, activate the skin associated with that draft.
2119
*/
2120
if( zPathInfo && strncmp(zPathInfo,"/draft",6)==0
2121
&& zPathInfo[6]>='1' && zPathInfo[6]<='9'
2122
&& (zPathInfo[7]=='/' || zPathInfo[7]==0)
2123
){
2124
int iSkin = zPathInfo[6] - '0';
2125
char *zNewScript;
2126
if( db_int(0,"SELECT count(*) FROM config WHERE name GLOB 'draft%d-*'",
2127
iSkin)<5 ){
2128
fossil_not_found_page();
2129
fossil_exit(0);
2130
}
2131
skin_use_draft(iSkin);
2132
zNewScript = mprintf("%T/draft%d", P("SCRIPT_NAME"), iSkin);
2133
if( g.zTop ) g.zTop = mprintf("%R/draft%d", iSkin);
2134
if( g.zBaseURL ) g.zBaseURL = mprintf("%s/draft%d", g.zBaseURL, iSkin);
2135
zPathInfo += 7;
2136
g.nExtraURL += 7;
2137
cgi_replace_parameter("PATH_INFO", zPathInfo);
2138
cgi_replace_parameter("SCRIPT_NAME", zNewScript);
2139
etag_cancel();
2140
}
2141
2142
/* If the content type is application/x-fossil or
2143
** application/x-fossil-debug, then a sync/push/pull/clone is
2144
** desired, so default the PATH_INFO to /xfer
2145
*/
2146
if( g.zContentType &&
2147
strncmp(g.zContentType, "application/x-fossil", 20)==0 ){
2148
/* Special case: If the content mimetype shows that it is "fossil sync"
2149
** payload, then pretend that the PATH_INFO is /xfer so that we always
2150
** invoke the sync page. */
2151
zPathInfo = "/xfer";
2152
}
2153
2154
/* Use the first element of PATH_INFO as the page name
2155
** and deliver the appropriate page back to the user.
2156
*/
2157
set_base_url(0);
2158
if( fossil_redirect_to_https_if_needed(2) ) return;
2159
if( zPathInfo==0 || zPathInfo[0]==0
2160
|| (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
2161
/* Second special case: If the PATH_INFO is blank, issue a
2162
** temporary 302 redirect:
2163
** (1) to "/ckout" if g.useLocalauth and g.localOpen are both set.
2164
** (2) to the home page identified by the "index-page" setting
2165
** in the repository CONFIG table
2166
** (3) to "/index" if there no "index-page" setting in CONFIG
2167
*/
2168
#ifdef FOSSIL_ENABLE_JSON
2169
if(g.json.isJsonMode){
2170
json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
2171
fossil_exit(0);
2172
}
2173
#endif
2174
if( g.useLocalauth && g.localOpen ){
2175
cgi_redirectf("%R/ckout");
2176
}else{
2177
fossil_redirect_home() /*does not return*/;
2178
}
2179
}else{
2180
zPath = fossil_strdup(zPathInfo);
2181
}
2182
2183
/* Make g.zPath point to the first element of the path. Make
2184
** g.zExtra point to everything past that point.
2185
*/
2186
g.zPath = &zPath[1];
2187
for(i=1; zPath[i] && zPath[i]!='/'; i++){}
2188
if( zPath[i]=='/' ){
2189
zPath[i] = 0;
2190
g.zExtra = &zPath[i+1];
2191
}else{
2192
g.zExtra = 0;
2193
}
2194
if( g.zExtra ){
2195
/* CGI parameters get this treatment elsewhere, but places like getfile
2196
** will use g.zExtra directly.
2197
** Reminder: the login mechanism uses 'name' differently, and may
2198
** eventually have a problem/collision with this.
2199
**
2200
** Disabled by stephan when running in JSON mode because this
2201
** particular parameter name is very common and i have had no end
2202
** of grief with this handling. The JSON API never relies on the
2203
** handling below, and by disabling it in JSON mode I can remove
2204
** lots of special-case handling in several JSON handlers.
2205
*/
2206
#ifdef FOSSIL_ENABLE_JSON
2207
if(g.json.isJsonMode==0){
2208
#endif
2209
dehttpize(g.zExtra);
2210
cgi_set_parameter_nocopy("name", g.zExtra, 1);
2211
#ifdef FOSSIL_ENABLE_JSON
2212
}
2213
#endif
2214
}
2215
2216
/* Locate the method specified by the path and execute the function
2217
** that implements that method.
2218
*/
2219
if( dispatch_name_search(g.zPath-1, CMDFLAG_WEBPAGE, &pCmd)
2220
&& dispatch_alias(g.zPath-1, &pCmd)
2221
){
2222
#ifdef FOSSIL_ENABLE_JSON
2223
if(g.json.isJsonMode!=0){
2224
json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,0);
2225
}else
2226
#endif
2227
{
2228
#ifdef FOSSIL_ENABLE_TH1_HOOKS
2229
int rc;
2230
if( !g.fNoThHook ){
2231
rc = Th_WebpageHook(g.zPath, 0);
2232
}else{
2233
rc = TH_OK;
2234
}
2235
if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
2236
if( rc==TH_OK || rc==TH_RETURN ){
2237
#endif
2238
cgi_set_status(404,"Not Found");
2239
@ <h1>Not Found</h1>
2240
@ <p>Page not found: %h(g.zPath)</p>
2241
#ifdef FOSSIL_ENABLE_TH1_HOOKS
2242
}
2243
if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
2244
Th_WebpageNotify(g.zPath, 0);
2245
}
2246
}
2247
#endif
2248
}
2249
}else if( pCmd->xFunc!=page_xfer && db_schema_is_outofdate() ){
2250
#ifdef FOSSIL_ENABLE_JSON
2251
if(g.json.isJsonMode!=0){
2252
json_err(FSL_JSON_E_DB_NEEDS_REBUILD,NULL,0);
2253
}else
2254
#endif
2255
{
2256
@ <h1>Server Configuration Error</h1>
2257
@ <p>The database schema on the server is out-of-date. Please ask
2258
@ the administrator to run <b>fossil rebuild</b>.</p>
2259
}
2260
}else{
2261
if(0==(CMDFLAG_LDAVG_EXEMPT & pCmd->eCmdFlags)){
2262
load_control();
2263
}
2264
#ifdef FOSSIL_ENABLE_JSON
2265
{
2266
static int jsonOnce = 0;
2267
if( jsonOnce==0 && g.json.isJsonMode!=0 ){
2268
assert(json_is_bootstrapped_early());
2269
json_bootstrap_late();
2270
jsonOnce = 1;
2271
}
2272
}
2273
#endif
2274
if( (pCmd->eCmdFlags & CMDFLAG_RAWCONTENT)==0 ){
2275
cgi_decode_post_parameters();
2276
if( !cgi_same_origin(0) ){
2277
isReadonly = 1;
2278
db_protect(PROTECT_READONLY);
2279
}
2280
}
2281
if( g.fCgiTrace ){
2282
fossil_trace("######## Calling %s #########\n", pCmd->zName);
2283
cgi_print_all(1, 1, 0);
2284
}
2285
#ifdef FOSSIL_ENABLE_TH1_HOOKS
2286
{
2287
/*
2288
** The TH1 return codes from the hook will be handled as follows:
2289
**
2290
** TH_OK: The xFunc() and the TH1 notification will both be executed.
2291
**
2292
** TH_ERROR: The xFunc() will be skipped, the TH1 notification will be
2293
** skipped. If the xFunc() is being hooked, the error message
2294
** will be emitted.
2295
**
2296
** TH_BREAK: The xFunc() and the TH1 notification will both be skipped.
2297
**
2298
** TH_RETURN: The xFunc() will be executed, the TH1 notification will be
2299
** skipped.
2300
**
2301
** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be
2302
** executed.
2303
*/
2304
int rc;
2305
if( !g.fNoThHook ){
2306
rc = Th_WebpageHook(pCmd->zName+1, pCmd->eCmdFlags);
2307
}else{
2308
rc = TH_OK;
2309
}
2310
if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
2311
if( rc==TH_OK || rc==TH_RETURN ){
2312
#endif
2313
g.zPhase = pCmd->zName;
2314
pCmd->xFunc();
2315
#ifdef FOSSIL_ENABLE_TH1_HOOKS
2316
}
2317
if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
2318
Th_WebpageNotify(pCmd->zName+1, pCmd->eCmdFlags);
2319
}
2320
}
2321
}
2322
#endif
2323
if( isReadonly ){
2324
db_protect_pop();
2325
}
2326
}
2327
2328
/* Return the result.
2329
*/
2330
g.zPhase = "web-page reply";
2331
cgi_reply();
2332
}
2333
2334
/* If the CGI program contains one or more lines of the form
2335
**
2336
** redirect: repository-filename http://hostname/path/%s
2337
**
2338
** then control jumps here. Search each repository for an artifact ID
2339
** or ticket ID that matches the "name" query parameter. If there is
2340
** no "name" query parameter, use PATH_INFO instead. If a match is
2341
** found, redirect to the corresponding URL. Substitute "%s" in the
2342
** URL with the value of the name query parameter before the redirect.
2343
**
2344
** If there is a line of the form:
2345
**
2346
** redirect: * URL
2347
**
2348
** Then a redirect is made to URL if no match is found. If URL contains
2349
** "%s" then substitute the "name" query parameter. If REPO is "*" and
2350
** URL does not contains "%s" and does not contain "?" then append
2351
** PATH_INFO and QUERY_STRING to the URL prior to the redirect.
2352
**
2353
** If no matches are found and if there is no "*" entry, then generate
2354
** a primitive error message.
2355
**
2356
** USE CASES:
2357
**
2358
** (1) Suppose you have two related projects projA and projB. You can
2359
** use this feature to set up an /info page that covers both
2360
** projects.
2361
**
2362
** redirect: /fossils/projA.fossil /proj-a/info/%s
2363
** redirect: /fossils/projB.fossil /proj-b/info/%s
2364
**
2365
** Then visits to the /info/HASH page will redirect to the
2366
** first project that contains that hash.
2367
**
2368
** (2) Use the "*" form for to redirect legacy URLs. On the Fossil
2369
** website we have an CGI at http://fossil.com/index.html (note
2370
** ".com" instead of ".org") that looks like this:
2371
**
2372
** #!/usr/bin/fossil
2373
** redirect: * https://fossil-scm.org/home
2374
**
2375
** Thus requests to the .com website redirect to the .org website.
2376
** This form uses a 301 Permanent redirect.
2377
**
2378
** On a "*" redirect, the PATH_INFO and QUERY_STRING of the query
2379
** that provoked the redirect are appended to the target. So, for
2380
** example, if the input URL for the redirect above were
2381
** "http://www.fossil.com/index.html/timeline?c=20250404", then
2382
** the redirect would be to:
2383
**
2384
** https://fossil-scm.org/home/timeline?c=20250404
2385
** ^^^^^^^^^^^^^^^^^^^^
2386
** Copied from input URL
2387
*/
2388
static void redirect_web_page(int nRedirect, char **azRedirect){
2389
int i; /* Loop counter */
2390
const char *zNotFound = 0; /* Not found URL */
2391
const char *zName = P("name");
2392
set_base_url(0);
2393
if( zName==0 ){
2394
zName = P("PATH_INFO");
2395
if( zName && zName[0]=='/' ) zName++;
2396
}
2397
if( zName ){
2398
for(i=0; i<nRedirect; i++){
2399
if( fossil_strcmp(azRedirect[i*2],"*")==0 ){
2400
zNotFound = azRedirect[i*2+1];
2401
continue;
2402
}else if( validate16(zName, strlen(zName)) ){
2403
db_open_repository(azRedirect[i*2]);
2404
if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'", zName) ||
2405
db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'",zName) ){
2406
cgi_redirectf(azRedirect[i*2+1] /*works-like:"%s"*/, zName);
2407
return;
2408
}
2409
db_close(1);
2410
}
2411
}
2412
}
2413
if( zNotFound ){
2414
Blob to;
2415
const char *z;
2416
if( strstr(zNotFound, "%s") ){
2417
char *zTarget = mprintf(zNotFound /*works-like:"%s"*/, zName);
2418
cgi_redirect_perm(zTarget);
2419
}
2420
if( strchr(zNotFound, '?') ){
2421
cgi_redirect_perm(zNotFound);
2422
}
2423
blob_init(&to, zNotFound, -1);
2424
z = P("PATH_INFO");
2425
if( z && z[0]=='/' ) blob_append(&to, z, -1);
2426
z = P("QUERY_STRING");
2427
if( z && z[0]!=0 ) blob_appendf(&to, "?%s", z);
2428
cgi_redirect_perm(blob_str(&to));
2429
}else{
2430
@ <html>
2431
@ <head><title>No Such Object</title></head>
2432
@ <body>
2433
@ <p>No such object: <b>%h(zName)</b></p>
2434
@ </body>
2435
cgi_reply();
2436
}
2437
}
2438
2439
/*
2440
** COMMAND: cgi*
2441
**
2442
** Usage: %fossil ?cgi? FILE
2443
**
2444
** This command causes Fossil to generate reply to a CGI request.
2445
**
2446
** The FILE argument is the name of a control file that provides Fossil
2447
** with important information such as where to find its repository. In
2448
** a typical CGI deployment, FILE is the name of the CGI script and will
2449
** typically look something like this:
2450
**
2451
** #!/usr/bin/fossil
2452
** repository: /home/somebody/project.db
2453
**
2454
** The command name, "cgi", may be omitted if the GATEWAY_INTERFACE
2455
** environment variable is set to "CGI", which should always be the
2456
** case for CGI scripts run by a webserver. Fossil ignores any lines
2457
** that begin with "#".
2458
**
2459
** The following control lines are recognized:
2460
**
2461
** repository: PATH Name of the Fossil repository
2462
**
2463
** directory: PATH Name of a directory containing many Fossil
2464
** repositories whose names all end with ".fossil".
2465
** There should only be one of "repository:"
2466
** or "directory:"
2467
**
2468
** notfound: URL When in "directory:" mode, redirect to
2469
** URL if no suitable repository is found.
2470
**
2471
** repolist When in "directory:" mode, display a page
2472
** showing a list of available repositories if
2473
** the URL is "/". Some control over the display
2474
** is accomplished using environment variables.
2475
** FOSSIL_REPOLIST_TITLE is the tital of the page.
2476
** FOSSIL_REPOLIST_SHOW cause the "Description"
2477
** column to display if it contains "description" as
2478
** as a substring, and causes the Login-Group column
2479
** to display if it contains the "login-group"
2480
** substring.
2481
**
2482
** localauth Grant administrator privileges to connections
2483
** from 127.0.0.1 or ::1.
2484
**
2485
** nossl Signal that no SSL connections are available.
2486
**
2487
** nocompress Do not compress HTTP replies.
2488
**
2489
** skin: LABEL Use the built-in skin called LABEL rather than
2490
** the default, or the default if LABEL is empty.
2491
** If there are no skins called LABEL then this
2492
** line is a no-op.
2493
**
2494
** files: GLOBLIST GLOBLIST is a comma-separated list of GLOB
2495
** patterns that specify files that can be
2496
** returned verbatim. This feature allows Fossil
2497
** to act as a web server returning static
2498
** content.
2499
**
2500
** setenv: NAME VALUE Set environment variable NAME to VALUE. Or
2501
** if VALUE is omitted, unset NAME.
2502
**
2503
** HOME: PATH Shorthand for "setenv: HOME PATH"
2504
**
2505
** cgi-debug: FILE Causing debugging information to be written
2506
** into FILE.
2507
**
2508
** errorlog: FILE Warnings, errors, and panics written to FILE.
2509
**
2510
** timeout: SECONDS Do not run for longer than SECONDS. The default
2511
** timeout is FOSSIL_DEFAULT_TIMEOUT (600) seconds.
2512
**
2513
** extroot: DIR Directory that is the root of the sub-CGI tree
2514
** on the /ext page.
2515
**
2516
** redirect: REPO URL Extract the "name" query parameter and search
2517
** REPO for a check-in or ticket that matches the
2518
** value of "name", then redirect to URL. There
2519
** can be multiple "redirect:" lines that are
2520
** processed in order. If the REPO is "*", then
2521
** an unconditional redirect to URL is taken.
2522
** When "*" is used a 301 permanent redirect is
2523
** issued and the tail and query string from the
2524
** original query are appended onto URL.
2525
**
2526
** jsmode: VALUE Specifies the delivery mode for JavaScript
2527
** files. See the help text for the --jsmode
2528
** flag of the http command.
2529
**
2530
** mainmenu: FILE Override the mainmenu config setting with the
2531
** contents of the given file.
2532
**
2533
** Most CGI files contain only a "repository:" line. It is uncommon to
2534
** use any other option.
2535
**
2536
** The lines are processed in the order they are read, which is most
2537
** significant for "errorlog:", which should be set before "repository:"
2538
** so that any warnings from the database when opening the repository
2539
** go to that log file.
2540
**
2541
** See also: [[http]], [[server]], [[winsrv]] [Windows only]
2542
*/
2543
void cmd_cgi(void){
2544
const char *zNotFound = 0;
2545
char **azRedirect = 0; /* List of repositories to redirect to */
2546
int nRedirect = 0; /* Number of entries in azRedirect */
2547
Glob *pFileGlob = 0; /* Pattern for files */
2548
int allowRepoList = 0; /* Allow lists of repository files */
2549
Blob config, line, key, value, value2;
2550
/* Initialize the CGI environment. */
2551
g.httpOut = stdout;
2552
g.httpIn = stdin;
2553
fossil_binary_mode(g.httpOut);
2554
fossil_binary_mode(g.httpIn);
2555
g.cgiOutput = 1;
2556
g.zReqType = "CGI";
2557
fossil_set_timeout(FOSSIL_DEFAULT_TIMEOUT);
2558
/* Find the name of the CGI control file */
2559
if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){
2560
g.zCgiFile = g.argv[2];
2561
}else if( g.argc>=2 ){
2562
g.zCgiFile = g.argv[1];
2563
}else{
2564
cgi_panic("No CGI control file specified");
2565
}
2566
/* Read and parse the CGI control file. */
2567
blob_read_from_file(&config, g.zCgiFile, ExtFILE);
2568
while( blob_line(&config, &line) ){
2569
if( !blob_token(&line, &key) ) continue;
2570
if( blob_buffer(&key)[0]=='#' ) continue;
2571
if( blob_eq(&key, "repository:") && blob_tail(&line, &value) ){
2572
/* repository: FILENAME
2573
**
2574
** The name of the Fossil repository to be served via CGI. Most
2575
** fossil CGI scripts have a single non-comment line that contains
2576
** this one entry.
2577
*/
2578
blob_trim(&value);
2579
db_open_repository(blob_str(&value));
2580
blob_reset(&value);
2581
continue;
2582
}
2583
if( blob_eq(&key, "directory:") && blob_token(&line, &value) ){
2584
/* directory: DIRECTORY
2585
**
2586
** If repository: is omitted, then terms of the PATH_INFO cgi parameter
2587
** are appended to DIRECTORY looking for a repository (whose name ends
2588
** in ".fossil") or a file in "files:".
2589
*/
2590
db_close(1);
2591
g.zRepositoryName = fossil_strdup(blob_str(&value));
2592
blob_reset(&value);
2593
continue;
2594
}
2595
if( blob_eq(&key, "notfound:") && blob_token(&line, &value) ){
2596
/* notfound: URL
2597
**
2598
** If using directory: and no suitable repository or file is found,
2599
** then redirect to URL.
2600
*/
2601
zNotFound = fossil_strdup(blob_str(&value));
2602
blob_reset(&value);
2603
continue;
2604
}
2605
if( blob_eq(&key, "localauth") ){
2606
/* localauth
2607
**
2608
** Grant "administrator" privileges to users connecting with HTTP
2609
** from IP address 127.0.0.1. Do not bother checking credentials.
2610
*/
2611
g.useLocalauth = 1;
2612
continue;
2613
}
2614
if( blob_eq(&key, "nossl") ){
2615
/* nossl
2616
**
2617
** Signal that no SSL connections are available.
2618
*/
2619
g.sslNotAvailable = 1;
2620
continue;
2621
}
2622
if( blob_eq(&key, "nocompress") ){
2623
/* nocompress
2624
**
2625
** Do not compress HTTP replies.
2626
*/
2627
g.fNoHttpCompress = 1;
2628
continue;
2629
}
2630
if( blob_eq(&key, "repolist") ){
2631
/* repolist
2632
**
2633
** If using "directory:" and the URL is "/" then generate a page
2634
** showing a list of available repositories.
2635
*/
2636
allowRepoList = 1;
2637
continue;
2638
}
2639
if( blob_eq(&key, "redirect:") && blob_token(&line, &value)
2640
&& blob_token(&line, &value2) ){
2641
/* See the header comment on the redirect_web_page() function
2642
** above for details. */
2643
nRedirect++;
2644
azRedirect = fossil_realloc(azRedirect, 2*nRedirect*sizeof(char*));
2645
azRedirect[nRedirect*2-2] = fossil_strdup(blob_str(&value));
2646
azRedirect[nRedirect*2-1] = fossil_strdup(blob_str(&value2));
2647
blob_reset(&value);
2648
blob_reset(&value2);
2649
continue;
2650
}
2651
if( blob_eq(&key, "files:") && blob_token(&line, &value) ){
2652
/* files: GLOBLIST
2653
**
2654
** GLOBLIST is a comma-separated list of filename globs. For
2655
** example: *.html,*.css,*.js
2656
**
2657
** If the repository: line is omitted and then PATH_INFO is searched
2658
** for files that match any of these GLOBs and if any such file is
2659
** found it is returned verbatim. This feature allows "fossil server"
2660
** to function as a primitive web-server delivering arbitrary content.
2661
*/
2662
pFileGlob = glob_create(blob_str(&value));
2663
blob_reset(&value);
2664
continue;
2665
}
2666
if( blob_eq(&key, "setenv:") && blob_token(&line, &value) ){
2667
/* setenv: NAME VALUE
2668
** setenv: NAME
2669
**
2670
** Sets environment variable NAME to VALUE. If VALUE is omitted, then
2671
** the environment variable is unset.
2672
*/
2673
char *zValue;
2674
blob_tail(&line,&value2);
2675
blob_trim(&value2);
2676
zValue = blob_str(&value2);
2677
while( fossil_isspace(zValue[0]) ){ zValue++; }
2678
fossil_setenv(blob_str(&value), zValue);
2679
blob_reset(&value);
2680
blob_reset(&value2);
2681
continue;
2682
}
2683
if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){
2684
/* errorlog: FILENAME
2685
**
2686
** Causes messages from warnings, errors, and panics to be appended
2687
** to FILENAME.
2688
*/
2689
g.zErrlog = fossil_strdup(blob_str(&value));
2690
blob_reset(&value);
2691
continue;
2692
}
2693
if( blob_eq(&key, "extroot:") && blob_token(&line, &value) ){
2694
/* extroot: DIRECTORY
2695
**
2696
** Enables the /ext webpage to use sub-cgi rooted at DIRECTORY
2697
*/
2698
g.zExtRoot = fossil_strdup(blob_str(&value));
2699
blob_reset(&value);
2700
continue;
2701
}
2702
if( blob_eq(&key, "timeout:") && blob_token(&line, &value) ){
2703
/* timeout: SECONDS
2704
**
2705
** Set an alarm() that kills the process after SECONDS. The
2706
** default value is FOSSIL_DEFAULT_TIMEOUT (600) seconds.
2707
*/
2708
fossil_set_timeout(atoi(blob_str(&value)));
2709
continue;
2710
}
2711
if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){
2712
/* HOME: VALUE
2713
**
2714
** Set CGI parameter "HOME" to VALUE. This is legacy. Use
2715
** setenv: instead.
2716
*/
2717
cgi_setenv("HOME", blob_str(&value));
2718
blob_reset(&value);
2719
continue;
2720
}
2721
if( blob_eq(&key, "skin:") ){
2722
/* skin: LABEL
2723
**
2724
** Use one of the built-in skins defined by LABEL. LABEL is the
2725
** name of the subdirectory under the skins/ directory that holds
2726
** the elements of the built-in skin. If LABEL does not match,
2727
** this directive is a silent no-op. It may alternately be
2728
** an absolute path to a directory which holds skin definition
2729
** files (header.txt, footer.txt, etc.). If LABEL is empty,
2730
** the skin stored in the CONFIG db table is used.
2731
*/
2732
blob_token(&line, &value);
2733
fossil_free(skin_use_alternative(blob_str(&value), 1, SKIN_FROM_CGI));
2734
blob_reset(&value);
2735
continue;
2736
}
2737
if( blob_eq(&key, "jsmode:") && blob_token(&line, &value) ){
2738
/* jsmode: MODE
2739
**
2740
** Change how JavaScript resources are delivered with each HTML
2741
** page. MODE is "inline" to put all JS inline, or "separate" to
2742
** cause each JS file to be requested using a separate HTTP request,
2743
** or "bundled" to have all JS files to be fetched with a single
2744
** auxiliary HTTP request. Noting, however, that "single" might
2745
** actually mean more than one, depending on the script-timing
2746
** requirements of any given page.
2747
*/
2748
builtin_set_js_delivery_mode(blob_str(&value),0);
2749
blob_reset(&value);
2750
continue;
2751
}
2752
if( blob_eq(&key, "mainmenu:") && blob_token(&line, &value) ){
2753
/* mainmenu: FILENAME
2754
**
2755
** Use the contents of FILENAME as the value of the site's
2756
** "mainmenu" setting, overriding the contents (for this
2757
** request) of the db-side setting or the hard-coded default.
2758
*/
2759
g.zMainMenuFile = fossil_strdup(blob_str(&value));
2760
blob_reset(&value);
2761
continue;
2762
}
2763
if( blob_eq(&key, "cgi-debug:") && blob_token(&line, &value) ){
2764
/* cgi-debug: FILENAME
2765
**
2766
** Causes output from cgi_debug() and CGIDEBUG(()) calls to go
2767
** into FILENAME. Useful for debugging CGI configuration problems.
2768
*/
2769
char *zNow = cgi_iso8601_datestamp();
2770
cgi_load_environment();
2771
g.fDebug = fossil_fopen(blob_str(&value), "ab");
2772
blob_reset(&value);
2773
cgi_debug("-------- BEGIN cgi at %s --------\n", zNow);
2774
fossil_free(zNow);
2775
cgi_print_all(1,2,0);
2776
continue;
2777
}
2778
}
2779
blob_reset(&config);
2780
if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){
2781
cgi_panic("Unable to find or open the project repository");
2782
}
2783
cgi_init();
2784
if( nRedirect ){
2785
redirect_web_page(nRedirect, azRedirect);
2786
}else{
2787
process_one_web_page(zNotFound, pFileGlob, allowRepoList);
2788
}
2789
}
2790
2791
/*
2792
** If g.argv[arg] exists then it is either the name of a repository
2793
** that will be used by a server, or else it is a directory that
2794
** contains multiple repositories that can be served. If g.argv[arg]
2795
** is a directory, the repositories it contains must be named
2796
** "*.fossil". If g.argv[arg] does not exist, then we must be within
2797
** an open check-out and the repository to serve is the repository of
2798
** that check-out.
2799
**
2800
** Open the repository to be served if it is known. If g.argv[arg] is
2801
** a directory full of repositories, then set g.zRepositoryName to
2802
** the name of that directory and the specific repository will be
2803
** opened later by process_one_web_page() based on the content of
2804
** the PATH_INFO variable.
2805
**
2806
** If the fCreate flag is set, then create the repository if it
2807
** does not already exist. Always use "auto" hash-policy in this case.
2808
*/
2809
static void find_server_repository(int arg, int fCreate){
2810
if( g.argc<=arg ){
2811
db_must_be_within_tree();
2812
}else{
2813
const char *zRepo = g.argv[arg];
2814
int isDir = file_isdir(zRepo, ExtFILE);
2815
if( isDir==1 ){
2816
g.zRepositoryName = fossil_strdup(zRepo);
2817
file_simplify_name(g.zRepositoryName, -1, 0);
2818
}else{
2819
if( isDir==0 && fCreate ){
2820
const char *zPassword;
2821
db_create_repository(zRepo);
2822
db_open_repository(zRepo);
2823
db_begin_transaction();
2824
g.eHashPolicy = HPOLICY_SHA3;
2825
db_set_int("hash-policy", HPOLICY_SHA3, 0);
2826
db_initial_setup(0, "now", g.zLogin);
2827
db_end_transaction(0);
2828
fossil_print("project-id: %s\n", db_get("project-code", 0));
2829
fossil_print("server-id: %s\n", db_get("server-code", 0));
2830
zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
2831
fossil_print("admin-user: %s (initial password is \"%s\")\n",
2832
g.zLogin, zPassword);
2833
hash_user_password(g.zLogin);
2834
cache_initialize();
2835
g.zLogin = 0;
2836
g.userUid = 0;
2837
}else{
2838
db_open_repository(zRepo);
2839
}
2840
}
2841
}
2842
}
2843
2844
#if USE_SEE
2845
/*
2846
** This function attempts to parse a string value in the following
2847
** format:
2848
**
2849
** "%lu:%p:%u"
2850
**
2851
** There are three parts, which must be delimited by colons. The
2852
** first part is an unsigned long integer in base-10 (decimal) format.
2853
** The second part is a numerical representation of a native pointer,
2854
** in the appropriate implementation defined format. The third part
2855
** is an unsigned integer in base-10 (decimal) format.
2856
**
2857
** If the specified value cannot be parsed, for any reason, a fatal
2858
** error will be raised and the process will be terminated.
2859
*/
2860
void parse_pid_key_value(
2861
const char *zPidKey, /* The value to be parsed. */
2862
PID_T *pProcessId, /* The extracted process identifier. */
2863
LPVOID *ppAddress, /* The extracted pointer value. */
2864
SIZE_T *pnSize /* The extracted size value. */
2865
){
2866
unsigned long processId = 0;
2867
unsigned int nSize = 0;
2868
if( sscanf(zPidKey, "%lu:%p:%u", &processId, ppAddress, &nSize)==3 ){
2869
*pProcessId = (PID_T)processId;
2870
*pnSize = (SIZE_T)nSize;
2871
}else{
2872
fossil_fatal("failed to parse pid key");
2873
}
2874
}
2875
#endif
2876
2877
/*
2878
** WEBPAGE: test-pid
2879
**
2880
** Return the process identifier of the running Fossil server instance.
2881
**
2882
** Query parameters:
2883
**
2884
** usepidkey When present and available, also return the
2885
** address and size, within this server process,
2886
** of the saved database encryption key. This
2887
** is only supported when using SEE on Windows
2888
** or Linux.
2889
*/
2890
void test_pid_page(void){
2891
login_check_credentials();
2892
if( !g.perm.Setup ){ login_needed(0); return; }
2893
#if USE_SEE
2894
if( P("usepidkey")!=0 ){
2895
if( g.zPidKey ){
2896
@ %s(g.zPidKey)
2897
return;
2898
}else{
2899
const char *zSavedKey = db_get_saved_encryption_key();
2900
size_t savedKeySize = db_get_saved_encryption_key_size();
2901
if( zSavedKey!=0 && savedKeySize>0 ){
2902
@ %lu(GETPID()):%p(zSavedKey):%u(savedKeySize)
2903
return;
2904
}
2905
}
2906
}
2907
#endif
2908
@ %d(GETPID())
2909
}
2910
2911
/*
2912
** Check for options to "fossil server" or "fossil ui" that imply that
2913
** SSL should be used, and initialize the SSL decoder.
2914
*/
2915
static void decode_ssl_options(void){
2916
#if FOSSIL_ENABLE_SSL
2917
const char *zCertFile = 0;
2918
const char *zKeyFile = 0;
2919
zCertFile = find_option("cert",0,1);
2920
zKeyFile = find_option("pkey",0,1);
2921
if( zCertFile ){
2922
g.httpUseSSL = 1;
2923
ssl_init_server(zCertFile, zKeyFile);
2924
}else if( zKeyFile ){
2925
fossil_fatal("--pkey without a corresponding --cert");
2926
}
2927
#endif
2928
}
2929
2930
/*
2931
** COMMAND: http*
2932
**
2933
** Usage: %fossil http ?REPOSITORY? ?OPTIONS?
2934
**
2935
** Handle a single HTTP request appearing on stdin. The resulting webpage
2936
** is delivered on stdout. This method is used to launch an HTTP request
2937
** handler from inetd, for example. The REPOSITORY argument is the name of
2938
** the repository.
2939
**
2940
** If REPOSITORY is a directory that contains one or more repositories,
2941
** either directly in REPOSITORY itself or in subdirectories, and
2942
** with names of the form "*.fossil" then a prefix of the URL pathname
2943
** selects from among the various repositories. If the pathname does
2944
** not select a valid repository and the --notfound option is available,
2945
** then the server redirects (HTTP code 302) to the URL of --notfound.
2946
** When REPOSITORY is a directory, the pathname must contain only
2947
** alphanumerics, "_", "/", "-" and "." and no "-" may occur after a "/"
2948
** and every "." must be surrounded on both sides by alphanumerics or else
2949
** a 404 error is returned. Static content files in the directory are
2950
** returned if they match comma-separated GLOB pattern specified by --files
2951
** and do not match "*.fossil*" and have a well-known suffix.
2952
**
2953
** Options:
2954
** --acme Deliver files from the ".well-known" subdirectory
2955
** --baseurl URL Base URL (useful with reverse proxies)
2956
** --cert FILE Use TLS (HTTPS) encryption with the certificate (the
2957
** fullchain.pem) taken from FILE.
2958
** --chroot DIR Use directory for chroot instead of repository path.
2959
** --ckout-alias N Treat URIs of the form /doc/N/... as if they were
2960
** /doc/ckout/...
2961
** --extroot DIR Document root for the /ext extension mechanism
2962
** --files GLOB Comma-separated glob patterns for static files to serve
2963
** --host NAME DNS Hostname of the server
2964
** --https The HTTP request originated from https but has already
2965
** been decoded by a reverse proxy. Hence, URLs created
2966
** by Fossil should use "https:" rather than "http:".
2967
** --in FILE Take input from FILE instead of standard input
2968
** --ipaddr ADDR Assume the request comes from the given IP address
2969
** --jsmode MODE Determine how JavaScript is delivered with pages.
2970
** Mode can be one of:
2971
** inline All JavaScript is inserted inline at
2972
** one or more points in the HTML file.
2973
** separate Separate HTTP requests are made for
2974
** each JavaScript file.
2975
** bundled Groups JavaScript files into one or
2976
** more bundled requests which
2977
** concatenate scripts together.
2978
** Depending on the needs of any given page, inline
2979
** and bundled modes might result in a single
2980
** amalgamated script or several, but both approaches
2981
** result in fewer HTTP requests than the separate mode.
2982
** --localauth Connections from localhost are given "setup"
2983
** privileges without having to log in
2984
** --mainmenu FILE Override the mainmenu config setting with the contents
2985
** of the given file
2986
** --nocompress Do not compress HTTP replies
2987
** --nodelay Omit backoffice processing if it would delay
2988
** process exit
2989
** --nojail Drop root privilege but do not enter the chroot jail
2990
** --nossl Do not do http: to https: redirects, regardless of
2991
** the redirect-to-https setting.
2992
** --notfound URL Use URL as the "HTTP 404, object not found" page
2993
** --out FILE Write the HTTP reply to FILE instead of to
2994
** standard output
2995
** --pkey FILE Read the private key used for TLS from FILE
2996
** --repolist If REPOSITORY is directory, URL "/" lists all repos
2997
** --scgi Interpret input as SCGI rather than HTTP
2998
** --skin LABEL Use override skin LABEL. Use an empty string ("")
2999
** to force use of the current local skin config.
3000
** --th-trace Trace TH1 execution (for debugging purposes)
3001
** --usepidkey Use saved encryption key from parent process. This is
3002
** only necessary when using SEE on Windows or Linux.
3003
**
3004
** See also: [[cgi]], [[server]], [[winsrv]] [Windows only]
3005
*/
3006
void cmd_http(void){
3007
const char *zIpAddr = 0;
3008
const char *zNotFound;
3009
const char *zHost;
3010
const char *zAltBase;
3011
const char *zFileGlob;
3012
const char *zInFile;
3013
const char *zOutFile;
3014
const char *zChRoot;
3015
int useSCGI;
3016
int noJail;
3017
int allowRepoList;
3018
3019
Th_InitTraceLog();
3020
builtin_set_js_delivery_mode(find_option("jsmode",0,1),0);
3021
3022
/* The winhttp module passes the --files option as --files-urlenc with
3023
** the argument being URL encoded, to avoid wildcard expansion in the
3024
** shell. This option is for internal use and is undocumented.
3025
*/
3026
zFileGlob = find_option("files-urlenc",0,1);
3027
if( zFileGlob ){
3028
char *z = fossil_strdup(zFileGlob);
3029
dehttpize(z);
3030
zFileGlob = z;
3031
}else{
3032
zFileGlob = find_option("files",0,1);
3033
}
3034
skin_override();
3035
zNotFound = find_option("notfound", 0, 1);
3036
zChRoot = find_option("chroot",0,1);
3037
noJail = find_option("nojail",0,0)!=0;
3038
allowRepoList = find_option("repolist",0,0)!=0;
3039
g.useLocalauth = find_option("localauth", 0, 0)!=0;
3040
g.sslNotAvailable = find_option("nossl", 0, 0)!=0;
3041
g.fNoHttpCompress = find_option("nocompress",0,0)!=0;
3042
g.zExtRoot = find_option("extroot",0,1);
3043
g.zCkoutAlias = find_option("ckout-alias",0,1);
3044
g.zReqType = "HTTP";
3045
zInFile = find_option("in",0,1);
3046
if( zInFile ){
3047
backoffice_disable();
3048
g.httpIn = fossil_fopen(zInFile, "rb");
3049
if( g.httpIn==0 ) fossil_fatal("cannot open \"%s\" for reading", zInFile);
3050
}else{
3051
g.httpIn = stdin;
3052
#if defined(_WIN32)
3053
_setmode(_fileno(stdin), _O_BINARY);
3054
#endif
3055
}
3056
zOutFile = find_option("out",0,1);
3057
if( zOutFile ){
3058
g.httpOut = fossil_fopen(zOutFile, "wb");
3059
if( g.httpOut==0 ) fossil_fatal("cannot open \"%s\" for writing", zOutFile);
3060
}else{
3061
g.httpOut = stdout;
3062
#if defined(_WIN32)
3063
_setmode(_fileno(stdout), _O_BINARY);
3064
#endif
3065
}
3066
zIpAddr = find_option("ipaddr",0,1);
3067
#if defined(_WIN32)
3068
/* The undocumented option "--as NAME" causes NAME to become
3069
** the fake command name. This only happens on Windows and only
3070
** if preceded by --in, --out, and --ipaddr. It is a work-around
3071
** to get the original command-name down into the "http" command that
3072
** is run in a subprocess to manage HTTP requests on Windows for
3073
** commands like "fossil ui" and "fossil server".
3074
*/
3075
if( zInFile && zOutFile && zIpAddr ){
3076
const char *z = find_option("as",0,1);
3077
if( z ) g.zCmdName = z;
3078
}
3079
#endif
3080
useSCGI = find_option("scgi", 0, 0)!=0;
3081
if( useSCGI ) g.zReqType = "SCGI";
3082
zAltBase = find_option("baseurl", 0, 1);
3083
if( find_option("nodelay",0,0)!=0 ) backoffice_no_delay();
3084
if( zAltBase ) set_base_url(zAltBase);
3085
if( find_option("https",0,0)!=0 ){
3086
zIpAddr = fossil_getenv("REMOTE_HOST"); /* From stunnel */
3087
cgi_replace_parameter("HTTPS","on");
3088
}
3089
zHost = find_option("host", 0, 1);
3090
if( zHost ) cgi_replace_parameter("HTTP_HOST",zHost);
3091
g.zMainMenuFile = find_option("mainmenu",0,1);
3092
if( g.zMainMenuFile!=0 && file_size(g.zMainMenuFile,ExtFILE)<0 ){
3093
fossil_fatal("Cannot read --mainmenu file %s", g.zMainMenuFile);
3094
}
3095
decode_ssl_options();
3096
if( find_option("acme",0,0)!=0 ) g.fAllowACME = 1;
3097
3098
/* We should be done with options.. */
3099
verify_all_options();
3100
if( g.httpUseSSL ){
3101
if( useSCGI ){
3102
fossil_fatal("SSL not (yet) supported for SCGI");
3103
}
3104
if( g.fSshClient & CGI_SSH_CLIENT ){
3105
fossil_fatal("SSL not compatible with SSH");
3106
}
3107
if( zInFile || zOutFile ){
3108
fossil_fatal("SSL usable only on a socket");
3109
}
3110
cgi_replace_parameter("HTTPS","on");
3111
}
3112
3113
if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
3114
g.cgiOutput = 1;
3115
g.fullHttpReply = 1;
3116
find_server_repository(2, 0);
3117
if( zIpAddr==0 ){
3118
zIpAddr = cgi_ssh_remote_addr(0);
3119
if( zIpAddr && zIpAddr[0] ){
3120
g.fSshClient |= CGI_SSH_CLIENT;
3121
}
3122
}
3123
g.zRepositoryName = enter_chroot_jail(
3124
zChRoot ? zChRoot : g.zRepositoryName, noJail);
3125
if( useSCGI ){
3126
cgi_handle_scgi_request();
3127
}else if( g.fSshClient & CGI_SSH_CLIENT ){
3128
ssh_request_loop(zIpAddr, glob_create(zFileGlob));
3129
}else{
3130
#if FOSSIL_ENABLE_SSL
3131
if( g.httpUseSSL ){
3132
g.httpSSLConn = ssl_new_server(0);
3133
}
3134
#endif
3135
cgi_handle_http_request(zIpAddr);
3136
}
3137
process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList);
3138
#if FOSSIL_ENABLE_SSL
3139
if( g.httpUseSSL && g.httpSSLConn ){
3140
ssl_close_server(g.httpSSLConn);
3141
g.httpSSLConn = 0;
3142
}
3143
#endif /* FOSSIL_ENABLE_SSL */
3144
}
3145
3146
/*
3147
** Process all requests in a single SSH connection if possible.
3148
*/
3149
void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){
3150
blob_zero(&g.cgiIn);
3151
do{
3152
cgi_handle_ssh_http_request(zIpAddr);
3153
process_one_web_page(0, FileGlob, 0);
3154
blob_reset(&g.cgiIn);
3155
} while ( g.fSshClient & CGI_SSH_FOSSIL ||
3156
g.fSshClient & CGI_SSH_COMPAT );
3157
}
3158
3159
/*
3160
** COMMAND: test-http
3161
**
3162
** Works like the [[http]] command but gives setup permission to all users,
3163
** or whatever permission is described by "--usercap CAP".
3164
**
3165
** This command can used for interactive debugging of web pages. For
3166
** example, one can put a simple HTTP request in a file like this:
3167
**
3168
** echo 'GET /timeline' >request.txt
3169
**
3170
** Then run (in a debugger) a command like this:
3171
**
3172
** fossil test-http <request.txt
3173
**
3174
** This command is also used internally by the "ssh" sync protocol. Some
3175
** special processing to support sync happens when this command is run
3176
** and the SSH_CONNECTION environment variable is set. Use the --test
3177
** option on interactive sessions to avoid that special processing when
3178
** using this command interactively over SSH. A better solution would be
3179
** to use a different command for "ssh" sync, but we cannot do that without
3180
** breaking legacy.
3181
**
3182
** Options:
3183
** --csrf-safe N Set cgi_csrf_safe() to return N
3184
** --nobody Pretend to be user "nobody"
3185
** --ssh-sim Pretend to be over an SSH connection
3186
** --test Do not do special "sync" processing when operating
3187
** over an SSH link
3188
** --th-trace Trace TH1 execution (for debugging purposes)
3189
** --usercap CAP User capability string (Default: "sxy")
3190
*/
3191
void cmd_test_http(void){
3192
const char *zIpAddr; /* IP address of remote client */
3193
const char *zUserCap;
3194
int bTest = 0;
3195
const char *zCsrfSafe = find_option("csrf-safe",0,1);
3196
3197
if( find_option("ssh-sim",0,0)!=0 ){
3198
putenv("SSH_CONNECTION=127.0.0.1 12345 127.0.0.2 23456");
3199
}
3200
Th_InitTraceLog();
3201
if( zCsrfSafe ) g.okCsrf = atoi(zCsrfSafe);
3202
zUserCap = find_option("usercap",0,1);
3203
if( !find_option("nobody",0,0) ){
3204
if( zUserCap==0 ){
3205
g.useLocalauth = 1;
3206
zUserCap = "sxy";
3207
}
3208
login_set_capabilities(zUserCap, 0);
3209
}
3210
bTest = find_option("test",0,0)!=0;
3211
g.httpIn = stdin;
3212
g.httpOut = stdout;
3213
fossil_binary_mode(g.httpOut);
3214
fossil_binary_mode(g.httpIn);
3215
g.zExtRoot = find_option("extroot",0,1);
3216
find_server_repository(2, 0);
3217
g.zReqType = "HTTP";
3218
g.cgiOutput = 1;
3219
g.fNoHttpCompress = 1;
3220
g.fullHttpReply = 1;
3221
g.sslNotAvailable = 1; /* Avoid attempts to redirect */
3222
zIpAddr = bTest ? 0 : cgi_ssh_remote_addr(0);
3223
if( zIpAddr && zIpAddr[0] ){
3224
g.fSshClient |= CGI_SSH_CLIENT;
3225
ssh_request_loop(zIpAddr, 0);
3226
}else{
3227
cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
3228
cgi_handle_http_request(0);
3229
process_one_web_page(0, 0, 1);
3230
}
3231
}
3232
3233
/*
3234
** Respond to a SIGALRM by writing a message to the error log (if there
3235
** is one) and exiting.
3236
*/
3237
#ifndef _WIN32
3238
static int nAlarmSeconds = 0;
3239
static void sigalrm_handler(int x){
3240
sqlite3_uint64 tmUser = 0, tmKernel = 0;
3241
fossil_cpu_times(&tmUser, &tmKernel);
3242
if( fossil_strcmp(g.zPhase, "web-page reply")==0
3243
&& tmUser+tmKernel<10000000
3244
){
3245
/* Do not log time-outs during web-page reply unless more than
3246
** 10 seconds of CPU time has been consumed */
3247
return;
3248
}
3249
fossil_panic("Timeout after %d seconds during %s"
3250
" - user %,llu µs, sys %,llu µs",
3251
nAlarmSeconds, g.zPhase, tmUser, tmKernel);
3252
}
3253
#endif
3254
3255
/*
3256
** Arrange to timeout using SIGALRM after N seconds. Or if N==0, cancel
3257
** any pending timeout.
3258
**
3259
** Bugs:
3260
** (1) This only works on unix systems.
3261
** (2) Any call to sleep() or sqlite3_sleep() will cancel the alarm.
3262
*/
3263
void fossil_set_timeout(int N){
3264
#ifndef _WIN32
3265
signal(SIGALRM, sigalrm_handler);
3266
alarm(N);
3267
nAlarmSeconds = N;
3268
#endif
3269
}
3270
3271
/*
3272
** COMMAND: server*
3273
** COMMAND: ui
3274
**
3275
** Usage: %fossil server ?OPTIONS? ?REPOSITORY?
3276
** or: %fossil ui ?OPTIONS? ?REPOSITORY?
3277
**
3278
** Open a socket and begin listening and responding to HTTP requests on
3279
** TCP port 8080, or on any other TCP port defined by the -P or
3280
** --port option. The optional REPOSITORY argument is the name of the
3281
** Fossil repository to be served. The REPOSITORY argument may be omitted
3282
** if the working directory is within an open check-out, in which case the
3283
** repository associated with that check-out is used.
3284
**
3285
** The "ui" command automatically starts a web browser after initializing
3286
** the web server. The "ui" command also binds to 127.0.0.1 and so will
3287
** only process HTTP traffic from the local machine.
3288
**
3289
** If REPOSITORY is a directory name which is the root of a
3290
** check-out, then use the repository associated with that check-out.
3291
** This only works for the "fossil ui" command, not the "fossil server"
3292
** command.
3293
**
3294
** If REPOSITORY begins with a "HOST:" or "USER@HOST:" prefix, then
3295
** the command is run on the remote host specified and the results are
3296
** tunneled back to the local machine via SSH. This feature only works for
3297
** the "fossil ui" command, not the "fossil server" command. The name of the
3298
** fossil executable on the remote host is specified by the --fossilcmd
3299
** option, or if there is no --fossilcmd, it first tries "fossil" and if it
3300
** is not found in the default $PATH set by SSH on the remote, it then adds
3301
** "$HOME/bin:/usr/local/bin:/opt/homebrew/bin" to the PATH and tries again to
3302
** run "fossil".
3303
**
3304
** REPOSITORY may also be a directory (aka folder) that contains one or
3305
** more repositories with names ending in ".fossil". In this case, a
3306
** prefix of the URL pathname is used to search the directory for an
3307
** appropriate repository. To thwart mischief, the pathname in the URL must
3308
** contain only alphanumerics, "_", "/", "-", and ".", and no "-" may
3309
** occur after "/", and every "." must be surrounded on both sides by
3310
** alphanumerics. Any pathname that does not satisfy these constraints
3311
** results in a 404 error. Files in REPOSITORY that match the comma-separated
3312
** list of glob patterns given by --files and that have known suffixes
3313
** such as ".txt" or ".html" or ".jpeg" and do not match the pattern
3314
** "*.fossil*" will be served as static content. With the "ui" command,
3315
** the REPOSITORY can only be a directory if the --notfound option is
3316
** also present.
3317
**
3318
** For the special case REPOSITORY name of "/", the global configuration
3319
** database is consulted for a list of all known repositories. The --repolist
3320
** option is implied by this special case. The "fossil ui /" command is
3321
** equivalent to "fossil all ui". To see all repositories owned by "user"
3322
** on machine "remote" via ssh, run "fossil ui user@remote:/".
3323
**
3324
** By default, the "ui" command provides full administrative access without
3325
** having to log in. This can be disabled by turning off the "localauth"
3326
** setting. Automatic login for the "server" command is available if the
3327
** --localauth option is present and the "localauth" setting is off and the
3328
** connection is from localhost. The "ui" command also enables --repolist
3329
** by default.
3330
**
3331
** Options:
3332
** --acme Deliver files from the ".well-known" subdirectory
3333
** --baseurl URL Use URL as the base (useful for reverse proxies)
3334
** --cert FILE Use TLS (HTTPS) encryption with the certificate (the
3335
** fullchain.pem) taken from FILE.
3336
** --chroot DIR Use directory for chroot instead of repository path
3337
** --ckout-alias NAME Treat URIs of the form /doc/NAME/... as if they were
3338
** /doc/ckout/...
3339
** --create Create a new REPOSITORY if it does not already exist
3340
** --errorlog FILE Append HTTP error messages to FILE
3341
** --extpage FILE Shortcut for "--extroot DIR --page ext/TAIL" where
3342
** DIR is the directory holding FILE and TAIL is the
3343
** filename at the end of FILE. Only works for "ui".
3344
** --extroot DIR Document root for the /ext extension mechanism
3345
** --files GLOBLIST Comma-separated list of glob patterns for static files
3346
** --fossilcmd PATH The pathname of the "fossil" executable on the remote
3347
** system when REPOSITORY is remote.
3348
** --from PATH Use PATH as the diff baseline for the /ckout page
3349
** --localauth Enable automatic login for requests from localhost
3350
** --localhost Listen on 127.0.0.1 only (always true for "ui")
3351
** --https Indicates that the input is coming through a reverse
3352
** proxy that has already translated HTTPS into HTTP.
3353
** --jsmode MODE Determine how JavaScript is delivered with pages.
3354
** Mode can be one of:
3355
** inline All JavaScript is inserted inline at
3356
** the end of the HTML file.
3357
** separate Separate HTTP requests are made for
3358
** each JavaScript file.
3359
** bundled One single separate HTTP fetches all
3360
** JavaScript concatenated together.
3361
** Depending on the needs of any given page, inline
3362
** and bundled modes might result in a single
3363
** amalgamated script or several, but both approaches
3364
** result in fewer HTTP requests than the separate mode.
3365
** --mainmenu FILE Override the mainmenu config setting with the contents
3366
** of the given file
3367
** --max-latency N Do not let any single HTTP request run for more than N
3368
** seconds (only works on unix)
3369
** -B|--nobrowser Do not automatically launch a web-browser for the
3370
** "fossil ui" command
3371
** --nocompress Do not compress HTTP replies
3372
** --nojail Drop root privileges but do not enter the chroot jail
3373
** --nossl Do not force redirects to SSL even if the repository
3374
** setting "redirect-to-https" requests it. This is set
3375
** by default for the "ui" command.
3376
** --notfound URL Redirect to URL if a page is not found.
3377
** -p|--page PAGE Start "ui" on PAGE. ex: --page "timeline?y=ci"
3378
** --pkey FILE Read the private key used for TLS from FILE
3379
** -P|--port [IP:]PORT Listen on the given IP (optional) and port
3380
** --repolist If REPOSITORY is dir, URL "/" lists repos
3381
** --scgi Accept SCGI rather than HTTP
3382
** --skin LABEL Use override skin LABEL, or the site's default skin if
3383
** LABEL is an empty string.
3384
** --socket-mode MODE File permissions to set for the unix socket created
3385
** by the --socket-name option.
3386
** --socket-name NAME Use a unix-domain socket called NAME instead of a
3387
** TCP/IP socket.
3388
** --socket-owner USR Try to set the owner of the unix socket to USR.
3389
** USR can be of the form USER:GROUP to set both
3390
** user and group.
3391
** --th-trace Trace TH1 execution (for debugging purposes)
3392
** --usepidkey Use saved encryption key from parent process. This is
3393
** only necessary when using SEE on Windows or Linux.
3394
**
3395
** See also: [[cgi]], [[http]], [[winsrv]] [Windows only]
3396
*/
3397
void cmd_webserver(void){
3398
int iPort, mxPort; /* Range of TCP ports allowed */
3399
const char *zPort; /* Value of the --port option */
3400
const char *zBrowser; /* Name of web browser program */
3401
char *zBrowserCmd = 0; /* Command to launch the web browser */
3402
int isUiCmd; /* True if command is "ui", not "server' */
3403
const char *zNotFound; /* The --notfound option or NULL */
3404
int flags = 0; /* Server flags */
3405
#if !defined(_WIN32)
3406
const char *zChRoot; /* Use for chroot instead of repository path */
3407
int noJail; /* Do not enter the chroot jail */
3408
const char *zTimeout = 0; /* Max runtime of any single HTTP request */
3409
#endif
3410
int allowRepoList; /* List repositories on URL "/" */
3411
const char *zAltBase; /* Argument to the --baseurl option */
3412
const char *zFileGlob; /* Static content must match this */
3413
char *zIpAddr = 0; /* Bind to this IP address or UN socket */
3414
int fCreate = 0; /* The --create flag */
3415
int fNoBrowser = 0; /* Do not auto-launch web-browser */
3416
const char *zInitPage = 0; /* Start on this page. --page option */
3417
int findServerArg = 2; /* argv index for find_server_repository() */
3418
char *zRemote = 0; /* Remote host on which to run "fossil ui" */
3419
const char *zJsMode; /* The --jsmode parameter */
3420
const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */
3421
const char *zFrom; /* Value for --from */
3422
const char *zExtPage = 0; /* Argument to --extpage */
3423
3424
3425
#if USE_SEE
3426
db_setup_for_saved_encryption_key();
3427
#endif
3428
3429
#if defined(_WIN32)
3430
const char *zStopperFile; /* Name of file used to terminate server */
3431
zStopperFile = find_option("stopper", 0, 1);
3432
#endif
3433
3434
if( g.zErrlog==0 ){
3435
g.zErrlog = "-";
3436
}
3437
g.zExtRoot = find_option("extroot",0,1);
3438
zJsMode = find_option("jsmode",0,1);
3439
builtin_set_js_delivery_mode(zJsMode,0);
3440
zFileGlob = find_option("files-urlenc",0,1);
3441
if( zFileGlob ){
3442
char *z = fossil_strdup(zFileGlob);
3443
dehttpize(z);
3444
zFileGlob = z;
3445
}else{
3446
zFileGlob = find_option("files",0,1);
3447
}
3448
skin_override();
3449
#if !defined(_WIN32)
3450
zChRoot = find_option("chroot",0,1);
3451
noJail = find_option("nojail",0,0)!=0;
3452
zTimeout = find_option("max-latency",0,1);
3453
#endif
3454
g.useLocalauth = find_option("localauth", 0, 0)!=0;
3455
Th_InitTraceLog();
3456
zPort = find_option("port", "P", 1);
3457
isUiCmd = g.argv[1][0]=='u';
3458
if( isUiCmd ){
3459
zFrom = find_option("from", 0, 1);
3460
if( zFrom && zFrom==file_tail(zFrom) ){
3461
fossil_fatal("the argument to --from must be a pathname for"
3462
" the \"ui\" command");
3463
}
3464
zExtPage = find_option("extpage",0,1);
3465
if( zExtPage ){
3466
char *zFullPath = file_canonical_name_dup(zExtPage);
3467
g.zExtRoot = file_dirname(zFullPath);
3468
zInitPage = mprintf("ext/%s",file_tail(zFullPath));
3469
fossil_free(zFullPath);
3470
}else{
3471
zInitPage = find_option("page", "p", 1);
3472
if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
3473
}
3474
zFossilCmd = find_option("fossilcmd", 0, 1);
3475
if( zFrom && zInitPage==0 ){
3476
zInitPage = mprintf("ckout?exbase=%H", zFrom);
3477
}
3478
}
3479
zNotFound = find_option("notfound", 0, 1);
3480
allowRepoList = find_option("repolist",0,0)!=0;
3481
if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1;
3482
zAltBase = find_option("baseurl", 0, 1);
3483
fCreate = find_option("create",0,0)!=0;
3484
g.zReqType = "HTTP";
3485
if( find_option("scgi", 0, 0)!=0 ){
3486
g.zReqType = "SCGI";
3487
flags |= HTTP_SERVER_SCGI;
3488
}
3489
if( zAltBase ){
3490
set_base_url(zAltBase);
3491
}
3492
g.sslNotAvailable = find_option("nossl", 0, 0)!=0 || isUiCmd;
3493
fNoBrowser = find_option("nobrowser", "B", 0)!=0;
3494
decode_ssl_options();
3495
if( find_option("https",0,0)!=0 || g.httpUseSSL ){
3496
cgi_replace_parameter("HTTPS","on");
3497
}
3498
if( find_option("localhost", 0, 0)!=0 ){
3499
flags |= HTTP_SERVER_LOCALHOST;
3500
}
3501
g.zCkoutAlias = find_option("ckout-alias",0,1);
3502
g.zMainMenuFile = find_option("mainmenu",0,1);
3503
if( g.zMainMenuFile!=0 && file_size(g.zMainMenuFile,ExtFILE)<0 ){
3504
fossil_fatal("Cannot read --mainmenu file %s", g.zMainMenuFile);
3505
}
3506
if( find_option("acme",0,0)!=0 ) g.fAllowACME = 1;
3507
g.zSockMode = find_option("socket-mode",0,1);
3508
g.zSockName = find_option("socket-name",0,1);
3509
g.zSockOwner = find_option("socket-owner",0,1);
3510
if( g.zSockName ){
3511
#if defined(_WIN32)
3512
fossil_fatal("unix sockets are not supported on Windows");
3513
#endif
3514
if( zPort ){
3515
fossil_fatal("cannot specify a port number for a unix socket");
3516
}
3517
if( isUiCmd && !fNoBrowser ){
3518
fossil_fatal("cannot start a web-browser on a unix socket");
3519
}
3520
flags |= HTTP_SERVER_UNIXSOCKET;
3521
}
3522
3523
/* Undocumented option: --debug-nofork
3524
**
3525
** This sets the HTTP_SERVER_NOFORK flag, which causes only the
3526
** very first incoming TCP/IP connection to be processed. Used for
3527
** debugging, since debugging across a fork() can be tricky
3528
*/
3529
if( find_option("debug-nofork",0,0)!=0 ){
3530
flags |= HTTP_SERVER_NOFORK;
3531
#if !defined(_WIN32)
3532
/* Disable the timeout during debugging */
3533
zTimeout = "100000000";
3534
#endif
3535
}
3536
/* We should be done with options.. */
3537
verify_all_options();
3538
3539
if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
3540
if( g.httpUseSSL && (flags & HTTP_SERVER_SCGI)!=0 ){
3541
fossil_fatal("SCGI does not (yet) support TLS-encrypted connections");
3542
}
3543
if( isUiCmd && 3==g.argc && file_isdir(g.argv[2], ExtFILE)>0 ){
3544
/* If REPOSITORY arg is the root of a check-out,
3545
** chdir to that check-out so that the current version
3546
** gets highlighted in the timeline by default. */
3547
const char * zDir = g.argv[2];
3548
if(dir_has_ckout_db(zDir)){
3549
if(0!=file_chdir(zDir, 0)){
3550
fossil_fatal("Cannot chdir to %s", zDir);
3551
}
3552
findServerArg = g.argc;
3553
fCreate = 0;
3554
g.argv[2] = 0;
3555
--g.argc;
3556
}
3557
}
3558
if( isUiCmd && 3==g.argc
3559
&& (zRemote = (char*)file_skip_userhost(g.argv[2]))!=0
3560
){
3561
/* The REPOSITORY argument has a USER@HOST: or HOST: prefix */
3562
const char *zRepoTail = file_skip_userhost(g.argv[2]);
3563
unsigned x;
3564
int n;
3565
sqlite3_randomness(2,&x);
3566
zPort = mprintf("%d", 8100+(x%32000));
3567
n = (int)(zRepoTail - g.argv[2]) - 1;
3568
zRemote = mprintf("%.*s", n, g.argv[2]);
3569
g.argv[2] = (char*)zRepoTail;
3570
}
3571
if( isUiCmd ){
3572
flags |= HTTP_SERVER_LOCALHOST|HTTP_SERVER_REPOLIST;
3573
g.useLocalauth = 1;
3574
allowRepoList = 1;
3575
}
3576
if( !zRemote ){
3577
find_server_repository(findServerArg, fCreate);
3578
}
3579
if( zInitPage==0 ){
3580
zInitPage = "";
3581
}
3582
if( zPort ){
3583
if( strchr(zPort,':') ){
3584
int i;
3585
for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){}
3586
if( i>0 ){
3587
if( zPort[0]=='[' && zPort[i-1]==']' ){
3588
zIpAddr = mprintf("%.*s", i-2, zPort+1);
3589
}else{
3590
zIpAddr = mprintf("%.*s", i, zPort);
3591
}
3592
zPort += i+1;
3593
}
3594
}
3595
iPort = mxPort = atoi(zPort);
3596
if( iPort<=0 ) fossil_fatal("port number must be greater than zero");
3597
}else{
3598
iPort = db_get_int("http-port", 8080);
3599
mxPort = iPort+100;
3600
}
3601
if( isUiCmd && !fNoBrowser ){
3602
char *zBrowserArg;
3603
const char *zProtocol = g.httpUseSSL ? "https" : "http";
3604
db_open_config(0,0);
3605
zBrowser = fossil_web_browser();
3606
if( zIpAddr==0 ){
3607
zBrowserArg = mprintf("%s://localhost:%%d/%s", zProtocol, zInitPage);
3608
}else if( strchr(zIpAddr,':') ){
3609
zBrowserArg = mprintf("%s://[%s]:%%d/%s", zProtocol, zIpAddr, zInitPage);
3610
}else{
3611
zBrowserArg = mprintf("%s://%s:%%d/%s", zProtocol, zIpAddr, zInitPage);
3612
}
3613
zBrowserCmd = mprintf("%s %!$ &", zBrowser, zBrowserArg);
3614
fossil_free(zBrowserArg);
3615
}
3616
if( zRemote ){
3617
/* If a USER@HOST:REPO argument is supplied, then use SSH to run
3618
** "fossil ui --nobrowser" on the remote system and to set up a
3619
** tunnel from the local machine to the remote. */
3620
FILE *sshIn;
3621
Blob ssh;
3622
int bRunning = 0; /* True when fossil starts up on the remote */
3623
int isRetry; /* True if on the second attempt */
3624
char zLine[1000];
3625
3626
blob_init(&ssh, 0, 0);
3627
for(isRetry=0; isRetry<2 && !bRunning; isRetry++){
3628
blob_reset(&ssh);
3629
transport_ssh_command(&ssh);
3630
blob_appendf(&ssh,
3631
" -t -L 127.0.0.1:%d:127.0.0.1:%d %!$",
3632
iPort, iPort, zRemote
3633
);
3634
if( zFossilCmd==0 ){
3635
if( ssh_needs_path_argument(zRemote,-1) ^ isRetry ){
3636
ssh_add_path_argument(&ssh);
3637
}
3638
blob_append_escaped_arg(&ssh, "fossil", 1);
3639
}else{
3640
blob_appendf(&ssh, " %$", zFossilCmd);
3641
}
3642
blob_appendf(&ssh, " ui --nobrowser --localauth --port 127.0.0.1:%d",
3643
iPort);
3644
if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound);
3645
if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob);
3646
if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias);
3647
if( zExtPage ){
3648
if( !file_is_absolute_path(zExtPage) ){
3649
zExtPage = mprintf("%s/%s", g.argv[2], zExtPage);
3650
}
3651
blob_appendf(&ssh, " --extpage %$", zExtPage);
3652
}else if( g.zExtRoot ){
3653
blob_appendf(&ssh, " --extroot %$", g.zExtRoot);
3654
}
3655
if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use());
3656
if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode);
3657
if( fCreate ) blob_appendf(&ssh, " --create");
3658
blob_appendf(&ssh, " %$", g.argv[2]);
3659
if( isRetry ){
3660
fossil_print("First attempt to run \"fossil\" on %s failed\n"
3661
"Retry: ", zRemote);
3662
}
3663
fossil_print("%s\n", blob_str(&ssh));
3664
sshIn = popen(blob_str(&ssh), "r");
3665
if( sshIn==0 ){
3666
fossil_fatal("unable to %s", blob_str(&ssh));
3667
}
3668
while( fgets(zLine, sizeof(zLine), sshIn) ){
3669
fputs(zLine, stdout);
3670
fflush(stdout);
3671
if( !bRunning && sqlite3_strglob("*Listening for HTTP*",zLine)==0 ){
3672
bRunning = 1;
3673
if( isRetry ){
3674
ssh_needs_path_argument(zRemote,99);
3675
}
3676
db_close_config();
3677
if( zBrowserCmd ){
3678
char *zCmd = mprintf(zBrowserCmd/*works-like:"%d"*/,iPort);
3679
fossil_system(zCmd);
3680
fossil_free(zCmd);
3681
fossil_free(zBrowserCmd);
3682
zBrowserCmd = 0;
3683
}
3684
}
3685
}
3686
pclose(sshIn);
3687
}
3688
fossil_free(zBrowserCmd);
3689
return;
3690
}
3691
if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY;
3692
if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT;
3693
db_close(1);
3694
#if !defined(_WIN32)
3695
if( 1 ){
3696
/* Modern kernels suppress SIGTERM to PID 1 to prevent root from
3697
** rebooting the system by nuking the init system. The only way
3698
** Fossil becomes that PID 1 is when it's running solo in a Linux
3699
** container or similar, so we do want to exit immediately, to
3700
** allow the container to shut down quickly.
3701
**
3702
** This has to happen ahead of the other signal() calls below.
3703
** They apply after the HTTP hit is handled, but this one needs
3704
** to be registered while we're waiting for that to occur.
3705
**/
3706
signal(SIGTERM, fossil_exit);
3707
signal(SIGINT, fossil_exit);
3708
}
3709
#endif /* !WIN32 */
3710
3711
/* Start up an HTTP server
3712
*/
3713
fossil_setenv("SERVER_SOFTWARE", "fossil version " RELEASE_VERSION
3714
" " MANIFEST_VERSION " " MANIFEST_DATE);
3715
#if !defined(_WIN32)
3716
/* Unix implementation */
3717
if( cgi_http_server(iPort, mxPort, zBrowserCmd, zIpAddr, flags) ){
3718
fossil_fatal("unable to listen on CGI socket");
3719
}
3720
/* For the parent process, the cgi_http_server() command above never
3721
** returns (except in the case of an error). Instead, for each incoming
3722
** client connection, a child process is created, file descriptors 0
3723
** and 1 are bound to that connection, and the child returns.
3724
**
3725
** So, when control reaches this point, we are running as a
3726
** child process, the HTTP or SCGI request is pending on file
3727
** descriptor 0 and the reply should be written to file descriptor 1.
3728
*/
3729
if( zTimeout ){
3730
fossil_set_timeout(atoi(zTimeout));
3731
}else{
3732
fossil_set_timeout(FOSSIL_DEFAULT_TIMEOUT);
3733
}
3734
g.httpIn = stdin;
3735
g.httpOut = stdout;
3736
signal(SIGSEGV, sigsegv_handler);
3737
signal(SIGPIPE, sigpipe_handler);
3738
if( g.fAnyTrace ){
3739
fprintf(stderr, "/***** Subprocess %d *****/\n", getpid());
3740
}
3741
g.cgiOutput = 1;
3742
find_server_repository(2, 0);
3743
if( fossil_strcmp(g.zRepositoryName,"/")==0 ){
3744
allowRepoList = 1;
3745
}else{
3746
g.zRepositoryName = enter_chroot_jail(
3747
zChRoot ? zChRoot : g.zRepositoryName, noJail);
3748
}
3749
if( flags & HTTP_SERVER_SCGI ){
3750
cgi_handle_scgi_request();
3751
}else if( g.httpUseSSL ){
3752
#if FOSSIL_ENABLE_SSL
3753
g.httpSSLConn = ssl_new_server(0);
3754
#endif
3755
cgi_handle_http_request(0);
3756
}else{
3757
cgi_handle_http_request(0);
3758
}
3759
process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList);
3760
if( g.fAnyTrace ){
3761
fprintf(stderr, "/***** Webpage finished in subprocess %d *****/\n",
3762
getpid());
3763
}
3764
#if FOSSIL_ENABLE_SSL
3765
if( g.httpUseSSL && g.httpSSLConn ){
3766
ssl_close_server(g.httpSSLConn);
3767
g.httpSSLConn = 0;
3768
}
3769
#endif /* FOSSIL_ENABLE_SSL */
3770
3771
#else /* WIN32 */
3772
/* Win32 implementation */
3773
if( fossil_strcmp(g.zRepositoryName,"/")==0 ){
3774
allowRepoList = 1;
3775
}
3776
if( allowRepoList ){
3777
flags |= HTTP_SERVER_REPOLIST;
3778
}
3779
if( win32_http_service(iPort, zAltBase, zNotFound, zFileGlob, flags) ){
3780
win32_http_server(iPort, mxPort, zBrowserCmd, zStopperFile,
3781
zAltBase, zNotFound, zFileGlob, zIpAddr, flags);
3782
}
3783
#endif
3784
}
3785
3786
/*
3787
** COMMAND: test-echo
3788
**
3789
** Usage: %fossil test-echo [--hex] ARGS...
3790
**
3791
** Echo all command-line arguments (enclosed in [...]) to the screen so that
3792
** wildcard expansion behavior of the host shell can be investigated.
3793
**
3794
** With the --hex option, show the output as hexadecimal. This can be used
3795
** to verify the fossil_path_to_utf8() routine on Windows and Mac.
3796
*/
3797
void test_echo_cmd(void){
3798
int i, j;
3799
if( find_option("hex",0,0)==0 ){
3800
fossil_print("g.nameOfExe = [%s]\n", g.nameOfExe);
3801
for(i=0; i<g.argc; i++){
3802
fossil_print("argv[%d] = [%s]\n", i, g.argv[i]);
3803
}
3804
}else{
3805
unsigned char *z, c;
3806
for(i=0; i<g.argc; i++){
3807
fossil_print("argv[%d] = [", i);
3808
z = (unsigned char*)g.argv[i];
3809
for(j=0; (c = z[j])!=0; j++){
3810
fossil_print("%02x", c);
3811
}
3812
fossil_print("]\n");
3813
}
3814
}
3815
}
3816
3817
/*
3818
** WEBPAGE: test-warning
3819
**
3820
** Test error and warning log operation. This webpage is accessible to
3821
** the administrator only.
3822
**
3823
** case=1 Issue a fossil_warning() while generating the page.
3824
** case=2 Extra db_begin_transaction()
3825
** case=3 Extra db_end_transaction()
3826
** case=4 Error during SQL processing
3827
** case=5 Call the segfault handler
3828
** case=6 Call webpage_assert()
3829
** case=7 Call webpage_error()
3830
** case=8 Simulate a timeout
3831
** case=9 Simulate a TH1 XSS vulnerability
3832
** case=10 Simulate a TH1 SQL-injection vulnerability
3833
*/
3834
void test_warning_page(void){
3835
int iCase = atoi(PD("case","0"));
3836
int i;
3837
login_check_credentials();
3838
if( !g.perm.Admin ){
3839
login_needed(0);
3840
return;
3841
}
3842
style_set_current_feature("test");
3843
style_header("Warning Test Page");
3844
style_submenu_element("Error Log","%R/errorlog");
3845
@ <p>This page will generate various kinds of errors to test Fossil's
3846
@ reaction. Depending on settings, a message might be written
3847
@ into the <a href="%R/errorlog">error log</a>. Click on
3848
@ one of the following hyperlinks to generate a simulated error:
3849
for(i=1; i<=10; i++){
3850
@ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
3851
}
3852
@ </p>
3853
@ <p><ol>
3854
@ <li value='1'> Call fossil_warning()
3855
if( iCase==1 ){
3856
fossil_warning("Test warning message from /test-warning");
3857
}
3858
@ <li value='2'> Call db_begin_transaction()
3859
if( iCase==2 ){
3860
db_begin_transaction();
3861
}
3862
@ <li value='3'> Call db_end_transaction()
3863
if( iCase==3 ){
3864
db_end_transaction(0);
3865
}
3866
@ <li value='4'> warning during SQL
3867
if( iCase==4 ){
3868
Stmt q;
3869
db_prepare(&q, "SELECT uuid FROM blob LIMIT 5");
3870
db_step(&q);
3871
sqlite3_log(SQLITE_ERROR, "Test warning message during SQL");
3872
db_finalize(&q);
3873
}
3874
@ <li value='5'> simulate segfault handling
3875
if( iCase==5 ){
3876
sigsegv_handler(0);
3877
}
3878
@ <li value='6'> call webpage_assert(0)
3879
if( iCase==6 ){
3880
webpage_assert( 5==7 );
3881
}
3882
@ <li value='7'> call webpage_error()
3883
if( iCase==7 ){
3884
cgi_reset_content();
3885
webpage_error("Case 7 from /test-warning");
3886
}
3887
@ <li value='8'> simulated timeout
3888
if( iCase==8 ){
3889
fossil_set_timeout(1);
3890
cgi_reset_content();
3891
sqlite3_sleep(1100);
3892
}
3893
@ <li value='9'> simulated TH1 XSS vulnerability
3894
@ <li value='10'> simulated TH1 SQL-injection vulnerability
3895
if( iCase==9 || iCase==10 ){
3896
const char *zR;
3897
int n, rc;
3898
static const char *zTH1[] = {
3899
/* case 9 */ "html [taint {<b>XSS</b>}]",
3900
/* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n"
3901
" html \"<b>[htmlize $msg]</b>\"\n"
3902
"}"
3903
};
3904
rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1);
3905
zR = Th_GetResult(g.interp, &n);
3906
if( rc==TH_OK ){
3907
@ <pre class="th1result">%h(zR)</pre>
3908
}else{
3909
@ <pre class="th1error">%h(zR)</pre>
3910
}
3911
}
3912
@ </ol>
3913
@ <p>End of test</p>
3914
style_finish_page();
3915
}
3916

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button