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