Fossil SCM

fossil-scm / src / cgi.c
Blame History Raw 3106 lines
1
/*
2
** Copyright (c) 2006 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
**
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This 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) transmitted via cookie instead of
1341
** in the sync payload. The format of this value is the same as a
1342
** "login" card, as parsed by xfer.c:page_xfer(). */
1343
rc |= 0x04;
1344
g.syncInfo.zLoginCard = fossil_strdup(z);
1345
g.syncInfo.fLoginCardMode |= 0x02;
1346
cgi_delete_parameter("x-f-l-c");
1347
}
1348
return rc;
1349
}
1350
1351
/*
1352
** Initialize the query parameter database. Information is pulled from
1353
** the QUERY_STRING environment variable (if it exists), from standard
1354
** input if there is POST data, and from HTTP_COOKIE.
1355
**
1356
** REQUEST_URI, PATH_INFO, and SCRIPT_NAME are related as follows:
1357
**
1358
** REQUEST_URI == SCRIPT_NAME + PATH_INFO
1359
**
1360
** Or if QUERY_STRING is not empty:
1361
**
1362
** REQUEST_URI == SCRIPT_NAME + PATH_INFO + '?' + QUERY_STRING
1363
**
1364
** Where "+" means concatenate. Fossil requires SCRIPT_NAME. If
1365
** REQUEST_URI is provided but PATH_INFO is not, then PATH_INFO is
1366
** computed from REQUEST_URI and SCRIPT_NAME. If PATH_INFO is provided
1367
** but REQUEST_URI is not, then compute REQUEST_URI from PATH_INFO and
1368
** SCRIPT_NAME. If neither REQUEST_URI nor PATH_INFO are provided, then
1369
** assume that PATH_INFO is an empty string and set REQUEST_URI equal
1370
** to PATH_INFO.
1371
**
1372
** Sometimes PATH_INFO is missing and SCRIPT_NAME is not a prefix of
1373
** REQUEST_URI. (See https://fossil-scm.org/forum/forumpost/049e8650ed)
1374
** In that case, truncate SCRIPT_NAME so that it is a proper prefix
1375
** of REQUEST_URI.
1376
**
1377
** SCGI typically omits PATH_INFO. CGI sometimes omits REQUEST_URI and
1378
** PATH_INFO when it is empty.
1379
**
1380
** CGI Parameter quick reference:
1381
**
1382
** REQUEST_URI
1383
** _____________|________________
1384
** / \
1385
** https://fossil-scm.org/forum/info/12736b30c072551a?t=c
1386
** \___/ \____________/\____/\____________________/ \_/
1387
** | | | | |
1388
** | HTTP_HOST | PATH_INFO QUERY_STRING
1389
** | |
1390
** REQUEST_SCHEMA SCRIPT_NAME
1391
**
1392
*/
1393
void cgi_init(void){
1394
char *z;
1395
const char *zType;
1396
const char *zSemi;
1397
int len;
1398
const char *zRequestUri = cgi_parameter("REQUEST_URI",0);
1399
const char *zScriptName = cgi_parameter("SCRIPT_NAME",0);
1400
const char *zPathInfo = cgi_parameter("PATH_INFO",0);
1401
const char *zContentLength = 0;
1402
#ifdef _WIN32
1403
const char *zServerSoftware = cgi_parameter("SERVER_SOFTWARE",0);
1404
#endif
1405
1406
#ifdef FOSSIL_ENABLE_JSON
1407
const int noJson = P("no_json")!=0;
1408
#endif
1409
g.isHTTP = 1;
1410
cgi_destination(CGI_BODY);
1411
1412
/* We must have SCRIPT_NAME. If the web server did not supply it, try
1413
** to compute it from REQUEST_URI and PATH_INFO. */
1414
if( zScriptName==0 ){
1415
if( zRequestUri==0 || zPathInfo==0 ){
1416
malformed_request("missing SCRIPT_NAME"); /* Does not return */
1417
}
1418
z = (char*)strstr(zRequestUri,zPathInfo);
1419
if( z==0 ){
1420
malformed_request("PATH_INFO not found in REQUEST_URI");
1421
}
1422
zScriptName = fossil_strndup(zRequestUri,(int)(z-zRequestUri));
1423
cgi_set_parameter("SCRIPT_NAME", zScriptName);
1424
}
1425
1426
#ifdef _WIN32
1427
/* The Microsoft IIS web server does not define REQUEST_URI, instead it uses
1428
** PATH_INFO for virtually the same purpose. Define REQUEST_URI the same as
1429
** PATH_INFO and redefine PATH_INFO with SCRIPT_NAME removed from the
1430
** beginning. */
1431
if( zServerSoftware && strstr(zServerSoftware, "Microsoft-IIS") ){
1432
int i, j;
1433
cgi_set_parameter("REQUEST_URI", zPathInfo);
1434
for(i=0; zPathInfo[i]==zScriptName[i] && zPathInfo[i]; i++){}
1435
for(j=i; zPathInfo[j] && zPathInfo[j]!='?'; j++){}
1436
zPathInfo = fossil_strndup(zPathInfo+i, j-i);
1437
cgi_replace_parameter("PATH_INFO", zPathInfo);
1438
}
1439
#endif
1440
if( zRequestUri==0 ){
1441
const char *z = zPathInfo;
1442
const char *zQS = cgi_parameter("QUERY_STRING",0);
1443
if( zPathInfo==0 ){
1444
malformed_request("missing PATH_INFO and/or REQUEST_URI");
1445
}
1446
if( z[0]=='/' ) z++;
1447
if( zQS && zQS[0] ){
1448
zRequestUri = mprintf("%s/%s?%s", zScriptName, z, zQS);
1449
}else{
1450
zRequestUri = mprintf("%s/%s", zScriptName, z);
1451
}
1452
cgi_set_parameter("REQUEST_URI", zRequestUri);
1453
}
1454
if( zPathInfo==0 ){
1455
int i, j;
1456
for(i=0; zRequestUri[i]==zScriptName[i] && zRequestUri[i]; i++){}
1457
for(j=i; zRequestUri[j] && zRequestUri[j]!='?'; j++){}
1458
zPathInfo = fossil_strndup(zRequestUri+i, j-i);
1459
cgi_set_parameter_nocopy("PATH_INFO", zPathInfo, 0);
1460
if( j>i && zScriptName[i]!=0 ){
1461
/* If SCRIPT_NAME is not a prefix of REQUEST_URI, truncate it so
1462
** that it is. See https://fossil-scm.org/forum/forumpost/049e8650ed
1463
*/
1464
char *zNew = fossil_strndup(zScriptName, i);
1465
cgi_replace_parameter("SCRIPT_NAME", zNew);
1466
}
1467
}
1468
#ifdef FOSSIL_ENABLE_JSON
1469
if(noJson==0 && json_request_is_json_api(zPathInfo)){
1470
/* We need to change some following behaviour depending on whether
1471
** we are operating in JSON mode or not. We cannot, however, be
1472
** certain whether we should/need to be in JSON mode until the
1473
** PATH_INFO is set up.
1474
*/
1475
g.json.isJsonMode = 1;
1476
json_bootstrap_early();
1477
}else{
1478
assert(!g.json.isJsonMode &&
1479
"Internal misconfiguration of g.json.isJsonMode");
1480
}
1481
#endif
1482
z = (char*)P("HTTP_COOKIE");
1483
if( z ){
1484
z = fossil_strdup(z);
1485
add_param_list(z, ';', 0);
1486
z = (char*)cookie_value("skin",0);
1487
if(z){
1488
skin_use_alternative(z, 2, SKIN_FROM_COOKIE);
1489
}
1490
}
1491
1492
cgi_setup_query_string();
1493
1494
z = (char*)P("REMOTE_ADDR");
1495
if( z ){
1496
g.zIpAddr = fossil_strdup(z);
1497
}
1498
1499
zContentLength = P("CONTENT_LENGTH");
1500
if( zContentLength==0 ){
1501
len = 0;
1502
if( sqlite3_stricmp(PD("REQUEST_METHOD",""),"POST")==0 ){
1503
malformed_request("missing CONTENT_LENGTH on a POST method");
1504
}
1505
}else{
1506
len = atoi(zContentLength);
1507
}
1508
zType = P("CONTENT_TYPE");
1509
zSemi = zType ? strchr(zType, ';') : 0;
1510
if( zSemi ){
1511
g.zContentType = fossil_strndup(zType, (int)(zSemi-zType));
1512
zType = g.zContentType;
1513
}else{
1514
g.zContentType = zType;
1515
}
1516
blob_zero(&g.cgiIn);
1517
if( len>0 && zType ){
1518
if( blob_read_from_cgi(&g.cgiIn, len)<len ){
1519
char *zMsg = mprintf("CGI content-length mismatch: Wanted %d bytes"
1520
" but got only %d\n", len, blob_size(&g.cgiIn));
1521
malformed_request(zMsg);
1522
}
1523
if( fossil_strcmp(zType, "application/x-fossil")==0 ){
1524
blob_uncompress(&g.cgiIn, &g.cgiIn);
1525
}
1526
#ifdef FOSSIL_ENABLE_JSON
1527
if( noJson==0 && g.json.isJsonMode!=0
1528
&& json_can_consume_content_type(zType)!=0 ){
1529
cgi_parse_POST_JSON(&g.cgiIn);
1530
cgi_set_content_type(json_guess_content_type());
1531
}
1532
#endif /* FOSSIL_ENABLE_JSON */
1533
}
1534
}
1535
1536
/*
1537
** Decode POST parameter information in the cgiIn content, if any.
1538
*/
1539
void cgi_decode_post_parameters(void){
1540
int len = blob_size(&g.cgiIn);
1541
if( len==0 ) return;
1542
if( fossil_strcmp(g.zContentType,"application/x-www-form-urlencoded")==0
1543
|| fossil_strncmp(g.zContentType,"multipart/form-data",19)==0
1544
){
1545
char *z = blob_str(&g.cgiIn);
1546
cgi_trace(z);
1547
if( g.zContentType[0]=='a' ){
1548
add_param_list(z, '&', 1);
1549
}else{
1550
process_multipart_form_data(z, len);
1551
}
1552
blob_init(&g.cgiIn, 0, 0);
1553
}
1554
}
1555
1556
/*
1557
** This is the comparison function used to sort the aParamQP[] array of
1558
** query parameters and cookies.
1559
*/
1560
static int qparam_compare(const void *a, const void *b){
1561
struct QParam *pA = (struct QParam*)a;
1562
struct QParam *pB = (struct QParam*)b;
1563
int c;
1564
c = fossil_strcmp(pA->zName, pB->zName);
1565
if( c==0 ){
1566
c = pA->seq - pB->seq;
1567
}
1568
return c;
1569
}
1570
1571
/*
1572
** Return the value of a query parameter or cookie whose name is zName.
1573
** If there is no query parameter or cookie named zName and the first
1574
** character of zName is uppercase, then check to see if there is an
1575
** environment variable by that name and return it if there is. As
1576
** a last resort when nothing else matches, return zDefault.
1577
*/
1578
const char *cgi_parameter(const char *zName, const char *zDefault){
1579
int lo, hi, mid, c;
1580
1581
/* The sortQP flag is set whenever a new query parameter is inserted.
1582
** It indicates that we need to resort the query parameters.
1583
*/
1584
if( sortQP ){
1585
int i, j;
1586
qsort(aParamQP, nUsedQP, sizeof(aParamQP[0]), qparam_compare);
1587
sortQP = 0;
1588
/* After sorting, remove duplicate parameters. The secondary sort
1589
** key is aParamQP[].seq and we keep the first entry. That means
1590
** with duplicate calls to cgi_set_parameter() the second and
1591
** subsequent calls are effectively no-ops. */
1592
for(i=j=1; i<nUsedQP; i++){
1593
if( fossil_strcmp(aParamQP[i].zName,aParamQP[i-1].zName)==0 ){
1594
continue;
1595
}
1596
if( j<i ){
1597
memcpy(&aParamQP[j], &aParamQP[i], sizeof(aParamQP[j]));
1598
}
1599
j++;
1600
}
1601
nUsedQP = j;
1602
}
1603
1604
/* Invoking with a NULL zName is just a way to cause the parameters
1605
** to be sorted. So go ahead and bail out in that case */
1606
if( zName==0 || zName[0]==0 ) return 0;
1607
1608
/* Do a binary search for a matching query parameter */
1609
lo = 0;
1610
hi = nUsedQP-1;
1611
while( lo<=hi ){
1612
mid = (lo+hi)/2;
1613
c = fossil_strcmp(aParamQP[mid].zName, zName);
1614
if( c==0 ){
1615
CGIDEBUG(("mem-match [%s] = [%s]\n", zName, aParamQP[mid].zValue));
1616
aParamQP[mid].isFetched = 1;
1617
return aParamQP[mid].zValue;
1618
}else if( c>0 ){
1619
hi = mid-1;
1620
}else{
1621
lo = mid+1;
1622
}
1623
}
1624
1625
/* If no match is found and the name begins with an upper-case
1626
** letter, then check to see if there is an environment variable
1627
** with the given name.
1628
*/
1629
if( fossil_isupper(zName[0]) ){
1630
const char *zValue = fossil_getenv(zName);
1631
if( zValue ){
1632
cgi_set_parameter_nocopy(zName, zValue, 0);
1633
CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue));
1634
return zValue;
1635
}
1636
}
1637
CGIDEBUG(("no-match [%s]\n", zName));
1638
return zDefault;
1639
}
1640
1641
/*
1642
** Return TRUE if the specific parameter exists and is a query parameter.
1643
** Return FALSE if the parameter is a cookie or environment variable.
1644
*/
1645
int cgi_is_qp(const char *zName){
1646
int i;
1647
if( zName==0 || fossil_isupper(zName[0]) ) return 0;
1648
for(i=0; i<nUsedQP; i++){
1649
if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){
1650
return aParamQP[i].isQP;
1651
}
1652
}
1653
return 0;
1654
}
1655
1656
/*
1657
** Renders the "begone, spider" page and exits.
1658
*/
1659
static void cgi_begone_spider(const char *zName){
1660
Blob content = empty_blob;
1661
cgi_set_content(&content);
1662
style_set_current_feature("test");
1663
style_submenu_enable(0);
1664
style_header("Malicious Query Detected");
1665
@ <h2>Begone, Knave!</h2>
1666
@ <p>This page was generated because Fossil detected an (unsuccessful)
1667
@ SQL injection attack or other nefarious content in your HTTP request.
1668
@
1669
@ <p>If you believe you are innocent and have reached this page in error,
1670
@ contact the Fossil developers on the Fossil-SCM Forum. Type
1671
@ "fossil-scm forum" into any search engine to locate the Fossil-SCM Forum.
1672
style_finish_page();
1673
cgi_set_status(418,"I'm a teapot");
1674
cgi_reply();
1675
fossil_errorlog("Xpossible hack attempt - 418 response on \"%s\"", zName);
1676
exit(0);
1677
}
1678
1679
/*
1680
** If looks_like_attack() returns true for the given string, call
1681
** cgi_begone_spider() and does not return, else this function has no
1682
** side effects. The range of checks performed by this function may
1683
** be extended in the future.
1684
**
1685
** Checks are omitted for any logged-in user.
1686
**
1687
** This is the primary defense against attack. Fossil should easily be
1688
** proof against SQL injection and XSS attacks even without this
1689
** routine. Rather, this is an attempt to avoid denial-of-service caused
1690
** by persistent spiders that hammer the server with dozens or hundreds of
1691
** probes per seconds as they look for vulnerabilities. In other
1692
** words, this is an effort to reduce the CPU load imposed by malicious
1693
** spiders. Though those routine might help make attacks harder, it is
1694
** not itself an impenetrably barrier against attack and should not be
1695
** relied upon as the only defense.
1696
*/
1697
void cgi_value_spider_check(const char *zTxt, const char *zName){
1698
if( g.zLogin==0 && looks_like_attack(zTxt) ){
1699
cgi_begone_spider(zName);
1700
}
1701
}
1702
1703
/*
1704
** A variant of cgi_parameter() with the same semantics except that if
1705
** cgi_parameter(zName,zDefault) returns a value other than zDefault
1706
** then it passes that value to cgi_value_spider_check().
1707
*/
1708
const char *cgi_parameter_no_attack(const char *zName, const char *zDefault){
1709
const char *zTxt = cgi_parameter(zName, zDefault);
1710
1711
if( zTxt!=zDefault ){
1712
cgi_value_spider_check(zTxt, zName);
1713
}
1714
return zTxt;
1715
}
1716
1717
/*
1718
** Return the value of the first defined query parameter or cookie whose
1719
** name appears in the list of arguments. Or if no parameter is found,
1720
** return NULL.
1721
*/
1722
const char *cgi_coalesce(const char *zName, ...){
1723
va_list ap;
1724
const char *z;
1725
const char *zX;
1726
if( zName==0 ) return 0;
1727
z = cgi_parameter(zName, 0);
1728
va_start(ap, zName);
1729
while( z==0 && (zX = va_arg(ap,const char*))!=0 ){
1730
z = cgi_parameter(zX, 0);
1731
}
1732
va_end(ap);
1733
return z;
1734
}
1735
1736
/*
1737
** Return the value of a CGI parameter with leading and trailing
1738
** spaces removed and with internal \r\n changed to just \n
1739
*/
1740
char *cgi_parameter_trimmed(const char *zName, const char *zDefault){
1741
const char *zIn;
1742
char *zOut, c;
1743
int i, j;
1744
zIn = cgi_parameter(zName, 0);
1745
if( zIn==0 ) zIn = zDefault;
1746
if( zIn==0 ) return 0;
1747
while( fossil_isspace(zIn[0]) ) zIn++;
1748
zOut = fossil_strdup(zIn);
1749
for(i=j=0; (c = zOut[i])!=0; i++){
1750
if( c=='\r' && zOut[i+1]=='\n' ) continue;
1751
zOut[j++] = c;
1752
}
1753
zOut[j] = 0;
1754
while( j>0 && fossil_isspace(zOut[j-1]) ) zOut[--j] = 0;
1755
return zOut;
1756
}
1757
1758
/*
1759
** Return true if the CGI parameter zName exists and is not equal to 0,
1760
** or "no" or "off".
1761
*/
1762
int cgi_parameter_boolean(const char *zName){
1763
const char *zIn = cgi_parameter(zName, 0);
1764
if( zIn==0 ) return 0;
1765
return zIn[0]==0 || is_truth(zIn);
1766
}
1767
1768
/*
1769
** Return either an empty string "" or the string "checked" depending
1770
** on whether or not parameter zName has value iValue. If parameter
1771
** zName does not exist, that is assumed to be the same as value 0.
1772
**
1773
** This routine implements the PCK(x) and PIF(x,y) macros. The PIF(x,y)
1774
** macro generateds " checked" if the value of parameter x equals integer y.
1775
** PCK(x) is the same as PIF(x,1). These macros are used to generate
1776
** the "checked" attribute on checkbox and radio controls of forms.
1777
*/
1778
const char *cgi_parameter_checked(const char *zName, int iValue){
1779
const char *zIn = cgi_parameter(zName,0);
1780
int x;
1781
if( zIn==0 ){
1782
x = 0;
1783
}else if( !fossil_isdigit(zIn[0]) ){
1784
x = is_truth(zIn);
1785
}else{
1786
x = atoi(zIn);
1787
}
1788
return x==iValue ? "checked" : "";
1789
}
1790
1791
/*
1792
** Return the name of the i-th CGI parameter. Return NULL if there
1793
** are fewer than i registered CGI parameters.
1794
*/
1795
const char *cgi_parameter_name(int i){
1796
if( i>=0 && i<nUsedQP ){
1797
return aParamQP[i].zName;
1798
}else{
1799
return 0;
1800
}
1801
}
1802
1803
/*
1804
** Print CGI debugging messages.
1805
*/
1806
void cgi_debug(const char *zFormat, ...){
1807
va_list ap;
1808
if( g.fDebug ){
1809
va_start(ap, zFormat);
1810
vfprintf(g.fDebug, zFormat, ap);
1811
va_end(ap);
1812
fflush(g.fDebug);
1813
}
1814
}
1815
1816
/*
1817
** Return true if any of the query parameters in the argument
1818
** list are defined.
1819
*/
1820
int cgi_any(const char *z, ...){
1821
va_list ap;
1822
char *z2;
1823
if( cgi_parameter(z,0)!=0 ) return 1;
1824
va_start(ap, z);
1825
while( (z2 = va_arg(ap, char*))!=0 ){
1826
if( cgi_parameter(z2,0)!=0 ) return 1;
1827
}
1828
va_end(ap);
1829
return 0;
1830
}
1831
1832
/*
1833
** Return true if all of the query parameters in the argument list
1834
** are defined.
1835
*/
1836
int cgi_all(const char *z, ...){
1837
va_list ap;
1838
char *z2;
1839
if( cgi_parameter(z,0)==0 ) return 0;
1840
va_start(ap, z);
1841
while( (z2 = va_arg(ap, char*))==0 ){
1842
if( cgi_parameter(z2,0)==0 ) return 0;
1843
}
1844
va_end(ap);
1845
return 1;
1846
}
1847
1848
/*
1849
** Load all relevant environment variables into the parameter buffer.
1850
** Invoke this routine prior to calling cgi_print_all() in order to see
1851
** the full CGI environment. This routine intended for debugging purposes
1852
** only.
1853
*/
1854
void cgi_load_environment(void){
1855
/* The following is a list of environment variables that Fossil considers
1856
** to be "relevant". */
1857
static const char *const azCgiVars[] = {
1858
"COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE", "SCGI",
1859
"HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING",
1860
"HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHENTICATION",
1861
"HTTP_CONNECTION", "HTTP_HOST",
1862
"HTTP_IF_NONE_MATCH", "HTTP_IF_MODIFIED_SINCE",
1863
"HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED",
1864
"QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT",
1865
"REMOTE_USER", "REQUEST_METHOD", "REQUEST_SCHEME",
1866
"REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_NAME",
1867
"SERVER_PROTOCOL", "HOME", "FOSSIL_HOME", "USERNAME", "USER",
1868
"FOSSIL_USER", "SQLITE_TMPDIR", "TMPDIR",
1869
"TEMP", "TMP", "FOSSIL_VFS",
1870
"FOSSIL_FORCE_TICKET_MODERATION", "FOSSIL_FORCE_WIKI_MODERATION",
1871
"FOSSIL_TCL_PATH", "TH1_DELETE_INTERP", "TH1_ENABLE_DOCS",
1872
"TH1_ENABLE_HOOKS", "TH1_ENABLE_TCL", "REMOTE_HOST",
1873
"CONTENT_TYPE", "CONTENT_LENGTH",
1874
};
1875
int i;
1876
for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
1877
}
1878
1879
/*
1880
** Print all query parameters on standard output.
1881
** This is used for testing and debugging.
1882
**
1883
** Omit the values of the cookies unless showAll is true.
1884
**
1885
** The eDest parameter determines where the output is shown:
1886
**
1887
** eDest==0: Rendering as HTML into the CGI reply
1888
** eDest==1: Written to fossil_trace
1889
** eDest==2: Written to cgi_debug
1890
** eDest==3: Written to out (Used only by fossil_errorlog())
1891
*/
1892
void cgi_print_all(int showAll, unsigned int eDest, FILE *out){
1893
int i;
1894
cgi_parameter("",""); /* Force the parameters into sorted order */
1895
for(i=0; i<nUsedQP; i++){
1896
const char *zName = aParamQP[i].zName;
1897
const char *zValue = aParamQP[i].zValue;
1898
if( fossil_stricmp("HTTP_COOKIE",zName)==0
1899
|| fossil_strnicmp("fossil-",zName,7)==0
1900
){
1901
if( !showAll ) continue;
1902
if( eDest==3 ) zValue = "...";
1903
}
1904
switch( eDest ){
1905
case 0: {
1906
cgi_printf("%h = %h <br>\n", zName, zValue);
1907
break;
1908
}
1909
case 1: {
1910
fossil_trace("%s = %s\n", zName, zValue);
1911
break;
1912
}
1913
case 2: {
1914
cgi_debug("%s = %s\n", zName, zValue);
1915
break;
1916
}
1917
case 3: {
1918
if( zValue!=0 && strlen(zValue)>100 ){
1919
fprintf(out,"%s = %.100s...\n", zName, zValue);
1920
}else{
1921
fprintf(out,"%s = %s\n", zName, zValue);
1922
}
1923
break;
1924
}
1925
}
1926
}
1927
}
1928
1929
/*
1930
** Put information about the N-th parameter into arguments.
1931
** Return non-zero on success, and return 0 if there is no N-th parameter.
1932
*/
1933
int cgi_param_info(
1934
int N,
1935
const char **pzName,
1936
const char **pzValue,
1937
int *pbIsQP
1938
){
1939
if( N>=0 && N<nUsedQP ){
1940
*pzName = aParamQP[N].zName;
1941
*pzValue = aParamQP[N].zValue;
1942
*pbIsQP = aParamQP[N].isQP;
1943
return 1;
1944
}else{
1945
*pzName = 0;
1946
*pzValue = 0;
1947
*pbIsQP = 0;
1948
return 0;
1949
}
1950
}
1951
1952
/*
1953
** Export all untagged query parameters (but not cookies or environment
1954
** variables) as hidden values of a form.
1955
*/
1956
void cgi_query_parameters_to_hidden(void){
1957
int i;
1958
const char *zN, *zV;
1959
for(i=0; i<nUsedQP; i++){
1960
if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue;
1961
zN = aParamQP[i].zName;
1962
zV = aParamQP[i].zValue;
1963
@ <input type="hidden" name="%h(zN)" value="%h(zV)">
1964
}
1965
}
1966
1967
/*
1968
** Export all untagged query parameters (but not cookies or environment
1969
** variables) to the HQuery object.
1970
*/
1971
void cgi_query_parameters_to_url(HQuery *p){
1972
int i;
1973
for(i=0; i<nUsedQP; i++){
1974
if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue;
1975
url_add_parameter(p, aParamQP[i].zName, aParamQP[i].zValue);
1976
}
1977
}
1978
1979
/*
1980
** Reconstruct the URL into memory obtained from fossil_malloc() and
1981
** return a pointer to that URL.
1982
*/
1983
char *cgi_reconstruct_original_url(void){
1984
int i;
1985
char cSep = '?';
1986
Blob url;
1987
blob_init(&url, 0, 0);
1988
blob_appendf(&url, "%s/%s", g.zBaseURL, g.zPath);
1989
for(i=0; i<nUsedQP; i++){
1990
if( aParamQP[i].isQP ){
1991
struct QParam *p = &aParamQP[i];
1992
if( p->zValue && p->zValue[0] ){
1993
blob_appendf(&url, "%c%t=%t", cSep, p->zName, p->zValue);
1994
}else{
1995
blob_appendf(&url, "%c%t", cSep, p->zName);
1996
}
1997
cSep = '&';
1998
}
1999
}
2000
return blob_str(&url);
2001
}
2002
2003
/*
2004
** Tag query parameter zName so that it is not exported by
2005
** cgi_query_parameters_to_hidden(). Or if zName==0, then
2006
** untag all query parameters.
2007
*/
2008
void cgi_tag_query_parameter(const char *zName){
2009
int i;
2010
if( zName==0 ){
2011
for(i=0; i<nUsedQP; i++) aParamQP[i].cTag = 0;
2012
}else{
2013
for(i=0; i<nUsedQP; i++){
2014
if( strcmp(zName,aParamQP[i].zName)==0 ) aParamQP[i].cTag = 1;
2015
}
2016
}
2017
}
2018
2019
/*
2020
** This routine works like "printf" except that it has the
2021
** extra formatting capabilities such as %h and %t.
2022
*/
2023
void cgi_printf(const char *zFormat, ...){
2024
va_list ap;
2025
va_start(ap,zFormat);
2026
vxprintf(pContent,zFormat,ap);
2027
va_end(ap);
2028
}
2029
2030
/*
2031
** This routine works like "vprintf" except that it has the
2032
** extra formatting capabilities such as %h and %t.
2033
*/
2034
void cgi_vprintf(const char *zFormat, va_list ap){
2035
vxprintf(pContent,zFormat,ap);
2036
}
2037
2038
2039
/*
2040
** Send a reply indicating that the HTTP request was malformed
2041
*/
2042
static NORETURN void malformed_request(const char *zMsg, ...){
2043
va_list ap;
2044
char *z;
2045
va_start(ap, zMsg);
2046
z = vmprintf(zMsg, ap);
2047
va_end(ap);
2048
cgi_set_status(400, "Bad Request");
2049
zReplyMimeType = "text/plain";
2050
if( g.zReqType==0 ) g.zReqType = "WWW";
2051
if( g.zReqType[0]=='C' && PD("SERVER_SOFTWARE",0)!=0 ){
2052
const char *zServer = PD("SERVER_SOFTWARE","");
2053
cgi_printf("Bad CGI Request from \"%s\": %s\n",zServer,z);
2054
}else{
2055
cgi_printf("Bad %s Request: %s\n", g.zReqType, z);
2056
}
2057
fossil_free(z);
2058
cgi_reply();
2059
fossil_exit(0);
2060
}
2061
2062
/*
2063
** Panic and die while processing a webpage.
2064
*/
2065
NORETURN void cgi_panic(const char *zFormat, ...){
2066
va_list ap;
2067
cgi_reset_content();
2068
#ifdef FOSSIL_ENABLE_JSON
2069
if( g.json.isJsonMode ){
2070
char * zMsg;
2071
va_start(ap, zFormat);
2072
zMsg = vmprintf(zFormat,ap);
2073
va_end(ap);
2074
json_err( FSL_JSON_E_PANIC, zMsg, 1 );
2075
free(zMsg);
2076
fossil_exit( g.isHTTP ? 0 : 1 );
2077
}else
2078
#endif /* FOSSIL_ENABLE_JSON */
2079
{
2080
cgi_set_status(500, "Internal Server Error");
2081
cgi_printf(
2082
"<html><body><h1>Internal Server Error</h1>\n"
2083
"<plaintext>"
2084
);
2085
va_start(ap, zFormat);
2086
vxprintf(pContent,zFormat,ap);
2087
va_end(ap);
2088
cgi_reply();
2089
fossil_exit(1);
2090
}
2091
}
2092
2093
/* z[] is the value of an X-FORWARDED-FOR: line in an HTTP header.
2094
** Return a pointer to a string containing the real IP address, or a
2095
** NULL pointer to stick with the IP address previously computed and
2096
** loaded into g.zIpAddr.
2097
*/
2098
static const char *cgi_accept_forwarded_for(const char *z){
2099
int i;
2100
if( !cgi_is_loopback(g.zIpAddr) ){
2101
/* Only accept X-FORWARDED-FOR if input coming from the local machine */
2102
return 0;
2103
}
2104
i = strlen(z)-1;
2105
while( i>=0 && z[i]!=',' && !fossil_isspace(z[i]) ) i--;
2106
return &z[++i];
2107
}
2108
2109
/*
2110
** Remove the first space-delimited token from a string and return
2111
** a pointer to it. Add a NULL to the string to terminate the token.
2112
** Make *zLeftOver point to the start of the next token.
2113
*/
2114
static char *extract_token(char *zInput, char **zLeftOver){
2115
char *zResult = 0;
2116
if( zInput==0 ){
2117
if( zLeftOver ) *zLeftOver = 0;
2118
return 0;
2119
}
2120
while( fossil_isspace(*zInput) ){ zInput++; }
2121
zResult = zInput;
2122
while( *zInput && !fossil_isspace(*zInput) ){ zInput++; }
2123
if( *zInput ){
2124
*zInput = 0;
2125
zInput++;
2126
while( fossil_isspace(*zInput) ){ zInput++; }
2127
}
2128
if( zLeftOver ){ *zLeftOver = zInput; }
2129
return zResult;
2130
}
2131
2132
/*
2133
** All possible forms of an IP address. Needed to work around GCC strict
2134
** aliasing rules.
2135
*/
2136
typedef union {
2137
struct sockaddr sa; /* Abstract superclass */
2138
struct sockaddr_in sa4; /* IPv4 */
2139
struct sockaddr_in6 sa6; /* IPv6 */
2140
struct sockaddr_storage sas; /* Should be the maximum of the above 3 */
2141
} address;
2142
2143
/*
2144
** Determine the IP address on the other side of a connection.
2145
** Return a pointer to a string. Or return 0 if unable.
2146
**
2147
** The string is held in a static buffer that is overwritten on
2148
** each call.
2149
*/
2150
char *cgi_remote_ip(int fd){
2151
address remoteAddr;
2152
socklen_t size = sizeof(remoteAddr);
2153
static char zHost[NI_MAXHOST];
2154
if( getpeername(0, &remoteAddr.sa, &size) ){
2155
return 0;
2156
}
2157
if( getnameinfo(&remoteAddr.sa, size, zHost, sizeof(zHost), 0, 0,
2158
NI_NUMERICHOST) ){
2159
return 0;
2160
}
2161
return zHost;
2162
}
2163
2164
/*
2165
** This routine handles a single HTTP request which is coming in on
2166
** g.httpIn and which replies on g.httpOut
2167
**
2168
** The HTTP request is read from g.httpIn and is used to initialize
2169
** entries in the cgi_parameter() hash, as if those entries were
2170
** environment variables. A call to cgi_init() completes
2171
** the setup. Once all the setup is finished, this procedure returns
2172
** and subsequent code handles the actual generation of the webpage.
2173
*/
2174
void cgi_handle_http_request(const char *zIpAddr){
2175
char *z, *zToken;
2176
int i;
2177
const char *zScheme = "http";
2178
char zLine[2000]; /* A single line of input. */
2179
g.fullHttpReply = 1;
2180
g.zReqType = "HTTP";
2181
2182
if( cgi_fgets(zLine, sizeof(zLine))==0 ){
2183
malformed_request("missing header");
2184
}
2185
blob_append(&g.httpHeader, zLine, -1);
2186
cgi_trace(zLine);
2187
zToken = extract_token(zLine, &z);
2188
if( zToken==0 ){
2189
malformed_request("malformed HTTP header");
2190
}
2191
if( fossil_strcmp(zToken,"GET")!=0
2192
&& fossil_strcmp(zToken,"POST")!=0
2193
&& fossil_strcmp(zToken,"HEAD")!=0
2194
){
2195
malformed_request("unsupported HTTP method: \"%s\" - Fossil only supports "
2196
"GET, POST, and HEAD", zToken);
2197
}
2198
cgi_setenv("GATEWAY_INTERFACE","CGI/1.0");
2199
cgi_setenv("REQUEST_METHOD",zToken);
2200
zToken = extract_token(z, &z);
2201
if( zToken==0 ){
2202
malformed_request("malformed URI in the HTTP header");
2203
}
2204
cgi_setenv("REQUEST_URI", zToken);
2205
cgi_setenv("SCRIPT_NAME", "");
2206
for(i=0; zToken[i] && zToken[i]!='?'; i++){}
2207
if( zToken[i] ) zToken[i++] = 0;
2208
cgi_setenv("PATH_INFO", zToken);
2209
cgi_setenv("QUERY_STRING", &zToken[i]);
2210
if( zIpAddr==0 ){
2211
zIpAddr = cgi_remote_ip(fossil_fileno(g.httpIn));
2212
}
2213
if( zIpAddr ){
2214
cgi_setenv("REMOTE_ADDR", zIpAddr);
2215
g.zIpAddr = fossil_strdup(zIpAddr);
2216
}
2217
2218
/* Get all the optional fields that follow the first line.
2219
*/
2220
while( cgi_fgets(zLine,sizeof(zLine)) ){
2221
char *zFieldName;
2222
char *zVal;
2223
2224
cgi_trace(zLine);
2225
blob_append(&g.httpHeader, zLine, -1);
2226
zFieldName = extract_token(zLine,&zVal);
2227
if( zFieldName==0 || *zFieldName==0 ) break;
2228
while( fossil_isspace(*zVal) ){ zVal++; }
2229
i = strlen(zVal);
2230
while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; }
2231
zVal[i] = 0;
2232
for(i=0; zFieldName[i]; i++){
2233
zFieldName[i] = fossil_tolower(zFieldName[i]);
2234
}
2235
if( fossil_strcmp(zFieldName,"accept-encoding:")==0 ){
2236
cgi_setenv("HTTP_ACCEPT_ENCODING", zVal);
2237
}else if( fossil_strcmp(zFieldName,"content-length:")==0 ){
2238
cgi_setenv("CONTENT_LENGTH", zVal);
2239
}else if( fossil_strcmp(zFieldName,"content-type:")==0 ){
2240
cgi_setenv("CONTENT_TYPE", zVal);
2241
}else if( fossil_strcmp(zFieldName,"cookie:")==0 ){
2242
cgi_setenv("HTTP_COOKIE", zVal);
2243
}else if( fossil_strcmp(zFieldName,"https:")==0 ){
2244
cgi_setenv("HTTPS", zVal);
2245
zScheme = "https";
2246
}else if( fossil_strcmp(zFieldName,"host:")==0 ){
2247
char *z;
2248
cgi_setenv("HTTP_HOST", zVal);
2249
z = strchr(zVal, ':');
2250
if( z ) z[0] = 0;
2251
cgi_setenv("SERVER_NAME", zVal);
2252
}else if( fossil_strcmp(zFieldName,"if-none-match:")==0 ){
2253
cgi_setenv("HTTP_IF_NONE_MATCH", zVal);
2254
}else if( fossil_strcmp(zFieldName,"if-modified-since:")==0 ){
2255
cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal);
2256
}else if( fossil_strcmp(zFieldName,"referer:")==0 ){
2257
cgi_setenv("HTTP_REFERER", zVal);
2258
}else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){
2259
cgi_setenv("HTTP_USER_AGENT", zVal);
2260
}else if( fossil_strcmp(zFieldName,"authorization:")==0 ){
2261
cgi_setenv("HTTP_AUTHORIZATION", zVal);
2262
}else if( fossil_strcmp(zFieldName,"accept-language:")==0 ){
2263
cgi_setenv("HTTP_ACCEPT_LANGUAGE", zVal);
2264
}else if( fossil_strcmp(zFieldName,"x-forwarded-for:")==0 ){
2265
const char *zIpAddr = cgi_accept_forwarded_for(zVal);
2266
if( zIpAddr!=0 ){
2267
g.zIpAddr = fossil_strdup(zIpAddr);
2268
cgi_replace_parameter("REMOTE_ADDR", g.zIpAddr);
2269
}
2270
}else if( fossil_strcmp(zFieldName,"range:")==0 ){
2271
int x1 = 0;
2272
int x2 = 0;
2273
if( sscanf(zVal,"bytes=%d-%d",&x1,&x2)==2 && x1>=0 && x1<=x2 ){
2274
rangeStart = x1;
2275
rangeEnd = x2+1;
2276
}
2277
}
2278
}
2279
cgi_setenv("REQUEST_SCHEME",zScheme);
2280
cgi_init();
2281
cgi_trace(0);
2282
}
2283
2284
/*
2285
** This routine handles a single HTTP request from an SSH client which is
2286
** coming in on g.httpIn and which replies on g.httpOut
2287
**
2288
** Once all the setup is finished, this procedure returns
2289
** and subsequent code handles the actual generation of the webpage.
2290
**
2291
** It is called in a loop so some variables will need to be replaced
2292
*/
2293
void cgi_handle_ssh_http_request(const char *zIpAddr){
2294
static int nCycles = 0;
2295
static char *zCmd = 0;
2296
char *z, *zToken;
2297
char *zMethod;
2298
int i;
2299
size_t n;
2300
char zLine[2000]; /* A single line of input. */
2301
2302
assert( !g.httpUseSSL );
2303
#ifdef FOSSIL_ENABLE_JSON
2304
if( nCycles==0 ){ json_bootstrap_early(); }
2305
#endif
2306
if( zIpAddr ){
2307
if( nCycles==0 ){
2308
cgi_setenv("REMOTE_ADDR", zIpAddr);
2309
g.zIpAddr = fossil_strdup(zIpAddr);
2310
}
2311
}else{
2312
fossil_fatal("missing SSH IP address");
2313
}
2314
g.zReqType = "HTTP";
2315
if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
2316
malformed_request("missing HTTP header");
2317
}
2318
cgi_trace(zLine);
2319
zToken = extract_token(zLine, &z);
2320
if( zToken==0 ){
2321
malformed_request("malformed HTTP header");
2322
}
2323
2324
if( fossil_strcmp(zToken, "echo")==0 ){
2325
/* start looking for probes to complete transport_open */
2326
zCmd = cgi_handle_ssh_probes(zLine, sizeof(zLine), z, zToken);
2327
if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
2328
malformed_request("missing HTTP header");
2329
}
2330
cgi_trace(zLine);
2331
zToken = extract_token(zLine, &z);
2332
if( zToken==0 ){
2333
malformed_request("malformed HTTP header");
2334
}
2335
}else if( zToken && strlen(zToken)==0 && zCmd ){
2336
/* transport_flip request and continued transport_open */
2337
cgi_handle_ssh_transport(zCmd);
2338
if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
2339
malformed_request("missing HTTP header");
2340
}
2341
cgi_trace(zLine);
2342
zToken = extract_token(zLine, &z);
2343
if( zToken==0 ){
2344
malformed_request("malformed HTTP header");
2345
}
2346
}
2347
2348
zMethod = fossil_strdup(zToken);
2349
if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0
2350
&& fossil_strcmp(zToken,"HEAD")!=0 ){
2351
malformed_request("unsupported HTTP method");
2352
}
2353
2354
if( nCycles==0 ){
2355
cgi_setenv("GATEWAY_INTERFACE","CGI/1.0");
2356
cgi_setenv("REQUEST_METHOD",zToken);
2357
}
2358
2359
zToken = extract_token(z, &z);
2360
if( zToken==0 ){
2361
malformed_request("malformed URL in HTTP header");
2362
}
2363
n = strlen(g.zRepositoryName);
2364
if( fossil_strncmp(g.zRepositoryName, zToken, n)==0
2365
&& (zToken[n]=='/' || zToken[n]==0)
2366
&& fossil_strcmp(zMethod,"GET")==0
2367
){
2368
zToken += n;
2369
if( zToken && strlen(zToken)==0 ){
2370
malformed_request("malformed URL in HTTP header");
2371
}
2372
}
2373
if( nCycles==0 ){
2374
cgi_setenv("REQUEST_URI", zToken);
2375
cgi_setenv("SCRIPT_NAME", "");
2376
}
2377
2378
for(i=0; zToken[i] && zToken[i]!='?'; i++){}
2379
if( zToken[i] ) zToken[i++] = 0;
2380
if( nCycles==0 ){
2381
cgi_setenv("PATH_INFO", zToken);
2382
cgi_setenv("QUERY_STRING",&zToken[i]);
2383
}else{
2384
cgi_replace_parameter("PATH_INFO", fossil_strdup(zToken));
2385
cgi_replace_parameter("QUERY_STRING",fossil_strdup(&zToken[i]));
2386
}
2387
2388
/* Get all the optional fields that follow the first line.
2389
*/
2390
while( fgets(zLine,sizeof(zLine),g.httpIn) ){
2391
char *zFieldName;
2392
char *zVal;
2393
2394
cgi_trace(zLine);
2395
zFieldName = extract_token(zLine,&zVal);
2396
if( zFieldName==0 || *zFieldName==0 ) break;
2397
while( fossil_isspace(*zVal) ){ zVal++; }
2398
i = strlen(zVal);
2399
while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; }
2400
zVal[i] = 0;
2401
for(i=0; zFieldName[i]; i++){
2402
zFieldName[i] = fossil_tolower(zFieldName[i]);
2403
}
2404
if( fossil_strcmp(zFieldName,"content-length:")==0 ){
2405
if( nCycles==0 ){
2406
cgi_setenv("CONTENT_LENGTH", zVal);
2407
}else{
2408
cgi_replace_parameter("CONTENT_LENGTH", zVal);
2409
}
2410
}else if( fossil_strcmp(zFieldName,"content-type:")==0 ){
2411
if( nCycles==0 ){
2412
cgi_setenv("CONTENT_TYPE", zVal);
2413
}
2414
}else if( fossil_strcmp(zFieldName,"host:")==0 ){
2415
if( nCycles==0 ){
2416
cgi_setenv("HTTP_HOST", zVal);
2417
}
2418
}else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){
2419
if( nCycles==0 ){
2420
cgi_setenv("HTTP_USER_AGENT", zVal);
2421
}
2422
}else if( fossil_strcmp(zFieldName,"x-fossil-transport:")==0 ){
2423
if( fossil_strnicmp(zVal, "ssh", 3)==0 ){
2424
if( nCycles==0 ){
2425
g.fSshClient |= CGI_SSH_FOSSIL;
2426
g.fullHttpReply = 0;
2427
}
2428
}
2429
}
2430
}
2431
2432
if( nCycles==0 ){
2433
if( ! ( g.fSshClient & CGI_SSH_FOSSIL ) ){
2434
/* did not find new fossil ssh transport */
2435
g.fSshClient &= ~CGI_SSH_CLIENT;
2436
g.fullHttpReply = 1;
2437
cgi_replace_parameter("REMOTE_ADDR", "127.0.0.1");
2438
}
2439
}
2440
2441
cgi_reset_content();
2442
cgi_destination(CGI_BODY);
2443
2444
cgi_init();
2445
cgi_trace(0);
2446
nCycles++;
2447
}
2448
2449
/*
2450
** This routine handles the old fossil SSH probes
2451
*/
2452
char *cgi_handle_ssh_probes(char *zLine, int zSize, char *z, char *zToken){
2453
/* Start looking for probes */
2454
assert( !g.httpUseSSL );
2455
while( fossil_strcmp(zToken, "echo")==0 ){
2456
zToken = extract_token(z, &z);
2457
if( zToken==0 ){
2458
malformed_request("malformed probe");
2459
}
2460
if( fossil_strncmp(zToken, "test", 4)==0 ||
2461
fossil_strncmp(zToken, "probe-", 6)==0 ){
2462
fprintf(g.httpOut, "%s\n", zToken);
2463
fflush(g.httpOut);
2464
}else{
2465
malformed_request("malformed probe");
2466
}
2467
if( fgets(zLine, zSize, g.httpIn)==0 ){
2468
malformed_request("malformed probe");
2469
}
2470
cgi_trace(zLine);
2471
zToken = extract_token(zLine, &z);
2472
if( zToken==0 ){
2473
malformed_request("malformed probe");
2474
}
2475
}
2476
2477
/* Got all probes now first transport_open is completed
2478
** so return the command that was requested
2479
*/
2480
g.fSshClient |= CGI_SSH_COMPAT;
2481
return fossil_strdup(zToken);
2482
}
2483
2484
/*
2485
** This routine handles the old fossil SSH transport_flip
2486
** and transport_open communications if detected.
2487
*/
2488
void cgi_handle_ssh_transport(const char *zCmd){
2489
char *z, *zToken;
2490
char zLine[2000]; /* A single line of input. */
2491
2492
assert( !g.httpUseSSL );
2493
/* look for second newline of transport_flip */
2494
if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
2495
malformed_request("incorrect transport_flip");
2496
}
2497
cgi_trace(zLine);
2498
zToken = extract_token(zLine, &z);
2499
if( zToken && strlen(zToken)==0 ){
2500
/* look for path to fossil */
2501
if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
2502
if( zCmd==0 ){
2503
malformed_request("missing fossil command");
2504
}else{
2505
/* no new command so exit */
2506
fossil_exit(0);
2507
}
2508
}
2509
cgi_trace(zLine);
2510
zToken = extract_token(zLine, &z);
2511
if( zToken==0 ){
2512
malformed_request("malformed fossil command");
2513
}
2514
/* see if we've seen the command */
2515
if( zCmd && zCmd[0] && fossil_strcmp(zToken, zCmd)==0 ){
2516
return;
2517
}else{
2518
malformed_request("transport_open failed");
2519
}
2520
}else{
2521
malformed_request("transport_flip failed");
2522
}
2523
}
2524
2525
/*
2526
** This routine handles a single SCGI request which is coming in on
2527
** g.httpIn and which replies on g.httpOut
2528
**
2529
** The SCGI request is read from g.httpIn and is used to initialize
2530
** entries in the cgi_parameter() hash, as if those entries were
2531
** environment variables. A call to cgi_init() completes
2532
** the setup. Once all the setup is finished, this procedure returns
2533
** and subsequent code handles the actual generation of the webpage.
2534
*/
2535
void cgi_handle_scgi_request(void){
2536
char *zHdr;
2537
char *zToFree;
2538
int nHdr = 0;
2539
int nRead;
2540
int c, n, m;
2541
2542
assert( !g.httpUseSSL );
2543
while( (c = fgetc(g.httpIn))!=EOF && fossil_isdigit((char)c) ){
2544
nHdr = nHdr*10 + (char)c - '0';
2545
}
2546
if( nHdr<16 ) malformed_request("SCGI header too short");
2547
zToFree = zHdr = fossil_malloc(nHdr);
2548
nRead = (int)fread(zHdr, 1, nHdr, g.httpIn);
2549
if( nRead<nHdr ) malformed_request("cannot read entire SCGI header");
2550
nHdr = nRead;
2551
while( nHdr ){
2552
for(n=0; n<nHdr && zHdr[n]; n++){}
2553
for(m=n+1; m<nHdr && zHdr[m]; m++){}
2554
if( m>=nHdr ) malformed_request("SCGI header formatting error");
2555
cgi_set_parameter(zHdr, zHdr+n+1);
2556
zHdr += m+1;
2557
nHdr -= m+1;
2558
}
2559
fossil_free(zToFree);
2560
(void)fgetc(g.httpIn); /* Read past the "," separating header from content */
2561
cgi_init();
2562
}
2563
2564
#if INTERFACE
2565
/*
2566
** Bitmap values for the flags parameter to cgi_http_server().
2567
*/
2568
#define HTTP_SERVER_LOCALHOST 0x0001 /* Bind to 127.0.0.1 only */
2569
#define HTTP_SERVER_SCGI 0x0002 /* SCGI instead of HTTP */
2570
#define HTTP_SERVER_HAD_REPOSITORY 0x0004 /* Was the repository open? */
2571
#define HTTP_SERVER_HAD_CHECKOUT 0x0008 /* Was a checkout open? */
2572
#define HTTP_SERVER_REPOLIST 0x0010 /* Allow repo listing */
2573
#define HTTP_SERVER_NOFORK 0x0020 /* Do not call fork() */
2574
#define HTTP_SERVER_UNIXSOCKET 0x0040 /* Use a unix-domain socket */
2575
2576
#endif /* INTERFACE */
2577
2578
/*
2579
** Maximum number of child processes that we can have running
2580
** at one time. Set this to 0 for "no limit".
2581
*/
2582
#ifndef FOSSIL_MAX_CONNECTIONS
2583
# define FOSSIL_MAX_CONNECTIONS 1000
2584
#endif
2585
2586
/*
2587
** Implement an HTTP server daemon listening on port iPort.
2588
**
2589
** As new connections arrive, fork a child and let child return
2590
** out of this procedure call. The child will handle the request.
2591
** The parent never returns from this procedure.
2592
**
2593
** Return 0 to each child as it runs. If unable to establish a
2594
** listening socket, return non-zero.
2595
*/
2596
int cgi_http_server(
2597
int mnPort, int mxPort, /* Range of TCP ports to try */
2598
const char *zBrowser, /* Run this browser, if not NULL */
2599
const char *zIpAddr, /* Bind to this IP address, if not null */
2600
int flags /* HTTP_SERVER_* flags */
2601
){
2602
#if defined(_WIN32)
2603
/* Use win32_http_server() instead */
2604
fossil_exit(1);
2605
#else
2606
int listen4 = -1; /* Main socket; IPv4 or unix-domain */
2607
int listen6 = -1; /* Aux socket for corresponding IPv6 */
2608
int mxListen = -1; /* Maximum of listen4 and listen6 */
2609
int connection; /* An incoming connection */
2610
int nRequest = 0; /* Number of requests handled so far */
2611
fd_set readfds; /* Set of file descriptors for select() */
2612
socklen_t lenaddr; /* Length of the inaddr structure */
2613
int child; /* PID of the child process */
2614
int nchildren = 0; /* Number of child processes */
2615
struct timeval delay; /* How long to wait inside select() */
2616
struct sockaddr_in6 inaddr6; /* Address for IPv6 */
2617
struct sockaddr_in inaddr4; /* Address for IPv4 */
2618
struct sockaddr_un uxaddr; /* The address for unix-domain sockets */
2619
int opt = 1; /* setsockopt flag */
2620
int rc; /* Result code from system calls */
2621
int iPort = mnPort; /* Port to try to use */
2622
const char *zRequestType; /* Type of requests to listen for */
2623
2624
2625
if( flags & HTTP_SERVER_SCGI ){
2626
zRequestType = "SCGI";
2627
}else if( g.httpUseSSL ){
2628
zRequestType = "TLS-encrypted HTTPS";
2629
}else{
2630
zRequestType = "HTTP";
2631
}
2632
2633
if( flags & HTTP_SERVER_UNIXSOCKET ){
2634
/* CASE 1: A unix socket named g.zSockName. After creation, set the
2635
** permissions on the new socket to g.zSockMode and make the
2636
** owner of the socket be g.zSockOwner.
2637
*/
2638
assert( g.zSockName!=0 );
2639
memset(&uxaddr, 0, sizeof(uxaddr));
2640
if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){
2641
fossil_fatal("name of unix socket too big: %s\nmax size: %d\n",
2642
g.zSockName, (int)sizeof(uxaddr.sun_path));
2643
}
2644
if( file_isdir(g.zSockName, ExtFILE)!=0 ){
2645
if( !file_issocket(g.zSockName) ){
2646
fossil_fatal("cannot name socket \"%s\" because another object"
2647
" with that name already exists", g.zSockName);
2648
}else{
2649
unlink(g.zSockName);
2650
}
2651
}
2652
uxaddr.sun_family = AF_UNIX;
2653
strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1);
2654
listen4 = socket(AF_UNIX, SOCK_STREAM, 0);
2655
if( listen4<0 ){
2656
fossil_fatal("unable to create a unix socket named %s",
2657
g.zSockName);
2658
}
2659
mxListen = listen4;
2660
listen6 = -1;
2661
2662
/* Set the access permission for the new socket. Default to 0660.
2663
** But use an alternative specified by --socket-mode if available.
2664
** Do this before bind() to avoid a race condition. */
2665
if( g.zSockMode ){
2666
file_set_mode(g.zSockName, listen4, g.zSockMode, 0);
2667
}else{
2668
file_set_mode(g.zSockName, listen4, "0660", 1);
2669
}
2670
rc = bind(listen4, (struct sockaddr*)&uxaddr, sizeof(uxaddr));
2671
/* Set the owner of the socket if requested by --socket-owner. This
2672
** must wait until after bind(), after the filesystem object has been
2673
** created. See https://lkml.org/lkml/2004/11/1/84 and
2674
** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */
2675
if( g.zSockOwner ){
2676
file_set_owner(g.zSockName, listen4, g.zSockOwner);
2677
}
2678
fossil_print("Listening for %s requests on unix socket %s\n",
2679
zRequestType, g.zSockName);
2680
fflush(stdout);
2681
}else if( zIpAddr && strchr(zIpAddr,':')!=0 ){
2682
/* CASE 2: TCP on IPv6 IP address specified by zIpAddr and on port iPort.
2683
*/
2684
assert( mnPort==mxPort );
2685
memset(&inaddr6, 0, sizeof(inaddr6));
2686
inaddr6.sin6_family = AF_INET6;
2687
inaddr6.sin6_port = htons(iPort);
2688
if( inet_pton(AF_INET6, zIpAddr, &inaddr6.sin6_addr)==0 ){
2689
fossil_fatal("not a valid IPv6 address: %s", zIpAddr);
2690
}
2691
listen6 = socket(AF_INET6, SOCK_STREAM, 0);
2692
if( listen6>0 ){
2693
opt = 1;
2694
setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2695
rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6));
2696
if( rc<0 ){
2697
close(listen6);
2698
listen6 = -1;
2699
}
2700
}
2701
if( listen6<0 ){
2702
fossil_fatal("cannot open a listening socket on [%s]:%d",
2703
zIpAddr, mnPort);
2704
}
2705
mxListen = listen6;
2706
listen4 = -1;
2707
fossil_print("Listening for %s requests on [%s]:%d\n",
2708
zRequestType, zIpAddr, iPort);
2709
fflush(stdout);
2710
}else if( zIpAddr && zIpAddr[0] ){
2711
/* CASE 3: TCP on IPv4 IP address specified by zIpAddr and on port iPort.
2712
*/
2713
assert( mnPort==mxPort );
2714
memset(&inaddr4, 0, sizeof(inaddr4));
2715
inaddr4.sin_family = AF_INET;
2716
inaddr4.sin_port = htons(iPort);
2717
if( strcmp(zIpAddr, "localhost")==0 ) zIpAddr = "127.0.0.1";
2718
inaddr4.sin_addr.s_addr = inet_addr(zIpAddr);
2719
if( inaddr4.sin_addr.s_addr == INADDR_NONE ){
2720
fossil_fatal("not a valid IPv4 address: %s", zIpAddr);
2721
}
2722
listen4 = socket(AF_INET, SOCK_STREAM, 0);
2723
if( listen4>0 ){
2724
setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2725
rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
2726
if( rc<0 ){
2727
close(listen4);
2728
listen4 = -1;
2729
}
2730
}
2731
if( listen4<0 ){
2732
fossil_fatal("cannot open a listening socket on %s:%d",
2733
zIpAddr, mnPort);
2734
}
2735
mxListen = listen4;
2736
listen6 = -1;
2737
fossil_print("Listening for %s requests on TCP port %s:%d\n",
2738
zRequestType, zIpAddr, iPort);
2739
fflush(stdout);
2740
}else{
2741
/* CASE 4: Listen on all available IP addresses, or on only loopback
2742
** addresses (if HTTP_SERVER_LOCALHOST). The TCP port is the
2743
** first available in the range of mnPort..mxPort. Listen
2744
** on both IPv4 and IPv6, if possible. The TCP port scan is done
2745
** on IPv4.
2746
*/
2747
while( iPort<=mxPort ){
2748
const char *zProto;
2749
memset(&inaddr4, 0, sizeof(inaddr4));
2750
inaddr4.sin_family = AF_INET;
2751
inaddr4.sin_port = htons(iPort);
2752
if( flags & HTTP_SERVER_LOCALHOST ){
2753
inaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
2754
}else{
2755
inaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
2756
}
2757
listen4 = socket(AF_INET, SOCK_STREAM, 0);
2758
if( listen4>0 ){
2759
setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2760
rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
2761
if( rc<0 ){
2762
close(listen4);
2763
listen4 = -1;
2764
}
2765
}
2766
if( listen4<0 ){
2767
iPort++;
2768
continue;
2769
}
2770
mxListen = listen4;
2771
2772
/* If we get here, that means we found an open TCP port at iPort for
2773
** IPv4. Try to set up a corresponding IPv6 socket on the same port.
2774
*/
2775
memset(&inaddr6, 0, sizeof(inaddr6));
2776
inaddr6.sin6_family = AF_INET6;
2777
inaddr6.sin6_port = htons(iPort);
2778
if( flags & HTTP_SERVER_LOCALHOST ){
2779
inaddr6.sin6_addr = in6addr_loopback;
2780
}else{
2781
inaddr6.sin6_addr = in6addr_any;
2782
}
2783
listen6 = socket(AF_INET6, SOCK_STREAM, 0);
2784
if( listen6>0 ){
2785
setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
2786
setsockopt(listen6, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
2787
rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6));
2788
if( rc<0 ){
2789
close(listen6);
2790
listen6 = -1;
2791
}
2792
}
2793
if( listen6<0 ){
2794
zProto = "IPv4 only";
2795
}else{
2796
zProto = "IPv4 and IPv6";
2797
if( listen6>listen4 ) mxListen = listen6;
2798
}
2799
2800
fossil_print("Listening for %s requests on TCP port %s%d, %s\n",
2801
zRequestType,
2802
(flags & HTTP_SERVER_LOCALHOST)!=0 ? "localhost:" : "",
2803
iPort, zProto);
2804
fflush(stdout);
2805
break;
2806
}
2807
if( iPort>mxPort ){
2808
fossil_fatal("no available TCP ports in the range %d..%d",
2809
mnPort, mxPort);
2810
}
2811
}
2812
2813
/* If we get to this point, that means there is at least one listening
2814
** socket on either listen4 or listen6 and perhaps on both. */
2815
assert( listen4>0 || listen6>0 );
2816
if( listen4>0 ) listen(listen4,10);
2817
if( listen6>0 ) listen(listen6,10);
2818
if( zBrowser && (flags & HTTP_SERVER_UNIXSOCKET)==0 ){
2819
assert( strstr(zBrowser,"%d")!=0 );
2820
zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort);
2821
#if defined(__CYGWIN__)
2822
/* On Cygwin, we can do better than "echo" */
2823
if( fossil_strncmp(zBrowser, "echo ", 5)==0 ){
2824
wchar_t *wUrl = fossil_utf8_to_unicode(zBrowser+5);
2825
wUrl[wcslen(wUrl)-2] = 0; /* Strip terminating " &" */
2826
if( (size_t)ShellExecuteW(0, L"open", wUrl, 0, 0, 1)<33 ){
2827
fossil_warning("cannot start browser\n");
2828
}
2829
}else
2830
#endif
2831
if( fossil_system(zBrowser)<0 ){
2832
fossil_warning("cannot start browser: %s\n", zBrowser);
2833
}
2834
}
2835
2836
/* What for incoming requests. For each request, fork() a child process
2837
** to deal with that request. The child process returns. The parent
2838
** keeps on listening and never returns.
2839
*/
2840
while( 1 ){
2841
#if FOSSIL_MAX_CONNECTIONS>0
2842
while( nchildren>=FOSSIL_MAX_CONNECTIONS ){
2843
if( wait(0)>=0 ) nchildren--;
2844
}
2845
#endif
2846
delay.tv_sec = 0;
2847
delay.tv_usec = 100000;
2848
FD_ZERO(&readfds);
2849
assert( listen4>0 || listen6>0 );
2850
if( listen4>0 ) FD_SET( listen4, &readfds);
2851
if( listen6>0 ) FD_SET( listen6, &readfds);
2852
select( mxListen+1, &readfds, 0, 0, &delay);
2853
if( listen4>0 && FD_ISSET(listen4, &readfds) ){
2854
lenaddr = sizeof(inaddr4);
2855
connection = accept(listen4, (struct sockaddr*)&inaddr4, &lenaddr);
2856
}else if( listen6>0 && FD_ISSET(listen6, &readfds) ){
2857
lenaddr = sizeof(inaddr6);
2858
connection = accept(listen6, (struct sockaddr*)&inaddr6, &lenaddr);
2859
}else{
2860
connection = -1;
2861
}
2862
if( connection>=0 ){
2863
if( flags & HTTP_SERVER_NOFORK ){
2864
child = 0;
2865
}else{
2866
child = fork();
2867
}
2868
if( child!=0 ){
2869
if( child>0 ){
2870
nchildren++;
2871
nRequest++;
2872
}
2873
close(connection);
2874
}else{
2875
int nErr = 0, fd;
2876
g.zSockName = 0 /* avoid deleting the socket via atexit() */;
2877
close(0);
2878
fd = dup(connection);
2879
if( fd!=0 ) nErr++;
2880
close(1);
2881
fd = dup(connection);
2882
if( fd!=1 ) nErr++;
2883
if( 0 && !g.fAnyTrace ){
2884
close(2);
2885
fd = dup(connection);
2886
if( fd!=2 ) nErr++;
2887
}
2888
close(connection);
2889
if( listen4>0 ) close(listen4);
2890
if( listen6>0 ) close(listen6);
2891
g.nPendingRequest = nchildren+1;
2892
g.nRequest = nRequest+1;
2893
return nErr;
2894
}
2895
}
2896
/* Bury dead children */
2897
if( nchildren ){
2898
while(1){
2899
int iStatus = 0;
2900
pid_t x = waitpid(-1, &iStatus, WNOHANG);
2901
if( x<=0 ) break;
2902
if( WIFSIGNALED(iStatus) && g.fAnyTrace ){
2903
fprintf(stderr, "/***** Child %d exited on signal %d (%s) *****/\n",
2904
x, WTERMSIG(iStatus), strsignal(WTERMSIG(iStatus)));
2905
}
2906
nchildren--;
2907
}
2908
}
2909
}
2910
/* NOT REACHED */
2911
fossil_exit(1);
2912
#endif
2913
/* NOT REACHED */
2914
return 0;
2915
}
2916
2917
2918
/*
2919
** Name of days and months.
2920
*/
2921
static const char *const azDays[] =
2922
{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 0};
2923
static const char *const azMonths[] =
2924
{"Jan", "Feb", "Mar", "Apr", "May", "Jun",
2925
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0};
2926
2927
2928
/*
2929
** Returns an RFC822-formatted time string suitable for HTTP headers.
2930
** The timezone is always GMT. The value returned is always a
2931
** string obtained from mprintf() and must be freed using fossil_free()
2932
** to avoid a memory leak.
2933
**
2934
** See http://www.faqs.org/rfcs/rfc822.html, section 5
2935
** and http://www.faqs.org/rfcs/rfc2616.html, section 3.3.
2936
*/
2937
char *cgi_rfc822_datestamp(time_t now){
2938
struct tm *pTm;
2939
pTm = gmtime(&now);
2940
if( pTm==0 ){
2941
return mprintf("");
2942
}else{
2943
return mprintf("%s, %d %s %02d %02d:%02d:%02d +0000",
2944
azDays[pTm->tm_wday], pTm->tm_mday, azMonths[pTm->tm_mon],
2945
pTm->tm_year+1900, pTm->tm_hour, pTm->tm_min, pTm->tm_sec);
2946
}
2947
}
2948
2949
/*
2950
** Returns an ISO8601-formatted time string suitable for debugging
2951
** purposes.
2952
**
2953
** The value returned is always a string obtained from mprintf() and must
2954
** be freed using fossil_free() to avoid a memory leak.
2955
*/
2956
char *cgi_iso8601_datestamp(void){
2957
struct tm *pTm;
2958
time_t now = time(0);
2959
pTm = gmtime(&now);
2960
if( pTm==0 ){
2961
return mprintf("");
2962
}else{
2963
return mprintf("%04d-%02d-%02d %02d:%02d:%02d",
2964
pTm->tm_year+1900, pTm->tm_mon+1, pTm->tm_mday,
2965
pTm->tm_hour, pTm->tm_min, pTm->tm_sec);
2966
}
2967
}
2968
2969
/*
2970
** Parse an RFC822-formatted timestamp as we'd expect from HTTP and return
2971
** a Unix epoch time. <= zero is returned on failure.
2972
**
2973
** Note that this won't handle all the _allowed_ HTTP formats, just the
2974
** most popular one (the one generated by cgi_rfc822_datestamp(), actually).
2975
*/
2976
time_t cgi_rfc822_parsedate(const char *zDate){
2977
int mday, mon, year, yday, hour, min, sec;
2978
char zIgnore[4];
2979
char zMonth[4];
2980
static const char *const azMonths[] =
2981
{"Jan", "Feb", "Mar", "Apr", "May", "Jun",
2982
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0};
2983
if( 7==sscanf(zDate, "%3[A-Za-z], %d %3[A-Za-z] %d %d:%d:%d", zIgnore,
2984
&mday, zMonth, &year, &hour, &min, &sec)){
2985
if( year > 1900 ) year -= 1900;
2986
for(mon=0; azMonths[mon]; mon++){
2987
if( !fossil_strncmp( azMonths[mon], zMonth, 3 )){
2988
int nDay;
2989
int isLeapYr;
2990
static int priorDays[] =
2991
{ 0, 31, 59, 90,120,151,181,212,243,273,304,334 };
2992
if( mon<0 ){
2993
int nYear = (11 - mon)/12;
2994
year -= nYear;
2995
mon += nYear*12;
2996
}else if( mon>11 ){
2997
year += mon/12;
2998
mon %= 12;
2999
}
3000
isLeapYr = year%4==0 && (year%100!=0 || (year+300)%400==0);
3001
yday = priorDays[mon] + mday - 1;
3002
if( isLeapYr && mon>1 ) yday++;
3003
nDay = (year-70)*365 + (year-69)/4 - year/100 + (year+300)/400 + yday;
3004
return ((time_t)(nDay*24 + hour)*60 + min)*60 + sec;
3005
}
3006
}
3007
}
3008
return 0;
3009
}
3010
3011
/*
3012
** Check the objectTime against the If-Modified-Since request header. If the
3013
** object time isn't any newer than the header, we immediately send back
3014
** a 304 reply and exit.
3015
*/
3016
void cgi_modified_since(time_t objectTime){
3017
const char *zIf = P("HTTP_IF_MODIFIED_SINCE");
3018
if( zIf==0 ) return;
3019
if( objectTime > cgi_rfc822_parsedate(zIf) ) return;
3020
cgi_set_status(304,"Not Modified");
3021
cgi_reset_content();
3022
cgi_reply();
3023
fossil_exit(0);
3024
}
3025
3026
/*
3027
** Check to see if the remote client is SSH and return
3028
** its IP or return default
3029
*/
3030
const char *cgi_ssh_remote_addr(const char *zDefault){
3031
char *zIndex;
3032
const char *zSshConn = fossil_getenv("SSH_CONNECTION");
3033
3034
if( zSshConn && zSshConn[0] ){
3035
char *zSshClient = fossil_strdup(zSshConn);
3036
if( (zIndex = strchr(zSshClient,' '))!=0 ){
3037
zSshClient[zIndex-zSshClient] = '\0';
3038
return zSshClient;
3039
}
3040
}
3041
return zDefault;
3042
}
3043
3044
/*
3045
** Return true if information is coming from the loopback network.
3046
*/
3047
int cgi_is_loopback(const char *zIpAddr){
3048
return fossil_strcmp(zIpAddr, "127.0.0.1")==0 ||
3049
fossil_strcmp(zIpAddr, "::ffff:127.0.0.1")==0 ||
3050
fossil_strcmp(zIpAddr, "::1")==0;
3051
}
3052
3053
/*
3054
** Return true if the HTTP request is likely to be from a small-screen
3055
** mobile device.
3056
**
3057
** The returned value is a guess. Use it only for setting up defaults.
3058
*/
3059
int cgi_from_mobile(void){
3060
const char *zAgent = P("HTTP_USER_AGENT");
3061
if( zAgent==0 ) return 0;
3062
if( sqlite3_strglob("*iPad*", zAgent)==0 ) return 0;
3063
return sqlite3_strlike("%mobile%", zAgent, 0)==0;
3064
}
3065
3066
/*
3067
** Look for query or POST parameters that:
3068
**
3069
** (1) Have not been used
3070
** (2) Appear to be malicious attempts to break into or otherwise
3071
** harm the system, for example via SQL injection
3072
**
3073
** If any such parameters are seen, a 418 ("I'm a teapot") return is
3074
** generated and processing aborts - this routine does not return.
3075
**
3076
** When Fossil is launched via CGI from althttpd, the 418 return signals
3077
** the webserver to put the requestor IP address into "timeout", blocking
3078
** subsequent requests for 5 minutes.
3079
**
3080
** Fossil is not subject to any SQL injections, as far as anybody knows.
3081
** This routine is not necessary for the security of the system (though
3082
** an extra layer of security never hurts). The main purpose here is
3083
** to shutdown malicious attack spiders and prevent them from burning
3084
** lots of CPU cycles and bogging down the website. In other words, the
3085
** objective of this routine is to help prevent denial-of-service.
3086
**
3087
** Usage Hint: Put a call to this routine as late in the webpage
3088
** implementation as possible, ideally just before it begins doing
3089
** potentially CPU-intensive computations and after all query parameters
3090
** have been consulted.
3091
*/
3092
void cgi_check_for_malice(void){
3093
struct QParam * pParam;
3094
int i;
3095
for(i=0; i<nUsedQP; ++i){
3096
pParam = &aParamQP[i];
3097
if( 0==pParam->isFetched
3098
&& pParam->zValue!=0
3099
&& pParam->zName!=0
3100
&& fossil_islower(pParam->zName[0])
3101
){
3102
cgi_value_spider_check(pParam->zValue, pParam->zName);
3103
}
3104
}
3105
}
3106

Keyboard Shortcuts

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