|
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 file began as a set of C functions and procedures used to interpret |
|
19
|
** CGI environment variables for Fossil web pages that were invoked by |
|
20
|
** CGI. That's where the file name comes from. But over the years it |
|
21
|
** has grown to incorporate lots of related functionality, including: |
|
22
|
** |
|
23
|
** * Interpreting CGI environment variables when Fossil is run as |
|
24
|
** CGI (the original purpose). |
|
25
|
** |
|
26
|
** * Interpreting HTTP requests received directly or via an SSH tunnel. |
|
27
|
** |
|
28
|
** * Interpreting SCGI requests |
|
29
|
** |
|
30
|
** * Generating appropriate replies to CGI, SCGI, and HTTP requests. |
|
31
|
** |
|
32
|
** * Listening for incoming HTTP requests and dispatching them. |
|
33
|
** (Used by "fossil ui" and "fossil server", for example). |
|
34
|
** |
|
35
|
** So, even though the name of this file implies that it only deals with |
|
36
|
** CGI, in fact, the code in this file is used to interpret webpage requests |
|
37
|
** received by a variety of means, and to generate well-formatted replies |
|
38
|
** to those requests. |
|
39
|
** |
|
40
|
** The code in this file abstracts the web-request so that downstream |
|
41
|
** modules that generate the body of the reply (based on the requested page) |
|
42
|
** do not need to know if the request is coming from CGI, direct HTTP, |
|
43
|
** SCGI, or some other means. |
|
44
|
** |
|
45
|
** This module gathers information about web page request into a key/value |
|
46
|
** store. Keys and values come from: |
|
47
|
** |
|
48
|
** * Query parameters |
|
49
|
** * POST parameter |
|
50
|
** * Cookies |
|
51
|
** * Environment variables |
|
52
|
** |
|
53
|
** The parameters are accessed using cgi_parameter() and similar functions |
|
54
|
** or their convenience macros P() and similar. |
|
55
|
** |
|
56
|
** Environment variable parameters are set as if the request were coming |
|
57
|
** in over CGI even if the request arrived via SCGI or direct HTTP. Thus |
|
58
|
** the downstream modules that are trying to interpret the request do not |
|
59
|
** need to know the request protocol - they can just request the values |
|
60
|
** of environment variables and everything will always work. |
|
61
|
** |
|
62
|
** This file contains routines used by Fossil when it is acting as a |
|
63
|
** CGI client. For the code used by Fossil when it is acting as a |
|
64
|
** CGI server (for the /ext webpage) see the "extcgi.c" source file. |
|
65
|
*/ |
|
66
|
#include "config.h" |
|
67
|
#ifdef _WIN32 |
|
68
|
# if !defined(_WIN32_WINNT) |
|
69
|
# define _WIN32_WINNT 0x0501 |
|
70
|
# endif |
|
71
|
# include <winsock2.h> |
|
72
|
# include <ws2tcpip.h> |
|
73
|
#else |
|
74
|
# include <sys/socket.h> |
|
75
|
# include <sys/un.h> |
|
76
|
# include <netinet/in.h> |
|
77
|
# include <netdb.h> |
|
78
|
# include <arpa/inet.h> |
|
79
|
# include <sys/times.h> |
|
80
|
# include <sys/time.h> |
|
81
|
# include <sys/wait.h> |
|
82
|
# include <sys/select.h> |
|
83
|
# include <errno.h> |
|
84
|
#endif |
|
85
|
#ifdef __EMX__ |
|
86
|
typedef int socklen_t; |
|
87
|
#endif |
|
88
|
#include <time.h> |
|
89
|
#include <stdio.h> |
|
90
|
#include <stdlib.h> |
|
91
|
#include <unistd.h> |
|
92
|
#include <assert.h> |
|
93
|
#include "cgi.h" |
|
94
|
#include "cygsup.h" |
|
95
|
|
|
96
|
#if INTERFACE |
|
97
|
/* |
|
98
|
** Shortcuts for cgi_parameter. P("x") returns the value of query parameter |
|
99
|
** or cookie "x", or NULL if there is no such parameter or cookie. PD("x","y") |
|
100
|
** does the same except "y" is returned in place of NULL if there is not match. |
|
101
|
*/ |
|
102
|
#define P(x) cgi_parameter((x),0) |
|
103
|
#define PD(x,y) cgi_parameter((x),(y)) |
|
104
|
#define PT(x) cgi_parameter_trimmed((x),0) |
|
105
|
#define PDT(x,y) cgi_parameter_trimmed((x),(y)) |
|
106
|
#define PB(x) cgi_parameter_boolean(x) |
|
107
|
#define PCK(x) cgi_parameter_checked(x,1) |
|
108
|
#define PIF(x,y) cgi_parameter_checked(x,y) |
|
109
|
#define P_NoBot(x) cgi_parameter_no_attack((x),0) |
|
110
|
#define PD_NoBot(x,y) cgi_parameter_no_attack((x),(y)) |
|
111
|
|
|
112
|
/* |
|
113
|
** Shortcut for the cgi_printf() routine. Instead of using the |
|
114
|
** |
|
115
|
** @ ... |
|
116
|
** |
|
117
|
** notation provided by the translate.c utility, you can also |
|
118
|
** optionally use: |
|
119
|
** |
|
120
|
** CX(...) |
|
121
|
*/ |
|
122
|
#define CX cgi_printf |
|
123
|
|
|
124
|
/* |
|
125
|
** Destinations for output text. |
|
126
|
*/ |
|
127
|
#define CGI_HEADER 0 |
|
128
|
#define CGI_BODY 1 |
|
129
|
|
|
130
|
/* |
|
131
|
** Flags for SSH HTTP clients |
|
132
|
*/ |
|
133
|
#define CGI_SSH_CLIENT 0x0001 /* Client is SSH */ |
|
134
|
#define CGI_SSH_COMPAT 0x0002 /* Compat for old SSH transport */ |
|
135
|
#define CGI_SSH_FOSSIL 0x0004 /* Use new Fossil SSH transport */ |
|
136
|
|
|
137
|
#endif /* INTERFACE */ |
|
138
|
|
|
139
|
/* |
|
140
|
** The reply content is generated in two pieces: the header and the body. |
|
141
|
** These pieces are generated separately because they are not necessarily |
|
142
|
** produced in order. Parts of the header might be built after all or |
|
143
|
** part of the body. The header and body are accumulated in separate |
|
144
|
** Blob structures then output sequentially once everything has been |
|
145
|
** built. |
|
146
|
** |
|
147
|
** Do not confuse the content header with the HTTP header. The content header |
|
148
|
** is generated by downstream code. The HTTP header is generated by the |
|
149
|
** cgi_reply() routine below. |
|
150
|
** |
|
151
|
** The content header and content body are *approximately* the <head> |
|
152
|
** element and the <body> elements for HTML replies. However this is only |
|
153
|
** approximate. The content header also includes parts of <body> that |
|
154
|
** show the banner and menu bar at the top of each page. Also note that |
|
155
|
** not all replies are HTML, but there can still be separate header and |
|
156
|
** body sections of the content. |
|
157
|
** |
|
158
|
** The cgi_destination() interface switches between the buffers. |
|
159
|
*/ |
|
160
|
static Blob cgiContent[2] = { BLOB_INITIALIZER, BLOB_INITIALIZER }; |
|
161
|
static Blob *pContent = &cgiContent[0]; |
|
162
|
|
|
163
|
/* |
|
164
|
** Set the destination buffer into which to accumulate CGI content. |
|
165
|
*/ |
|
166
|
void cgi_destination(int dest){ |
|
167
|
switch( dest ){ |
|
168
|
case CGI_HEADER: { |
|
169
|
pContent = &cgiContent[0]; |
|
170
|
break; |
|
171
|
} |
|
172
|
case CGI_BODY: { |
|
173
|
pContent = &cgiContent[1]; |
|
174
|
break; |
|
175
|
} |
|
176
|
default: { |
|
177
|
cgi_panic("bad destination"); |
|
178
|
} |
|
179
|
} |
|
180
|
} |
|
181
|
|
|
182
|
/* |
|
183
|
** Check to see if the content header or body contains the zNeedle string. |
|
184
|
** Return true if it does and false if it does not. |
|
185
|
*/ |
|
186
|
int cgi_header_contains(const char *zNeedle){ |
|
187
|
return strstr(blob_str(&cgiContent[0]), zNeedle)!=0; |
|
188
|
} |
|
189
|
int cgi_body_contains(const char *zNeedle){ |
|
190
|
return strstr(blob_str(&cgiContent[1]), zNeedle)!=0; |
|
191
|
} |
|
192
|
|
|
193
|
/* |
|
194
|
** Append new reply content to what already exists. |
|
195
|
*/ |
|
196
|
void cgi_append_content(const char *zData, int nAmt){ |
|
197
|
blob_append(pContent, zData, nAmt); |
|
198
|
} |
|
199
|
|
|
200
|
/* |
|
201
|
** Reset both reply content buffers to be empty. |
|
202
|
*/ |
|
203
|
void cgi_reset_content(void){ |
|
204
|
blob_reset(&cgiContent[0]); |
|
205
|
blob_reset(&cgiContent[1]); |
|
206
|
} |
|
207
|
|
|
208
|
/* |
|
209
|
** Return a pointer to Blob that is currently accumulating reply content. |
|
210
|
*/ |
|
211
|
Blob *cgi_output_blob(void){ |
|
212
|
return pContent; |
|
213
|
} |
|
214
|
|
|
215
|
/* |
|
216
|
** Return the content header as a text string |
|
217
|
*/ |
|
218
|
const char *cgi_header(void){ |
|
219
|
return blob_str(&cgiContent[0]); |
|
220
|
} |
|
221
|
|
|
222
|
/* |
|
223
|
** Combine the header and body content all into the header buffer. |
|
224
|
** In other words, append the body content to the end of the header |
|
225
|
** content. |
|
226
|
*/ |
|
227
|
static void cgi_combine_header_and_body(void){ |
|
228
|
int size = blob_size(&cgiContent[1]); |
|
229
|
if( size>0 ){ |
|
230
|
blob_append(&cgiContent[0], blob_buffer(&cgiContent[1]), size); |
|
231
|
blob_reset(&cgiContent[1]); |
|
232
|
} |
|
233
|
} |
|
234
|
|
|
235
|
/* |
|
236
|
** Return a pointer to the combined header+body content. |
|
237
|
*/ |
|
238
|
char *cgi_extract_content(void){ |
|
239
|
cgi_combine_header_and_body(); |
|
240
|
return blob_buffer(&cgiContent[0]); |
|
241
|
} |
|
242
|
|
|
243
|
/* |
|
244
|
** Additional information used to form the HTTP reply |
|
245
|
*/ |
|
246
|
static const char *zReplyMimeType = "text/html"; /* Content type of the reply */ |
|
247
|
static const char *zReplyStatus = "OK"; /* Reply status description */ |
|
248
|
static int iReplyStatus = 200; /* Reply status code */ |
|
249
|
static Blob extraHeader = BLOB_INITIALIZER; /* Extra header text */ |
|
250
|
static int rangeStart = 0; /* Start of Range: */ |
|
251
|
static int rangeEnd = 0; /* End of Range: plus 1 */ |
|
252
|
|
|
253
|
/* |
|
254
|
** Set the reply content type. |
|
255
|
** |
|
256
|
** The reply content type defaults to "text/html". It only needs to be |
|
257
|
** changed (by calling this routine) in the exceptional case where some |
|
258
|
** other content type is being returned. |
|
259
|
*/ |
|
260
|
void cgi_set_content_type(const char *zType){ |
|
261
|
int i; |
|
262
|
for(i=0; zType[i]>='+' && zType[i]<='z'; i++){} |
|
263
|
zReplyMimeType = fossil_strndup(zType, i); |
|
264
|
} |
|
265
|
|
|
266
|
/* |
|
267
|
** Erase any existing reply content. Replace is with a pNewContent. |
|
268
|
** |
|
269
|
** This routine erases pNewContent. In other words, it move pNewContent |
|
270
|
** into the content buffer. |
|
271
|
*/ |
|
272
|
void cgi_set_content(Blob *pNewContent){ |
|
273
|
cgi_reset_content(); |
|
274
|
cgi_destination(CGI_HEADER); |
|
275
|
cgiContent[0] = *pNewContent; |
|
276
|
blob_zero(pNewContent); |
|
277
|
} |
|
278
|
|
|
279
|
/* |
|
280
|
** Set the reply status code |
|
281
|
*/ |
|
282
|
void cgi_set_status(int iStat, const char *zStat){ |
|
283
|
zReplyStatus = fossil_strdup(zStat); |
|
284
|
iReplyStatus = iStat; |
|
285
|
} |
|
286
|
|
|
287
|
/* |
|
288
|
** Append text to the content header buffer. |
|
289
|
*/ |
|
290
|
void cgi_append_header(const char *zLine){ |
|
291
|
blob_append(&extraHeader, zLine, -1); |
|
292
|
} |
|
293
|
void cgi_printf_header(const char *zLine, ...){ |
|
294
|
va_list ap; |
|
295
|
va_start(ap, zLine); |
|
296
|
blob_vappendf(&extraHeader, zLine, ap); |
|
297
|
va_end(ap); |
|
298
|
} |
|
299
|
|
|
300
|
/* |
|
301
|
** Set a cookie by queuing up the appropriate HTTP header output. If |
|
302
|
** !g.isHTTP, this is a no-op. |
|
303
|
** |
|
304
|
** Zero lifetime implies a session cookie. A negative one expires |
|
305
|
** the cookie immediately. |
|
306
|
*/ |
|
307
|
void cgi_set_cookie( |
|
308
|
const char *zName, /* Name of the cookie */ |
|
309
|
const char *zValue, /* Value of the cookie. Automatically escaped */ |
|
310
|
const char *zPath, /* Path cookie applies to. NULL means "/" */ |
|
311
|
int lifetime /* Expiration of the cookie in seconds from now */ |
|
312
|
){ |
|
313
|
char const *zSecure = ""; |
|
314
|
if(!g.isHTTP) return /* e.g. JSON CLI mode, where g.zTop is not set */; |
|
315
|
else if( zPath==0 ){ |
|
316
|
zPath = g.zTop; |
|
317
|
if( zPath[0]==0 ) zPath = "/"; |
|
318
|
} |
|
319
|
if( g.zBaseURL!=0 && fossil_strncmp(g.zBaseURL, "https:", 6)==0 ){ |
|
320
|
zSecure = " secure;"; |
|
321
|
} |
|
322
|
if( lifetime!=0 ){ |
|
323
|
blob_appendf(&extraHeader, |
|
324
|
"Set-Cookie: %s=%t; Path=%s; max-age=%d; HttpOnly; %s\r\n", |
|
325
|
zName, lifetime>0 ? zValue : "null", zPath, lifetime, zSecure); |
|
326
|
}else{ |
|
327
|
blob_appendf(&extraHeader, |
|
328
|
"Set-Cookie: %s=%t; Path=%s; HttpOnly; %s\r\n", |
|
329
|
zName, zValue, zPath, zSecure); |
|
330
|
} |
|
331
|
} |
|
332
|
|
|
333
|
|
|
334
|
/* |
|
335
|
** Return true if the response should be sent with Content-Encoding: gzip. |
|
336
|
*/ |
|
337
|
static int is_gzippable(void){ |
|
338
|
if( g.fNoHttpCompress ) return 0; |
|
339
|
if( strstr(PD("HTTP_ACCEPT_ENCODING", ""), "gzip")==0 ) return 0; |
|
340
|
/* Maintenance note: this oddball structure is intended to make |
|
341
|
** adding new mimetypes to this list less of a performance hit than |
|
342
|
** doing a strcmp/glob over a growing set of compressible types. */ |
|
343
|
switch(zReplyMimeType ? *zReplyMimeType : 0){ |
|
344
|
case (int)'a': |
|
345
|
if(0==fossil_strncmp("application/",zReplyMimeType,12)){ |
|
346
|
const char * z = &zReplyMimeType[12]; |
|
347
|
switch(*z){ |
|
348
|
case (int)'j': |
|
349
|
return fossil_strcmp("javascript", z)==0 |
|
350
|
|| fossil_strcmp("json", z)==0; |
|
351
|
case (int)'w': return fossil_strcmp("wasm", z)==0; |
|
352
|
case (int)'x': |
|
353
|
return fossil_strcmp("x-tcl", z)==0 |
|
354
|
|| fossil_strcmp("x-tar", z)==0; |
|
355
|
default: |
|
356
|
return sqlite3_strglob("*xml", z)==0; |
|
357
|
} |
|
358
|
} |
|
359
|
break; |
|
360
|
case (int)'i': |
|
361
|
return fossil_strcmp(zReplyMimeType, "image/svg+xml")==0 |
|
362
|
|| fossil_strcmp(zReplyMimeType, "image/vnd.microsoft.icon")==0; |
|
363
|
case (int)'t': |
|
364
|
return fossil_strncmp(zReplyMimeType, "text/", 5)==0; |
|
365
|
} |
|
366
|
return 0; |
|
367
|
} |
|
368
|
|
|
369
|
|
|
370
|
/* |
|
371
|
** The following routines read or write content from/to the wire for |
|
372
|
** an HTTP request. Depending on settings the content might be coming |
|
373
|
** from or going to a socket, or a file, or it might come from or go |
|
374
|
** to an SSL decoder/encoder. |
|
375
|
*/ |
|
376
|
/* |
|
377
|
** Works like fgets(): |
|
378
|
** |
|
379
|
** Read a single line of input into s[]. Ensure that s[] is zero-terminated. |
|
380
|
** The s[] buffer is size bytes and so at most size-1 bytes will be read. |
|
381
|
** |
|
382
|
** Return a pointer to s[] on success, or NULL at end-of-input. |
|
383
|
*/ |
|
384
|
static char *cgi_fgets(char *s, int size){ |
|
385
|
if( !g.httpUseSSL ){ |
|
386
|
return fgets(s, size, g.httpIn); |
|
387
|
} |
|
388
|
#ifdef FOSSIL_ENABLE_SSL |
|
389
|
return ssl_gets(g.httpSSLConn, s, size); |
|
390
|
#else |
|
391
|
fossil_fatal("SSL not available"); |
|
392
|
#endif |
|
393
|
} |
|
394
|
|
|
395
|
/* Works like fread(): |
|
396
|
** |
|
397
|
** Read as many as bytes of content as we can, up to a maximum of nmemb |
|
398
|
** bytes. Return the number of bytes read. Return 0 if there is no |
|
399
|
** further input or if an I/O error occurs. |
|
400
|
*/ |
|
401
|
size_t cgi_fread(void *ptr, size_t nmemb){ |
|
402
|
if( !g.httpUseSSL ){ |
|
403
|
return fread(ptr, 1, nmemb, g.httpIn); |
|
404
|
} |
|
405
|
#ifdef FOSSIL_ENABLE_SSL |
|
406
|
return ssl_read_server(g.httpSSLConn, ptr, nmemb, 1); |
|
407
|
#else |
|
408
|
fossil_fatal("SSL not available"); |
|
409
|
/* NOT REACHED */ |
|
410
|
return 0; |
|
411
|
#endif |
|
412
|
} |
|
413
|
|
|
414
|
/* Works like feof(): |
|
415
|
** |
|
416
|
** Return true if end-of-input has been reached. |
|
417
|
*/ |
|
418
|
int cgi_feof(void){ |
|
419
|
if( !g.httpUseSSL ){ |
|
420
|
return feof(g.httpIn); |
|
421
|
} |
|
422
|
#ifdef FOSSIL_ENABLE_SSL |
|
423
|
return ssl_eof(g.httpSSLConn); |
|
424
|
#else |
|
425
|
return 1; |
|
426
|
#endif |
|
427
|
} |
|
428
|
|
|
429
|
/* Works like fwrite(): |
|
430
|
** |
|
431
|
** Try to output nmemb bytes of content. Return the number of |
|
432
|
** bytes actually written. |
|
433
|
*/ |
|
434
|
static size_t cgi_fwrite(void *ptr, size_t nmemb){ |
|
435
|
if( !g.httpUseSSL ){ |
|
436
|
return fwrite(ptr, 1, nmemb, g.httpOut); |
|
437
|
} |
|
438
|
#ifdef FOSSIL_ENABLE_SSL |
|
439
|
return ssl_write_server(g.httpSSLConn, ptr, nmemb); |
|
440
|
#else |
|
441
|
fossil_fatal("SSL not available"); |
|
442
|
#endif |
|
443
|
} |
|
444
|
|
|
445
|
/* Works like fflush(): |
|
446
|
** |
|
447
|
** Make sure I/O has completed. |
|
448
|
*/ |
|
449
|
static void cgi_fflush(void){ |
|
450
|
if( !g.httpUseSSL ){ |
|
451
|
fflush(g.httpOut); |
|
452
|
} |
|
453
|
} |
|
454
|
|
|
455
|
/* |
|
456
|
** Given a Content-Type value, returns a string suitable for appending |
|
457
|
** to the Content-Type header for adding (or not) the "; charset=..." |
|
458
|
** part. It returns an empty string for most types or if zReplyMimeType |
|
459
|
** is NULL. |
|
460
|
** |
|
461
|
** See forum post f60dece061c364d1 for the discussions which lead to |
|
462
|
** this. Previously we always appended the charset, but WASM loaders |
|
463
|
** are pedantic and refuse to load any responses which have a |
|
464
|
** charset. Also, adding a charset is not strictly appropriate for |
|
465
|
** most types (and not required for many others which may ostensibly |
|
466
|
** benefit from one, as detailed in that forum post). |
|
467
|
*/ |
|
468
|
static const char * content_type_charset(const char *zReplyMimeType){ |
|
469
|
if(0==fossil_strncmp(zReplyMimeType,"text/",5)){ |
|
470
|
return "; charset=utf-8"; |
|
471
|
} |
|
472
|
return ""; |
|
473
|
} |
|
474
|
|
|
475
|
/* |
|
476
|
** Generate the reply to a web request. The output might be an |
|
477
|
** full HTTP response, or a CGI response, depending on how things have |
|
478
|
** be set up. |
|
479
|
** |
|
480
|
** The reply consists of a response header (an HTTP or CGI response header) |
|
481
|
** followed by the concatenation of the content header and content body. |
|
482
|
*/ |
|
483
|
void cgi_reply(void){ |
|
484
|
Blob hdr = BLOB_INITIALIZER; |
|
485
|
int total_size; |
|
486
|
if( iReplyStatus<=0 ){ |
|
487
|
iReplyStatus = 200; |
|
488
|
zReplyStatus = "OK"; |
|
489
|
} |
|
490
|
|
|
491
|
if( g.fullHttpReply ){ |
|
492
|
if( rangeEnd>0 |
|
493
|
&& iReplyStatus==200 |
|
494
|
&& fossil_strcmp(P("REQUEST_METHOD"),"GET")==0 |
|
495
|
){ |
|
496
|
iReplyStatus = 206; |
|
497
|
zReplyStatus = "Partial Content"; |
|
498
|
} |
|
499
|
blob_appendf(&hdr, "HTTP/1.0 %d %s\r\n", iReplyStatus, zReplyStatus); |
|
500
|
blob_appendf(&hdr, "Date: %s\r\n", cgi_rfc822_datestamp(time(0))); |
|
501
|
blob_appendf(&hdr, "Connection: close\r\n"); |
|
502
|
blob_appendf(&hdr, "X-UA-Compatible: IE=edge\r\n"); |
|
503
|
}else{ |
|
504
|
assert( rangeEnd==0 ); |
|
505
|
blob_appendf(&hdr, "Status: %d %s\r\n", iReplyStatus, zReplyStatus); |
|
506
|
} |
|
507
|
if( etag_tag()[0]!=0 |
|
508
|
&& iReplyStatus==200 |
|
509
|
&& strcmp(zReplyMimeType,"text/html")!=0 |
|
510
|
){ |
|
511
|
/* Do not cache HTML replies as those will have been generated and |
|
512
|
** will likely, therefore, contains a nonce and we want that nonce to |
|
513
|
** be different every time. */ |
|
514
|
blob_appendf(&hdr, "ETag: \"%s\"\r\n", etag_tag()); |
|
515
|
blob_appendf(&hdr, "Cache-Control: max-age=%d\r\n", etag_maxage()); |
|
516
|
if( etag_mtime()>0 ){ |
|
517
|
blob_appendf(&hdr, "Last-Modified: %s\r\n", |
|
518
|
cgi_rfc822_datestamp(etag_mtime())); |
|
519
|
} |
|
520
|
}else if( g.isConst ){ |
|
521
|
/* isConst means that the reply is guaranteed to be invariant, even |
|
522
|
** after configuration changes and/or Fossil binary recompiles. */ |
|
523
|
blob_appendf(&hdr, "Cache-Control: max-age=315360000, immutable\r\n"); |
|
524
|
}else{ |
|
525
|
blob_appendf(&hdr, "Cache-control: no-cache\r\n"); |
|
526
|
} |
|
527
|
|
|
528
|
if( blob_size(&extraHeader)>0 ){ |
|
529
|
blob_appendf(&hdr, "%s", blob_buffer(&extraHeader)); |
|
530
|
} |
|
531
|
|
|
532
|
/* Add headers to turn on useful security options in browsers. */ |
|
533
|
blob_appendf(&hdr, "X-Frame-Options: SAMEORIGIN\r\n"); |
|
534
|
/* The previous stops fossil pages appearing in frames or iframes, preventing |
|
535
|
** click-jacking attacks on supporting browsers. |
|
536
|
** |
|
537
|
** Other good headers would be |
|
538
|
** Strict-Transport-Security: max-age=62208000 |
|
539
|
** if we're using https. However, this would break sites which serve different |
|
540
|
** content on http and https protocols. Also, |
|
541
|
** X-Content-Security-Policy: allow 'self' |
|
542
|
** would help mitigate some XSS and data injection attacks, but will break |
|
543
|
** deliberate inclusion of external resources, such as JavaScript syntax |
|
544
|
** highlighter scripts. |
|
545
|
** |
|
546
|
** These headers are probably best added by the web server hosting fossil as |
|
547
|
** a CGI script. |
|
548
|
*/ |
|
549
|
|
|
550
|
if( iReplyStatus!=304 ) { |
|
551
|
blob_appendf(&hdr, "Content-Type: %s%s\r\n", zReplyMimeType, |
|
552
|
content_type_charset(zReplyMimeType)); |
|
553
|
if( fossil_strcmp(zReplyMimeType,"application/x-fossil")==0 ){ |
|
554
|
cgi_combine_header_and_body(); |
|
555
|
blob_compress(&cgiContent[0], &cgiContent[0]); |
|
556
|
} |
|
557
|
|
|
558
|
if( is_gzippable() && iReplyStatus!=206 ){ |
|
559
|
int i; |
|
560
|
gzip_begin(0); |
|
561
|
for( i=0; i<2; i++ ){ |
|
562
|
int size = blob_size(&cgiContent[i]); |
|
563
|
if( size>0 ) gzip_step(blob_buffer(&cgiContent[i]), size); |
|
564
|
blob_reset(&cgiContent[i]); |
|
565
|
} |
|
566
|
gzip_finish(&cgiContent[0]); |
|
567
|
blob_appendf(&hdr, "Content-Encoding: gzip\r\n"); |
|
568
|
blob_appendf(&hdr, "Vary: Accept-Encoding\r\n"); |
|
569
|
} |
|
570
|
total_size = blob_size(&cgiContent[0]) + blob_size(&cgiContent[1]); |
|
571
|
if( iReplyStatus==206 ){ |
|
572
|
blob_appendf(&hdr, "Content-Range: bytes %d-%d/%d\r\n", |
|
573
|
rangeStart, rangeEnd-1, total_size); |
|
574
|
total_size = rangeEnd - rangeStart; |
|
575
|
} |
|
576
|
blob_appendf(&hdr, "Content-Length: %d\r\n", total_size); |
|
577
|
}else{ |
|
578
|
total_size = 0; |
|
579
|
} |
|
580
|
blob_appendf(&hdr, "\r\n"); |
|
581
|
cgi_fwrite(blob_buffer(&hdr), blob_size(&hdr)); |
|
582
|
blob_reset(&hdr); |
|
583
|
if( total_size>0 |
|
584
|
&& iReplyStatus!=304 |
|
585
|
&& fossil_strcmp(P("REQUEST_METHOD"),"HEAD")!=0 |
|
586
|
){ |
|
587
|
int i, size; |
|
588
|
for(i=0; i<2; i++){ |
|
589
|
size = blob_size(&cgiContent[i]); |
|
590
|
if( size<=rangeStart ){ |
|
591
|
rangeStart -= size; |
|
592
|
}else{ |
|
593
|
int n = size - rangeStart; |
|
594
|
if( n>total_size ){ |
|
595
|
n = total_size; |
|
596
|
} |
|
597
|
cgi_fwrite(blob_buffer(&cgiContent[i])+rangeStart, n); |
|
598
|
rangeStart = 0; |
|
599
|
total_size -= n; |
|
600
|
} |
|
601
|
} |
|
602
|
} |
|
603
|
cgi_fflush(); |
|
604
|
CGIDEBUG(("-------- END cgi ---------\n")); |
|
605
|
|
|
606
|
/* After the webpage has been sent, do any useful background |
|
607
|
** processing. |
|
608
|
*/ |
|
609
|
g.cgiOutput = 2; |
|
610
|
if( g.db!=0 && iReplyStatus==200 ){ |
|
611
|
backoffice_check_if_needed(); |
|
612
|
} |
|
613
|
} |
|
614
|
|
|
615
|
/* |
|
616
|
** Generate an HTTP or CGI redirect response that causes a redirect |
|
617
|
** to the URL given in the argument. |
|
618
|
** |
|
619
|
** The URL must be relative to the base of the fossil server. |
|
620
|
*/ |
|
621
|
NORETURN void cgi_redirect_with_status( |
|
622
|
const char *zURL, |
|
623
|
int iStat, |
|
624
|
const char *zStat |
|
625
|
){ |
|
626
|
char *zLocation; |
|
627
|
CGIDEBUG(("redirect to %s\n", zURL)); |
|
628
|
if( fossil_strncmp(zURL,"http:",5)==0 |
|
629
|
|| fossil_strncmp(zURL,"https:",6)==0 ){ |
|
630
|
zLocation = mprintf("Location: %s\r\n", zURL); |
|
631
|
}else if( *zURL=='/' ){ |
|
632
|
int n1 = (int)strlen(g.zBaseURL); |
|
633
|
int n2 = (int)strlen(g.zTop); |
|
634
|
if( g.zBaseURL[n1-1]=='/' ) zURL++; |
|
635
|
zLocation = mprintf("Location: %.*s%s\r\n", n1-n2, g.zBaseURL, zURL); |
|
636
|
}else{ |
|
637
|
zLocation = mprintf("Location: %s/%s\r\n", g.zBaseURL, zURL); |
|
638
|
} |
|
639
|
cgi_append_header(zLocation); |
|
640
|
cgi_reset_content(); |
|
641
|
cgi_printf("<html>\n<p>Redirect to %h</p>\n</html>\n", zLocation); |
|
642
|
cgi_set_status(iStat, zStat); |
|
643
|
free(zLocation); |
|
644
|
cgi_reply(); |
|
645
|
fossil_exit(0); |
|
646
|
} |
|
647
|
NORETURN void cgi_redirect_perm(const char *zURL){ |
|
648
|
cgi_redirect_with_status(zURL, 301, "Moved Permanently"); |
|
649
|
} |
|
650
|
NORETURN void cgi_redirect(const char *zURL){ |
|
651
|
cgi_redirect_with_status(zURL, 302, "Moved Temporarily"); |
|
652
|
} |
|
653
|
NORETURN void cgi_redirect_with_method(const char *zURL){ |
|
654
|
cgi_redirect_with_status(zURL, 307, "Temporary Redirect"); |
|
655
|
} |
|
656
|
NORETURN void cgi_redirectf(const char *zFormat, ...){ |
|
657
|
va_list ap; |
|
658
|
va_start(ap, zFormat); |
|
659
|
cgi_redirect(vmprintf(zFormat, ap)); |
|
660
|
va_end(ap); |
|
661
|
} |
|
662
|
|
|
663
|
/* |
|
664
|
** Add a "Content-disposition: attachment; filename=%s" header to the reply. |
|
665
|
*/ |
|
666
|
void cgi_content_disposition_filename(const char *zFilename){ |
|
667
|
char *z; |
|
668
|
int i, n; |
|
669
|
|
|
670
|
/* 0123456789 123456789 123456789 123456789 123456*/ |
|
671
|
z = mprintf("Content-Disposition: attachment; filename=\"%s\";\r\n", |
|
672
|
file_tail(zFilename)); |
|
673
|
n = (int)strlen(z); |
|
674
|
for(i=43; i<n-4; i++){ |
|
675
|
char c = z[i]; |
|
676
|
if( fossil_isalnum(c) ) continue; |
|
677
|
if( c=='.' || c=='-' || c=='/' ) continue; |
|
678
|
z[i] = '_'; |
|
679
|
} |
|
680
|
cgi_append_header(z); |
|
681
|
fossil_free(z); |
|
682
|
} |
|
683
|
|
|
684
|
/* |
|
685
|
** Return the URL for the caller. This is obtained from either the |
|
686
|
** "referer" CGI parameter, if it exists, or the HTTP_REFERER HTTP parameter. |
|
687
|
** If neither exist, return zDefault. |
|
688
|
*/ |
|
689
|
const char *cgi_referer(const char *zDefault){ |
|
690
|
const char *zRef = P("referer"); |
|
691
|
if( zRef==0 ){ |
|
692
|
zRef = P("HTTP_REFERER"); |
|
693
|
if( zRef==0 ) zRef = zDefault; |
|
694
|
} |
|
695
|
return zRef; |
|
696
|
} |
|
697
|
|
|
698
|
|
|
699
|
/* |
|
700
|
** Return true if the current request is coming from the same origin. |
|
701
|
** |
|
702
|
** If the request comes from a different origin and bErrorLog is true, then |
|
703
|
** put a warning message on the error log as this was a possible hack |
|
704
|
** attempt. |
|
705
|
*/ |
|
706
|
int cgi_same_origin(int bErrorLog){ |
|
707
|
const char *zRef; |
|
708
|
char *zToFree = 0; |
|
709
|
int nBase; |
|
710
|
int rc; |
|
711
|
if( g.zBaseURL==0 ) return 0; |
|
712
|
zRef = P("HTTP_REFERER"); |
|
713
|
if( zRef==0 ) return 0; |
|
714
|
if( strchr(zRef,'%')!=0 ){ |
|
715
|
zToFree = strdup(zRef); |
|
716
|
dehttpize(zToFree); |
|
717
|
zRef = zToFree; |
|
718
|
} |
|
719
|
nBase = (int)strlen(g.zBaseURL); |
|
720
|
if( fossil_strncmp(g.zBaseURL,zRef,nBase)!=0 ){ |
|
721
|
rc = 0; |
|
722
|
}else if( zRef[nBase]!=0 && zRef[nBase]!='/' ){ |
|
723
|
rc = 0; |
|
724
|
}else{ |
|
725
|
rc = 1; |
|
726
|
} |
|
727
|
if( rc==0 && bErrorLog && fossil_strcmp(P("REQUST_METHOD"),"POST")==0 ){ |
|
728
|
fossil_errorlog("warning: POST from different origin"); |
|
729
|
} |
|
730
|
fossil_free(zToFree); |
|
731
|
return rc; |
|
732
|
} |
|
733
|
|
|
734
|
/* |
|
735
|
** Return true if the current CGI request is a POST request |
|
736
|
*/ |
|
737
|
static int cgi_is_post_request(void){ |
|
738
|
const char *zMethod = P("REQUEST_METHOD"); |
|
739
|
if( zMethod==0 ) return 0; |
|
740
|
if( strcmp(zMethod,"POST")!=0 ) return 0; |
|
741
|
return 1; |
|
742
|
} |
|
743
|
|
|
744
|
/* |
|
745
|
** Return true if the current request appears to be safe from a |
|
746
|
** Cross-Site Request Forgery (CSRF) attack. The level of checking |
|
747
|
** is determined by the parameter. The higher the number, the more |
|
748
|
** secure we are: |
|
749
|
** |
|
750
|
** 0: Request must come from the same origin |
|
751
|
** 1: Same origin and must be a POST request |
|
752
|
** 2: All of the above plus must have a valid CSRF token |
|
753
|
** |
|
754
|
** Results are cached in the g.okCsrf variable. The g.okCsrf value |
|
755
|
** has meaning as follows: |
|
756
|
** |
|
757
|
** -1: Not a secure request |
|
758
|
** 0: Status unknown |
|
759
|
** 1: Request comes from the same origin |
|
760
|
** 2: (1) plus it is a POST request |
|
761
|
** 3: (2) plus there is a valid "csrf" token in the request |
|
762
|
*/ |
|
763
|
int cgi_csrf_safe(int securityLevel){ |
|
764
|
if( g.okCsrf<0 ) return 0; |
|
765
|
if( g.okCsrf==0 ){ |
|
766
|
if( !cgi_same_origin(1) ){ |
|
767
|
g.okCsrf = -1; |
|
768
|
}else{ |
|
769
|
g.okCsrf = 1; |
|
770
|
if( cgi_is_post_request() ){ |
|
771
|
g.okCsrf = 2; |
|
772
|
if( fossil_strcmp(P("csrf"), g.zCsrfToken)==0 ){ |
|
773
|
g.okCsrf = 3; |
|
774
|
} |
|
775
|
} |
|
776
|
} |
|
777
|
} |
|
778
|
return g.okCsrf >= (securityLevel+1); |
|
779
|
} |
|
780
|
|
|
781
|
/* |
|
782
|
** Verify that CSRF defenses are maximal - that the request comes from |
|
783
|
** the same origin, that it is a POST request, and that there is a valid |
|
784
|
** "csrf" token. If this is not the case, fail immediately. |
|
785
|
*/ |
|
786
|
void cgi_csrf_verify(void){ |
|
787
|
if( !cgi_csrf_safe(2) ){ |
|
788
|
fossil_fatal("Cross-site Request Forgery detected"); |
|
789
|
} |
|
790
|
} |
|
791
|
|
|
792
|
/* |
|
793
|
** Information about all query parameters, post parameter, cookies and |
|
794
|
** CGI environment variables are stored in a hash table as follows: |
|
795
|
*/ |
|
796
|
static int nAllocQP = 0; /* Space allocated for aParamQP[] */ |
|
797
|
static int nUsedQP = 0; /* Space actually used in aParamQP[] */ |
|
798
|
static int sortQP = 0; /* True if aParamQP[] needs sorting */ |
|
799
|
static int seqQP = 0; /* Sequence numbers */ |
|
800
|
static struct QParam { /* One entry for each query parameter or cookie */ |
|
801
|
const char *zName; /* Parameter or cookie name */ |
|
802
|
const char *zValue; /* Value of the query parameter or cookie */ |
|
803
|
int seq; /* Order of insertion */ |
|
804
|
char isQP; /* True for query parameters */ |
|
805
|
char cTag; /* Tag on query parameters */ |
|
806
|
char isFetched; /* 1 if the var is requested via P/PD() */ |
|
807
|
} *aParamQP; /* An array of all parameters and cookies */ |
|
808
|
|
|
809
|
/* |
|
810
|
** Add another query parameter or cookie to the parameter set. |
|
811
|
** zName is the name of the query parameter or cookie and zValue |
|
812
|
** is its fully decoded value. |
|
813
|
** |
|
814
|
** zName and zValue are not copied and must not change or be |
|
815
|
** deallocated after this routine returns. |
|
816
|
*/ |
|
817
|
void cgi_set_parameter_nocopy(const char *zName, const char *zValue, int isQP){ |
|
818
|
if( nAllocQP<=nUsedQP ){ |
|
819
|
nAllocQP = nAllocQP*2 + 10; |
|
820
|
if( nAllocQP>1000 ){ |
|
821
|
/* Prevent a DOS service attack against the framework */ |
|
822
|
fossil_fatal("Too many query parameters"); |
|
823
|
} |
|
824
|
aParamQP = fossil_realloc( aParamQP, nAllocQP*sizeof(aParamQP[0]) ); |
|
825
|
} |
|
826
|
aParamQP[nUsedQP].zName = zName; |
|
827
|
aParamQP[nUsedQP].zValue = zValue; |
|
828
|
if( g.fHttpTrace ){ |
|
829
|
fprintf(stderr, "# cgi: %s = [%s]\n", zName, zValue); |
|
830
|
} |
|
831
|
aParamQP[nUsedQP].seq = seqQP++; |
|
832
|
aParamQP[nUsedQP].isQP = isQP; |
|
833
|
aParamQP[nUsedQP].cTag = 0; |
|
834
|
aParamQP[nUsedQP].isFetched = 0; |
|
835
|
nUsedQP++; |
|
836
|
sortQP = 1; |
|
837
|
} |
|
838
|
|
|
839
|
/* |
|
840
|
** Add another query parameter or cookie to the parameter set. |
|
841
|
** zName is the name of the query parameter or cookie and zValue |
|
842
|
** is its fully decoded value. zName will be modified to be an |
|
843
|
** all lowercase string. |
|
844
|
** |
|
845
|
** zName and zValue are not copied and must not change or be |
|
846
|
** deallocated after this routine returns. This routine changes |
|
847
|
** all ASCII alphabetic characters in zName to lower case. The |
|
848
|
** caller must not change them back. |
|
849
|
*/ |
|
850
|
void cgi_set_parameter_nocopy_tolower( |
|
851
|
char *zName, |
|
852
|
const char *zValue, |
|
853
|
int isQP |
|
854
|
){ |
|
855
|
int i; |
|
856
|
for(i=0; zName[i]; i++){ zName[i] = fossil_tolower(zName[i]); } |
|
857
|
cgi_set_parameter_nocopy(zName, zValue, isQP); |
|
858
|
} |
|
859
|
|
|
860
|
/* |
|
861
|
** Add another query parameter or cookie to the parameter set. |
|
862
|
** zName is the name of the query parameter or cookie and zValue |
|
863
|
** is its fully decoded value. |
|
864
|
** |
|
865
|
** Copies are made of both the zName and zValue parameters. |
|
866
|
*/ |
|
867
|
void cgi_set_parameter(const char *zName, const char *zValue){ |
|
868
|
cgi_set_parameter_nocopy(fossil_strdup(zName),fossil_strdup(zValue), 0); |
|
869
|
} |
|
870
|
void cgi_set_query_parameter(const char *zName, const char *zValue){ |
|
871
|
cgi_set_parameter_nocopy(fossil_strdup(zName),fossil_strdup(zValue), 1); |
|
872
|
} |
|
873
|
|
|
874
|
/* |
|
875
|
** Replace a parameter with a new value. |
|
876
|
*/ |
|
877
|
void cgi_replace_parameter(const char *zName, const char *zValue){ |
|
878
|
int i; |
|
879
|
for(i=0; i<nUsedQP; i++){ |
|
880
|
if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){ |
|
881
|
aParamQP[i].zValue = zValue; |
|
882
|
return; |
|
883
|
} |
|
884
|
} |
|
885
|
cgi_set_parameter_nocopy(zName, zValue, 0); |
|
886
|
} |
|
887
|
void cgi_replace_query_parameter(const char *zName, const char *zValue){ |
|
888
|
int i; |
|
889
|
for(i=0; i<nUsedQP; i++){ |
|
890
|
if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){ |
|
891
|
aParamQP[i].zValue = zValue; |
|
892
|
assert( aParamQP[i].isQP ); |
|
893
|
return; |
|
894
|
} |
|
895
|
} |
|
896
|
cgi_set_parameter_nocopy(zName, zValue, 1); |
|
897
|
} |
|
898
|
void cgi_replace_query_parameter_tolower(char *zName, const char *zValue){ |
|
899
|
int i; |
|
900
|
for(i=0; zName[i]; i++){ zName[i] = fossil_tolower(zName[i]); } |
|
901
|
cgi_replace_query_parameter(zName, zValue); |
|
902
|
} |
|
903
|
|
|
904
|
/* |
|
905
|
** Delete a parameter. |
|
906
|
*/ |
|
907
|
void cgi_delete_parameter(const char *zName){ |
|
908
|
int i; |
|
909
|
for(i=0; i<nUsedQP; i++){ |
|
910
|
if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){ |
|
911
|
--nUsedQP; |
|
912
|
if( i<nUsedQP ){ |
|
913
|
memmove(aParamQP+i, aParamQP+i+1, sizeof(*aParamQP)*(nUsedQP-i)); |
|
914
|
} |
|
915
|
return; |
|
916
|
} |
|
917
|
} |
|
918
|
} |
|
919
|
void cgi_delete_query_parameter(const char *zName){ |
|
920
|
int i; |
|
921
|
for(i=0; i<nUsedQP; i++){ |
|
922
|
if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){ |
|
923
|
assert( aParamQP[i].isQP ); |
|
924
|
--nUsedQP; |
|
925
|
if( i<nUsedQP ){ |
|
926
|
memmove(aParamQP+i, aParamQP+i+1, sizeof(*aParamQP)*(nUsedQP-i)); |
|
927
|
} |
|
928
|
return; |
|
929
|
} |
|
930
|
} |
|
931
|
} |
|
932
|
|
|
933
|
/* |
|
934
|
** Return the number of query parameters. Cookies and environment variables |
|
935
|
** do not count. Also, do not count the special QP "name". |
|
936
|
*/ |
|
937
|
int cgi_qp_count(void){ |
|
938
|
int cnt = 0; |
|
939
|
int i; |
|
940
|
for(i=0; i<nUsedQP; i++){ |
|
941
|
if( aParamQP[i].isQP && fossil_strcmp(aParamQP[i].zName,"name")!=0 ) cnt++; |
|
942
|
} |
|
943
|
return cnt; |
|
944
|
} |
|
945
|
|
|
946
|
/* |
|
947
|
** Add an environment variable value to the parameter set. The zName |
|
948
|
** portion is fixed but a copy is made of zValue. |
|
949
|
*/ |
|
950
|
void cgi_setenv(const char *zName, const char *zValue){ |
|
951
|
cgi_set_parameter_nocopy(zName, fossil_strdup(zValue), 0); |
|
952
|
} |
|
953
|
|
|
954
|
/* |
|
955
|
** Returns true if NUL-terminated z contains any non-NUL |
|
956
|
** control characters (<0x20, 32d). |
|
957
|
*/ |
|
958
|
static int contains_ctrl(const char *zIn){ |
|
959
|
const unsigned char *z = (const unsigned char*)zIn; |
|
960
|
assert(z); |
|
961
|
for( ; *z>=0x20; ++z ){} |
|
962
|
return 0!=*z; |
|
963
|
} |
|
964
|
|
|
965
|
/* |
|
966
|
** Add a list of query parameters or cookies to the parameter set. |
|
967
|
** |
|
968
|
** Each parameter is of the form NAME=VALUE. Both the NAME and the |
|
969
|
** VALUE may be url-encoded ("+" for space, "%HH" for other special |
|
970
|
** characters). But this routine assumes that NAME contains no |
|
971
|
** special character and therefore does not decode it. |
|
972
|
** |
|
973
|
** If NAME begins with something other than a lower-case letter then |
|
974
|
** the entire NAME=VALUE term is ignored. Hence: |
|
975
|
** |
|
976
|
** * cookies and query parameters that have uppercase names |
|
977
|
** are ignored. |
|
978
|
** |
|
979
|
** * it is impossible for a cookie or query parameter to |
|
980
|
** override the value of an environment variable since |
|
981
|
** environment variables always have uppercase names. |
|
982
|
** |
|
983
|
** 2018-03-29: Also ignore the entry if NAME that contains any characters |
|
984
|
** other than [-a-zA-Z0-9_]. There are no known exploits involving unusual |
|
985
|
** names that contain characters outside that set, but it never hurts to |
|
986
|
** be extra cautious when sanitizing inputs. |
|
987
|
** |
|
988
|
** Parameters are separated by the "terminator" character. Whitespace |
|
989
|
** before the NAME is ignored. |
|
990
|
** |
|
991
|
** The input string "z" is modified but no copies is made. "z" |
|
992
|
** should not be deallocated or changed again after this routine |
|
993
|
** returns or it will corrupt the parameter table. |
|
994
|
** |
|
995
|
** If bPermitCtrl is false and the decoded value of any entry in z |
|
996
|
** contains control characters (<0x20, 32d) then that key/value pair |
|
997
|
** are skipped. |
|
998
|
*/ |
|
999
|
static void add_param_list(char *z, int terminator, int bPermitCtrl){ |
|
1000
|
int isQP = terminator=='&'; |
|
1001
|
while( *z ){ |
|
1002
|
char *zName; |
|
1003
|
char *zValue; |
|
1004
|
while( fossil_isspace(*z) ){ z++; } |
|
1005
|
zName = z; |
|
1006
|
while( *z && *z!='=' && *z!=terminator ){ z++; } |
|
1007
|
if( *z=='=' ){ |
|
1008
|
*z = 0; |
|
1009
|
z++; |
|
1010
|
zValue = z; |
|
1011
|
while( *z && *z!=terminator ){ z++; } |
|
1012
|
if( *z ){ |
|
1013
|
*z = 0; |
|
1014
|
z++; |
|
1015
|
} |
|
1016
|
dehttpize(zValue); |
|
1017
|
}else{ |
|
1018
|
if( *z ){ *z++ = 0; } |
|
1019
|
zValue = ""; |
|
1020
|
} |
|
1021
|
if( zName[0] && fossil_no_strange_characters(zName+1) ){ |
|
1022
|
if( 0==bPermitCtrl && contains_ctrl(zValue) ){ |
|
1023
|
continue /* Reject it. An argument could be made |
|
1024
|
** for break instead of continue. */; |
|
1025
|
}else if( fossil_islower(zName[0]) ){ |
|
1026
|
cgi_set_parameter_nocopy(zName, zValue, isQP); |
|
1027
|
}else if( fossil_isupper(zName[0]) ){ |
|
1028
|
cgi_set_parameter_nocopy_tolower(zName, zValue, isQP); |
|
1029
|
} |
|
1030
|
} |
|
1031
|
#ifdef FOSSIL_ENABLE_JSON |
|
1032
|
json_setenv( zName, cson_value_new_string(zValue,strlen(zValue)) ); |
|
1033
|
#endif /* FOSSIL_ENABLE_JSON */ |
|
1034
|
} |
|
1035
|
} |
|
1036
|
|
|
1037
|
/* |
|
1038
|
** *pz is a string that consists of multiple lines of text. This |
|
1039
|
** routine finds the end of the current line of text and converts |
|
1040
|
** the "\n" or "\r\n" that ends that line into a "\000". It then |
|
1041
|
** advances *pz to the beginning of the next line and returns the |
|
1042
|
** previous value of *pz (which is the start of the current line.) |
|
1043
|
*/ |
|
1044
|
static char *get_line_from_string(char **pz, int *pLen){ |
|
1045
|
char *z = *pz; |
|
1046
|
int i; |
|
1047
|
if( z[0]==0 ) return 0; |
|
1048
|
for(i=0; z[i]; i++){ |
|
1049
|
if( z[i]=='\n' ){ |
|
1050
|
if( i>0 && z[i-1]=='\r' ){ |
|
1051
|
z[i-1] = 0; |
|
1052
|
}else{ |
|
1053
|
z[i] = 0; |
|
1054
|
} |
|
1055
|
i++; |
|
1056
|
break; |
|
1057
|
} |
|
1058
|
} |
|
1059
|
*pz = &z[i]; |
|
1060
|
*pLen -= i; |
|
1061
|
return z; |
|
1062
|
} |
|
1063
|
|
|
1064
|
/* |
|
1065
|
** The input *pz points to content that is terminated by a "\r\n" |
|
1066
|
** followed by the boundary marker zBoundary. An extra "--" may or |
|
1067
|
** may not be appended to the boundary marker. There are *pLen characters |
|
1068
|
** in *pz. |
|
1069
|
** |
|
1070
|
** This routine adds a "\000" to the end of the content (overwriting |
|
1071
|
** the "\r\n") and returns a pointer to the content. The *pz input |
|
1072
|
** is adjusted to point to the first line following the boundary. |
|
1073
|
** The length of the content is stored in *pnContent. |
|
1074
|
*/ |
|
1075
|
static char *get_bounded_content( |
|
1076
|
char **pz, /* Content taken from here */ |
|
1077
|
int *pLen, /* Number of bytes of data in (*pz)[] */ |
|
1078
|
char *zBoundary, /* Boundary text marking the end of content */ |
|
1079
|
int *pnContent /* Write the size of the content here */ |
|
1080
|
){ |
|
1081
|
char *z = *pz; |
|
1082
|
int len = *pLen; |
|
1083
|
int i; |
|
1084
|
int nBoundary = strlen(zBoundary); |
|
1085
|
*pnContent = len; |
|
1086
|
for(i=0; i<len; i++){ |
|
1087
|
if( z[i]=='\n' && fossil_strncmp(zBoundary, &z[i+1], |
|
1088
|
nBoundary)==0 ){ |
|
1089
|
if( i>0 && z[i-1]=='\r' ) i--; |
|
1090
|
z[i] = 0; |
|
1091
|
*pnContent = i; |
|
1092
|
i += nBoundary; |
|
1093
|
break; |
|
1094
|
} |
|
1095
|
} |
|
1096
|
*pz = &z[i]; |
|
1097
|
get_line_from_string(pz, pLen); |
|
1098
|
return z; |
|
1099
|
} |
|
1100
|
|
|
1101
|
/* |
|
1102
|
** Tokenize a line of text into as many as nArg tokens. Make |
|
1103
|
** azArg[] point to the start of each token. |
|
1104
|
** |
|
1105
|
** Tokens consist of space or semi-colon delimited words or |
|
1106
|
** strings inside double-quotes. Example: |
|
1107
|
** |
|
1108
|
** content-disposition: form-data; name="fn"; filename="index.html" |
|
1109
|
** |
|
1110
|
** The line above is tokenized as follows: |
|
1111
|
** |
|
1112
|
** azArg[0] = "content-disposition:" |
|
1113
|
** azArg[1] = "form-data" |
|
1114
|
** azArg[2] = "name=" |
|
1115
|
** azArg[3] = "fn" |
|
1116
|
** azArg[4] = "filename=" |
|
1117
|
** azArg[5] = "index.html" |
|
1118
|
** azArg[6] = 0; |
|
1119
|
** |
|
1120
|
** '\000' characters are inserted in z[] at the end of each token. |
|
1121
|
** This routine returns the total number of tokens on the line, 6 |
|
1122
|
** in the example above. |
|
1123
|
*/ |
|
1124
|
static int tokenize_line(char *z, int mxArg, char **azArg){ |
|
1125
|
int i = 0; |
|
1126
|
while( *z ){ |
|
1127
|
while( fossil_isspace(*z) || *z==';' ){ z++; } |
|
1128
|
if( *z=='"' && z[1] ){ |
|
1129
|
*z = 0; |
|
1130
|
z++; |
|
1131
|
if( i<mxArg-1 ){ azArg[i++] = z; } |
|
1132
|
while( *z && *z!='"' ){ z++; } |
|
1133
|
if( *z==0 ) break; |
|
1134
|
*z = 0; |
|
1135
|
z++; |
|
1136
|
}else{ |
|
1137
|
if( i<mxArg-1 ){ azArg[i++] = z; } |
|
1138
|
while( *z && !fossil_isspace(*z) && *z!=';' && *z!='"' ){ z++; } |
|
1139
|
if( *z && *z!='"' ){ |
|
1140
|
*z = 0; |
|
1141
|
z++; |
|
1142
|
} |
|
1143
|
} |
|
1144
|
} |
|
1145
|
azArg[i] = 0; |
|
1146
|
return i; |
|
1147
|
} |
|
1148
|
|
|
1149
|
/* |
|
1150
|
** Scan the multipart-form content and make appropriate entries |
|
1151
|
** into the parameter table. |
|
1152
|
** |
|
1153
|
** The content string "z" is modified by this routine but it is |
|
1154
|
** not copied. The calling function must not deallocate or modify |
|
1155
|
** "z" after this routine finishes or it could corrupt the parameter |
|
1156
|
** table. |
|
1157
|
*/ |
|
1158
|
static void process_multipart_form_data(char *z, int len){ |
|
1159
|
char *zLine; |
|
1160
|
int nArg, i; |
|
1161
|
char *zBoundary; |
|
1162
|
char *zValue; |
|
1163
|
char *zName = 0; |
|
1164
|
int showBytes = 0; |
|
1165
|
char *azArg[50]; |
|
1166
|
|
|
1167
|
zBoundary = get_line_from_string(&z, &len); |
|
1168
|
if( zBoundary==0 ) return; |
|
1169
|
while( (zLine = get_line_from_string(&z, &len))!=0 ){ |
|
1170
|
if( zLine[0]==0 ){ |
|
1171
|
int nContent = 0; |
|
1172
|
zValue = get_bounded_content(&z, &len, zBoundary, &nContent); |
|
1173
|
if( zName && zValue ){ |
|
1174
|
if( fossil_islower(zName[0]) ){ |
|
1175
|
cgi_set_parameter_nocopy(zName, zValue, 1); |
|
1176
|
if( showBytes ){ |
|
1177
|
cgi_set_parameter_nocopy(mprintf("%s:bytes", zName), |
|
1178
|
mprintf("%d",nContent), 1); |
|
1179
|
} |
|
1180
|
}else if( fossil_isupper(zName[0]) ){ |
|
1181
|
cgi_set_parameter_nocopy_tolower(zName, zValue, 1); |
|
1182
|
if( showBytes ){ |
|
1183
|
cgi_set_parameter_nocopy_tolower(mprintf("%s:bytes", zName), |
|
1184
|
mprintf("%d",nContent), 1); |
|
1185
|
} |
|
1186
|
} |
|
1187
|
} |
|
1188
|
zName = 0; |
|
1189
|
showBytes = 0; |
|
1190
|
}else{ |
|
1191
|
nArg = tokenize_line(zLine, count(azArg), azArg); |
|
1192
|
for(i=0; i<nArg; i++){ |
|
1193
|
int c = fossil_tolower(azArg[i][0]); |
|
1194
|
int n = strlen(azArg[i]); |
|
1195
|
if( c=='c' && sqlite3_strnicmp(azArg[i],"content-disposition:",n)==0 ){ |
|
1196
|
i++; |
|
1197
|
}else if( c=='n' && sqlite3_strnicmp(azArg[i],"name=",n)==0 ){ |
|
1198
|
zName = azArg[++i]; |
|
1199
|
}else if( c=='f' && sqlite3_strnicmp(azArg[i],"filename=",n)==0 ){ |
|
1200
|
char *z = azArg[++i]; |
|
1201
|
if( zName && z ){ |
|
1202
|
if( fossil_islower(zName[0]) ){ |
|
1203
|
cgi_set_parameter_nocopy(mprintf("%s:filename",zName), z, 1); |
|
1204
|
}else if( fossil_isupper(zName[0]) ){ |
|
1205
|
cgi_set_parameter_nocopy_tolower(mprintf("%s:filename",zName), |
|
1206
|
z, 1); |
|
1207
|
} |
|
1208
|
} |
|
1209
|
showBytes = 1; |
|
1210
|
}else if( c=='c' && sqlite3_strnicmp(azArg[i],"content-type:",n)==0 ){ |
|
1211
|
char *z = azArg[++i]; |
|
1212
|
if( zName && z ){ |
|
1213
|
if( fossil_islower(zName[0]) ){ |
|
1214
|
cgi_set_parameter_nocopy(mprintf("%s:mimetype",zName), z, 1); |
|
1215
|
}else if( fossil_isupper(zName[0]) ){ |
|
1216
|
cgi_set_parameter_nocopy_tolower(mprintf("%s:mimetype",zName), |
|
1217
|
z, 1); |
|
1218
|
} |
|
1219
|
} |
|
1220
|
} |
|
1221
|
} |
|
1222
|
} |
|
1223
|
} |
|
1224
|
} |
|
1225
|
|
|
1226
|
|
|
1227
|
#ifdef FOSSIL_ENABLE_JSON |
|
1228
|
/* |
|
1229
|
** Reads a JSON object from the given blob, which is assumed to have |
|
1230
|
** been populated by the caller from stdin, the SSL API, or a file, as |
|
1231
|
** appropriate for the particular use case. On success g.json.post is |
|
1232
|
** updated to hold the content. On error a FSL_JSON_E_INVALID_REQUEST |
|
1233
|
** response is output and fossil_exit() is called (in HTTP mode exit |
|
1234
|
** code 0 is used). |
|
1235
|
*/ |
|
1236
|
void cgi_parse_POST_JSON( Blob * pIn ){ |
|
1237
|
cson_value * jv = NULL; |
|
1238
|
cson_parse_info pinfo = cson_parse_info_empty; |
|
1239
|
assert(g.json.gc.a && "json_bootstrap_early() was not called!"); |
|
1240
|
jv = cson_parse_Blob(pIn, &pinfo); |
|
1241
|
if( jv==NULL ){ |
|
1242
|
goto invalidRequest; |
|
1243
|
}else{ |
|
1244
|
json_gc_add( "POST.JSON", jv ); |
|
1245
|
g.json.post.v = jv; |
|
1246
|
g.json.post.o = cson_value_get_object( jv ); |
|
1247
|
if( !g.json.post.o ){ /* we don't support non-Object (Array) requests */ |
|
1248
|
goto invalidRequest; |
|
1249
|
} |
|
1250
|
} |
|
1251
|
return; |
|
1252
|
invalidRequest: |
|
1253
|
cgi_set_content_type(json_guess_content_type()); |
|
1254
|
if(0 != pinfo.errorCode){ /* fancy error message */ |
|
1255
|
char * msg = mprintf("JSON parse error at line %u, column %u, " |
|
1256
|
"byte offset %u: %s", |
|
1257
|
pinfo.line, pinfo.col, pinfo.length, |
|
1258
|
cson_rc_string(pinfo.errorCode)); |
|
1259
|
json_err( FSL_JSON_E_INVALID_REQUEST, msg, 1 ); |
|
1260
|
fossil_free(msg); |
|
1261
|
}else if(jv && !g.json.post.o){ |
|
1262
|
json_err( FSL_JSON_E_INVALID_REQUEST, |
|
1263
|
"Request envelope must be a JSON Object (not array).", 1 ); |
|
1264
|
}else{ /* generic error message */ |
|
1265
|
json_err( FSL_JSON_E_INVALID_REQUEST, NULL, 1 ); |
|
1266
|
} |
|
1267
|
fossil_exit( g.isHTTP ? 0 : 1); |
|
1268
|
} |
|
1269
|
#endif /* FOSSIL_ENABLE_JSON */ |
|
1270
|
|
|
1271
|
/* |
|
1272
|
** Log HTTP traffic to a file. Begin the log on first use. Close the log |
|
1273
|
** when the argument is NULL. |
|
1274
|
*/ |
|
1275
|
void cgi_trace(const char *z){ |
|
1276
|
static FILE *pLog = 0; |
|
1277
|
if( g.fHttpTrace==0 ) return; |
|
1278
|
if( z==0 ){ |
|
1279
|
if( pLog ) fclose(pLog); |
|
1280
|
pLog = 0; |
|
1281
|
return; |
|
1282
|
} |
|
1283
|
if( pLog==0 ){ |
|
1284
|
char zFile[50]; |
|
1285
|
#if defined(_WIN32) |
|
1286
|
unsigned r; |
|
1287
|
sqlite3_randomness(sizeof(r), &r); |
|
1288
|
sqlite3_snprintf(sizeof(zFile), zFile, "httplog-%08x.txt", r); |
|
1289
|
#else |
|
1290
|
sqlite3_snprintf(sizeof(zFile), zFile, "httplog-%05d.txt", getpid()); |
|
1291
|
#endif |
|
1292
|
pLog = fossil_fopen(zFile, "wb"); |
|
1293
|
if( pLog ){ |
|
1294
|
fprintf(stderr, "# open log on %s\n", zFile); |
|
1295
|
}else{ |
|
1296
|
fprintf(stderr, "# failed to open %s\n", zFile); |
|
1297
|
return; |
|
1298
|
} |
|
1299
|
} |
|
1300
|
fputs(z, pLog); |
|
1301
|
} |
|
1302
|
|
|
1303
|
/* Forward declaration */ |
|
1304
|
static NORETURN void malformed_request(const char *zMsg, ...); |
|
1305
|
|
|
1306
|
/* |
|
1307
|
** Checks the QUERY_STRING environment variable, sets it up via |
|
1308
|
** add_param_list() and, if found, applies its "skin" setting. Returns |
|
1309
|
** 0 if no QUERY_STRING is set, else it returns a bitmask of: |
|
1310
|
** |
|
1311
|
** 0x01 = QUERY_STRING was set up |
|
1312
|
** 0x02 = "skin" URL param arg was processed |
|
1313
|
** 0x04 = "x-f-l-c" cookie arg was processed. |
|
1314
|
** |
|
1315
|
* In the case of the skin, the cookie may still need flushing |
|
1316
|
** by the page, via cookie_render(). |
|
1317
|
*/ |
|
1318
|
int cgi_setup_query_string(void){ |
|
1319
|
int rc = 0; |
|
1320
|
char * z = (char*)P("QUERY_STRING"); |
|
1321
|
if( z ){ |
|
1322
|
rc = 0x01; |
|
1323
|
z = fossil_strdup(z); |
|
1324
|
add_param_list(z, '&', 0); |
|
1325
|
z = (char*)P("skin"); |
|
1326
|
if( z ){ |
|
1327
|
char *zErr = skin_use_alternative(z, 2, SKIN_FROM_QPARAM); |
|
1328
|
rc |= 0x02; |
|
1329
|
if( !zErr && P("once")==0 ){ |
|
1330
|
cookie_write_parameter("skin","skin",z); |
|
1331
|
/* Per /chat discussion, passing ?skin=... without "once" |
|
1332
|
** implies the "udc" argument, so we force that into the |
|
1333
|
** environment here. */ |
|
1334
|
cgi_set_parameter_nocopy("udc", "1", 1); |
|
1335
|
} |
|
1336
|
fossil_free(zErr); |
|
1337
|
} |
|
1338
|
} |
|
1339
|
if( !g.syncInfo.zLoginCard && 0!=(z=(char*)P("x-f-l-c")) ){ |
|
1340
|
/* x-f-l-c (X-Fossil-Login-Card card transmitted via cookie |
|
1341
|
** instead of in the sync payload. */ |
|
1342
|
rc |= 0x04; |
|
1343
|
g.syncInfo.zLoginCard = fossil_strdup(z); |
|
1344
|
g.syncInfo.fLoginCardMode |= 0x02; |
|
1345
|
cgi_delete_parameter("x-f-l-c"); |
|
1346
|
} |
|
1347
|
return rc; |
|
1348
|
} |
|
1349
|
|
|
1350
|
/* |
|
1351
|
** Initialize the query parameter database. Information is pulled from |
|
1352
|
** the QUERY_STRING environment variable (if it exists), from standard |
|
1353
|
** input if there is POST data, and from HTTP_COOKIE. |
|
1354
|
** |
|
1355
|
** REQUEST_URI, PATH_INFO, and SCRIPT_NAME are related as follows: |
|
1356
|
** |
|
1357
|
** REQUEST_URI == SCRIPT_NAME + PATH_INFO |
|
1358
|
** |
|
1359
|
** Or if QUERY_STRING is not empty: |
|
1360
|
** |
|
1361
|
** REQUEST_URI == SCRIPT_NAME + PATH_INFO + '?' + QUERY_STRING |
|
1362
|
** |
|
1363
|
** Where "+" means concatenate. Fossil requires SCRIPT_NAME. If |
|
1364
|
** REQUEST_URI is provided but PATH_INFO is not, then PATH_INFO is |
|
1365
|
** computed from REQUEST_URI and SCRIPT_NAME. If PATH_INFO is provided |
|
1366
|
** but REQUEST_URI is not, then compute REQUEST_URI from PATH_INFO and |
|
1367
|
** SCRIPT_NAME. If neither REQUEST_URI nor PATH_INFO are provided, then |
|
1368
|
** assume that PATH_INFO is an empty string and set REQUEST_URI equal |
|
1369
|
** to PATH_INFO. |
|
1370
|
** |
|
1371
|
** Sometimes PATH_INFO is missing and SCRIPT_NAME is not a prefix of |
|
1372
|
** REQUEST_URI. (See https://fossil-scm.org/forum/forumpost/049e8650ed) |
|
1373
|
** In that case, truncate SCRIPT_NAME so that it is a proper prefix |
|
1374
|
** of REQUEST_URI. |
|
1375
|
** |
|
1376
|
** SCGI typically omits PATH_INFO. CGI sometimes omits REQUEST_URI and |
|
1377
|
** PATH_INFO when it is empty. |
|
1378
|
** |
|
1379
|
** CGI Parameter quick reference: |
|
1380
|
** |
|
1381
|
** REQUEST_URI |
|
1382
|
** _____________|________________ |
|
1383
|
** / \ |
|
1384
|
** https://fossil-scm.org/forum/info/12736b30c072551a?t=c |
|
1385
|
** \___/ \____________/\____/\____________________/ \_/ |
|
1386
|
** | | | | | |
|
1387
|
** | HTTP_HOST | PATH_INFO QUERY_STRING |
|
1388
|
** | | |
|
1389
|
** REQUEST_SCHEMA SCRIPT_NAME |
|
1390
|
** |
|
1391
|
*/ |
|
1392
|
void cgi_init(void){ |
|
1393
|
char *z; |
|
1394
|
const char *zType; |
|
1395
|
char *zSemi; |
|
1396
|
int len; |
|
1397
|
const char *zRequestUri = cgi_parameter("REQUEST_URI",0); |
|
1398
|
const char *zScriptName = cgi_parameter("SCRIPT_NAME",0); |
|
1399
|
const char *zPathInfo = cgi_parameter("PATH_INFO",0); |
|
1400
|
const char *zContentLength = 0; |
|
1401
|
#ifdef _WIN32 |
|
1402
|
const char *zServerSoftware = cgi_parameter("SERVER_SOFTWARE",0); |
|
1403
|
#endif |
|
1404
|
|
|
1405
|
#ifdef FOSSIL_ENABLE_JSON |
|
1406
|
const int noJson = P("no_json")!=0; |
|
1407
|
#endif |
|
1408
|
g.isHTTP = 1; |
|
1409
|
cgi_destination(CGI_BODY); |
|
1410
|
|
|
1411
|
/* We must have SCRIPT_NAME. If the web server did not supply it, try |
|
1412
|
** to compute it from REQUEST_URI and PATH_INFO. */ |
|
1413
|
if( zScriptName==0 ){ |
|
1414
|
if( zRequestUri==0 || zPathInfo==0 ){ |
|
1415
|
malformed_request("missing SCRIPT_NAME"); /* Does not return */ |
|
1416
|
} |
|
1417
|
z = strstr(zRequestUri,zPathInfo); |
|
1418
|
if( z==0 ){ |
|
1419
|
malformed_request("PATH_INFO not found in REQUEST_URI"); |
|
1420
|
} |
|
1421
|
zScriptName = fossil_strndup(zRequestUri,(int)(z-zRequestUri)); |
|
1422
|
cgi_set_parameter("SCRIPT_NAME", zScriptName); |
|
1423
|
} |
|
1424
|
|
|
1425
|
#ifdef _WIN32 |
|
1426
|
/* The Microsoft IIS web server does not define REQUEST_URI, instead it uses |
|
1427
|
** PATH_INFO for virtually the same purpose. Define REQUEST_URI the same as |
|
1428
|
** PATH_INFO and redefine PATH_INFO with SCRIPT_NAME removed from the |
|
1429
|
** beginning. */ |
|
1430
|
if( zServerSoftware && strstr(zServerSoftware, "Microsoft-IIS") ){ |
|
1431
|
int i, j; |
|
1432
|
cgi_set_parameter("REQUEST_URI", zPathInfo); |
|
1433
|
for(i=0; zPathInfo[i]==zScriptName[i] && zPathInfo[i]; i++){} |
|
1434
|
for(j=i; zPathInfo[j] && zPathInfo[j]!='?'; j++){} |
|
1435
|
zPathInfo = fossil_strndup(zPathInfo+i, j-i); |
|
1436
|
cgi_replace_parameter("PATH_INFO", zPathInfo); |
|
1437
|
} |
|
1438
|
#endif |
|
1439
|
if( zRequestUri==0 ){ |
|
1440
|
const char *z = zPathInfo; |
|
1441
|
const char *zQS = cgi_parameter("QUERY_STRING",0); |
|
1442
|
if( zPathInfo==0 ){ |
|
1443
|
malformed_request("missing PATH_INFO and/or REQUEST_URI"); |
|
1444
|
} |
|
1445
|
if( z[0]=='/' ) z++; |
|
1446
|
if( zQS && zQS[0] ){ |
|
1447
|
zRequestUri = mprintf("%s/%s?%s", zScriptName, z, zQS); |
|
1448
|
}else{ |
|
1449
|
zRequestUri = mprintf("%s/%s", zScriptName, z); |
|
1450
|
} |
|
1451
|
cgi_set_parameter("REQUEST_URI", zRequestUri); |
|
1452
|
} |
|
1453
|
if( zPathInfo==0 ){ |
|
1454
|
int i, j; |
|
1455
|
for(i=0; zRequestUri[i]==zScriptName[i] && zRequestUri[i]; i++){} |
|
1456
|
for(j=i; zRequestUri[j] && zRequestUri[j]!='?'; j++){} |
|
1457
|
zPathInfo = fossil_strndup(zRequestUri+i, j-i); |
|
1458
|
cgi_set_parameter_nocopy("PATH_INFO", zPathInfo, 0); |
|
1459
|
if( j>i && zScriptName[i]!=0 ){ |
|
1460
|
/* If SCRIPT_NAME is not a prefix of REQUEST_URI, truncate it so |
|
1461
|
** that it is. See https://fossil-scm.org/forum/forumpost/049e8650ed |
|
1462
|
*/ |
|
1463
|
char *zNew = fossil_strndup(zScriptName, i); |
|
1464
|
cgi_replace_parameter("SCRIPT_NAME", zNew); |
|
1465
|
} |
|
1466
|
} |
|
1467
|
#ifdef FOSSIL_ENABLE_JSON |
|
1468
|
if(noJson==0 && json_request_is_json_api(zPathInfo)){ |
|
1469
|
/* We need to change some following behaviour depending on whether |
|
1470
|
** we are operating in JSON mode or not. We cannot, however, be |
|
1471
|
** certain whether we should/need to be in JSON mode until the |
|
1472
|
** PATH_INFO is set up. |
|
1473
|
*/ |
|
1474
|
g.json.isJsonMode = 1; |
|
1475
|
json_bootstrap_early(); |
|
1476
|
}else{ |
|
1477
|
assert(!g.json.isJsonMode && |
|
1478
|
"Internal misconfiguration of g.json.isJsonMode"); |
|
1479
|
} |
|
1480
|
#endif |
|
1481
|
z = (char*)P("HTTP_COOKIE"); |
|
1482
|
if( z ){ |
|
1483
|
z = fossil_strdup(z); |
|
1484
|
add_param_list(z, ';', 0); |
|
1485
|
z = (char*)cookie_value("skin",0); |
|
1486
|
if(z){ |
|
1487
|
skin_use_alternative(z, 2, SKIN_FROM_COOKIE); |
|
1488
|
} |
|
1489
|
} |
|
1490
|
|
|
1491
|
cgi_setup_query_string(); |
|
1492
|
|
|
1493
|
z = (char*)P("REMOTE_ADDR"); |
|
1494
|
if( z ){ |
|
1495
|
g.zIpAddr = fossil_strdup(z); |
|
1496
|
} |
|
1497
|
|
|
1498
|
zContentLength = P("CONTENT_LENGTH"); |
|
1499
|
if( zContentLength==0 ){ |
|
1500
|
len = 0; |
|
1501
|
if( sqlite3_stricmp(PD("REQUEST_METHOD",""),"POST")==0 ){ |
|
1502
|
malformed_request("missing CONTENT_LENGTH on a POST method"); |
|
1503
|
} |
|
1504
|
}else{ |
|
1505
|
len = atoi(zContentLength); |
|
1506
|
} |
|
1507
|
zType = P("CONTENT_TYPE"); |
|
1508
|
zSemi = zType ? strchr(zType, ';') : 0; |
|
1509
|
if( zSemi ){ |
|
1510
|
g.zContentType = fossil_strndup(zType, (int)(zSemi-zType)); |
|
1511
|
zType = g.zContentType; |
|
1512
|
}else{ |
|
1513
|
g.zContentType = zType; |
|
1514
|
} |
|
1515
|
blob_zero(&g.cgiIn); |
|
1516
|
if( len>0 && zType ){ |
|
1517
|
if( blob_read_from_cgi(&g.cgiIn, len)<len ){ |
|
1518
|
char *zMsg = mprintf("CGI content-length mismatch: Wanted %d bytes" |
|
1519
|
" but got only %d\n", len, blob_size(&g.cgiIn)); |
|
1520
|
malformed_request(zMsg); |
|
1521
|
} |
|
1522
|
if( fossil_strcmp(zType, "application/x-fossil")==0 ){ |
|
1523
|
blob_uncompress(&g.cgiIn, &g.cgiIn); |
|
1524
|
} |
|
1525
|
#ifdef FOSSIL_ENABLE_JSON |
|
1526
|
if( noJson==0 && g.json.isJsonMode!=0 |
|
1527
|
&& json_can_consume_content_type(zType)!=0 ){ |
|
1528
|
cgi_parse_POST_JSON(&g.cgiIn); |
|
1529
|
cgi_set_content_type(json_guess_content_type()); |
|
1530
|
} |
|
1531
|
#endif /* FOSSIL_ENABLE_JSON */ |
|
1532
|
} |
|
1533
|
} |
|
1534
|
|
|
1535
|
/* |
|
1536
|
** Decode POST parameter information in the cgiIn content, if any. |
|
1537
|
*/ |
|
1538
|
void cgi_decode_post_parameters(void){ |
|
1539
|
int len = blob_size(&g.cgiIn); |
|
1540
|
if( len==0 ) return; |
|
1541
|
if( fossil_strcmp(g.zContentType,"application/x-www-form-urlencoded")==0 |
|
1542
|
|| fossil_strncmp(g.zContentType,"multipart/form-data",19)==0 |
|
1543
|
){ |
|
1544
|
char *z = blob_str(&g.cgiIn); |
|
1545
|
cgi_trace(z); |
|
1546
|
if( g.zContentType[0]=='a' ){ |
|
1547
|
add_param_list(z, '&', 1); |
|
1548
|
}else{ |
|
1549
|
process_multipart_form_data(z, len); |
|
1550
|
} |
|
1551
|
blob_init(&g.cgiIn, 0, 0); |
|
1552
|
} |
|
1553
|
} |
|
1554
|
|
|
1555
|
/* |
|
1556
|
** This is the comparison function used to sort the aParamQP[] array of |
|
1557
|
** query parameters and cookies. |
|
1558
|
*/ |
|
1559
|
static int qparam_compare(const void *a, const void *b){ |
|
1560
|
struct QParam *pA = (struct QParam*)a; |
|
1561
|
struct QParam *pB = (struct QParam*)b; |
|
1562
|
int c; |
|
1563
|
c = fossil_strcmp(pA->zName, pB->zName); |
|
1564
|
if( c==0 ){ |
|
1565
|
c = pA->seq - pB->seq; |
|
1566
|
} |
|
1567
|
return c; |
|
1568
|
} |
|
1569
|
|
|
1570
|
/* |
|
1571
|
** Return the value of a query parameter or cookie whose name is zName. |
|
1572
|
** If there is no query parameter or cookie named zName and the first |
|
1573
|
** character of zName is uppercase, then check to see if there is an |
|
1574
|
** environment variable by that name and return it if there is. As |
|
1575
|
** a last resort when nothing else matches, return zDefault. |
|
1576
|
*/ |
|
1577
|
const char *cgi_parameter(const char *zName, const char *zDefault){ |
|
1578
|
int lo, hi, mid, c; |
|
1579
|
|
|
1580
|
/* The sortQP flag is set whenever a new query parameter is inserted. |
|
1581
|
** It indicates that we need to resort the query parameters. |
|
1582
|
*/ |
|
1583
|
if( sortQP ){ |
|
1584
|
int i, j; |
|
1585
|
qsort(aParamQP, nUsedQP, sizeof(aParamQP[0]), qparam_compare); |
|
1586
|
sortQP = 0; |
|
1587
|
/* After sorting, remove duplicate parameters. The secondary sort |
|
1588
|
** key is aParamQP[].seq and we keep the first entry. That means |
|
1589
|
** with duplicate calls to cgi_set_parameter() the second and |
|
1590
|
** subsequent calls are effectively no-ops. */ |
|
1591
|
for(i=j=1; i<nUsedQP; i++){ |
|
1592
|
if( fossil_strcmp(aParamQP[i].zName,aParamQP[i-1].zName)==0 ){ |
|
1593
|
continue; |
|
1594
|
} |
|
1595
|
if( j<i ){ |
|
1596
|
memcpy(&aParamQP[j], &aParamQP[i], sizeof(aParamQP[j])); |
|
1597
|
} |
|
1598
|
j++; |
|
1599
|
} |
|
1600
|
nUsedQP = j; |
|
1601
|
} |
|
1602
|
|
|
1603
|
/* Invoking with a NULL zName is just a way to cause the parameters |
|
1604
|
** to be sorted. So go ahead and bail out in that case */ |
|
1605
|
if( zName==0 || zName[0]==0 ) return 0; |
|
1606
|
|
|
1607
|
/* Do a binary search for a matching query parameter */ |
|
1608
|
lo = 0; |
|
1609
|
hi = nUsedQP-1; |
|
1610
|
while( lo<=hi ){ |
|
1611
|
mid = (lo+hi)/2; |
|
1612
|
c = fossil_strcmp(aParamQP[mid].zName, zName); |
|
1613
|
if( c==0 ){ |
|
1614
|
CGIDEBUG(("mem-match [%s] = [%s]\n", zName, aParamQP[mid].zValue)); |
|
1615
|
aParamQP[mid].isFetched = 1; |
|
1616
|
return aParamQP[mid].zValue; |
|
1617
|
}else if( c>0 ){ |
|
1618
|
hi = mid-1; |
|
1619
|
}else{ |
|
1620
|
lo = mid+1; |
|
1621
|
} |
|
1622
|
} |
|
1623
|
|
|
1624
|
/* If no match is found and the name begins with an upper-case |
|
1625
|
** letter, then check to see if there is an environment variable |
|
1626
|
** with the given name. |
|
1627
|
*/ |
|
1628
|
if( fossil_isupper(zName[0]) ){ |
|
1629
|
const char *zValue = fossil_getenv(zName); |
|
1630
|
if( zValue ){ |
|
1631
|
cgi_set_parameter_nocopy(zName, zValue, 0); |
|
1632
|
CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue)); |
|
1633
|
return zValue; |
|
1634
|
} |
|
1635
|
} |
|
1636
|
CGIDEBUG(("no-match [%s]\n", zName)); |
|
1637
|
return zDefault; |
|
1638
|
} |
|
1639
|
|
|
1640
|
/* |
|
1641
|
** Return TRUE if the specific parameter exists and is a query parameter. |
|
1642
|
** Return FALSE if the parameter is a cookie or environment variable. |
|
1643
|
*/ |
|
1644
|
int cgi_is_qp(const char *zName){ |
|
1645
|
int i; |
|
1646
|
if( zName==0 || fossil_isupper(zName[0]) ) return 0; |
|
1647
|
for(i=0; i<nUsedQP; i++){ |
|
1648
|
if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){ |
|
1649
|
return aParamQP[i].isQP; |
|
1650
|
} |
|
1651
|
} |
|
1652
|
return 0; |
|
1653
|
} |
|
1654
|
|
|
1655
|
/* |
|
1656
|
** Renders the "begone, spider" page and exits. |
|
1657
|
*/ |
|
1658
|
static void cgi_begone_spider(const char *zName){ |
|
1659
|
Blob content = empty_blob; |
|
1660
|
cgi_set_content(&content); |
|
1661
|
style_set_current_feature("test"); |
|
1662
|
style_submenu_enable(0); |
|
1663
|
style_header("Malicious Query Detected"); |
|
1664
|
@ <h2>Begone, Knave!</h2> |
|
1665
|
@ <p>This page was generated because Fossil detected an (unsuccessful) |
|
1666
|
@ SQL injection attack or other nefarious content in your HTTP request. |
|
1667
|
@ |
|
1668
|
@ <p>If you believe you are innocent and have reached this page in error, |
|
1669
|
@ contact the Fossil developers on the Fossil-SCM Forum. Type |
|
1670
|
@ "fossil-scm forum" into any search engine to locate the Fossil-SCM Forum. |
|
1671
|
style_finish_page(); |
|
1672
|
cgi_set_status(418,"I'm a teapot"); |
|
1673
|
cgi_reply(); |
|
1674
|
fossil_errorlog("Xpossible hack attempt - 418 response on \"%s\"", zName); |
|
1675
|
exit(0); |
|
1676
|
} |
|
1677
|
|
|
1678
|
/* |
|
1679
|
** If looks_like_attack() returns true for the given string, call |
|
1680
|
** cgi_begone_spider() and does not return, else this function has no |
|
1681
|
** side effects. The range of checks performed by this function may |
|
1682
|
** be extended in the future. |
|
1683
|
** |
|
1684
|
** Checks are omitted for any logged-in user. |
|
1685
|
** |
|
1686
|
** This is the primary defense against attack. Fossil should easily be |
|
1687
|
** proof against SQL injection and XSS attacks even without this |
|
1688
|
** routine. Rather, this is an attempt to avoid denial-of-service caused |
|
1689
|
** by persistent spiders that hammer the server with dozens or hundreds of |
|
1690
|
** probes per seconds as they look for vulnerabilities. In other |
|
1691
|
** words, this is an effort to reduce the CPU load imposed by malicious |
|
1692
|
** spiders. Though those routine might help make attacks harder, it is |
|
1693
|
** not itself an impenetrably barrier against attack and should not be |
|
1694
|
** relied upon as the only defense. |
|
1695
|
*/ |
|
1696
|
void cgi_value_spider_check(const char *zTxt, const char *zName){ |
|
1697
|
if( g.zLogin==0 && looks_like_attack(zTxt) ){ |
|
1698
|
cgi_begone_spider(zName); |
|
1699
|
} |
|
1700
|
} |
|
1701
|
|
|
1702
|
/* |
|
1703
|
** A variant of cgi_parameter() with the same semantics except that if |
|
1704
|
** cgi_parameter(zName,zDefault) returns a value other than zDefault |
|
1705
|
** then it passes that value to cgi_value_spider_check(). |
|
1706
|
*/ |
|
1707
|
const char *cgi_parameter_no_attack(const char *zName, const char *zDefault){ |
|
1708
|
const char *zTxt = cgi_parameter(zName, zDefault); |
|
1709
|
|
|
1710
|
if( zTxt!=zDefault ){ |
|
1711
|
cgi_value_spider_check(zTxt, zName); |
|
1712
|
} |
|
1713
|
return zTxt; |
|
1714
|
} |
|
1715
|
|
|
1716
|
/* |
|
1717
|
** Return the value of the first defined query parameter or cookie whose |
|
1718
|
** name appears in the list of arguments. Or if no parameter is found, |
|
1719
|
** return NULL. |
|
1720
|
*/ |
|
1721
|
const char *cgi_coalesce(const char *zName, ...){ |
|
1722
|
va_list ap; |
|
1723
|
const char *z; |
|
1724
|
const char *zX; |
|
1725
|
if( zName==0 ) return 0; |
|
1726
|
z = cgi_parameter(zName, 0); |
|
1727
|
va_start(ap, zName); |
|
1728
|
while( z==0 && (zX = va_arg(ap,const char*))!=0 ){ |
|
1729
|
z = cgi_parameter(zX, 0); |
|
1730
|
} |
|
1731
|
va_end(ap); |
|
1732
|
return z; |
|
1733
|
} |
|
1734
|
|
|
1735
|
/* |
|
1736
|
** Return the value of a CGI parameter with leading and trailing |
|
1737
|
** spaces removed and with internal \r\n changed to just \n |
|
1738
|
*/ |
|
1739
|
char *cgi_parameter_trimmed(const char *zName, const char *zDefault){ |
|
1740
|
const char *zIn; |
|
1741
|
char *zOut, c; |
|
1742
|
int i, j; |
|
1743
|
zIn = cgi_parameter(zName, 0); |
|
1744
|
if( zIn==0 ) zIn = zDefault; |
|
1745
|
if( zIn==0 ) return 0; |
|
1746
|
while( fossil_isspace(zIn[0]) ) zIn++; |
|
1747
|
zOut = fossil_strdup(zIn); |
|
1748
|
for(i=j=0; (c = zOut[i])!=0; i++){ |
|
1749
|
if( c=='\r' && zOut[i+1]=='\n' ) continue; |
|
1750
|
zOut[j++] = c; |
|
1751
|
} |
|
1752
|
zOut[j] = 0; |
|
1753
|
while( j>0 && fossil_isspace(zOut[j-1]) ) zOut[--j] = 0; |
|
1754
|
return zOut; |
|
1755
|
} |
|
1756
|
|
|
1757
|
/* |
|
1758
|
** Return true if the CGI parameter zName exists and is not equal to 0, |
|
1759
|
** or "no" or "off". |
|
1760
|
*/ |
|
1761
|
int cgi_parameter_boolean(const char *zName){ |
|
1762
|
const char *zIn = cgi_parameter(zName, 0); |
|
1763
|
if( zIn==0 ) return 0; |
|
1764
|
return zIn[0]==0 || is_truth(zIn); |
|
1765
|
} |
|
1766
|
|
|
1767
|
/* |
|
1768
|
** Return either an empty string "" or the string "checked" depending |
|
1769
|
** on whether or not parameter zName has value iValue. If parameter |
|
1770
|
** zName does not exist, that is assumed to be the same as value 0. |
|
1771
|
** |
|
1772
|
** This routine implements the PCK(x) and PIF(x,y) macros. The PIF(x,y) |
|
1773
|
** macro generateds " checked" if the value of parameter x equals integer y. |
|
1774
|
** PCK(x) is the same as PIF(x,1). These macros are used to generate |
|
1775
|
** the "checked" attribute on checkbox and radio controls of forms. |
|
1776
|
*/ |
|
1777
|
const char *cgi_parameter_checked(const char *zName, int iValue){ |
|
1778
|
const char *zIn = cgi_parameter(zName,0); |
|
1779
|
int x; |
|
1780
|
if( zIn==0 ){ |
|
1781
|
x = 0; |
|
1782
|
}else if( !fossil_isdigit(zIn[0]) ){ |
|
1783
|
x = is_truth(zIn); |
|
1784
|
}else{ |
|
1785
|
x = atoi(zIn); |
|
1786
|
} |
|
1787
|
return x==iValue ? "checked" : ""; |
|
1788
|
} |
|
1789
|
|
|
1790
|
/* |
|
1791
|
** Return the name of the i-th CGI parameter. Return NULL if there |
|
1792
|
** are fewer than i registered CGI parameters. |
|
1793
|
*/ |
|
1794
|
const char *cgi_parameter_name(int i){ |
|
1795
|
if( i>=0 && i<nUsedQP ){ |
|
1796
|
return aParamQP[i].zName; |
|
1797
|
}else{ |
|
1798
|
return 0; |
|
1799
|
} |
|
1800
|
} |
|
1801
|
|
|
1802
|
/* |
|
1803
|
** Print CGI debugging messages. |
|
1804
|
*/ |
|
1805
|
void cgi_debug(const char *zFormat, ...){ |
|
1806
|
va_list ap; |
|
1807
|
if( g.fDebug ){ |
|
1808
|
va_start(ap, zFormat); |
|
1809
|
vfprintf(g.fDebug, zFormat, ap); |
|
1810
|
va_end(ap); |
|
1811
|
fflush(g.fDebug); |
|
1812
|
} |
|
1813
|
} |
|
1814
|
|
|
1815
|
/* |
|
1816
|
** Return true if any of the query parameters in the argument |
|
1817
|
** list are defined. |
|
1818
|
*/ |
|
1819
|
int cgi_any(const char *z, ...){ |
|
1820
|
va_list ap; |
|
1821
|
char *z2; |
|
1822
|
if( cgi_parameter(z,0)!=0 ) return 1; |
|
1823
|
va_start(ap, z); |
|
1824
|
while( (z2 = va_arg(ap, char*))!=0 ){ |
|
1825
|
if( cgi_parameter(z2,0)!=0 ) return 1; |
|
1826
|
} |
|
1827
|
va_end(ap); |
|
1828
|
return 0; |
|
1829
|
} |
|
1830
|
|
|
1831
|
/* |
|
1832
|
** Return true if all of the query parameters in the argument list |
|
1833
|
** are defined. |
|
1834
|
*/ |
|
1835
|
int cgi_all(const char *z, ...){ |
|
1836
|
va_list ap; |
|
1837
|
char *z2; |
|
1838
|
if( cgi_parameter(z,0)==0 ) return 0; |
|
1839
|
va_start(ap, z); |
|
1840
|
while( (z2 = va_arg(ap, char*))==0 ){ |
|
1841
|
if( cgi_parameter(z2,0)==0 ) return 0; |
|
1842
|
} |
|
1843
|
va_end(ap); |
|
1844
|
return 1; |
|
1845
|
} |
|
1846
|
|
|
1847
|
/* |
|
1848
|
** Load all relevant environment variables into the parameter buffer. |
|
1849
|
** Invoke this routine prior to calling cgi_print_all() in order to see |
|
1850
|
** the full CGI environment. This routine intended for debugging purposes |
|
1851
|
** only. |
|
1852
|
*/ |
|
1853
|
void cgi_load_environment(void){ |
|
1854
|
/* The following is a list of environment variables that Fossil considers |
|
1855
|
** to be "relevant". */ |
|
1856
|
static const char *const azCgiVars[] = { |
|
1857
|
"COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE", "SCGI", |
|
1858
|
"HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING", |
|
1859
|
"HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHENTICATION", |
|
1860
|
"HTTP_CONNECTION", "HTTP_HOST", |
|
1861
|
"HTTP_IF_NONE_MATCH", "HTTP_IF_MODIFIED_SINCE", |
|
1862
|
"HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED", |
|
1863
|
"QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT", |
|
1864
|
"REMOTE_USER", "REQUEST_METHOD", "REQUEST_SCHEME", |
|
1865
|
"REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_NAME", |
|
1866
|
"SERVER_PROTOCOL", "HOME", "FOSSIL_HOME", "USERNAME", "USER", |
|
1867
|
"FOSSIL_USER", "SQLITE_TMPDIR", "TMPDIR", |
|
1868
|
"TEMP", "TMP", "FOSSIL_VFS", |
|
1869
|
"FOSSIL_FORCE_TICKET_MODERATION", "FOSSIL_FORCE_WIKI_MODERATION", |
|
1870
|
"FOSSIL_TCL_PATH", "TH1_DELETE_INTERP", "TH1_ENABLE_DOCS", |
|
1871
|
"TH1_ENABLE_HOOKS", "TH1_ENABLE_TCL", "REMOTE_HOST", |
|
1872
|
"CONTENT_TYPE", "CONTENT_LENGTH", |
|
1873
|
}; |
|
1874
|
int i; |
|
1875
|
for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]); |
|
1876
|
} |
|
1877
|
|
|
1878
|
/* |
|
1879
|
** Print all query parameters on standard output. |
|
1880
|
** This is used for testing and debugging. |
|
1881
|
** |
|
1882
|
** Omit the values of the cookies unless showAll is true. |
|
1883
|
** |
|
1884
|
** The eDest parameter determines where the output is shown: |
|
1885
|
** |
|
1886
|
** eDest==0: Rendering as HTML into the CGI reply |
|
1887
|
** eDest==1: Written to fossil_trace |
|
1888
|
** eDest==2: Written to cgi_debug |
|
1889
|
** eDest==3: Written to out (Used only by fossil_errorlog()) |
|
1890
|
*/ |
|
1891
|
void cgi_print_all(int showAll, unsigned int eDest, FILE *out){ |
|
1892
|
int i; |
|
1893
|
cgi_parameter("",""); /* Force the parameters into sorted order */ |
|
1894
|
for(i=0; i<nUsedQP; i++){ |
|
1895
|
const char *zName = aParamQP[i].zName; |
|
1896
|
const char *zValue = aParamQP[i].zValue; |
|
1897
|
if( fossil_stricmp("HTTP_COOKIE",zName)==0 |
|
1898
|
|| fossil_strnicmp("fossil-",zName,7)==0 |
|
1899
|
){ |
|
1900
|
if( !showAll ) continue; |
|
1901
|
if( eDest==3 ) zValue = "..."; |
|
1902
|
} |
|
1903
|
switch( eDest ){ |
|
1904
|
case 0: { |
|
1905
|
cgi_printf("%h = %h <br>\n", zName, zValue); |
|
1906
|
break; |
|
1907
|
} |
|
1908
|
case 1: { |
|
1909
|
fossil_trace("%s = %s\n", zName, zValue); |
|
1910
|
break; |
|
1911
|
} |
|
1912
|
case 2: { |
|
1913
|
cgi_debug("%s = %s\n", zName, zValue); |
|
1914
|
break; |
|
1915
|
} |
|
1916
|
case 3: { |
|
1917
|
if( zValue!=0 && strlen(zValue)>100 ){ |
|
1918
|
fprintf(out,"%s = %.100s...\n", zName, zValue); |
|
1919
|
}else{ |
|
1920
|
fprintf(out,"%s = %s\n", zName, zValue); |
|
1921
|
} |
|
1922
|
break; |
|
1923
|
} |
|
1924
|
} |
|
1925
|
} |
|
1926
|
} |
|
1927
|
|
|
1928
|
/* |
|
1929
|
** Put information about the N-th parameter into arguments. |
|
1930
|
** Return non-zero on success, and return 0 if there is no N-th parameter. |
|
1931
|
*/ |
|
1932
|
int cgi_param_info( |
|
1933
|
int N, |
|
1934
|
const char **pzName, |
|
1935
|
const char **pzValue, |
|
1936
|
int *pbIsQP |
|
1937
|
){ |
|
1938
|
if( N>=0 && N<nUsedQP ){ |
|
1939
|
*pzName = aParamQP[N].zName; |
|
1940
|
*pzValue = aParamQP[N].zValue; |
|
1941
|
*pbIsQP = aParamQP[N].isQP; |
|
1942
|
return 1; |
|
1943
|
}else{ |
|
1944
|
*pzName = 0; |
|
1945
|
*pzValue = 0; |
|
1946
|
*pbIsQP = 0; |
|
1947
|
return 0; |
|
1948
|
} |
|
1949
|
} |
|
1950
|
|
|
1951
|
/* |
|
1952
|
** Export all untagged query parameters (but not cookies or environment |
|
1953
|
** variables) as hidden values of a form. |
|
1954
|
*/ |
|
1955
|
void cgi_query_parameters_to_hidden(void){ |
|
1956
|
int i; |
|
1957
|
const char *zN, *zV; |
|
1958
|
for(i=0; i<nUsedQP; i++){ |
|
1959
|
if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue; |
|
1960
|
zN = aParamQP[i].zName; |
|
1961
|
zV = aParamQP[i].zValue; |
|
1962
|
@ <input type="hidden" name="%h(zN)" value="%h(zV)"> |
|
1963
|
} |
|
1964
|
} |
|
1965
|
|
|
1966
|
/* |
|
1967
|
** Export all untagged query parameters (but not cookies or environment |
|
1968
|
** variables) to the HQuery object. |
|
1969
|
*/ |
|
1970
|
void cgi_query_parameters_to_url(HQuery *p){ |
|
1971
|
int i; |
|
1972
|
for(i=0; i<nUsedQP; i++){ |
|
1973
|
if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue; |
|
1974
|
url_add_parameter(p, aParamQP[i].zName, aParamQP[i].zValue); |
|
1975
|
} |
|
1976
|
} |
|
1977
|
|
|
1978
|
/* |
|
1979
|
** Reconstruct the URL into memory obtained from fossil_malloc() and |
|
1980
|
** return a pointer to that URL. |
|
1981
|
*/ |
|
1982
|
char *cgi_reconstruct_original_url(void){ |
|
1983
|
int i; |
|
1984
|
char cSep = '?'; |
|
1985
|
Blob url; |
|
1986
|
blob_init(&url, 0, 0); |
|
1987
|
blob_appendf(&url, "%s/%s", g.zBaseURL, g.zPath); |
|
1988
|
for(i=0; i<nUsedQP; i++){ |
|
1989
|
if( aParamQP[i].isQP ){ |
|
1990
|
struct QParam *p = &aParamQP[i]; |
|
1991
|
if( p->zValue && p->zValue[0] ){ |
|
1992
|
blob_appendf(&url, "%c%t=%t", cSep, p->zName, p->zValue); |
|
1993
|
}else{ |
|
1994
|
blob_appendf(&url, "%c%t", cSep, p->zName); |
|
1995
|
} |
|
1996
|
cSep = '&'; |
|
1997
|
} |
|
1998
|
} |
|
1999
|
return blob_str(&url); |
|
2000
|
} |
|
2001
|
|
|
2002
|
/* |
|
2003
|
** Tag query parameter zName so that it is not exported by |
|
2004
|
** cgi_query_parameters_to_hidden(). Or if zName==0, then |
|
2005
|
** untag all query parameters. |
|
2006
|
*/ |
|
2007
|
void cgi_tag_query_parameter(const char *zName){ |
|
2008
|
int i; |
|
2009
|
if( zName==0 ){ |
|
2010
|
for(i=0; i<nUsedQP; i++) aParamQP[i].cTag = 0; |
|
2011
|
}else{ |
|
2012
|
for(i=0; i<nUsedQP; i++){ |
|
2013
|
if( strcmp(zName,aParamQP[i].zName)==0 ) aParamQP[i].cTag = 1; |
|
2014
|
} |
|
2015
|
} |
|
2016
|
} |
|
2017
|
|
|
2018
|
/* |
|
2019
|
** This routine works like "printf" except that it has the |
|
2020
|
** extra formatting capabilities such as %h and %t. |
|
2021
|
*/ |
|
2022
|
void cgi_printf(const char *zFormat, ...){ |
|
2023
|
va_list ap; |
|
2024
|
va_start(ap,zFormat); |
|
2025
|
vxprintf(pContent,zFormat,ap); |
|
2026
|
va_end(ap); |
|
2027
|
} |
|
2028
|
|
|
2029
|
/* |
|
2030
|
** This routine works like "vprintf" except that it has the |
|
2031
|
** extra formatting capabilities such as %h and %t. |
|
2032
|
*/ |
|
2033
|
void cgi_vprintf(const char *zFormat, va_list ap){ |
|
2034
|
vxprintf(pContent,zFormat,ap); |
|
2035
|
} |
|
2036
|
|
|
2037
|
|
|
2038
|
/* |
|
2039
|
** Send a reply indicating that the HTTP request was malformed |
|
2040
|
*/ |
|
2041
|
static NORETURN void malformed_request(const char *zMsg, ...){ |
|
2042
|
va_list ap; |
|
2043
|
char *z; |
|
2044
|
va_start(ap, zMsg); |
|
2045
|
z = vmprintf(zMsg, ap); |
|
2046
|
va_end(ap); |
|
2047
|
cgi_set_status(400, "Bad Request"); |
|
2048
|
zReplyMimeType = "text/plain"; |
|
2049
|
if( g.zReqType==0 ) g.zReqType = "WWW"; |
|
2050
|
if( g.zReqType[0]=='C' && PD("SERVER_SOFTWARE",0)!=0 ){ |
|
2051
|
const char *zServer = PD("SERVER_SOFTWARE",""); |
|
2052
|
cgi_printf("Bad CGI Request from \"%s\": %s\n",zServer,z); |
|
2053
|
}else{ |
|
2054
|
cgi_printf("Bad %s Request: %s\n", g.zReqType, z); |
|
2055
|
} |
|
2056
|
fossil_free(z); |
|
2057
|
cgi_reply(); |
|
2058
|
fossil_exit(0); |
|
2059
|
} |
|
2060
|
|
|
2061
|
/* |
|
2062
|
** Panic and die while processing a webpage. |
|
2063
|
*/ |
|
2064
|
NORETURN void cgi_panic(const char *zFormat, ...){ |
|
2065
|
va_list ap; |
|
2066
|
cgi_reset_content(); |
|
2067
|
#ifdef FOSSIL_ENABLE_JSON |
|
2068
|
if( g.json.isJsonMode ){ |
|
2069
|
char * zMsg; |
|
2070
|
va_start(ap, zFormat); |
|
2071
|
zMsg = vmprintf(zFormat,ap); |
|
2072
|
va_end(ap); |
|
2073
|
json_err( FSL_JSON_E_PANIC, zMsg, 1 ); |
|
2074
|
free(zMsg); |
|
2075
|
fossil_exit( g.isHTTP ? 0 : 1 ); |
|
2076
|
}else |
|
2077
|
#endif /* FOSSIL_ENABLE_JSON */ |
|
2078
|
{ |
|
2079
|
cgi_set_status(500, "Internal Server Error"); |
|
2080
|
cgi_printf( |
|
2081
|
"<html><body><h1>Internal Server Error</h1>\n" |
|
2082
|
"<plaintext>" |
|
2083
|
); |
|
2084
|
va_start(ap, zFormat); |
|
2085
|
vxprintf(pContent,zFormat,ap); |
|
2086
|
va_end(ap); |
|
2087
|
cgi_reply(); |
|
2088
|
fossil_exit(1); |
|
2089
|
} |
|
2090
|
} |
|
2091
|
|
|
2092
|
/* z[] is the value of an X-FORWARDED-FOR: line in an HTTP header. |
|
2093
|
** Return a pointer to a string containing the real IP address, or a |
|
2094
|
** NULL pointer to stick with the IP address previously computed and |
|
2095
|
** loaded into g.zIpAddr. |
|
2096
|
*/ |
|
2097
|
static const char *cgi_accept_forwarded_for(const char *z){ |
|
2098
|
int i; |
|
2099
|
if( !cgi_is_loopback(g.zIpAddr) ){ |
|
2100
|
/* Only accept X-FORWARDED-FOR if input coming from the local machine */ |
|
2101
|
return 0; |
|
2102
|
} |
|
2103
|
i = strlen(z)-1; |
|
2104
|
while( i>=0 && z[i]!=',' && !fossil_isspace(z[i]) ) i--; |
|
2105
|
return &z[++i]; |
|
2106
|
} |
|
2107
|
|
|
2108
|
/* |
|
2109
|
** Remove the first space-delimited token from a string and return |
|
2110
|
** a pointer to it. Add a NULL to the string to terminate the token. |
|
2111
|
** Make *zLeftOver point to the start of the next token. |
|
2112
|
*/ |
|
2113
|
static char *extract_token(char *zInput, char **zLeftOver){ |
|
2114
|
char *zResult = 0; |
|
2115
|
if( zInput==0 ){ |
|
2116
|
if( zLeftOver ) *zLeftOver = 0; |
|
2117
|
return 0; |
|
2118
|
} |
|
2119
|
while( fossil_isspace(*zInput) ){ zInput++; } |
|
2120
|
zResult = zInput; |
|
2121
|
while( *zInput && !fossil_isspace(*zInput) ){ zInput++; } |
|
2122
|
if( *zInput ){ |
|
2123
|
*zInput = 0; |
|
2124
|
zInput++; |
|
2125
|
while( fossil_isspace(*zInput) ){ zInput++; } |
|
2126
|
} |
|
2127
|
if( zLeftOver ){ *zLeftOver = zInput; } |
|
2128
|
return zResult; |
|
2129
|
} |
|
2130
|
|
|
2131
|
/* |
|
2132
|
** All possible forms of an IP address. Needed to work around GCC strict |
|
2133
|
** aliasing rules. |
|
2134
|
*/ |
|
2135
|
typedef union { |
|
2136
|
struct sockaddr sa; /* Abstract superclass */ |
|
2137
|
struct sockaddr_in sa4; /* IPv4 */ |
|
2138
|
struct sockaddr_in6 sa6; /* IPv6 */ |
|
2139
|
struct sockaddr_storage sas; /* Should be the maximum of the above 3 */ |
|
2140
|
} address; |
|
2141
|
|
|
2142
|
/* |
|
2143
|
** Determine the IP address on the other side of a connection. |
|
2144
|
** Return a pointer to a string. Or return 0 if unable. |
|
2145
|
** |
|
2146
|
** The string is held in a static buffer that is overwritten on |
|
2147
|
** each call. |
|
2148
|
*/ |
|
2149
|
char *cgi_remote_ip(int fd){ |
|
2150
|
address remoteAddr; |
|
2151
|
socklen_t size = sizeof(remoteAddr); |
|
2152
|
static char zHost[NI_MAXHOST]; |
|
2153
|
if( getpeername(0, &remoteAddr.sa, &size) ){ |
|
2154
|
return 0; |
|
2155
|
} |
|
2156
|
if( getnameinfo(&remoteAddr.sa, size, zHost, sizeof(zHost), 0, 0, |
|
2157
|
NI_NUMERICHOST) ){ |
|
2158
|
return 0; |
|
2159
|
} |
|
2160
|
return zHost; |
|
2161
|
} |
|
2162
|
|
|
2163
|
/* |
|
2164
|
** This routine handles a single HTTP request which is coming in on |
|
2165
|
** g.httpIn and which replies on g.httpOut |
|
2166
|
** |
|
2167
|
** The HTTP request is read from g.httpIn and is used to initialize |
|
2168
|
** entries in the cgi_parameter() hash, as if those entries were |
|
2169
|
** environment variables. A call to cgi_init() completes |
|
2170
|
** the setup. Once all the setup is finished, this procedure returns |
|
2171
|
** and subsequent code handles the actual generation of the webpage. |
|
2172
|
*/ |
|
2173
|
void cgi_handle_http_request(const char *zIpAddr){ |
|
2174
|
char *z, *zToken; |
|
2175
|
int i; |
|
2176
|
const char *zScheme = "http"; |
|
2177
|
char zLine[2000]; /* A single line of input. */ |
|
2178
|
g.fullHttpReply = 1; |
|
2179
|
g.zReqType = "HTTP"; |
|
2180
|
|
|
2181
|
if( cgi_fgets(zLine, sizeof(zLine))==0 ){ |
|
2182
|
malformed_request("missing header"); |
|
2183
|
} |
|
2184
|
blob_append(&g.httpHeader, zLine, -1); |
|
2185
|
cgi_trace(zLine); |
|
2186
|
zToken = extract_token(zLine, &z); |
|
2187
|
if( zToken==0 ){ |
|
2188
|
malformed_request("malformed HTTP header"); |
|
2189
|
} |
|
2190
|
if( fossil_strcmp(zToken,"GET")!=0 |
|
2191
|
&& fossil_strcmp(zToken,"POST")!=0 |
|
2192
|
&& fossil_strcmp(zToken,"HEAD")!=0 |
|
2193
|
){ |
|
2194
|
malformed_request("unsupported HTTP method: \"%s\" - Fossil only supports " |
|
2195
|
"GET, POST, and HEAD", zToken); |
|
2196
|
} |
|
2197
|
cgi_setenv("GATEWAY_INTERFACE","CGI/1.0"); |
|
2198
|
cgi_setenv("REQUEST_METHOD",zToken); |
|
2199
|
zToken = extract_token(z, &z); |
|
2200
|
if( zToken==0 ){ |
|
2201
|
malformed_request("malformed URI in the HTTP header"); |
|
2202
|
} |
|
2203
|
cgi_setenv("REQUEST_URI", zToken); |
|
2204
|
cgi_setenv("SCRIPT_NAME", ""); |
|
2205
|
for(i=0; zToken[i] && zToken[i]!='?'; i++){} |
|
2206
|
if( zToken[i] ) zToken[i++] = 0; |
|
2207
|
cgi_setenv("PATH_INFO", zToken); |
|
2208
|
cgi_setenv("QUERY_STRING", &zToken[i]); |
|
2209
|
if( zIpAddr==0 ){ |
|
2210
|
zIpAddr = cgi_remote_ip(fossil_fileno(g.httpIn)); |
|
2211
|
} |
|
2212
|
if( zIpAddr ){ |
|
2213
|
cgi_setenv("REMOTE_ADDR", zIpAddr); |
|
2214
|
g.zIpAddr = fossil_strdup(zIpAddr); |
|
2215
|
} |
|
2216
|
|
|
2217
|
/* Get all the optional fields that follow the first line. |
|
2218
|
*/ |
|
2219
|
while( cgi_fgets(zLine,sizeof(zLine)) ){ |
|
2220
|
char *zFieldName; |
|
2221
|
char *zVal; |
|
2222
|
|
|
2223
|
cgi_trace(zLine); |
|
2224
|
blob_append(&g.httpHeader, zLine, -1); |
|
2225
|
zFieldName = extract_token(zLine,&zVal); |
|
2226
|
if( zFieldName==0 || *zFieldName==0 ) break; |
|
2227
|
while( fossil_isspace(*zVal) ){ zVal++; } |
|
2228
|
i = strlen(zVal); |
|
2229
|
while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; } |
|
2230
|
zVal[i] = 0; |
|
2231
|
for(i=0; zFieldName[i]; i++){ |
|
2232
|
zFieldName[i] = fossil_tolower(zFieldName[i]); |
|
2233
|
} |
|
2234
|
if( fossil_strcmp(zFieldName,"accept-encoding:")==0 ){ |
|
2235
|
cgi_setenv("HTTP_ACCEPT_ENCODING", zVal); |
|
2236
|
}else if( fossil_strcmp(zFieldName,"content-length:")==0 ){ |
|
2237
|
cgi_setenv("CONTENT_LENGTH", zVal); |
|
2238
|
}else if( fossil_strcmp(zFieldName,"content-type:")==0 ){ |
|
2239
|
cgi_setenv("CONTENT_TYPE", zVal); |
|
2240
|
}else if( fossil_strcmp(zFieldName,"cookie:")==0 ){ |
|
2241
|
cgi_setenv("HTTP_COOKIE", zVal); |
|
2242
|
}else if( fossil_strcmp(zFieldName,"https:")==0 ){ |
|
2243
|
cgi_setenv("HTTPS", zVal); |
|
2244
|
zScheme = "https"; |
|
2245
|
}else if( fossil_strcmp(zFieldName,"host:")==0 ){ |
|
2246
|
char *z; |
|
2247
|
cgi_setenv("HTTP_HOST", zVal); |
|
2248
|
z = strchr(zVal, ':'); |
|
2249
|
if( z ) z[0] = 0; |
|
2250
|
cgi_setenv("SERVER_NAME", zVal); |
|
2251
|
}else if( fossil_strcmp(zFieldName,"if-none-match:")==0 ){ |
|
2252
|
cgi_setenv("HTTP_IF_NONE_MATCH", zVal); |
|
2253
|
}else if( fossil_strcmp(zFieldName,"if-modified-since:")==0 ){ |
|
2254
|
cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal); |
|
2255
|
}else if( fossil_strcmp(zFieldName,"referer:")==0 ){ |
|
2256
|
cgi_setenv("HTTP_REFERER", zVal); |
|
2257
|
}else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){ |
|
2258
|
cgi_setenv("HTTP_USER_AGENT", zVal); |
|
2259
|
}else if( fossil_strcmp(zFieldName,"authorization:")==0 ){ |
|
2260
|
cgi_setenv("HTTP_AUTHORIZATION", zVal); |
|
2261
|
}else if( fossil_strcmp(zFieldName,"accept-language:")==0 ){ |
|
2262
|
cgi_setenv("HTTP_ACCEPT_LANGUAGE", zVal); |
|
2263
|
}else if( fossil_strcmp(zFieldName,"x-forwarded-for:")==0 ){ |
|
2264
|
const char *zIpAddr = cgi_accept_forwarded_for(zVal); |
|
2265
|
if( zIpAddr!=0 ){ |
|
2266
|
g.zIpAddr = fossil_strdup(zIpAddr); |
|
2267
|
cgi_replace_parameter("REMOTE_ADDR", g.zIpAddr); |
|
2268
|
} |
|
2269
|
}else if( fossil_strcmp(zFieldName,"range:")==0 ){ |
|
2270
|
int x1 = 0; |
|
2271
|
int x2 = 0; |
|
2272
|
if( sscanf(zVal,"bytes=%d-%d",&x1,&x2)==2 && x1>=0 && x1<=x2 ){ |
|
2273
|
rangeStart = x1; |
|
2274
|
rangeEnd = x2+1; |
|
2275
|
} |
|
2276
|
} |
|
2277
|
} |
|
2278
|
cgi_setenv("REQUEST_SCHEME",zScheme); |
|
2279
|
cgi_init(); |
|
2280
|
cgi_trace(0); |
|
2281
|
} |
|
2282
|
|
|
2283
|
/* |
|
2284
|
** This routine handles a single HTTP request from an SSH client which is |
|
2285
|
** coming in on g.httpIn and which replies on g.httpOut |
|
2286
|
** |
|
2287
|
** Once all the setup is finished, this procedure returns |
|
2288
|
** and subsequent code handles the actual generation of the webpage. |
|
2289
|
** |
|
2290
|
** It is called in a loop so some variables will need to be replaced |
|
2291
|
*/ |
|
2292
|
void cgi_handle_ssh_http_request(const char *zIpAddr){ |
|
2293
|
static int nCycles = 0; |
|
2294
|
static char *zCmd = 0; |
|
2295
|
char *z, *zToken; |
|
2296
|
char *zMethod; |
|
2297
|
int i; |
|
2298
|
size_t n; |
|
2299
|
char zLine[2000]; /* A single line of input. */ |
|
2300
|
|
|
2301
|
assert( !g.httpUseSSL ); |
|
2302
|
#ifdef FOSSIL_ENABLE_JSON |
|
2303
|
if( nCycles==0 ){ json_bootstrap_early(); } |
|
2304
|
#endif |
|
2305
|
if( zIpAddr ){ |
|
2306
|
if( nCycles==0 ){ |
|
2307
|
cgi_setenv("REMOTE_ADDR", zIpAddr); |
|
2308
|
g.zIpAddr = fossil_strdup(zIpAddr); |
|
2309
|
} |
|
2310
|
}else{ |
|
2311
|
fossil_fatal("missing SSH IP address"); |
|
2312
|
} |
|
2313
|
g.zReqType = "HTTP"; |
|
2314
|
if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ |
|
2315
|
malformed_request("missing HTTP header"); |
|
2316
|
} |
|
2317
|
cgi_trace(zLine); |
|
2318
|
zToken = extract_token(zLine, &z); |
|
2319
|
if( zToken==0 ){ |
|
2320
|
malformed_request("malformed HTTP header"); |
|
2321
|
} |
|
2322
|
|
|
2323
|
if( fossil_strcmp(zToken, "echo")==0 ){ |
|
2324
|
/* start looking for probes to complete transport_open */ |
|
2325
|
zCmd = cgi_handle_ssh_probes(zLine, sizeof(zLine), z, zToken); |
|
2326
|
if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ |
|
2327
|
malformed_request("missing HTTP header"); |
|
2328
|
} |
|
2329
|
cgi_trace(zLine); |
|
2330
|
zToken = extract_token(zLine, &z); |
|
2331
|
if( zToken==0 ){ |
|
2332
|
malformed_request("malformed HTTP header"); |
|
2333
|
} |
|
2334
|
}else if( zToken && strlen(zToken)==0 && zCmd ){ |
|
2335
|
/* transport_flip request and continued transport_open */ |
|
2336
|
cgi_handle_ssh_transport(zCmd); |
|
2337
|
if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ |
|
2338
|
malformed_request("missing HTTP header"); |
|
2339
|
} |
|
2340
|
cgi_trace(zLine); |
|
2341
|
zToken = extract_token(zLine, &z); |
|
2342
|
if( zToken==0 ){ |
|
2343
|
malformed_request("malformed HTTP header"); |
|
2344
|
} |
|
2345
|
} |
|
2346
|
|
|
2347
|
zMethod = fossil_strdup(zToken); |
|
2348
|
if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0 |
|
2349
|
&& fossil_strcmp(zToken,"HEAD")!=0 ){ |
|
2350
|
malformed_request("unsupported HTTP method"); |
|
2351
|
} |
|
2352
|
|
|
2353
|
if( nCycles==0 ){ |
|
2354
|
cgi_setenv("GATEWAY_INTERFACE","CGI/1.0"); |
|
2355
|
cgi_setenv("REQUEST_METHOD",zToken); |
|
2356
|
} |
|
2357
|
|
|
2358
|
zToken = extract_token(z, &z); |
|
2359
|
if( zToken==0 ){ |
|
2360
|
malformed_request("malformed URL in HTTP header"); |
|
2361
|
} |
|
2362
|
n = strlen(g.zRepositoryName); |
|
2363
|
if( fossil_strncmp(g.zRepositoryName, zToken, n)==0 |
|
2364
|
&& (zToken[n]=='/' || zToken[n]==0) |
|
2365
|
&& fossil_strcmp(zMethod,"GET")==0 |
|
2366
|
){ |
|
2367
|
zToken += n; |
|
2368
|
if( zToken && strlen(zToken)==0 ){ |
|
2369
|
malformed_request("malformed URL in HTTP header"); |
|
2370
|
} |
|
2371
|
} |
|
2372
|
if( nCycles==0 ){ |
|
2373
|
cgi_setenv("REQUEST_URI", zToken); |
|
2374
|
cgi_setenv("SCRIPT_NAME", ""); |
|
2375
|
} |
|
2376
|
|
|
2377
|
for(i=0; zToken[i] && zToken[i]!='?'; i++){} |
|
2378
|
if( zToken[i] ) zToken[i++] = 0; |
|
2379
|
if( nCycles==0 ){ |
|
2380
|
cgi_setenv("PATH_INFO", zToken); |
|
2381
|
cgi_setenv("QUERY_STRING",&zToken[i]); |
|
2382
|
}else{ |
|
2383
|
cgi_replace_parameter("PATH_INFO", fossil_strdup(zToken)); |
|
2384
|
cgi_replace_parameter("QUERY_STRING",fossil_strdup(&zToken[i])); |
|
2385
|
} |
|
2386
|
|
|
2387
|
/* Get all the optional fields that follow the first line. |
|
2388
|
*/ |
|
2389
|
while( fgets(zLine,sizeof(zLine),g.httpIn) ){ |
|
2390
|
char *zFieldName; |
|
2391
|
char *zVal; |
|
2392
|
|
|
2393
|
cgi_trace(zLine); |
|
2394
|
zFieldName = extract_token(zLine,&zVal); |
|
2395
|
if( zFieldName==0 || *zFieldName==0 ) break; |
|
2396
|
while( fossil_isspace(*zVal) ){ zVal++; } |
|
2397
|
i = strlen(zVal); |
|
2398
|
while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; } |
|
2399
|
zVal[i] = 0; |
|
2400
|
for(i=0; zFieldName[i]; i++){ |
|
2401
|
zFieldName[i] = fossil_tolower(zFieldName[i]); |
|
2402
|
} |
|
2403
|
if( fossil_strcmp(zFieldName,"content-length:")==0 ){ |
|
2404
|
if( nCycles==0 ){ |
|
2405
|
cgi_setenv("CONTENT_LENGTH", zVal); |
|
2406
|
}else{ |
|
2407
|
cgi_replace_parameter("CONTENT_LENGTH", zVal); |
|
2408
|
} |
|
2409
|
}else if( fossil_strcmp(zFieldName,"content-type:")==0 ){ |
|
2410
|
if( nCycles==0 ){ |
|
2411
|
cgi_setenv("CONTENT_TYPE", zVal); |
|
2412
|
} |
|
2413
|
}else if( fossil_strcmp(zFieldName,"host:")==0 ){ |
|
2414
|
if( nCycles==0 ){ |
|
2415
|
cgi_setenv("HTTP_HOST", zVal); |
|
2416
|
} |
|
2417
|
}else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){ |
|
2418
|
if( nCycles==0 ){ |
|
2419
|
cgi_setenv("HTTP_USER_AGENT", zVal); |
|
2420
|
} |
|
2421
|
}else if( fossil_strcmp(zFieldName,"x-fossil-transport:")==0 ){ |
|
2422
|
if( fossil_strnicmp(zVal, "ssh", 3)==0 ){ |
|
2423
|
if( nCycles==0 ){ |
|
2424
|
g.fSshClient |= CGI_SSH_FOSSIL; |
|
2425
|
g.fullHttpReply = 0; |
|
2426
|
} |
|
2427
|
} |
|
2428
|
} |
|
2429
|
} |
|
2430
|
|
|
2431
|
if( nCycles==0 ){ |
|
2432
|
if( ! ( g.fSshClient & CGI_SSH_FOSSIL ) ){ |
|
2433
|
/* did not find new fossil ssh transport */ |
|
2434
|
g.fSshClient &= ~CGI_SSH_CLIENT; |
|
2435
|
g.fullHttpReply = 1; |
|
2436
|
cgi_replace_parameter("REMOTE_ADDR", "127.0.0.1"); |
|
2437
|
} |
|
2438
|
} |
|
2439
|
|
|
2440
|
cgi_reset_content(); |
|
2441
|
cgi_destination(CGI_BODY); |
|
2442
|
|
|
2443
|
cgi_init(); |
|
2444
|
cgi_trace(0); |
|
2445
|
nCycles++; |
|
2446
|
} |
|
2447
|
|
|
2448
|
/* |
|
2449
|
** This routine handles the old fossil SSH probes |
|
2450
|
*/ |
|
2451
|
char *cgi_handle_ssh_probes(char *zLine, int zSize, char *z, char *zToken){ |
|
2452
|
/* Start looking for probes */ |
|
2453
|
assert( !g.httpUseSSL ); |
|
2454
|
while( fossil_strcmp(zToken, "echo")==0 ){ |
|
2455
|
zToken = extract_token(z, &z); |
|
2456
|
if( zToken==0 ){ |
|
2457
|
malformed_request("malformed probe"); |
|
2458
|
} |
|
2459
|
if( fossil_strncmp(zToken, "test", 4)==0 || |
|
2460
|
fossil_strncmp(zToken, "probe-", 6)==0 ){ |
|
2461
|
fprintf(g.httpOut, "%s\n", zToken); |
|
2462
|
fflush(g.httpOut); |
|
2463
|
}else{ |
|
2464
|
malformed_request("malformed probe"); |
|
2465
|
} |
|
2466
|
if( fgets(zLine, zSize, g.httpIn)==0 ){ |
|
2467
|
malformed_request("malformed probe"); |
|
2468
|
} |
|
2469
|
cgi_trace(zLine); |
|
2470
|
zToken = extract_token(zLine, &z); |
|
2471
|
if( zToken==0 ){ |
|
2472
|
malformed_request("malformed probe"); |
|
2473
|
} |
|
2474
|
} |
|
2475
|
|
|
2476
|
/* Got all probes now first transport_open is completed |
|
2477
|
** so return the command that was requested |
|
2478
|
*/ |
|
2479
|
g.fSshClient |= CGI_SSH_COMPAT; |
|
2480
|
return fossil_strdup(zToken); |
|
2481
|
} |
|
2482
|
|
|
2483
|
/* |
|
2484
|
** This routine handles the old fossil SSH transport_flip |
|
2485
|
** and transport_open communications if detected. |
|
2486
|
*/ |
|
2487
|
void cgi_handle_ssh_transport(const char *zCmd){ |
|
2488
|
char *z, *zToken; |
|
2489
|
char zLine[2000]; /* A single line of input. */ |
|
2490
|
|
|
2491
|
assert( !g.httpUseSSL ); |
|
2492
|
/* look for second newline of transport_flip */ |
|
2493
|
if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ |
|
2494
|
malformed_request("incorrect transport_flip"); |
|
2495
|
} |
|
2496
|
cgi_trace(zLine); |
|
2497
|
zToken = extract_token(zLine, &z); |
|
2498
|
if( zToken && strlen(zToken)==0 ){ |
|
2499
|
/* look for path to fossil */ |
|
2500
|
if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ |
|
2501
|
if( zCmd==0 ){ |
|
2502
|
malformed_request("missing fossil command"); |
|
2503
|
}else{ |
|
2504
|
/* no new command so exit */ |
|
2505
|
fossil_exit(0); |
|
2506
|
} |
|
2507
|
} |
|
2508
|
cgi_trace(zLine); |
|
2509
|
zToken = extract_token(zLine, &z); |
|
2510
|
if( zToken==0 ){ |
|
2511
|
malformed_request("malformed fossil command"); |
|
2512
|
} |
|
2513
|
/* see if we've seen the command */ |
|
2514
|
if( zCmd && zCmd[0] && fossil_strcmp(zToken, zCmd)==0 ){ |
|
2515
|
return; |
|
2516
|
}else{ |
|
2517
|
malformed_request("transport_open failed"); |
|
2518
|
} |
|
2519
|
}else{ |
|
2520
|
malformed_request("transport_flip failed"); |
|
2521
|
} |
|
2522
|
} |
|
2523
|
|
|
2524
|
/* |
|
2525
|
** This routine handles a single SCGI request which is coming in on |
|
2526
|
** g.httpIn and which replies on g.httpOut |
|
2527
|
** |
|
2528
|
** The SCGI request is read from g.httpIn and is used to initialize |
|
2529
|
** entries in the cgi_parameter() hash, as if those entries were |
|
2530
|
** environment variables. A call to cgi_init() completes |
|
2531
|
** the setup. Once all the setup is finished, this procedure returns |
|
2532
|
** and subsequent code handles the actual generation of the webpage. |
|
2533
|
*/ |
|
2534
|
void cgi_handle_scgi_request(void){ |
|
2535
|
char *zHdr; |
|
2536
|
char *zToFree; |
|
2537
|
int nHdr = 0; |
|
2538
|
int nRead; |
|
2539
|
int c, n, m; |
|
2540
|
|
|
2541
|
assert( !g.httpUseSSL ); |
|
2542
|
while( (c = fgetc(g.httpIn))!=EOF && fossil_isdigit((char)c) ){ |
|
2543
|
nHdr = nHdr*10 + (char)c - '0'; |
|
2544
|
} |
|
2545
|
if( nHdr<16 ) malformed_request("SCGI header too short"); |
|
2546
|
zToFree = zHdr = fossil_malloc(nHdr); |
|
2547
|
nRead = (int)fread(zHdr, 1, nHdr, g.httpIn); |
|
2548
|
if( nRead<nHdr ) malformed_request("cannot read entire SCGI header"); |
|
2549
|
nHdr = nRead; |
|
2550
|
while( nHdr ){ |
|
2551
|
for(n=0; n<nHdr && zHdr[n]; n++){} |
|
2552
|
for(m=n+1; m<nHdr && zHdr[m]; m++){} |
|
2553
|
if( m>=nHdr ) malformed_request("SCGI header formatting error"); |
|
2554
|
cgi_set_parameter(zHdr, zHdr+n+1); |
|
2555
|
zHdr += m+1; |
|
2556
|
nHdr -= m+1; |
|
2557
|
} |
|
2558
|
fossil_free(zToFree); |
|
2559
|
(void)fgetc(g.httpIn); /* Read past the "," separating header from content */ |
|
2560
|
cgi_init(); |
|
2561
|
} |
|
2562
|
|
|
2563
|
#if INTERFACE |
|
2564
|
/* |
|
2565
|
** Bitmap values for the flags parameter to cgi_http_server(). |
|
2566
|
*/ |
|
2567
|
#define HTTP_SERVER_LOCALHOST 0x0001 /* Bind to 127.0.0.1 only */ |
|
2568
|
#define HTTP_SERVER_SCGI 0x0002 /* SCGI instead of HTTP */ |
|
2569
|
#define HTTP_SERVER_HAD_REPOSITORY 0x0004 /* Was the repository open? */ |
|
2570
|
#define HTTP_SERVER_HAD_CHECKOUT 0x0008 /* Was a checkout open? */ |
|
2571
|
#define HTTP_SERVER_REPOLIST 0x0010 /* Allow repo listing */ |
|
2572
|
#define HTTP_SERVER_NOFORK 0x0020 /* Do not call fork() */ |
|
2573
|
#define HTTP_SERVER_UNIXSOCKET 0x0040 /* Use a unix-domain socket */ |
|
2574
|
|
|
2575
|
#endif /* INTERFACE */ |
|
2576
|
|
|
2577
|
/* |
|
2578
|
** Maximum number of child processes that we can have running |
|
2579
|
** at one time. Set this to 0 for "no limit". |
|
2580
|
*/ |
|
2581
|
#ifndef FOSSIL_MAX_CONNECTIONS |
|
2582
|
# define FOSSIL_MAX_CONNECTIONS 1000 |
|
2583
|
#endif |
|
2584
|
|
|
2585
|
/* |
|
2586
|
** Implement an HTTP server daemon listening on port iPort. |
|
2587
|
** |
|
2588
|
** As new connections arrive, fork a child and let child return |
|
2589
|
** out of this procedure call. The child will handle the request. |
|
2590
|
** The parent never returns from this procedure. |
|
2591
|
** |
|
2592
|
** Return 0 to each child as it runs. If unable to establish a |
|
2593
|
** listening socket, return non-zero. |
|
2594
|
*/ |
|
2595
|
int cgi_http_server( |
|
2596
|
int mnPort, int mxPort, /* Range of TCP ports to try */ |
|
2597
|
const char *zBrowser, /* Run this browser, if not NULL */ |
|
2598
|
const char *zIpAddr, /* Bind to this IP address, if not null */ |
|
2599
|
int flags /* HTTP_SERVER_* flags */ |
|
2600
|
){ |
|
2601
|
#if defined(_WIN32) |
|
2602
|
/* Use win32_http_server() instead */ |
|
2603
|
fossil_exit(1); |
|
2604
|
#else |
|
2605
|
int listen4 = -1; /* Main socket; IPv4 or unix-domain */ |
|
2606
|
int listen6 = -1; /* Aux socket for corresponding IPv6 */ |
|
2607
|
int mxListen = -1; /* Maximum of listen4 and listen6 */ |
|
2608
|
int connection; /* An incoming connection */ |
|
2609
|
int nRequest = 0; /* Number of requests handled so far */ |
|
2610
|
fd_set readfds; /* Set of file descriptors for select() */ |
|
2611
|
socklen_t lenaddr; /* Length of the inaddr structure */ |
|
2612
|
int child; /* PID of the child process */ |
|
2613
|
int nchildren = 0; /* Number of child processes */ |
|
2614
|
struct timeval delay; /* How long to wait inside select() */ |
|
2615
|
struct sockaddr_in6 inaddr6; /* Address for IPv6 */ |
|
2616
|
struct sockaddr_in inaddr4; /* Address for IPv4 */ |
|
2617
|
struct sockaddr_un uxaddr; /* The address for unix-domain sockets */ |
|
2618
|
int opt = 1; /* setsockopt flag */ |
|
2619
|
int rc; /* Result code from system calls */ |
|
2620
|
int iPort = mnPort; /* Port to try to use */ |
|
2621
|
const char *zRequestType; /* Type of requests to listen for */ |
|
2622
|
|
|
2623
|
|
|
2624
|
if( flags & HTTP_SERVER_SCGI ){ |
|
2625
|
zRequestType = "SCGI"; |
|
2626
|
}else if( g.httpUseSSL ){ |
|
2627
|
zRequestType = "TLS-encrypted HTTPS"; |
|
2628
|
}else{ |
|
2629
|
zRequestType = "HTTP"; |
|
2630
|
} |
|
2631
|
|
|
2632
|
if( flags & HTTP_SERVER_UNIXSOCKET ){ |
|
2633
|
/* CASE 1: A unix socket named g.zSockName. After creation, set the |
|
2634
|
** permissions on the new socket to g.zSockMode and make the |
|
2635
|
** owner of the socket be g.zSockOwner. |
|
2636
|
*/ |
|
2637
|
assert( g.zSockName!=0 ); |
|
2638
|
memset(&uxaddr, 0, sizeof(uxaddr)); |
|
2639
|
if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){ |
|
2640
|
fossil_fatal("name of unix socket too big: %s\nmax size: %d\n", |
|
2641
|
g.zSockName, (int)sizeof(uxaddr.sun_path)); |
|
2642
|
} |
|
2643
|
if( file_isdir(g.zSockName, ExtFILE)!=0 ){ |
|
2644
|
if( !file_issocket(g.zSockName) ){ |
|
2645
|
fossil_fatal("cannot name socket \"%s\" because another object" |
|
2646
|
" with that name already exists", g.zSockName); |
|
2647
|
}else{ |
|
2648
|
unlink(g.zSockName); |
|
2649
|
} |
|
2650
|
} |
|
2651
|
uxaddr.sun_family = AF_UNIX; |
|
2652
|
strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1); |
|
2653
|
listen4 = socket(AF_UNIX, SOCK_STREAM, 0); |
|
2654
|
if( listen4<0 ){ |
|
2655
|
fossil_fatal("unable to create a unix socket named %s", |
|
2656
|
g.zSockName); |
|
2657
|
} |
|
2658
|
mxListen = listen4; |
|
2659
|
listen6 = -1; |
|
2660
|
|
|
2661
|
/* Set the access permission for the new socket. Default to 0660. |
|
2662
|
** But use an alternative specified by --socket-mode if available. |
|
2663
|
** Do this before bind() to avoid a race condition. */ |
|
2664
|
if( g.zSockMode ){ |
|
2665
|
file_set_mode(g.zSockName, listen4, g.zSockMode, 0); |
|
2666
|
}else{ |
|
2667
|
file_set_mode(g.zSockName, listen4, "0660", 1); |
|
2668
|
} |
|
2669
|
rc = bind(listen4, (struct sockaddr*)&uxaddr, sizeof(uxaddr)); |
|
2670
|
/* Set the owner of the socket if requested by --socket-owner. This |
|
2671
|
** must wait until after bind(), after the filesystem object has been |
|
2672
|
** created. See https://lkml.org/lkml/2004/11/1/84 and |
|
2673
|
** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */ |
|
2674
|
if( g.zSockOwner ){ |
|
2675
|
file_set_owner(g.zSockName, listen4, g.zSockOwner); |
|
2676
|
} |
|
2677
|
fossil_print("Listening for %s requests on unix socket %s\n", |
|
2678
|
zRequestType, g.zSockName); |
|
2679
|
fflush(stdout); |
|
2680
|
}else if( zIpAddr && strchr(zIpAddr,':')!=0 ){ |
|
2681
|
/* CASE 2: TCP on IPv6 IP address specified by zIpAddr and on port iPort. |
|
2682
|
*/ |
|
2683
|
assert( mnPort==mxPort ); |
|
2684
|
memset(&inaddr6, 0, sizeof(inaddr6)); |
|
2685
|
inaddr6.sin6_family = AF_INET6; |
|
2686
|
inaddr6.sin6_port = htons(iPort); |
|
2687
|
if( inet_pton(AF_INET6, zIpAddr, &inaddr6.sin6_addr)==0 ){ |
|
2688
|
fossil_fatal("not a valid IPv6 address: %s", zIpAddr); |
|
2689
|
} |
|
2690
|
listen6 = socket(AF_INET6, SOCK_STREAM, 0); |
|
2691
|
if( listen6>0 ){ |
|
2692
|
opt = 1; |
|
2693
|
setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); |
|
2694
|
rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6)); |
|
2695
|
if( rc<0 ){ |
|
2696
|
close(listen6); |
|
2697
|
listen6 = -1; |
|
2698
|
} |
|
2699
|
} |
|
2700
|
if( listen6<0 ){ |
|
2701
|
fossil_fatal("cannot open a listening socket on [%s]:%d", |
|
2702
|
zIpAddr, mnPort); |
|
2703
|
} |
|
2704
|
mxListen = listen6; |
|
2705
|
listen4 = -1; |
|
2706
|
fossil_print("Listening for %s requests on [%s]:%d\n", |
|
2707
|
zRequestType, zIpAddr, iPort); |
|
2708
|
fflush(stdout); |
|
2709
|
}else if( zIpAddr && zIpAddr[0] ){ |
|
2710
|
/* CASE 3: TCP on IPv4 IP address specified by zIpAddr and on port iPort. |
|
2711
|
*/ |
|
2712
|
assert( mnPort==mxPort ); |
|
2713
|
memset(&inaddr4, 0, sizeof(inaddr4)); |
|
2714
|
inaddr4.sin_family = AF_INET; |
|
2715
|
inaddr4.sin_port = htons(iPort); |
|
2716
|
if( strcmp(zIpAddr, "localhost")==0 ) zIpAddr = "127.0.0.1"; |
|
2717
|
inaddr4.sin_addr.s_addr = inet_addr(zIpAddr); |
|
2718
|
if( inaddr4.sin_addr.s_addr == INADDR_NONE ){ |
|
2719
|
fossil_fatal("not a valid IPv4 address: %s", zIpAddr); |
|
2720
|
} |
|
2721
|
listen4 = socket(AF_INET, SOCK_STREAM, 0); |
|
2722
|
if( listen4>0 ){ |
|
2723
|
setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); |
|
2724
|
rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4)); |
|
2725
|
if( rc<0 ){ |
|
2726
|
close(listen4); |
|
2727
|
listen4 = -1; |
|
2728
|
} |
|
2729
|
} |
|
2730
|
if( listen4<0 ){ |
|
2731
|
fossil_fatal("cannot open a listening socket on %s:%d", |
|
2732
|
zIpAddr, mnPort); |
|
2733
|
} |
|
2734
|
mxListen = listen4; |
|
2735
|
listen6 = -1; |
|
2736
|
fossil_print("Listening for %s requests on TCP port %s:%d\n", |
|
2737
|
zRequestType, zIpAddr, iPort); |
|
2738
|
fflush(stdout); |
|
2739
|
}else{ |
|
2740
|
/* CASE 4: Listen on all available IP addresses, or on only loopback |
|
2741
|
** addresses (if HTTP_SERVER_LOCALHOST). The TCP port is the |
|
2742
|
** first available in the range of mnPort..mxPort. Listen |
|
2743
|
** on both IPv4 and IPv6, if possible. The TCP port scan is done |
|
2744
|
** on IPv4. |
|
2745
|
*/ |
|
2746
|
while( iPort<=mxPort ){ |
|
2747
|
const char *zProto; |
|
2748
|
memset(&inaddr4, 0, sizeof(inaddr4)); |
|
2749
|
inaddr4.sin_family = AF_INET; |
|
2750
|
inaddr4.sin_port = htons(iPort); |
|
2751
|
if( flags & HTTP_SERVER_LOCALHOST ){ |
|
2752
|
inaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
|
2753
|
}else{ |
|
2754
|
inaddr4.sin_addr.s_addr = htonl(INADDR_ANY); |
|
2755
|
} |
|
2756
|
listen4 = socket(AF_INET, SOCK_STREAM, 0); |
|
2757
|
if( listen4>0 ){ |
|
2758
|
setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); |
|
2759
|
rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4)); |
|
2760
|
if( rc<0 ){ |
|
2761
|
close(listen4); |
|
2762
|
listen4 = -1; |
|
2763
|
} |
|
2764
|
} |
|
2765
|
if( listen4<0 ){ |
|
2766
|
iPort++; |
|
2767
|
continue; |
|
2768
|
} |
|
2769
|
mxListen = listen4; |
|
2770
|
|
|
2771
|
/* If we get here, that means we found an open TCP port at iPort for |
|
2772
|
** IPv4. Try to set up a corresponding IPv6 socket on the same port. |
|
2773
|
*/ |
|
2774
|
memset(&inaddr6, 0, sizeof(inaddr6)); |
|
2775
|
inaddr6.sin6_family = AF_INET6; |
|
2776
|
inaddr6.sin6_port = htons(iPort); |
|
2777
|
if( flags & HTTP_SERVER_LOCALHOST ){ |
|
2778
|
inaddr6.sin6_addr = in6addr_loopback; |
|
2779
|
}else{ |
|
2780
|
inaddr6.sin6_addr = in6addr_any; |
|
2781
|
} |
|
2782
|
listen6 = socket(AF_INET6, SOCK_STREAM, 0); |
|
2783
|
if( listen6>0 ){ |
|
2784
|
setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); |
|
2785
|
setsockopt(listen6, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); |
|
2786
|
rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6)); |
|
2787
|
if( rc<0 ){ |
|
2788
|
close(listen6); |
|
2789
|
listen6 = -1; |
|
2790
|
} |
|
2791
|
} |
|
2792
|
if( listen6<0 ){ |
|
2793
|
zProto = "IPv4 only"; |
|
2794
|
}else{ |
|
2795
|
zProto = "IPv4 and IPv6"; |
|
2796
|
if( listen6>listen4 ) mxListen = listen6; |
|
2797
|
} |
|
2798
|
|
|
2799
|
fossil_print("Listening for %s requests on TCP port %s%d, %s\n", |
|
2800
|
zRequestType, |
|
2801
|
(flags & HTTP_SERVER_LOCALHOST)!=0 ? "localhost:" : "", |
|
2802
|
iPort, zProto); |
|
2803
|
fflush(stdout); |
|
2804
|
break; |
|
2805
|
} |
|
2806
|
if( iPort>mxPort ){ |
|
2807
|
fossil_fatal("no available TCP ports in the range %d..%d", |
|
2808
|
mnPort, mxPort); |
|
2809
|
} |
|
2810
|
} |
|
2811
|
|
|
2812
|
/* If we get to this point, that means there is at least one listening |
|
2813
|
** socket on either listen4 or listen6 and perhaps on both. */ |
|
2814
|
assert( listen4>0 || listen6>0 ); |
|
2815
|
if( listen4>0 ) listen(listen4,10); |
|
2816
|
if( listen6>0 ) listen(listen6,10); |
|
2817
|
if( zBrowser && (flags & HTTP_SERVER_UNIXSOCKET)==0 ){ |
|
2818
|
assert( strstr(zBrowser,"%d")!=0 ); |
|
2819
|
zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort); |
|
2820
|
#if defined(__CYGWIN__) |
|
2821
|
/* On Cygwin, we can do better than "echo" */ |
|
2822
|
if( fossil_strncmp(zBrowser, "echo ", 5)==0 ){ |
|
2823
|
wchar_t *wUrl = fossil_utf8_to_unicode(zBrowser+5); |
|
2824
|
wUrl[wcslen(wUrl)-2] = 0; /* Strip terminating " &" */ |
|
2825
|
if( (size_t)ShellExecuteW(0, L"open", wUrl, 0, 0, 1)<33 ){ |
|
2826
|
fossil_warning("cannot start browser\n"); |
|
2827
|
} |
|
2828
|
}else |
|
2829
|
#endif |
|
2830
|
if( fossil_system(zBrowser)<0 ){ |
|
2831
|
fossil_warning("cannot start browser: %s\n", zBrowser); |
|
2832
|
} |
|
2833
|
} |
|
2834
|
|
|
2835
|
/* What for incoming requests. For each request, fork() a child process |
|
2836
|
** to deal with that request. The child process returns. The parent |
|
2837
|
** keeps on listening and never returns. |
|
2838
|
*/ |
|
2839
|
while( 1 ){ |
|
2840
|
#if FOSSIL_MAX_CONNECTIONS>0 |
|
2841
|
while( nchildren>=FOSSIL_MAX_CONNECTIONS ){ |
|
2842
|
if( wait(0)>=0 ) nchildren--; |
|
2843
|
} |
|
2844
|
#endif |
|
2845
|
delay.tv_sec = 0; |
|
2846
|
delay.tv_usec = 100000; |
|
2847
|
FD_ZERO(&readfds); |
|
2848
|
assert( listen4>0 || listen6>0 ); |
|
2849
|
if( listen4>0 ) FD_SET( listen4, &readfds); |
|
2850
|
if( listen6>0 ) FD_SET( listen6, &readfds); |
|
2851
|
select( mxListen+1, &readfds, 0, 0, &delay); |
|
2852
|
if( listen4>0 && FD_ISSET(listen4, &readfds) ){ |
|
2853
|
lenaddr = sizeof(inaddr4); |
|
2854
|
connection = accept(listen4, (struct sockaddr*)&inaddr4, &lenaddr); |
|
2855
|
}else if( listen6>0 && FD_ISSET(listen6, &readfds) ){ |
|
2856
|
lenaddr = sizeof(inaddr6); |
|
2857
|
connection = accept(listen6, (struct sockaddr*)&inaddr6, &lenaddr); |
|
2858
|
}else{ |
|
2859
|
connection = -1; |
|
2860
|
} |
|
2861
|
if( connection>=0 ){ |
|
2862
|
if( flags & HTTP_SERVER_NOFORK ){ |
|
2863
|
child = 0; |
|
2864
|
}else{ |
|
2865
|
child = fork(); |
|
2866
|
} |
|
2867
|
if( child!=0 ){ |
|
2868
|
if( child>0 ){ |
|
2869
|
nchildren++; |
|
2870
|
nRequest++; |
|
2871
|
} |
|
2872
|
close(connection); |
|
2873
|
}else{ |
|
2874
|
int nErr = 0, fd; |
|
2875
|
g.zSockName = 0 /* avoid deleting the socket via atexit() */; |
|
2876
|
close(0); |
|
2877
|
fd = dup(connection); |
|
2878
|
if( fd!=0 ) nErr++; |
|
2879
|
close(1); |
|
2880
|
fd = dup(connection); |
|
2881
|
if( fd!=1 ) nErr++; |
|
2882
|
if( 0 && !g.fAnyTrace ){ |
|
2883
|
close(2); |
|
2884
|
fd = dup(connection); |
|
2885
|
if( fd!=2 ) nErr++; |
|
2886
|
} |
|
2887
|
close(connection); |
|
2888
|
if( listen4>0 ) close(listen4); |
|
2889
|
if( listen6>0 ) close(listen6); |
|
2890
|
g.nPendingRequest = nchildren+1; |
|
2891
|
g.nRequest = nRequest+1; |
|
2892
|
return nErr; |
|
2893
|
} |
|
2894
|
} |
|
2895
|
/* Bury dead children */ |
|
2896
|
if( nchildren ){ |
|
2897
|
while(1){ |
|
2898
|
int iStatus = 0; |
|
2899
|
pid_t x = waitpid(-1, &iStatus, WNOHANG); |
|
2900
|
if( x<=0 ) break; |
|
2901
|
if( WIFSIGNALED(iStatus) && g.fAnyTrace ){ |
|
2902
|
fprintf(stderr, "/***** Child %d exited on signal %d (%s) *****/\n", |
|
2903
|
x, WTERMSIG(iStatus), strsignal(WTERMSIG(iStatus))); |
|
2904
|
} |
|
2905
|
nchildren--; |
|
2906
|
} |
|
2907
|
} |
|
2908
|
} |
|
2909
|
/* NOT REACHED */ |
|
2910
|
fossil_exit(1); |
|
2911
|
#endif |
|
2912
|
/* NOT REACHED */ |
|
2913
|
return 0; |
|
2914
|
} |
|
2915
|
|
|
2916
|
|
|
2917
|
/* |
|
2918
|
** Name of days and months. |
|
2919
|
*/ |
|
2920
|
static const char *const azDays[] = |
|
2921
|
{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 0}; |
|
2922
|
static const char *const azMonths[] = |
|
2923
|
{"Jan", "Feb", "Mar", "Apr", "May", "Jun", |
|
2924
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0}; |
|
2925
|
|
|
2926
|
|
|
2927
|
/* |
|
2928
|
** Returns an RFC822-formatted time string suitable for HTTP headers. |
|
2929
|
** The timezone is always GMT. The value returned is always a |
|
2930
|
** string obtained from mprintf() and must be freed using fossil_free() |
|
2931
|
** to avoid a memory leak. |
|
2932
|
** |
|
2933
|
** See http://www.faqs.org/rfcs/rfc822.html, section 5 |
|
2934
|
** and http://www.faqs.org/rfcs/rfc2616.html, section 3.3. |
|
2935
|
*/ |
|
2936
|
char *cgi_rfc822_datestamp(time_t now){ |
|
2937
|
struct tm *pTm; |
|
2938
|
pTm = gmtime(&now); |
|
2939
|
if( pTm==0 ){ |
|
2940
|
return mprintf(""); |
|
2941
|
}else{ |
|
2942
|
return mprintf("%s, %d %s %02d %02d:%02d:%02d +0000", |
|
2943
|
azDays[pTm->tm_wday], pTm->tm_mday, azMonths[pTm->tm_mon], |
|
2944
|
pTm->tm_year+1900, pTm->tm_hour, pTm->tm_min, pTm->tm_sec); |
|
2945
|
} |
|
2946
|
} |
|
2947
|
|
|
2948
|
/* |
|
2949
|
** Returns an ISO8601-formatted time string suitable for debugging |
|
2950
|
** purposes. |
|
2951
|
** |
|
2952
|
** The value returned is always a string obtained from mprintf() and must |
|
2953
|
** be freed using fossil_free() to avoid a memory leak. |
|
2954
|
*/ |
|
2955
|
char *cgi_iso8601_datestamp(void){ |
|
2956
|
struct tm *pTm; |
|
2957
|
time_t now = time(0); |
|
2958
|
pTm = gmtime(&now); |
|
2959
|
if( pTm==0 ){ |
|
2960
|
return mprintf(""); |
|
2961
|
}else{ |
|
2962
|
return mprintf("%04d-%02d-%02d %02d:%02d:%02d", |
|
2963
|
pTm->tm_year+1900, pTm->tm_mon+1, pTm->tm_mday, |
|
2964
|
pTm->tm_hour, pTm->tm_min, pTm->tm_sec); |
|
2965
|
} |
|
2966
|
} |
|
2967
|
|
|
2968
|
/* |
|
2969
|
** Parse an RFC822-formatted timestamp as we'd expect from HTTP and return |
|
2970
|
** a Unix epoch time. <= zero is returned on failure. |
|
2971
|
** |
|
2972
|
** Note that this won't handle all the _allowed_ HTTP formats, just the |
|
2973
|
** most popular one (the one generated by cgi_rfc822_datestamp(), actually). |
|
2974
|
*/ |
|
2975
|
time_t cgi_rfc822_parsedate(const char *zDate){ |
|
2976
|
int mday, mon, year, yday, hour, min, sec; |
|
2977
|
char zIgnore[4]; |
|
2978
|
char zMonth[4]; |
|
2979
|
static const char *const azMonths[] = |
|
2980
|
{"Jan", "Feb", "Mar", "Apr", "May", "Jun", |
|
2981
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0}; |
|
2982
|
if( 7==sscanf(zDate, "%3[A-Za-z], %d %3[A-Za-z] %d %d:%d:%d", zIgnore, |
|
2983
|
&mday, zMonth, &year, &hour, &min, &sec)){ |
|
2984
|
if( year > 1900 ) year -= 1900; |
|
2985
|
for(mon=0; azMonths[mon]; mon++){ |
|
2986
|
if( !fossil_strncmp( azMonths[mon], zMonth, 3 )){ |
|
2987
|
int nDay; |
|
2988
|
int isLeapYr; |
|
2989
|
static int priorDays[] = |
|
2990
|
{ 0, 31, 59, 90,120,151,181,212,243,273,304,334 }; |
|
2991
|
if( mon<0 ){ |
|
2992
|
int nYear = (11 - mon)/12; |
|
2993
|
year -= nYear; |
|
2994
|
mon += nYear*12; |
|
2995
|
}else if( mon>11 ){ |
|
2996
|
year += mon/12; |
|
2997
|
mon %= 12; |
|
2998
|
} |
|
2999
|
isLeapYr = year%4==0 && (year%100!=0 || (year+300)%400==0); |
|
3000
|
yday = priorDays[mon] + mday - 1; |
|
3001
|
if( isLeapYr && mon>1 ) yday++; |
|
3002
|
nDay = (year-70)*365 + (year-69)/4 - year/100 + (year+300)/400 + yday; |
|
3003
|
return ((time_t)(nDay*24 + hour)*60 + min)*60 + sec; |
|
3004
|
} |
|
3005
|
} |
|
3006
|
} |
|
3007
|
return 0; |
|
3008
|
} |
|
3009
|
|
|
3010
|
/* |
|
3011
|
** Check the objectTime against the If-Modified-Since request header. If the |
|
3012
|
** object time isn't any newer than the header, we immediately send back |
|
3013
|
** a 304 reply and exit. |
|
3014
|
*/ |
|
3015
|
void cgi_modified_since(time_t objectTime){ |
|
3016
|
const char *zIf = P("HTTP_IF_MODIFIED_SINCE"); |
|
3017
|
if( zIf==0 ) return; |
|
3018
|
if( objectTime > cgi_rfc822_parsedate(zIf) ) return; |
|
3019
|
cgi_set_status(304,"Not Modified"); |
|
3020
|
cgi_reset_content(); |
|
3021
|
cgi_reply(); |
|
3022
|
fossil_exit(0); |
|
3023
|
} |
|
3024
|
|
|
3025
|
/* |
|
3026
|
** Check to see if the remote client is SSH and return |
|
3027
|
** its IP or return default |
|
3028
|
*/ |
|
3029
|
const char *cgi_ssh_remote_addr(const char *zDefault){ |
|
3030
|
char *zIndex; |
|
3031
|
const char *zSshConn = fossil_getenv("SSH_CONNECTION"); |
|
3032
|
|
|
3033
|
if( zSshConn && zSshConn[0] ){ |
|
3034
|
char *zSshClient = fossil_strdup(zSshConn); |
|
3035
|
if( (zIndex = strchr(zSshClient,' '))!=0 ){ |
|
3036
|
zSshClient[zIndex-zSshClient] = '\0'; |
|
3037
|
return zSshClient; |
|
3038
|
} |
|
3039
|
} |
|
3040
|
return zDefault; |
|
3041
|
} |
|
3042
|
|
|
3043
|
/* |
|
3044
|
** Return true if information is coming from the loopback network. |
|
3045
|
*/ |
|
3046
|
int cgi_is_loopback(const char *zIpAddr){ |
|
3047
|
return fossil_strcmp(zIpAddr, "127.0.0.1")==0 || |
|
3048
|
fossil_strcmp(zIpAddr, "::ffff:127.0.0.1")==0 || |
|
3049
|
fossil_strcmp(zIpAddr, "::1")==0; |
|
3050
|
} |
|
3051
|
|
|
3052
|
/* |
|
3053
|
** Return true if the HTTP request is likely to be from a small-screen |
|
3054
|
** mobile device. |
|
3055
|
** |
|
3056
|
** The returned value is a guess. Use it only for setting up defaults. |
|
3057
|
*/ |
|
3058
|
int cgi_from_mobile(void){ |
|
3059
|
const char *zAgent = P("HTTP_USER_AGENT"); |
|
3060
|
if( zAgent==0 ) return 0; |
|
3061
|
if( sqlite3_strglob("*iPad*", zAgent)==0 ) return 0; |
|
3062
|
return sqlite3_strlike("%mobile%", zAgent, 0)==0; |
|
3063
|
} |
|
3064
|
|
|
3065
|
/* |
|
3066
|
** Look for query or POST parameters that: |
|
3067
|
** |
|
3068
|
** (1) Have not been used |
|
3069
|
** (2) Appear to be malicious attempts to break into or otherwise |
|
3070
|
** harm the system, for example via SQL injection |
|
3071
|
** |
|
3072
|
** If any such parameters are seen, a 418 ("I'm a teapot") return is |
|
3073
|
** generated and processing aborts - this routine does not return. |
|
3074
|
** |
|
3075
|
** When Fossil is launched via CGI from althttpd, the 418 return signals |
|
3076
|
** the webserver to put the requestor IP address into "timeout", blocking |
|
3077
|
** subsequent requests for 5 minutes. |
|
3078
|
** |
|
3079
|
** Fossil is not subject to any SQL injections, as far as anybody knows. |
|
3080
|
** This routine is not necessary for the security of the system (though |
|
3081
|
** an extra layer of security never hurts). The main purpose here is |
|
3082
|
** to shutdown malicious attack spiders and prevent them from burning |
|
3083
|
** lots of CPU cycles and bogging down the website. In other words, the |
|
3084
|
** objective of this routine is to help prevent denial-of-service. |
|
3085
|
** |
|
3086
|
** Usage Hint: Put a call to this routine as late in the webpage |
|
3087
|
** implementation as possible, ideally just before it begins doing |
|
3088
|
** potentially CPU-intensive computations and after all query parameters |
|
3089
|
** have been consulted. |
|
3090
|
*/ |
|
3091
|
void cgi_check_for_malice(void){ |
|
3092
|
struct QParam * pParam; |
|
3093
|
int i; |
|
3094
|
for(i=0; i<nUsedQP; ++i){ |
|
3095
|
pParam = &aParamQP[i]; |
|
3096
|
if( 0==pParam->isFetched |
|
3097
|
&& pParam->zValue!=0 |
|
3098
|
&& pParam->zName!=0 |
|
3099
|
&& fossil_islower(pParam->zName[0]) |
|
3100
|
){ |
|
3101
|
cgi_value_spider_check(pParam->zValue, pParam->zName); |
|
3102
|
} |
|
3103
|
} |
|
3104
|
} |
|
3105
|
|