Fossil SCM

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

Keyboard Shortcuts

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