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 **   drh@hwaci.com
14 **   http://www.hwaci.com/drh/
15 **
16 *******************************************************************************
17 **
18 ** This file contains C functions and procedures used by CGI programs
19 ** (Fossil launched as CGI) to interpret CGI environment variables,
20 ** gather the results, and send they reply back to the CGI server.
21 ** This file also contains routines for running a simple web-server
22 ** (the "fossil ui" or "fossil server" command) and launching subprocesses
23 ** to handle each inbound HTTP request using CGI.
24 **
25 ** This file contains routines used by Fossil when it is acting as a
26 ** CGI client.  For the code used by Fossil when it is acting as a
27 ** CGI server (for the /ext webpage) see the "extcgi.c" source file.
28 */
29 #include "config.h"
30 #ifdef _WIN32
31 # if !defined(_WIN32_WINNT)
32 #  define _WIN32_WINNT 0x0501
33 # endif
34 # include <winsock2.h>
35 # include <ws2tcpip.h>
36 #else
37 # include <sys/socket.h>
38 # include <netinet/in.h>
39 # include <arpa/inet.h>
40 # include <sys/times.h>
41 # include <sys/time.h>
42 # include <sys/wait.h>
43 # include <sys/select.h>
44 #endif
45 #ifdef __EMX__
46   typedef int socklen_t;
47 #endif
48 #include <time.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <unistd.h>
52 #include "cgi.h"
53 #include "cygsup.h"
54 
55 #if INTERFACE
56 /*
57 ** Shortcuts for cgi_parameter.  P("x") returns the value of query parameter
58 ** or cookie "x", or NULL if there is no such parameter or cookie.  PD("x","y")
59 ** does the same except "y" is returned in place of NULL if there is not match.
60 */
61 #define P(x)        cgi_parameter((x),0)
62 #define PD(x,y)     cgi_parameter((x),(y))
63 #define PT(x)       cgi_parameter_trimmed((x),0)
64 #define PDT(x,y)    cgi_parameter_trimmed((x),(y))
65 #define PB(x)       cgi_parameter_boolean(x)
66 #define PCK(x)      cgi_parameter_checked(x,1)
67 #define PIF(x,y)    cgi_parameter_checked(x,y)
68 
69 /*
70 ** Shortcut for the cgi_printf() routine.  Instead of using the
71 **
72 **    @ ...
73 **
74 ** notation provided by the translate.c utility, you can also
75 ** optionally use:
76 **
77 **    CX(...)
78 */
79 #define CX cgi_printf
80 
81 /*
82 ** Destinations for output text.
83 */
84 #define CGI_HEADER   0
85 #define CGI_BODY     1
86 
87 /*
88 ** Flags for SSH HTTP clients
89 */
90 #define CGI_SSH_CLIENT           0x0001     /* Client is SSH */
91 #define CGI_SSH_COMPAT           0x0002     /* Compat for old SSH transport */
92 #define CGI_SSH_FOSSIL           0x0004     /* Use new Fossil SSH transport */
93 
94 #endif /* INTERFACE */
95 
96 /*
97 ** The HTTP reply is generated in two pieces: the header and the body.
98 ** These pieces are generated separately because they are not necessarily
99 ** produced in order.  Parts of the header might be built after all or
100 ** part of the body.  The header and body are accumulated in separate
101 ** Blob structures then output sequentially once everything has been
102 ** built.
103 **
104 ** The cgi_destination() interface switches between the buffers.
105 */
106 static Blob cgiContent[2] = { BLOB_INITIALIZER, BLOB_INITIALIZER };
107 static Blob *pContent = &cgiContent[0];
108 
109 /*
110 ** Set the destination buffer into which to accumulate CGI content.
111 */
cgi_destination(int dest)112 void cgi_destination(int dest){
113   switch( dest ){
114     case CGI_HEADER: {
115       pContent = &cgiContent[0];
116       break;
117     }
118     case CGI_BODY: {
119       pContent = &cgiContent[1];
120       break;
121     }
122     default: {
123       cgi_panic("bad destination");
124     }
125   }
126 }
127 
128 /*
129 ** Check to see if the header contains the zNeedle string.  Return true
130 ** if it does and false if it does not.
131 */
cgi_header_contains(const char * zNeedle)132 int cgi_header_contains(const char *zNeedle){
133   return strstr(blob_str(&cgiContent[0]), zNeedle)!=0;
134 }
cgi_body_contains(const char * zNeedle)135 int cgi_body_contains(const char *zNeedle){
136   return strstr(blob_str(&cgiContent[1]), zNeedle)!=0;
137 }
138 
139 /*
140 ** Append reply content to what already exists.
141 */
cgi_append_content(const char * zData,int nAmt)142 void cgi_append_content(const char *zData, int nAmt){
143   blob_append(pContent, zData, nAmt);
144 }
145 
146 /*
147 ** Reset the HTTP reply text to be an empty string.
148 */
cgi_reset_content(void)149 void cgi_reset_content(void){
150   blob_reset(&cgiContent[0]);
151   blob_reset(&cgiContent[1]);
152 }
153 
154 /*
155 ** Return a pointer to the CGI output blob.
156 */
cgi_output_blob(void)157 Blob *cgi_output_blob(void){
158   return pContent;
159 }
160 
161 /*
162 ** Return complete text of the output header
163 */
cgi_header(void)164 const char *cgi_header(void){
165   return blob_str(&cgiContent[0]);
166 }
167 
168 /*
169 ** Combine the header and body of the CGI into a single string.
170 */
cgi_combine_header_and_body(void)171 static void cgi_combine_header_and_body(void){
172   int size = blob_size(&cgiContent[1]);
173   if( size>0 ){
174     blob_append(&cgiContent[0], blob_buffer(&cgiContent[1]), size);
175     blob_reset(&cgiContent[1]);
176   }
177 }
178 
179 /*
180 ** Return a pointer to the HTTP reply text.
181 */
cgi_extract_content(void)182 char *cgi_extract_content(void){
183   cgi_combine_header_and_body();
184   return blob_buffer(&cgiContent[0]);
185 }
186 
187 /*
188 ** Additional information used to form the HTTP reply
189 */
190 static const char *zContentType = "text/html";   /* Content type of the reply */
191 static const char *zReplyStatus = "OK";          /* Reply status description */
192 static int iReplyStatus = 200;               /* Reply status code */
193 static Blob extraHeader = BLOB_INITIALIZER;  /* Extra header text */
194 static int rangeStart = 0;                   /* Start of Range: */
195 static int rangeEnd = 0;                     /* End of Range: plus 1 */
196 
197 /*
198 ** Set the reply content type
199 */
cgi_set_content_type(const char * zType)200 void cgi_set_content_type(const char *zType){
201   zContentType = fossil_strdup(zType);
202 }
203 
204 /*
205 ** Set the reply content to the specified BLOB.
206 */
cgi_set_content(Blob * pNewContent)207 void cgi_set_content(Blob *pNewContent){
208   cgi_reset_content();
209   cgi_destination(CGI_HEADER);
210   cgiContent[0] = *pNewContent;
211   blob_zero(pNewContent);
212 }
213 
214 /*
215 ** Set the reply status code
216 */
cgi_set_status(int iStat,const char * zStat)217 void cgi_set_status(int iStat, const char *zStat){
218   zReplyStatus = fossil_strdup(zStat);
219   iReplyStatus = iStat;
220 }
221 
222 /*
223 ** Append text to the header of an HTTP reply
224 */
cgi_append_header(const char * zLine)225 void cgi_append_header(const char *zLine){
226   blob_append(&extraHeader, zLine, -1);
227 }
cgi_printf_header(const char * zLine,...)228 void cgi_printf_header(const char *zLine, ...){
229   va_list ap;
230   va_start(ap, zLine);
231   blob_vappendf(&extraHeader, zLine, ap);
232   va_end(ap);
233 }
234 
235 /*
236 ** Set a cookie by queuing up the appropriate HTTP header output. If
237 ** !g.isHTTP, this is a no-op.
238 **
239 ** Zero lifetime implies a session cookie. A negative one expires
240 ** the cookie immediately.
241 */
cgi_set_cookie(const char * zName,const char * zValue,const char * zPath,int lifetime)242 void cgi_set_cookie(
243   const char *zName,    /* Name of the cookie */
244   const char *zValue,   /* Value of the cookie.  Automatically escaped */
245   const char *zPath,    /* Path cookie applies to.  NULL means "/" */
246   int lifetime          /* Expiration of the cookie in seconds from now */
247 ){
248   char const *zSecure = "";
249   if(!g.isHTTP) return /* e.g. JSON CLI mode, where g.zTop is not set */;
250   else if( zPath==0 ){
251     zPath = g.zTop;
252     if( zPath[0]==0 ) zPath = "/";
253   }
254   if( g.zBaseURL!=0 && strncmp(g.zBaseURL, "https:", 6)==0 ){
255     zSecure = " secure;";
256   }
257   if( lifetime!=0 ){
258     blob_appendf(&extraHeader,
259        "Set-Cookie: %s=%t; Path=%s; max-age=%d; HttpOnly; "
260        "%s Version=1\r\n",
261        zName, lifetime>0 ? zValue : "null", zPath, lifetime, zSecure);
262   }else{
263     blob_appendf(&extraHeader,
264        "Set-Cookie: %s=%t; Path=%s; HttpOnly; "
265        "%s Version=1\r\n",
266        zName, zValue, zPath, zSecure);
267   }
268 }
269 
270 
271 /*
272 ** Return true if the response should be sent with Content-Encoding: gzip.
273 */
is_gzippable(void)274 static int is_gzippable(void){
275   if( g.fNoHttpCompress ) return 0;
276   if( strstr(PD("HTTP_ACCEPT_ENCODING", ""), "gzip")==0 ) return 0;
277   return strncmp(zContentType, "text/", 5)==0
278     || sqlite3_strglob("application/*xml", zContentType)==0
279     || sqlite3_strglob("application/*javascript", zContentType)==0;
280 }
281 
282 /*
283 ** Do a normal HTTP reply
284 */
cgi_reply(void)285 void cgi_reply(void){
286   int total_size;
287   if( iReplyStatus<=0 ){
288     iReplyStatus = 200;
289     zReplyStatus = "OK";
290   }
291 
292   if( g.fullHttpReply ){
293     if( rangeEnd>0
294      && iReplyStatus==200
295      && fossil_strcmp(P("REQUEST_METHOD"),"GET")==0
296     ){
297       iReplyStatus = 206;
298       zReplyStatus = "Partial Content";
299     }
300     fprintf(g.httpOut, "HTTP/1.0 %d %s\r\n", iReplyStatus, zReplyStatus);
301     fprintf(g.httpOut, "Date: %s\r\n", cgi_rfc822_datestamp(time(0)));
302     fprintf(g.httpOut, "Connection: close\r\n");
303     fprintf(g.httpOut, "X-UA-Compatible: IE=edge\r\n");
304   }else{
305     assert( rangeEnd==0 );
306     fprintf(g.httpOut, "Status: %d %s\r\n", iReplyStatus, zReplyStatus);
307   }
308   if( etag_tag()[0]!=0 ){
309     fprintf(g.httpOut, "ETag: %s\r\n", etag_tag());
310     fprintf(g.httpOut, "Cache-Control: max-age=%d\r\n", etag_maxage());
311     if( etag_mtime()>0 ){
312       fprintf(g.httpOut, "Last-Modified: %s\r\n",
313               cgi_rfc822_datestamp(etag_mtime()));
314     }
315   }else if( g.isConst ){
316     /* isConst means that the reply is guaranteed to be invariant, even
317     ** after configuration changes and/or Fossil binary recompiles. */
318     fprintf(g.httpOut, "Cache-Control: max-age=315360000, immutable\r\n");
319   }else{
320     fprintf(g.httpOut, "Cache-control: no-cache\r\n");
321   }
322 
323   if( blob_size(&extraHeader)>0 ){
324     fprintf(g.httpOut, "%s", blob_buffer(&extraHeader));
325   }
326 
327   /* Add headers to turn on useful security options in browsers. */
328   fprintf(g.httpOut, "X-Frame-Options: SAMEORIGIN\r\n");
329   /* This stops fossil pages appearing in frames or iframes, preventing
330   ** click-jacking attacks on supporting browsers.
331   **
332   ** Other good headers would be
333   **   Strict-Transport-Security: max-age=62208000
334   ** if we're using https. However, this would break sites which serve different
335   ** content on http and https protocols. Also,
336   **   X-Content-Security-Policy: allow 'self'
337   ** would help mitigate some XSS and data injection attacks, but will break
338   ** deliberate inclusion of external resources, such as JavaScript syntax
339   ** highlighter scripts.
340   **
341   ** These headers are probably best added by the web server hosting fossil as
342   ** a CGI script.
343   */
344 
345   /* Content intended for logged in users should only be cached in
346   ** the browser, not some shared location.
347   */
348   if( iReplyStatus!=304 ) {
349     fprintf(g.httpOut, "Content-Type: %s; charset=utf-8\r\n", zContentType);
350     if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){
351       cgi_combine_header_and_body();
352       blob_compress(&cgiContent[0], &cgiContent[0]);
353     }
354 
355     if( is_gzippable() && iReplyStatus!=206 ){
356       int i;
357       gzip_begin(0);
358       for( i=0; i<2; i++ ){
359         int size = blob_size(&cgiContent[i]);
360         if( size>0 ) gzip_step(blob_buffer(&cgiContent[i]), size);
361         blob_reset(&cgiContent[i]);
362       }
363       gzip_finish(&cgiContent[0]);
364       fprintf(g.httpOut, "Content-Encoding: gzip\r\n");
365       fprintf(g.httpOut, "Vary: Accept-Encoding\r\n");
366     }
367     total_size = blob_size(&cgiContent[0]) + blob_size(&cgiContent[1]);
368     if( iReplyStatus==206 ){
369       fprintf(g.httpOut, "Content-Range: bytes %d-%d/%d\r\n",
370               rangeStart, rangeEnd-1, total_size);
371       total_size = rangeEnd - rangeStart;
372     }
373     fprintf(g.httpOut, "Content-Length: %d\r\n", total_size);
374   }else{
375     total_size = 0;
376   }
377   fprintf(g.httpOut, "\r\n");
378   if( total_size>0
379    && iReplyStatus!=304
380    && fossil_strcmp(P("REQUEST_METHOD"),"HEAD")!=0
381   ){
382     int i, size;
383     for(i=0; i<2; i++){
384       size = blob_size(&cgiContent[i]);
385       if( size<=rangeStart ){
386         rangeStart -= size;
387       }else{
388         int n = size - rangeStart;
389         if( n>total_size ){
390           n = total_size;
391         }
392         fwrite(blob_buffer(&cgiContent[i])+rangeStart, 1, n, g.httpOut);
393         rangeStart = 0;
394         total_size -= n;
395       }
396     }
397   }
398   fflush(g.httpOut);
399   CGIDEBUG(("-------- END cgi ---------\n"));
400 
401   /* After the webpage has been sent, do any useful background
402   ** processing.
403   */
404   g.cgiOutput = 2;
405   if( g.db!=0 && iReplyStatus==200 ){
406     backoffice_check_if_needed();
407   }
408 }
409 
410 /*
411 ** Do a redirect request to the URL given in the argument.
412 **
413 ** The URL must be relative to the base of the fossil server.
414 */
cgi_redirect_with_status(const char * zURL,int iStat,const char * zStat)415 NORETURN void cgi_redirect_with_status(
416   const char *zURL,
417   int iStat,
418   const char *zStat
419 ){
420   char *zLocation;
421   CGIDEBUG(("redirect to %s\n", zURL));
422   if( strncmp(zURL,"http:",5)==0 || strncmp(zURL,"https:",6)==0 ){
423     zLocation = mprintf("Location: %s\r\n", zURL);
424   }else if( *zURL=='/' ){
425     int n1 = (int)strlen(g.zBaseURL);
426     int n2 = (int)strlen(g.zTop);
427     if( g.zBaseURL[n1-1]=='/' ) zURL++;
428     zLocation = mprintf("Location: %.*s%s\r\n", n1-n2, g.zBaseURL, zURL);
429   }else{
430     zLocation = mprintf("Location: %s/%s\r\n", g.zBaseURL, zURL);
431   }
432   cgi_append_header(zLocation);
433   cgi_reset_content();
434   cgi_printf("<html>\n<p>Redirect to %h</p>\n</html>\n", zLocation);
435   cgi_set_status(iStat, zStat);
436   free(zLocation);
437   cgi_reply();
438   fossil_exit(0);
439 }
cgi_redirect(const char * zURL)440 NORETURN void cgi_redirect(const char *zURL){
441   cgi_redirect_with_status(zURL, 302, "Moved Temporarily");
442 }
cgi_redirect_with_method(const char * zURL)443 NORETURN void cgi_redirect_with_method(const char *zURL){
444   cgi_redirect_with_status(zURL, 307, "Temporary Redirect");
445 }
cgi_redirectf(const char * zFormat,...)446 NORETURN void cgi_redirectf(const char *zFormat, ...){
447   va_list ap;
448   va_start(ap, zFormat);
449   cgi_redirect(vmprintf(zFormat, ap));
450   va_end(ap);
451 }
452 
453 /*
454 ** Add a "Content-disposition: attachment; filename=%s" header to the reply.
455 */
cgi_content_disposition_filename(const char * zFilename)456 void cgi_content_disposition_filename(const char *zFilename){
457   char *z;
458   int i, n;
459 
460            /*  0123456789 123456789 123456789 123456789 123456*/
461   z = mprintf("Content-Disposition: attachment; filename=\"%s\";\r\n",
462                     file_tail(zFilename));
463   n = (int)strlen(z);
464   for(i=43; i<n-4; i++){
465     char c = z[i];
466     if( fossil_isalnum(c) ) continue;
467     if( c=='.' || c=='-' || c=='/' ) continue;
468     z[i] = '_';
469   }
470   cgi_append_header(z);
471   fossil_free(z);
472 }
473 
474 /*
475 ** Return the URL for the caller.  This is obtained from either the
476 ** referer CGI parameter, if it exists, or the HTTP_REFERER HTTP parameter.
477 ** If neither exist, return zDefault.
478 */
cgi_referer(const char * zDefault)479 const char *cgi_referer(const char *zDefault){
480   const char *zRef = P("referer");
481   if( zRef==0 ){
482     zRef = P("HTTP_REFERER");
483     if( zRef==0 ) zRef = zDefault;
484   }
485   return zRef;
486 }
487 
488 /*
489 ** Return true if the current request appears to be safe from a
490 ** Cross-Site Request Forgery (CSRF) attack.  Conditions that must
491 ** be met:
492 **
493 **    *   The HTTP_REFERER must have the same origin
494 **    *   The REQUEST_METHOD must be POST - or requirePost==0
495 */
cgi_csrf_safe(int requirePost)496 int cgi_csrf_safe(int requirePost){
497   const char *zRef = P("HTTP_REFERER");
498   int nBase;
499   if( zRef==0 ) return 0;
500   if( requirePost ){
501     const char *zMethod = P("REQUEST_METHOD");
502     if( zMethod==0 ) return 0;
503     if( strcmp(zMethod,"POST")!=0 ) return 0;
504   }
505   nBase = (int)strlen(g.zBaseURL);
506   if( strncmp(g.zBaseURL,zRef,nBase)!=0 ) return 0;
507   if( zRef[nBase]!=0 && zRef[nBase]!='/' ) return 0;
508   return 1;
509 }
510 
511 /*
512 ** Information about all query parameters and cookies are stored
513 ** in these variables.
514 */
515 static int nAllocQP = 0; /* Space allocated for aParamQP[] */
516 static int nUsedQP = 0;  /* Space actually used in aParamQP[] */
517 static int sortQP = 0;   /* True if aParamQP[] needs sorting */
518 static int seqQP = 0;    /* Sequence numbers */
519 static struct QParam {   /* One entry for each query parameter or cookie */
520   const char *zName;        /* Parameter or cookie name */
521   const char *zValue;       /* Value of the query parameter or cookie */
522   int seq;                  /* Order of insertion */
523   char isQP;                /* True for query parameters */
524   char cTag;                /* Tag on query parameters */
525 } *aParamQP;             /* An array of all parameters and cookies */
526 
527 /*
528 ** Add another query parameter or cookie to the parameter set.
529 ** zName is the name of the query parameter or cookie and zValue
530 ** is its fully decoded value.
531 **
532 ** zName and zValue are not copied and must not change or be
533 ** deallocated after this routine returns.
534 */
cgi_set_parameter_nocopy(const char * zName,const char * zValue,int isQP)535 void cgi_set_parameter_nocopy(const char *zName, const char *zValue, int isQP){
536   if( nAllocQP<=nUsedQP ){
537     nAllocQP = nAllocQP*2 + 10;
538     if( nAllocQP>1000 ){
539       /* Prevent a DOS service attack against the framework */
540       fossil_fatal("Too many query parameters");
541     }
542     aParamQP = fossil_realloc( aParamQP, nAllocQP*sizeof(aParamQP[0]) );
543   }
544   aParamQP[nUsedQP].zName = zName;
545   aParamQP[nUsedQP].zValue = zValue;
546   if( g.fHttpTrace ){
547     fprintf(stderr, "# cgi: %s = [%s]\n", zName, zValue);
548   }
549   aParamQP[nUsedQP].seq = seqQP++;
550   aParamQP[nUsedQP].isQP = isQP;
551   aParamQP[nUsedQP].cTag = 0;
552   nUsedQP++;
553   sortQP = 1;
554 }
555 
556 /*
557 ** Add another query parameter or cookie to the parameter set.
558 ** zName is the name of the query parameter or cookie and zValue
559 ** is its fully decoded value.  zName will be modified to be an
560 ** all lowercase string.
561 **
562 ** zName and zValue are not copied and must not change or be
563 ** deallocated after this routine returns.  This routine changes
564 ** all ASCII alphabetic characters in zName to lower case.  The
565 ** caller must not change them back.
566 */
cgi_set_parameter_nocopy_tolower(char * zName,const char * zValue,int isQP)567 void cgi_set_parameter_nocopy_tolower(
568   char *zName,
569   const char *zValue,
570   int isQP
571 ){
572   int i;
573   for(i=0; zName[i]; i++){ zName[i] = fossil_tolower(zName[i]); }
574   cgi_set_parameter_nocopy(zName, zValue, isQP);
575 }
576 
577 /*
578 ** Add another query parameter or cookie to the parameter set.
579 ** zName is the name of the query parameter or cookie and zValue
580 ** is its fully decoded value.
581 **
582 ** Copies are made of both the zName and zValue parameters.
583 */
cgi_set_parameter(const char * zName,const char * zValue)584 void cgi_set_parameter(const char *zName, const char *zValue){
585   cgi_set_parameter_nocopy(fossil_strdup(zName),fossil_strdup(zValue), 0);
586 }
cgi_set_query_parameter(const char * zName,const char * zValue)587 void cgi_set_query_parameter(const char *zName, const char *zValue){
588   cgi_set_parameter_nocopy(fossil_strdup(zName),fossil_strdup(zValue), 1);
589 }
590 
591 /*
592 ** Replace a parameter with a new value.
593 */
cgi_replace_parameter(const char * zName,const char * zValue)594 void cgi_replace_parameter(const char *zName, const char *zValue){
595   int i;
596   for(i=0; i<nUsedQP; i++){
597     if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){
598       aParamQP[i].zValue = zValue;
599       return;
600     }
601   }
602   cgi_set_parameter_nocopy(zName, zValue, 0);
603 }
cgi_replace_query_parameter(const char * zName,const char * zValue)604 void cgi_replace_query_parameter(const char *zName, const char *zValue){
605   int i;
606   for(i=0; i<nUsedQP; i++){
607     if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){
608       aParamQP[i].zValue = zValue;
609       assert( aParamQP[i].isQP );
610       return;
611     }
612   }
613   cgi_set_parameter_nocopy(zName, zValue, 1);
614 }
cgi_replace_query_parameter_tolower(char * zName,const char * zValue)615 void cgi_replace_query_parameter_tolower(char *zName, const char *zValue){
616   int i;
617   for(i=0; zName[i]; i++){ zName[i] = fossil_tolower(zName[i]); }
618   cgi_replace_query_parameter(zName, zValue);
619 }
620 
621 /*
622 ** Delete a parameter.
623 */
cgi_delete_parameter(const char * zName)624 void cgi_delete_parameter(const char *zName){
625   int i;
626   for(i=0; i<nUsedQP; i++){
627     if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){
628       --nUsedQP;
629       if( i<nUsedQP ){
630         memmove(aParamQP+i, aParamQP+i+1, sizeof(*aParamQP)*(nUsedQP-i));
631       }
632       return;
633     }
634   }
635 }
cgi_delete_query_parameter(const char * zName)636 void cgi_delete_query_parameter(const char *zName){
637   int i;
638   for(i=0; i<nUsedQP; i++){
639     if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){
640       assert( aParamQP[i].isQP );
641       --nUsedQP;
642       if( i<nUsedQP ){
643         memmove(aParamQP+i, aParamQP+i+1, sizeof(*aParamQP)*(nUsedQP-i));
644       }
645       return;
646     }
647   }
648 }
649 
650 /*
651 ** Add a query parameter.  The zName portion is fixed but a copy
652 ** must be made of zValue.
653 */
cgi_setenv(const char * zName,const char * zValue)654 void cgi_setenv(const char *zName, const char *zValue){
655   cgi_set_parameter_nocopy(zName, fossil_strdup(zValue), 0);
656 }
657 
658 /*
659 ** Add a list of query parameters or cookies to the parameter set.
660 **
661 ** Each parameter is of the form NAME=VALUE.  Both the NAME and the
662 ** VALUE may be url-encoded ("+" for space, "%HH" for other special
663 ** characters).  But this routine assumes that NAME contains no
664 ** special character and therefore does not decode it.
665 **
666 ** If NAME begins with another other than a lower-case letter then
667 ** the entire NAME=VALUE term is ignored.  Hence:
668 **
669 **      *  cookies and query parameters that have uppercase names
670 **         are ignored.
671 **
672 **      *  it is impossible for a cookie or query parameter to
673 **         override the value of an environment variable since
674 **         environment variables always have uppercase names.
675 **
676 ** 2018-03-29:  Also ignore the entry if NAME that contains any characters
677 ** other than [a-zA-Z0-9_].  There are no known exploits involving unusual
678 ** names that contain characters outside that set, but it never hurts to
679 ** be extra cautious when sanitizing inputs.
680 **
681 ** Parameters are separated by the "terminator" character.  Whitespace
682 ** before the NAME is ignored.
683 **
684 ** The input string "z" is modified but no copies is made.  "z"
685 ** should not be deallocated or changed again after this routine
686 ** returns or it will corrupt the parameter table.
687 */
add_param_list(char * z,int terminator)688 static void add_param_list(char *z, int terminator){
689   int isQP = terminator=='&';
690   while( *z ){
691     char *zName;
692     char *zValue;
693     while( fossil_isspace(*z) ){ z++; }
694     zName = z;
695     while( *z && *z!='=' && *z!=terminator ){ z++; }
696     if( *z=='=' ){
697       *z = 0;
698       z++;
699       zValue = z;
700       while( *z && *z!=terminator ){ z++; }
701       if( *z ){
702         *z = 0;
703         z++;
704       }
705       dehttpize(zValue);
706     }else{
707       if( *z ){ *z++ = 0; }
708       zValue = "";
709     }
710     if( zName[0] && fossil_no_strange_characters(zName+1) ){
711       if( fossil_islower(zName[0]) ){
712         cgi_set_parameter_nocopy(zName, zValue, isQP);
713       }else if( fossil_isupper(zName[0]) ){
714         cgi_set_parameter_nocopy_tolower(zName, zValue, isQP);
715       }
716     }
717 #ifdef FOSSIL_ENABLE_JSON
718     json_setenv( zName, cson_value_new_string(zValue,strlen(zValue)) );
719 #endif /* FOSSIL_ENABLE_JSON */
720   }
721 }
722 
723 /*
724 ** *pz is a string that consists of multiple lines of text.  This
725 ** routine finds the end of the current line of text and converts
726 ** the "\n" or "\r\n" that ends that line into a "\000".  It then
727 ** advances *pz to the beginning of the next line and returns the
728 ** previous value of *pz (which is the start of the current line.)
729 */
get_line_from_string(char ** pz,int * pLen)730 static char *get_line_from_string(char **pz, int *pLen){
731   char *z = *pz;
732   int i;
733   if( z[0]==0 ) return 0;
734   for(i=0; z[i]; i++){
735     if( z[i]=='\n' ){
736       if( i>0 && z[i-1]=='\r' ){
737         z[i-1] = 0;
738       }else{
739         z[i] = 0;
740       }
741       i++;
742       break;
743     }
744   }
745   *pz = &z[i];
746   *pLen -= i;
747   return z;
748 }
749 
750 /*
751 ** The input *pz points to content that is terminated by a "\r\n"
752 ** followed by the boundary marker zBoundary.  An extra "--" may or
753 ** may not be appended to the boundary marker.  There are *pLen characters
754 ** in *pz.
755 **
756 ** This routine adds a "\000" to the end of the content (overwriting
757 ** the "\r\n") and returns a pointer to the content.  The *pz input
758 ** is adjusted to point to the first line following the boundary.
759 ** The length of the content is stored in *pnContent.
760 */
get_bounded_content(char ** pz,int * pLen,char * zBoundary,int * pnContent)761 static char *get_bounded_content(
762   char **pz,         /* Content taken from here */
763   int *pLen,         /* Number of bytes of data in (*pz)[] */
764   char *zBoundary,    /* Boundary text marking the end of content */
765   int *pnContent     /* Write the size of the content here */
766 ){
767   char *z = *pz;
768   int len = *pLen;
769   int i;
770   int nBoundary = strlen(zBoundary);
771   *pnContent = len;
772   for(i=0; i<len; i++){
773     if( z[i]=='\n' && strncmp(zBoundary, &z[i+1], nBoundary)==0 ){
774       if( i>0 && z[i-1]=='\r' ) i--;
775       z[i] = 0;
776       *pnContent = i;
777       i += nBoundary;
778       break;
779     }
780   }
781   *pz = &z[i];
782   get_line_from_string(pz, pLen);
783   return z;
784 }
785 
786 /*
787 ** Tokenize a line of text into as many as nArg tokens.  Make
788 ** azArg[] point to the start of each token.
789 **
790 ** Tokens consist of space or semi-colon delimited words or
791 ** strings inside double-quotes.  Example:
792 **
793 **    content-disposition: form-data; name="fn"; filename="index.html"
794 **
795 ** The line above is tokenized as follows:
796 **
797 **    azArg[0] = "content-disposition:"
798 **    azArg[1] = "form-data"
799 **    azArg[2] = "name="
800 **    azArg[3] = "fn"
801 **    azArg[4] = "filename="
802 **    azArg[5] = "index.html"
803 **    azArg[6] = 0;
804 **
805 ** '\000' characters are inserted in z[] at the end of each token.
806 ** This routine returns the total number of tokens on the line, 6
807 ** in the example above.
808 */
tokenize_line(char * z,int mxArg,char ** azArg)809 static int tokenize_line(char *z, int mxArg, char **azArg){
810   int i = 0;
811   while( *z ){
812     while( fossil_isspace(*z) || *z==';' ){ z++; }
813     if( *z=='"' && z[1] ){
814       *z = 0;
815       z++;
816       if( i<mxArg-1 ){ azArg[i++] = z; }
817       while( *z && *z!='"' ){ z++; }
818       if( *z==0 ) break;
819       *z = 0;
820       z++;
821     }else{
822       if( i<mxArg-1 ){ azArg[i++] = z; }
823       while( *z && !fossil_isspace(*z) && *z!=';' && *z!='"' ){ z++; }
824       if( *z && *z!='"' ){
825         *z = 0;
826         z++;
827       }
828     }
829   }
830   azArg[i] = 0;
831   return i;
832 }
833 
834 /*
835 ** Scan the multipart-form content and make appropriate entries
836 ** into the parameter table.
837 **
838 ** The content string "z" is modified by this routine but it is
839 ** not copied.  The calling function must not deallocate or modify
840 ** "z" after this routine finishes or it could corrupt the parameter
841 ** table.
842 */
process_multipart_form_data(char * z,int len)843 static void process_multipart_form_data(char *z, int len){
844   char *zLine;
845   int nArg, i;
846   char *zBoundary;
847   char *zValue;
848   char *zName = 0;
849   int showBytes = 0;
850   char *azArg[50];
851 
852   zBoundary = get_line_from_string(&z, &len);
853   if( zBoundary==0 ) return;
854   while( (zLine = get_line_from_string(&z, &len))!=0 ){
855     if( zLine[0]==0 ){
856       int nContent = 0;
857       zValue = get_bounded_content(&z, &len, zBoundary, &nContent);
858       if( zName && zValue ){
859         if( fossil_islower(zName[0]) ){
860           cgi_set_parameter_nocopy(zName, zValue, 1);
861           if( showBytes ){
862             cgi_set_parameter_nocopy(mprintf("%s:bytes", zName),
863                  mprintf("%d",nContent), 1);
864           }
865         }else if( fossil_isupper(zName[0]) ){
866           cgi_set_parameter_nocopy_tolower(zName, zValue, 1);
867           if( showBytes ){
868             cgi_set_parameter_nocopy_tolower(mprintf("%s:bytes", zName),
869                  mprintf("%d",nContent), 1);
870           }
871         }
872       }
873       zName = 0;
874       showBytes = 0;
875     }else{
876       nArg = tokenize_line(zLine, count(azArg), azArg);
877       for(i=0; i<nArg; i++){
878         int c = fossil_tolower(azArg[i][0]);
879         int n = strlen(azArg[i]);
880         if( c=='c' && sqlite3_strnicmp(azArg[i],"content-disposition:",n)==0 ){
881           i++;
882         }else if( c=='n' && sqlite3_strnicmp(azArg[i],"name=",n)==0 ){
883           zName = azArg[++i];
884         }else if( c=='f' && sqlite3_strnicmp(azArg[i],"filename=",n)==0 ){
885           char *z = azArg[++i];
886           if( zName && z ){
887             if( fossil_islower(zName[0]) ){
888               cgi_set_parameter_nocopy(mprintf("%s:filename",zName), z, 1);
889             }else if( fossil_isupper(zName[0]) ){
890               cgi_set_parameter_nocopy_tolower(mprintf("%s:filename",zName),
891                                                z, 1);
892             }
893           }
894           showBytes = 1;
895         }else if( c=='c' && sqlite3_strnicmp(azArg[i],"content-type:",n)==0 ){
896           char *z = azArg[++i];
897           if( zName && z ){
898             if( fossil_islower(zName[0]) ){
899               cgi_set_parameter_nocopy(mprintf("%s:mimetype",zName), z, 1);
900             }else if( fossil_isupper(zName[0]) ){
901               cgi_set_parameter_nocopy_tolower(mprintf("%s:mimetype",zName),
902                                                z, 1);
903             }
904           }
905         }
906       }
907     }
908   }
909 }
910 
911 
912 #ifdef FOSSIL_ENABLE_JSON
913 /*
914 ** Internal helper for cson_data_source_FILE_n().
915 */
916 typedef struct CgiPostReadState_ {
917     FILE * fh;
918     unsigned int len;
919     unsigned int pos;
920 } CgiPostReadState;
921 
922 /*
923 ** cson_data_source_f() impl which reads only up to
924 ** a specified amount of data from its input FILE.
925 ** state MUST be a full populated (CgiPostReadState*).
926 */
cson_data_source_FILE_n(void * state,void * dest,unsigned int * n)927 static int cson_data_source_FILE_n( void * state,
928                                     void * dest,
929                                     unsigned int * n ){
930     if( ! state || !dest || !n ) return cson_rc.ArgError;
931     else {
932       CgiPostReadState * st = (CgiPostReadState *)state;
933       if( st->pos >= st->len ){
934         *n = 0;
935         return 0;
936       }else if( !*n || ((st->pos + *n) > st->len) ){
937         return cson_rc.RangeError;
938       }else{
939         unsigned int rsz = (unsigned int)fread( dest, 1, *n, st->fh );
940         if( ! rsz ){
941           *n = rsz;
942           return feof(st->fh) ? 0 : cson_rc.IOError;
943         }else{
944           *n = rsz;
945           st->pos += *n;
946           return 0;
947         }
948       }
949     }
950 }
951 
952 /*
953 ** Reads a JSON object from the first contentLen bytes of zIn.  On
954 ** g.json.post is updated to hold the content. On error a
955 ** FSL_JSON_E_INVALID_REQUEST response is output and fossil_exit() is
956 ** called (in HTTP mode exit code 0 is used).
957 **
958 ** If contentLen is 0 then the whole file is read.
959 */
cgi_parse_POST_JSON(FILE * zIn,unsigned int contentLen)960 void cgi_parse_POST_JSON( FILE * zIn, unsigned int contentLen ){
961   cson_value * jv = NULL;
962   int rc;
963   CgiPostReadState state;
964   cson_parse_opt popt = cson_parse_opt_empty;
965   cson_parse_info pinfo = cson_parse_info_empty;
966   assert(g.json.gc.a && "json_bootstrap_early() was not called!");
967   popt.maxDepth = 15;
968   state.fh = zIn;
969   state.len = contentLen;
970   state.pos = 0;
971   rc = cson_parse( &jv,
972                    contentLen ? cson_data_source_FILE_n : cson_data_source_FILE,
973                    contentLen ? (void *)&state : (void *)zIn, &popt, &pinfo );
974   if(rc){
975     goto invalidRequest;
976   }else{
977     json_gc_add( "POST.JSON", jv );
978     g.json.post.v = jv;
979     g.json.post.o = cson_value_get_object( jv );
980     if( !g.json.post.o ){ /* we don't support non-Object (Array) requests */
981       goto invalidRequest;
982     }
983   }
984   return;
985   invalidRequest:
986   cgi_set_content_type(json_guess_content_type());
987   if(0 != pinfo.errorCode){ /* fancy error message */
988       char * msg = mprintf("JSON parse error at line %u, column %u, "
989                            "byte offset %u: %s",
990                            pinfo.line, pinfo.col, pinfo.length,
991                            cson_rc_string(pinfo.errorCode));
992       json_err( FSL_JSON_E_INVALID_REQUEST, msg, 1 );
993       free(msg);
994   }else if(jv && !g.json.post.o){
995       json_err( FSL_JSON_E_INVALID_REQUEST,
996                 "Request envelope must be a JSON Object (not array).", 1 );
997   }else{ /* generic error message */
998       json_err( FSL_JSON_E_INVALID_REQUEST, NULL, 1 );
999   }
1000   fossil_exit( g.isHTTP ? 0 : 1);
1001 }
1002 #endif /* FOSSIL_ENABLE_JSON */
1003 
1004 /*
1005 ** Log HTTP traffic to a file.  Begin the log on first use.  Close the log
1006 ** when the argument is NULL.
1007 */
cgi_trace(const char * z)1008 void cgi_trace(const char *z){
1009   static FILE *pLog = 0;
1010   if( g.fHttpTrace==0 ) return;
1011   if( z==0 ){
1012     if( pLog ) fclose(pLog);
1013     pLog = 0;
1014     return;
1015   }
1016   if( pLog==0 ){
1017     char zFile[50];
1018 #if defined(_WIN32)
1019     unsigned r;
1020     sqlite3_randomness(sizeof(r), &r);
1021     sqlite3_snprintf(sizeof(zFile), zFile, "httplog-%08x.txt", r);
1022 #else
1023     sqlite3_snprintf(sizeof(zFile), zFile, "httplog-%05d.txt", getpid());
1024 #endif
1025     pLog = fossil_fopen(zFile, "wb");
1026     if( pLog ){
1027       fprintf(stderr, "# open log on %s\n", zFile);
1028     }else{
1029       fprintf(stderr, "# failed to open %s\n", zFile);
1030       return;
1031     }
1032   }
1033   fputs(z, pLog);
1034 }
1035 
1036 /* Forward declaration */
1037 static NORETURN void malformed_request(const char *zMsg);
1038 
1039 /*
1040 ** Initialize the query parameter database.  Information is pulled from
1041 ** the QUERY_STRING environment variable (if it exists), from standard
1042 ** input if there is POST data, and from HTTP_COOKIE.
1043 **
1044 ** REQUEST_URI, PATH_INFO, and SCRIPT_NAME are related as follows:
1045 **
1046 **      REQUEST_URI == SCRIPT_NAME + PATH_INFO
1047 **
1048 ** Where "+" means concatenate.  Fossil requires SCRIPT_NAME.  If
1049 ** REQUEST_URI is provided but PATH_INFO is not, then PATH_INFO is
1050 ** computed from REQUEST_URI and SCRIPT_NAME.  If PATH_INFO is provided
1051 ** but REQUEST_URI is not, then compute REQUEST_URI from PATH_INFO and
1052 ** SCRIPT_NAME.  If neither REQUEST_URI nor PATH_INFO are provided, then
1053 ** assume that PATH_INFO is an empty string and set REQUEST_URI equal
1054 ** to PATH_INFO.
1055 **
1056 ** Sometimes PATH_INFO is missing and SCRIPT_NAME is not a prefix of
1057 ** REQUEST_URI.  (See https://fossil-scm.org/forum/forumpost/049e8650ed)
1058 ** In that case, truncate SCRIPT_NAME so that it is a proper prefix
1059 ** of REQUEST_URI.
1060 **
1061 ** SCGI typically omits PATH_INFO.  CGI sometimes omits REQUEST_URI and
1062 ** PATH_INFO when it is empty.
1063 **
1064 ** CGI Parameter quick reference:
1065 **
1066 **                                      REQUEST_URI
1067 **                               _____________|____________
1068 **                              /                          \
1069 **    https://www.fossil-scm.org/forum/info/12736b30c072551a?t=c
1070 **            \________________/\____/\____________________/ \_/
1071 **                    |            |             |            |
1072 **               HTTP_HOST         |        PATH_INFO     QUERY_STRING
1073 **                            SCRIPT_NAME
1074 */
cgi_init(void)1075 void cgi_init(void){
1076   char *z;
1077   const char *zType;
1078   char *zSemi;
1079   int len;
1080   const char *zRequestUri = cgi_parameter("REQUEST_URI",0);
1081   const char *zScriptName = cgi_parameter("SCRIPT_NAME",0);
1082   const char *zPathInfo = cgi_parameter("PATH_INFO",0);
1083 #ifdef _WIN32
1084   const char *zServerSoftware = cgi_parameter("SERVER_SOFTWARE",0);
1085 #endif
1086 
1087 #ifdef FOSSIL_ENABLE_JSON
1088   const int noJson = P("no_json")!=0;
1089 #endif
1090   g.isHTTP = 1;
1091   cgi_destination(CGI_BODY);
1092 
1093   /* We must have SCRIPT_NAME. If the web server did not supply it, try
1094   ** to compute it from REQUEST_URI and PATH_INFO. */
1095   if( zScriptName==0 ){
1096     size_t nRU, nPI;
1097     if( zRequestUri==0 || zPathInfo==0 ){
1098       malformed_request("missing SCRIPT_NAME");  /* Does not return */
1099     }
1100     nRU = strlen(zRequestUri);
1101     nPI = strlen(zPathInfo);
1102     if( nRU<nPI ){
1103       malformed_request("PATH_INFO is longer than REQUEST_URI");
1104     }
1105     zScriptName = fossil_strndup(zRequestUri,(int)(nRU-nPI));
1106     cgi_set_parameter("SCRIPT_NAME", zScriptName);
1107   }
1108 
1109 #ifdef _WIN32
1110   /* The Microsoft IIS web server does not define REQUEST_URI, instead it uses
1111   ** PATH_INFO for virtually the same purpose.  Define REQUEST_URI the same as
1112   ** PATH_INFO and redefine PATH_INFO with SCRIPT_NAME removed from the
1113   ** beginning. */
1114   if( zServerSoftware && strstr(zServerSoftware, "Microsoft-IIS") ){
1115     int i, j;
1116     cgi_set_parameter("REQUEST_URI", zPathInfo);
1117     for(i=0; zPathInfo[i]==zScriptName[i] && zPathInfo[i]; i++){}
1118     for(j=i; zPathInfo[j] && zPathInfo[j]!='?'; j++){}
1119     zPathInfo = fossil_strndup(zPathInfo+i, j-i);
1120     cgi_replace_parameter("PATH_INFO", zPathInfo);
1121   }
1122 #endif
1123   if( zRequestUri==0 ){
1124     const char *z = zPathInfo;
1125     if( zPathInfo==0 ){
1126       malformed_request("missing PATH_INFO and/or REQUEST_URI");
1127     }
1128     if( z[0]=='/' ) z++;
1129     zRequestUri = mprintf("%s/%s", zScriptName, z);
1130     cgi_set_parameter("REQUEST_URI", zRequestUri);
1131   }
1132   if( zPathInfo==0 ){
1133     int i, j;
1134     for(i=0; zRequestUri[i]==zScriptName[i] && zRequestUri[i]; i++){}
1135     for(j=i; zRequestUri[j] && zRequestUri[j]!='?'; j++){}
1136     zPathInfo = fossil_strndup(zRequestUri+i, j-i);
1137     cgi_set_parameter_nocopy("PATH_INFO", zPathInfo, 0);
1138     if( j>i && zScriptName[i]!=0 ){
1139       /* If SCRIPT_NAME is not a prefix of REQUEST_URI, truncate it so
1140       ** that it is.  See https://fossil-scm.org/forum/forumpost/049e8650ed
1141       */
1142       char *zNew = fossil_strndup(zScriptName, i);
1143       cgi_replace_parameter("SCRIPT_NAME", zNew);
1144     }
1145   }
1146 #ifdef FOSSIL_ENABLE_JSON
1147   if(noJson==0 && json_request_is_json_api(zPathInfo)){
1148     /* We need to change some following behaviour depending on whether
1149     ** we are operating in JSON mode or not. We cannot, however, be
1150     ** certain whether we should/need to be in JSON mode until the
1151     ** PATH_INFO is set up.
1152     */
1153     g.json.isJsonMode = 1;
1154     json_bootstrap_early();
1155   }else{
1156     assert(!g.json.isJsonMode &&
1157            "Internal misconfiguration of g.json.isJsonMode");
1158   }
1159 #endif
1160   z = (char*)P("HTTP_COOKIE");
1161   if( z ){
1162     z = fossil_strdup(z);
1163     add_param_list(z, ';');
1164     z = (char*)cookie_value("skin",0);
1165     if(z){
1166       skin_use_alternative(z, 2);
1167     }
1168   }
1169 
1170   z = (char*)P("QUERY_STRING");
1171   if( z ){
1172     z = fossil_strdup(z);
1173     add_param_list(z, '&');
1174     z = (char*)P("skin");
1175     if(z){
1176       char *zErr = skin_use_alternative(z, 2);
1177       if(!zErr && !P("once")){
1178         cookie_write_parameter("skin","skin",z);
1179       }
1180       fossil_free(zErr);
1181     }
1182   }
1183 
1184   z = (char*)P("REMOTE_ADDR");
1185   if( z ){
1186     g.zIpAddr = fossil_strdup(z);
1187   }
1188 
1189   len = atoi(PD("CONTENT_LENGTH", "0"));
1190   zType = P("CONTENT_TYPE");
1191   zSemi = zType ? strchr(zType, ';') : 0;
1192   if( zSemi ){
1193     g.zContentType = fossil_strndup(zType, (int)(zSemi-zType));
1194     zType = g.zContentType;
1195   }else{
1196     g.zContentType = zType;
1197   }
1198   blob_zero(&g.cgiIn);
1199   if( len>0 && zType ){
1200     if( fossil_strcmp(zType, "application/x-fossil")==0 ){
1201       blob_read_from_channel(&g.cgiIn, g.httpIn, len);
1202       blob_uncompress(&g.cgiIn, &g.cgiIn);
1203     }
1204 #ifdef FOSSIL_ENABLE_JSON
1205     else if( noJson==0 && g.json.isJsonMode!=0
1206              && json_can_consume_content_type(zType)!=0 ){
1207       cgi_parse_POST_JSON(g.httpIn, (unsigned int)len);
1208       /*
1209        Potential TODOs:
1210 
1211        1) If parsing fails, immediately return an error response
1212        without dispatching the ostensibly-upcoming JSON API.
1213       */
1214       cgi_set_content_type(json_guess_content_type());
1215     }
1216 #endif /* FOSSIL_ENABLE_JSON */
1217     else{
1218       blob_read_from_channel(&g.cgiIn, g.httpIn, len);
1219     }
1220   }
1221 }
1222 
1223 /*
1224 ** Decode POST parameter information in the cgiIn content, if any.
1225 */
cgi_decode_post_parameters(void)1226 void cgi_decode_post_parameters(void){
1227   int len = blob_size(&g.cgiIn);
1228   if( len==0 ) return;
1229   if( fossil_strcmp(g.zContentType,"application/x-www-form-urlencoded")==0
1230    || strncmp(g.zContentType,"multipart/form-data",19)==0
1231   ){
1232     char *z = blob_str(&g.cgiIn);
1233     cgi_trace(z);
1234     if( g.zContentType[0]=='a' ){
1235       add_param_list(z, '&');
1236     }else{
1237       process_multipart_form_data(z, len);
1238     }
1239     blob_init(&g.cgiIn, 0, 0);
1240   }
1241 }
1242 
1243 /*
1244 ** This is the comparison function used to sort the aParamQP[] array of
1245 ** query parameters and cookies.
1246 */
qparam_compare(const void * a,const void * b)1247 static int qparam_compare(const void *a, const void *b){
1248   struct QParam *pA = (struct QParam*)a;
1249   struct QParam *pB = (struct QParam*)b;
1250   int c;
1251   c = fossil_strcmp(pA->zName, pB->zName);
1252   if( c==0 ){
1253     c = pA->seq - pB->seq;
1254   }
1255   return c;
1256 }
1257 
1258 /*
1259 ** Return the value of a query parameter or cookie whose name is zName.
1260 ** If there is no query parameter or cookie named zName and the first
1261 ** character of zName is uppercase, then check to see if there is an
1262 ** environment variable by that name and return it if there is.  As
1263 ** a last resort when nothing else matches, return zDefault.
1264 */
cgi_parameter(const char * zName,const char * zDefault)1265 const char *cgi_parameter(const char *zName, const char *zDefault){
1266   int lo, hi, mid, c;
1267 
1268   /* The sortQP flag is set whenever a new query parameter is inserted.
1269   ** It indicates that we need to resort the query parameters.
1270   */
1271   if( sortQP ){
1272     int i, j;
1273     qsort(aParamQP, nUsedQP, sizeof(aParamQP[0]), qparam_compare);
1274     sortQP = 0;
1275     /* After sorting, remove duplicate parameters.  The secondary sort
1276     ** key is aParamQP[].seq and we keep the first entry.  That means
1277     ** with duplicate calls to cgi_set_parameter() the second and
1278     ** subsequent calls are effectively no-ops. */
1279     for(i=j=1; i<nUsedQP; i++){
1280       if( fossil_strcmp(aParamQP[i].zName,aParamQP[i-1].zName)==0 ){
1281         continue;
1282       }
1283       if( j<i ){
1284         memcpy(&aParamQP[j], &aParamQP[i], sizeof(aParamQP[j]));
1285       }
1286       j++;
1287     }
1288     nUsedQP = j;
1289   }
1290 
1291   /* Invoking with a NULL zName is just a way to cause the parameters
1292   ** to be sorted.  So go ahead and bail out in that case */
1293   if( zName==0 || zName[0]==0 ) return 0;
1294 
1295   /* Do a binary search for a matching query parameter */
1296   lo = 0;
1297   hi = nUsedQP-1;
1298   while( lo<=hi ){
1299     mid = (lo+hi)/2;
1300     c = fossil_strcmp(aParamQP[mid].zName, zName);
1301     if( c==0 ){
1302       CGIDEBUG(("mem-match [%s] = [%s]\n", zName, aParamQP[mid].zValue));
1303       return aParamQP[mid].zValue;
1304     }else if( c>0 ){
1305       hi = mid-1;
1306     }else{
1307       lo = mid+1;
1308     }
1309   }
1310 
1311   /* If no match is found and the name begins with an upper-case
1312   ** letter, then check to see if there is an environment variable
1313   ** with the given name.
1314   */
1315   if( fossil_isupper(zName[0]) ){
1316     const char *zValue = fossil_getenv(zName);
1317     if( zValue ){
1318       cgi_set_parameter_nocopy(zName, zValue, 0);
1319       CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue));
1320       return zValue;
1321     }
1322   }
1323   CGIDEBUG(("no-match [%s]\n", zName));
1324   return zDefault;
1325 }
1326 
1327 /*
1328 ** Return the value of the first defined query parameter or cookie whose
1329 ** name appears in the list of arguments.  Or if no parameter is found,
1330 ** return NULL.
1331 */
cgi_coalesce(const char * zName,...)1332 const char *cgi_coalesce(const char *zName, ...){
1333   va_list ap;
1334   const char *z;
1335   const char *zX;
1336   if( zName==0 ) return 0;
1337   z = cgi_parameter(zName, 0);
1338   va_start(ap, zName);
1339   while( z==0 && (zX = va_arg(ap,const char*))!=0 ){
1340     z = cgi_parameter(zX, 0);
1341   }
1342   va_end(ap);
1343   return z;
1344 }
1345 
1346 /*
1347 ** Return the value of a CGI parameter with leading and trailing
1348 ** spaces removed and with internal \r\n changed to just \n
1349 */
cgi_parameter_trimmed(const char * zName,const char * zDefault)1350 char *cgi_parameter_trimmed(const char *zName, const char *zDefault){
1351   const char *zIn;
1352   char *zOut, c;
1353   int i, j;
1354   zIn = cgi_parameter(zName, 0);
1355   if( zIn==0 ) zIn = zDefault;
1356   if( zIn==0 ) return 0;
1357   while( fossil_isspace(zIn[0]) ) zIn++;
1358   zOut = fossil_strdup(zIn);
1359   for(i=j=0; (c = zOut[i])!=0; i++){
1360     if( c=='\r' && zOut[i+1]=='\n' ) continue;
1361     zOut[j++] = c;
1362   }
1363   zOut[j] = 0;
1364   while( j>0 && fossil_isspace(zOut[j-1]) ) zOut[--j] = 0;
1365   return zOut;
1366 }
1367 
1368 /*
1369 ** Return true if the CGI parameter zName exists and is not equal to 0,
1370 ** or "no" or "off".
1371 */
cgi_parameter_boolean(const char * zName)1372 int cgi_parameter_boolean(const char *zName){
1373   const char *zIn = cgi_parameter(zName, 0);
1374   if( zIn==0 ) return 0;
1375   return zIn[0]==0 || is_truth(zIn);
1376 }
1377 
1378 /*
1379 ** Return either an empty string "" or the string "checked" depending
1380 ** on whether or not parameter zName has value iValue.  If parameter
1381 ** zName does not exist, that is assumed to be the same as value 0.
1382 **
1383 ** This routine implements the PCK(x) and PIF(x,y) macros.  The PIF(x,y)
1384 ** macro generateds " checked" if the value of parameter x equals integer y.
1385 ** PCK(x) is the same as PIF(x,1).  These macros are used to generate
1386 ** the "checked" attribute on checkbox and radio controls of forms.
1387 */
cgi_parameter_checked(const char * zName,int iValue)1388 const char *cgi_parameter_checked(const char *zName, int iValue){
1389   const char *zIn = cgi_parameter(zName,0);
1390   int x;
1391   if( zIn==0 ){
1392     x = 0;
1393   }else if( !fossil_isdigit(zIn[0]) ){
1394     x = is_truth(zIn);
1395   }else{
1396     x = atoi(zIn);
1397   }
1398   return x==iValue ? "checked" : "";
1399 }
1400 
1401 /*
1402 ** Return the name of the i-th CGI parameter.  Return NULL if there
1403 ** are fewer than i registered CGI parameters.
1404 */
cgi_parameter_name(int i)1405 const char *cgi_parameter_name(int i){
1406   if( i>=0 && i<nUsedQP ){
1407     return aParamQP[i].zName;
1408   }else{
1409     return 0;
1410   }
1411 }
1412 
1413 /*
1414 ** Print CGI debugging messages.
1415 */
cgi_debug(const char * zFormat,...)1416 void cgi_debug(const char *zFormat, ...){
1417   va_list ap;
1418   if( g.fDebug ){
1419     va_start(ap, zFormat);
1420     vfprintf(g.fDebug, zFormat, ap);
1421     va_end(ap);
1422     fflush(g.fDebug);
1423   }
1424 }
1425 
1426 /*
1427 ** Return true if any of the query parameters in the argument
1428 ** list are defined.
1429 */
cgi_any(const char * z,...)1430 int cgi_any(const char *z, ...){
1431   va_list ap;
1432   char *z2;
1433   if( cgi_parameter(z,0)!=0 ) return 1;
1434   va_start(ap, z);
1435   while( (z2 = va_arg(ap, char*))!=0 ){
1436     if( cgi_parameter(z2,0)!=0 ) return 1;
1437   }
1438   va_end(ap);
1439   return 0;
1440 }
1441 
1442 /*
1443 ** Return true if all of the query parameters in the argument list
1444 ** are defined.
1445 */
cgi_all(const char * z,...)1446 int cgi_all(const char *z, ...){
1447   va_list ap;
1448   char *z2;
1449   if( cgi_parameter(z,0)==0 ) return 0;
1450   va_start(ap, z);
1451   while( (z2 = va_arg(ap, char*))==0 ){
1452     if( cgi_parameter(z2,0)==0 ) return 0;
1453   }
1454   va_end(ap);
1455   return 1;
1456 }
1457 
1458 /*
1459 ** Load all relevant environment variables into the parameter buffer.
1460 ** Invoke this routine prior to calling cgi_print_all() in order to see
1461 ** the full CGI environment.  This routine intended for debugging purposes
1462 ** only.
1463 */
cgi_load_environment(void)1464 void cgi_load_environment(void){
1465   /* The following is a list of environment variables that Fossil considers
1466   ** to be "relevant". */
1467   static const char *const azCgiVars[] = {
1468     "COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE", "SCGI",
1469     "HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING",
1470     "HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHENICATION",
1471     "HTTP_CONNECTION", "HTTP_HOST",
1472     "HTTP_IF_NONE_MATCH", "HTTP_IF_MODIFIED_SINCE",
1473     "HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED",
1474     "QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT",
1475     "REMOTE_USER", "REQUEST_METHOD", "REQUEST_SCHEME",
1476     "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_NAME",
1477     "SERVER_PROTOCOL", "HOME", "FOSSIL_HOME", "USERNAME", "USER",
1478     "FOSSIL_USER", "SQLITE_TMPDIR", "TMPDIR",
1479     "TEMP", "TMP", "FOSSIL_VFS",
1480     "FOSSIL_FORCE_TICKET_MODERATION", "FOSSIL_FORCE_WIKI_MODERATION",
1481     "FOSSIL_TCL_PATH", "TH1_DELETE_INTERP", "TH1_ENABLE_DOCS",
1482     "TH1_ENABLE_HOOKS", "TH1_ENABLE_TCL", "REMOTE_HOST",
1483   };
1484   int i;
1485   for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]);
1486 }
1487 
1488 /*
1489 ** Print all query parameters on standard output.
1490 ** This is used for testing and debugging.
1491 **
1492 ** Omit the values of the cookies unless showAll is true.
1493 **
1494 ** The eDest parameter determines where the output is shown:
1495 **
1496 **     eDest==0:    Rendering as HTML into the CGI reply
1497 **     eDest==1:    Written to stderr
1498 **     eDest==2:    Written to cgi_debug
1499 */
cgi_print_all(int showAll,unsigned int eDest)1500 void cgi_print_all(int showAll, unsigned int eDest){
1501   int i;
1502   cgi_parameter("","");  /* Force the parameters into sorted order */
1503   for(i=0; i<nUsedQP; i++){
1504     const char *zName = aParamQP[i].zName;
1505     if( !showAll ){
1506       if( fossil_stricmp("HTTP_COOKIE",zName)==0 ) continue;
1507       if( fossil_strnicmp("fossil-",zName,7)==0 ) continue;
1508     }
1509     switch( eDest ){
1510       case 0: {
1511         cgi_printf("%h = %h  <br />\n", zName, aParamQP[i].zValue);
1512         break;
1513       }
1514       case 1: {
1515         fossil_trace("%s = %s\n", zName, aParamQP[i].zValue);
1516         break;
1517       }
1518       case 2: {
1519         cgi_debug("%s = %s\n", zName, aParamQP[i].zValue);
1520         break;
1521       }
1522     }
1523   }
1524 }
1525 
1526 /*
1527 ** Put information about the N-th parameter into arguments.
1528 ** Return non-zero on success, and return 0 if there is no N-th parameter.
1529 */
cgi_param_info(int N,const char ** pzName,const char ** pzValue,int * pbIsQP)1530 int cgi_param_info(
1531   int N,
1532   const char **pzName,
1533   const char **pzValue,
1534   int *pbIsQP
1535 ){
1536   if( N>=0 && N<nUsedQP ){
1537     *pzName = aParamQP[N].zName;
1538     *pzValue = aParamQP[N].zValue;
1539     *pbIsQP = aParamQP[N].isQP;
1540     return 1;
1541   }else{
1542     *pzName = 0;
1543     *pzValue = 0;
1544     *pbIsQP = 0;
1545     return 0;
1546   }
1547 }
1548 
1549 /*
1550 ** Export all untagged query parameters (but not cookies or environment
1551 ** variables) as hidden values of a form.
1552 */
cgi_query_parameters_to_hidden(void)1553 void cgi_query_parameters_to_hidden(void){
1554   int i;
1555   const char *zN, *zV;
1556   for(i=0; i<nUsedQP; i++){
1557     if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue;
1558     zN = aParamQP[i].zName;
1559     zV = aParamQP[i].zValue;
1560     @ <input type="hidden" name="%h(zN)" value="%h(zV)">
1561   }
1562 }
1563 
1564 /*
1565 ** Export all untagged query parameters (but not cookies or environment
1566 ** variables) to the HQuery object.
1567 */
cgi_query_parameters_to_url(HQuery * p)1568 void cgi_query_parameters_to_url(HQuery *p){
1569   int i;
1570   for(i=0; i<nUsedQP; i++){
1571     if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue;
1572     url_add_parameter(p, aParamQP[i].zName, aParamQP[i].zValue);
1573   }
1574 }
1575 
1576 /*
1577 ** Tag query parameter zName so that it is not exported by
1578 ** cgi_query_parameters_to_hidden().  Or if zName==0, then
1579 ** untag all query parameters.
1580 */
cgi_tag_query_parameter(const char * zName)1581 void cgi_tag_query_parameter(const char *zName){
1582   int i;
1583   if( zName==0 ){
1584     for(i=0; i<nUsedQP; i++) aParamQP[i].cTag = 0;
1585   }else{
1586     for(i=0; i<nUsedQP; i++){
1587       if( strcmp(zName,aParamQP[i].zName)==0 ) aParamQP[i].cTag = 1;
1588     }
1589   }
1590 }
1591 
1592 /*
1593 ** This routine works like "printf" except that it has the
1594 ** extra formatting capabilities such as %h and %t.
1595 */
cgi_printf(const char * zFormat,...)1596 void cgi_printf(const char *zFormat, ...){
1597   va_list ap;
1598   va_start(ap,zFormat);
1599   vxprintf(pContent,zFormat,ap);
1600   va_end(ap);
1601 }
1602 
1603 /*
1604 ** This routine works like "vprintf" except that it has the
1605 ** extra formatting capabilities such as %h and %t.
1606 */
cgi_vprintf(const char * zFormat,va_list ap)1607 void cgi_vprintf(const char *zFormat, va_list ap){
1608   vxprintf(pContent,zFormat,ap);
1609 }
1610 
1611 
1612 /*
1613 ** Send a reply indicating that the HTTP request was malformed
1614 */
malformed_request(const char * zMsg)1615 static NORETURN void malformed_request(const char *zMsg){
1616   cgi_set_status(501, "Not Implemented");
1617   cgi_printf(
1618     "<html><body><p>Bad Request: %s</p></body></html>\n", zMsg
1619   );
1620   cgi_reply();
1621   fossil_exit(0);
1622 }
1623 
1624 /*
1625 ** Panic and die while processing a webpage.
1626 */
cgi_panic(const char * zFormat,...)1627 NORETURN void cgi_panic(const char *zFormat, ...){
1628   va_list ap;
1629   cgi_reset_content();
1630 #ifdef FOSSIL_ENABLE_JSON
1631   if( g.json.isJsonMode ){
1632     char * zMsg;
1633     va_start(ap, zFormat);
1634     zMsg = vmprintf(zFormat,ap);
1635     va_end(ap);
1636     json_err( FSL_JSON_E_PANIC, zMsg, 1 );
1637     free(zMsg);
1638     fossil_exit( g.isHTTP ? 0 : 1 );
1639   }else
1640 #endif /* FOSSIL_ENABLE_JSON */
1641   {
1642     cgi_set_status(500, "Internal Server Error");
1643     cgi_printf(
1644                "<html><body><h1>Internal Server Error</h1>\n"
1645                "<plaintext>"
1646                );
1647     va_start(ap, zFormat);
1648     vxprintf(pContent,zFormat,ap);
1649     va_end(ap);
1650     cgi_reply();
1651     fossil_exit(1);
1652   }
1653 }
1654 
1655 /* z[] is the value of an X-FORWARDED-FOR: line in an HTTP header.
1656 ** Return a pointer to a string containing the real IP address, or a
1657 ** NULL pointer to stick with the IP address previously computed and
1658 ** loaded into g.zIpAddr.
1659 */
cgi_accept_forwarded_for(const char * z)1660 static const char *cgi_accept_forwarded_for(const char *z){
1661   int i;
1662   if( !cgi_is_loopback(g.zIpAddr) ){
1663     /* Only accept X-FORWARDED-FOR if input coming from the local machine */
1664     return 0;
1665   }
1666   i = strlen(z)-1;
1667   while( i>=0 && z[i]!=',' && !fossil_isspace(z[i]) ) i--;
1668   return &z[++i];
1669 }
1670 
1671 /*
1672 ** Remove the first space-delimited token from a string and return
1673 ** a pointer to it.  Add a NULL to the string to terminate the token.
1674 ** Make *zLeftOver point to the start of the next token.
1675 */
extract_token(char * zInput,char ** zLeftOver)1676 static char *extract_token(char *zInput, char **zLeftOver){
1677   char *zResult = 0;
1678   if( zInput==0 ){
1679     if( zLeftOver ) *zLeftOver = 0;
1680     return 0;
1681   }
1682   while( fossil_isspace(*zInput) ){ zInput++; }
1683   zResult = zInput;
1684   while( *zInput && !fossil_isspace(*zInput) ){ zInput++; }
1685   if( *zInput ){
1686     *zInput = 0;
1687     zInput++;
1688     while( fossil_isspace(*zInput) ){ zInput++; }
1689   }
1690   if( zLeftOver ){ *zLeftOver = zInput; }
1691   return zResult;
1692 }
1693 
1694 /*
1695 ** Determine the IP address on the other side of a connection.
1696 ** Return a pointer to a string.  Or return 0 if unable.
1697 **
1698 ** The string is held in a static buffer that is overwritten on
1699 ** each call.
1700 */
cgi_remote_ip(int fd)1701 char *cgi_remote_ip(int fd){
1702 #if 0
1703   static char zIp[100];
1704   struct sockaddr_in6 addr;
1705   socklen_t sz = sizeof(addr);
1706   if( getpeername(fd, &addr, &sz) ) return 0;
1707   zIp[0] = 0;
1708   if( inet_ntop(AF_INET6, &addr, zIp, sizeof(zIp))==0 ){
1709     return 0;
1710   }
1711   return zIp;
1712 #else
1713   struct sockaddr_in remoteName;
1714   socklen_t size = sizeof(struct sockaddr_in);
1715   if( getpeername(fd, (struct sockaddr*)&remoteName, &size) ) return 0;
1716   return inet_ntoa(remoteName.sin_addr);
1717 #endif
1718 }
1719 
1720 /*
1721 ** This routine handles a single HTTP request which is coming in on
1722 ** g.httpIn and which replies on g.httpOut
1723 **
1724 ** The HTTP request is read from g.httpIn and is used to initialize
1725 ** entries in the cgi_parameter() hash, as if those entries were
1726 ** environment variables.  A call to cgi_init() completes
1727 ** the setup.  Once all the setup is finished, this procedure returns
1728 ** and subsequent code handles the actual generation of the webpage.
1729 */
cgi_handle_http_request(const char * zIpAddr)1730 void cgi_handle_http_request(const char *zIpAddr){
1731   char *z, *zToken;
1732   int i;
1733   const char *zScheme = "http";
1734   char zLine[2000];     /* A single line of input. */
1735   g.fullHttpReply = 1;
1736   if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
1737     malformed_request("missing HTTP header");
1738   }
1739   blob_append(&g.httpHeader, zLine, -1);
1740   cgi_trace(zLine);
1741   zToken = extract_token(zLine, &z);
1742   if( zToken==0 ){
1743     malformed_request("malformed HTTP header");
1744   }
1745   if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0
1746       && fossil_strcmp(zToken,"HEAD")!=0 ){
1747     malformed_request("unsupported HTTP method");
1748   }
1749   cgi_setenv("GATEWAY_INTERFACE","CGI/1.0");
1750   cgi_setenv("REQUEST_METHOD",zToken);
1751   zToken = extract_token(z, &z);
1752   if( zToken==0 ){
1753     malformed_request("malformed URL in HTTP header");
1754   }
1755   cgi_setenv("REQUEST_URI", zToken);
1756   cgi_setenv("SCRIPT_NAME", "");
1757   for(i=0; zToken[i] && zToken[i]!='?'; i++){}
1758   if( zToken[i] ) zToken[i++] = 0;
1759   cgi_setenv("PATH_INFO", zToken);
1760   cgi_setenv("QUERY_STRING", &zToken[i]);
1761   if( zIpAddr==0 ){
1762     zIpAddr = cgi_remote_ip(fileno(g.httpIn));
1763   }
1764   if( zIpAddr ){
1765     cgi_setenv("REMOTE_ADDR", zIpAddr);
1766     g.zIpAddr = fossil_strdup(zIpAddr);
1767   }
1768 
1769 
1770   /* Get all the optional fields that follow the first line.
1771   */
1772   while( fgets(zLine,sizeof(zLine),g.httpIn) ){
1773     char *zFieldName;
1774     char *zVal;
1775 
1776     cgi_trace(zLine);
1777     blob_append(&g.httpHeader, zLine, -1);
1778     zFieldName = extract_token(zLine,&zVal);
1779     if( zFieldName==0 || *zFieldName==0 ) break;
1780     while( fossil_isspace(*zVal) ){ zVal++; }
1781     i = strlen(zVal);
1782     while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; }
1783     zVal[i] = 0;
1784     for(i=0; zFieldName[i]; i++){
1785       zFieldName[i] = fossil_tolower(zFieldName[i]);
1786     }
1787     if( fossil_strcmp(zFieldName,"accept-encoding:")==0 ){
1788       cgi_setenv("HTTP_ACCEPT_ENCODING", zVal);
1789     }else if( fossil_strcmp(zFieldName,"content-length:")==0 ){
1790       cgi_setenv("CONTENT_LENGTH", zVal);
1791     }else if( fossil_strcmp(zFieldName,"content-type:")==0 ){
1792       cgi_setenv("CONTENT_TYPE", zVal);
1793     }else if( fossil_strcmp(zFieldName,"cookie:")==0 ){
1794       cgi_setenv("HTTP_COOKIE", zVal);
1795     }else if( fossil_strcmp(zFieldName,"https:")==0 ){
1796       cgi_setenv("HTTPS", zVal);
1797       zScheme = "https";
1798     }else if( fossil_strcmp(zFieldName,"host:")==0 ){
1799       char *z;
1800       cgi_setenv("HTTP_HOST", zVal);
1801       z = strchr(zVal, ':');
1802       if( z ) z[0] = 0;
1803       cgi_setenv("SERVER_NAME", zVal);
1804     }else if( fossil_strcmp(zFieldName,"if-none-match:")==0 ){
1805       cgi_setenv("HTTP_IF_NONE_MATCH", zVal);
1806     }else if( fossil_strcmp(zFieldName,"if-modified-since:")==0 ){
1807       cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal);
1808     }else if( fossil_strcmp(zFieldName,"referer:")==0 ){
1809       cgi_setenv("HTTP_REFERER", zVal);
1810     }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){
1811       cgi_setenv("HTTP_USER_AGENT", zVal);
1812     }else if( fossil_strcmp(zFieldName,"authorization:")==0 ){
1813       cgi_setenv("HTTP_AUTHORIZATION", zVal);
1814     }else if( fossil_strcmp(zFieldName,"x-forwarded-for:")==0 ){
1815       const char *zIpAddr = cgi_accept_forwarded_for(zVal);
1816       if( zIpAddr!=0 ){
1817         g.zIpAddr = fossil_strdup(zIpAddr);
1818         cgi_replace_parameter("REMOTE_ADDR", g.zIpAddr);
1819       }
1820     }else if( fossil_strcmp(zFieldName,"range:")==0 ){
1821       int x1 = 0;
1822       int x2 = 0;
1823       if( sscanf(zVal,"bytes=%d-%d",&x1,&x2)==2 && x1>=0 && x1<=x2 ){
1824         rangeStart = x1;
1825         rangeEnd = x2+1;
1826       }
1827     }
1828   }
1829   cgi_setenv("REQUEST_SCHEME",zScheme);
1830   cgi_init();
1831   cgi_trace(0);
1832 }
1833 
1834 /*
1835 ** This routine handles a single HTTP request from an SSH client which is
1836 ** coming in on g.httpIn and which replies on g.httpOut
1837 **
1838 ** Once all the setup is finished, this procedure returns
1839 ** and subsequent code handles the actual generation of the webpage.
1840 **
1841 ** It is called in a loop so some variables will need to be replaced
1842 */
cgi_handle_ssh_http_request(const char * zIpAddr)1843 void cgi_handle_ssh_http_request(const char *zIpAddr){
1844   static int nCycles = 0;
1845   static char *zCmd = 0;
1846   char *z, *zToken;
1847   const char *zType = 0;
1848   int i, content_length = 0;
1849   char zLine[2000];     /* A single line of input. */
1850 
1851 #ifdef FOSSIL_ENABLE_JSON
1852   if( nCycles==0 ){ json_bootstrap_early(); }
1853 #endif
1854   if( zIpAddr ){
1855     if( nCycles==0 ){
1856       cgi_setenv("REMOTE_ADDR", zIpAddr);
1857       g.zIpAddr = fossil_strdup(zIpAddr);
1858     }
1859   }else{
1860     fossil_fatal("missing SSH IP address");
1861   }
1862   if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
1863     malformed_request("missing HTTP header");
1864   }
1865   cgi_trace(zLine);
1866   zToken = extract_token(zLine, &z);
1867   if( zToken==0 ){
1868     malformed_request("malformed HTTP header");
1869   }
1870 
1871   if( fossil_strcmp(zToken, "echo")==0 ){
1872     /* start looking for probes to complete transport_open */
1873     zCmd = cgi_handle_ssh_probes(zLine, sizeof(zLine), z, zToken);
1874     if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
1875       malformed_request("missing HTTP header");
1876     }
1877     cgi_trace(zLine);
1878     zToken = extract_token(zLine, &z);
1879     if( zToken==0 ){
1880       malformed_request("malformed HTTP header");
1881     }
1882   }else if( zToken && strlen(zToken)==0 && zCmd ){
1883     /* transport_flip request and continued transport_open */
1884     cgi_handle_ssh_transport(zCmd);
1885     if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
1886       malformed_request("missing HTTP header");
1887     }
1888     cgi_trace(zLine);
1889     zToken = extract_token(zLine, &z);
1890     if( zToken==0 ){
1891       malformed_request("malformed HTTP header");
1892     }
1893   }
1894 
1895   if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0
1896       && fossil_strcmp(zToken,"HEAD")!=0 ){
1897     malformed_request("unsupported HTTP method");
1898   }
1899 
1900   if( nCycles==0 ){
1901     cgi_setenv("GATEWAY_INTERFACE","CGI/1.0");
1902     cgi_setenv("REQUEST_METHOD",zToken);
1903   }
1904 
1905   zToken = extract_token(z, &z);
1906   if( zToken==0 ){
1907     malformed_request("malformed URL in HTTP header");
1908   }
1909   if( nCycles==0 ){
1910     cgi_setenv("REQUEST_URI", zToken);
1911     cgi_setenv("SCRIPT_NAME", "");
1912   }
1913 
1914   for(i=0; zToken[i] && zToken[i]!='?'; i++){}
1915   if( zToken[i] ) zToken[i++] = 0;
1916   if( nCycles==0 ){
1917     cgi_setenv("PATH_INFO", zToken);
1918   }else{
1919     cgi_replace_parameter("PATH_INFO", fossil_strdup(zToken));
1920   }
1921 
1922   /* Get all the optional fields that follow the first line.
1923   */
1924   while( fgets(zLine,sizeof(zLine),g.httpIn) ){
1925     char *zFieldName;
1926     char *zVal;
1927 
1928     cgi_trace(zLine);
1929     zFieldName = extract_token(zLine,&zVal);
1930     if( zFieldName==0 || *zFieldName==0 ) break;
1931     while( fossil_isspace(*zVal) ){ zVal++; }
1932     i = strlen(zVal);
1933     while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; }
1934     zVal[i] = 0;
1935     for(i=0; zFieldName[i]; i++){
1936       zFieldName[i] = fossil_tolower(zFieldName[i]);
1937     }
1938     if( fossil_strcmp(zFieldName,"content-length:")==0 ){
1939       content_length = atoi(zVal);
1940     }else if( fossil_strcmp(zFieldName,"content-type:")==0 ){
1941       g.zContentType = zType = fossil_strdup(zVal);
1942     }else if( fossil_strcmp(zFieldName,"host:")==0 ){
1943       if( nCycles==0 ){
1944         cgi_setenv("HTTP_HOST", zVal);
1945       }
1946     }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){
1947       if( nCycles==0 ){
1948         cgi_setenv("HTTP_USER_AGENT", zVal);
1949       }
1950     }else if( fossil_strcmp(zFieldName,"x-fossil-transport:")==0 ){
1951       if( fossil_strnicmp(zVal, "ssh", 3)==0 ){
1952         if( nCycles==0 ){
1953           g.fSshClient |= CGI_SSH_FOSSIL;
1954           g.fullHttpReply = 0;
1955         }
1956       }
1957     }
1958   }
1959 
1960   if( nCycles==0 ){
1961     if( ! ( g.fSshClient & CGI_SSH_FOSSIL ) ){
1962       /* did not find new fossil ssh transport */
1963       g.fSshClient &= ~CGI_SSH_CLIENT;
1964       g.fullHttpReply = 1;
1965       cgi_replace_parameter("REMOTE_ADDR", "127.0.0.1");
1966     }
1967   }
1968 
1969   cgi_reset_content();
1970   cgi_destination(CGI_BODY);
1971 
1972   if( content_length>0 && zType ){
1973     blob_zero(&g.cgiIn);
1974     if( fossil_strcmp(zType, "application/x-fossil")==0 ){
1975       blob_read_from_channel(&g.cgiIn, g.httpIn, content_length);
1976       blob_uncompress(&g.cgiIn, &g.cgiIn);
1977     }else if( fossil_strcmp(zType, "application/x-fossil-debug")==0 ){
1978       blob_read_from_channel(&g.cgiIn, g.httpIn, content_length);
1979     }else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){
1980       blob_read_from_channel(&g.cgiIn, g.httpIn, content_length);
1981     }
1982   }
1983   cgi_trace(0);
1984   nCycles++;
1985 }
1986 
1987 /*
1988 ** This routine handles the old fossil SSH probes
1989 */
cgi_handle_ssh_probes(char * zLine,int zSize,char * z,char * zToken)1990 char *cgi_handle_ssh_probes(char *zLine, int zSize, char *z, char *zToken){
1991   /* Start looking for probes */
1992   while( fossil_strcmp(zToken, "echo")==0 ){
1993     zToken = extract_token(z, &z);
1994     if( zToken==0 ){
1995       malformed_request("malformed probe");
1996     }
1997     if( fossil_strncmp(zToken, "test", 4)==0 ||
1998         fossil_strncmp(zToken, "probe-", 6)==0 ){
1999       fprintf(g.httpOut, "%s\n", zToken);
2000       fflush(g.httpOut);
2001     }else{
2002       malformed_request("malformed probe");
2003     }
2004     if( fgets(zLine, zSize, g.httpIn)==0 ){
2005       malformed_request("malformed probe");
2006     }
2007     cgi_trace(zLine);
2008     zToken = extract_token(zLine, &z);
2009     if( zToken==0 ){
2010       malformed_request("malformed probe");
2011     }
2012   }
2013 
2014   /* Got all probes now first transport_open is completed
2015   ** so return the command that was requested
2016   */
2017   g.fSshClient |= CGI_SSH_COMPAT;
2018   return fossil_strdup(zToken);
2019 }
2020 
2021 /*
2022 ** This routine handles the old fossil SSH transport_flip
2023 ** and transport_open communications if detected.
2024 */
cgi_handle_ssh_transport(const char * zCmd)2025 void cgi_handle_ssh_transport(const char *zCmd){
2026   char *z, *zToken;
2027   char zLine[2000];     /* A single line of input. */
2028 
2029   /* look for second newline of transport_flip */
2030   if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
2031     malformed_request("incorrect transport_flip");
2032   }
2033   cgi_trace(zLine);
2034   zToken = extract_token(zLine, &z);
2035   if( zToken && strlen(zToken)==0 ){
2036     /* look for path to fossil */
2037     if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
2038       if( zCmd==0 ){
2039         malformed_request("missing fossil command");
2040       }else{
2041         /* no new command so exit */
2042         fossil_exit(0);
2043       }
2044     }
2045     cgi_trace(zLine);
2046     zToken = extract_token(zLine, &z);
2047     if( zToken==0 ){
2048       malformed_request("malformed fossil command");
2049     }
2050     /* see if we've seen the command */
2051     if( zCmd && zCmd[0] && fossil_strcmp(zToken, zCmd)==0 ){
2052       return;
2053     }else{
2054       malformed_request("transport_open failed");
2055     }
2056   }else{
2057     malformed_request("transport_flip failed");
2058   }
2059 }
2060 
2061 /*
2062 ** This routine handles a single SCGI request which is coming in on
2063 ** g.httpIn and which replies on g.httpOut
2064 **
2065 ** The SCGI request is read from g.httpIn and is used to initialize
2066 ** entries in the cgi_parameter() hash, as if those entries were
2067 ** environment variables.  A call to cgi_init() completes
2068 ** the setup.  Once all the setup is finished, this procedure returns
2069 ** and subsequent code handles the actual generation of the webpage.
2070 */
cgi_handle_scgi_request(void)2071 void cgi_handle_scgi_request(void){
2072   char *zHdr;
2073   char *zToFree;
2074   int nHdr = 0;
2075   int nRead;
2076   int c, n, m;
2077 
2078   while( (c = fgetc(g.httpIn))!=EOF && fossil_isdigit((char)c) ){
2079     nHdr = nHdr*10 + (char)c - '0';
2080   }
2081   if( nHdr<16 ) malformed_request("SCGI header too short");
2082   zToFree = zHdr = fossil_malloc(nHdr);
2083   nRead = (int)fread(zHdr, 1, nHdr, g.httpIn);
2084   if( nRead<nHdr ) malformed_request("cannot read entire SCGI header");
2085   nHdr = nRead;
2086   while( nHdr ){
2087     for(n=0; n<nHdr && zHdr[n]; n++){}
2088     for(m=n+1; m<nHdr && zHdr[m]; m++){}
2089     if( m>=nHdr ) malformed_request("SCGI header formatting error");
2090     cgi_set_parameter(zHdr, zHdr+n+1);
2091     zHdr += m+1;
2092     nHdr -= m+1;
2093   }
2094   fossil_free(zToFree);
2095   fgetc(g.httpIn);  /* Read past the "," separating header from content */
2096   cgi_init();
2097 }
2098 
2099 
2100 #if INTERFACE
2101 /*
2102 ** Bitmap values for the flags parameter to cgi_http_server().
2103 */
2104 #define HTTP_SERVER_LOCALHOST      0x0001     /* Bind to 127.0.0.1 only */
2105 #define HTTP_SERVER_SCGI           0x0002     /* SCGI instead of HTTP */
2106 #define HTTP_SERVER_HAD_REPOSITORY 0x0004     /* Was the repository open? */
2107 #define HTTP_SERVER_HAD_CHECKOUT   0x0008     /* Was a checkout open? */
2108 #define HTTP_SERVER_REPOLIST       0x0010     /* Allow repo listing */
2109 
2110 #endif /* INTERFACE */
2111 
2112 /*
2113 ** Maximum number of child processes that we can have running
2114 ** at one time.  Set this to 0 for "no limit".
2115 */
2116 #ifndef FOSSIL_MAX_CONNECTIONS
2117 # define FOSSIL_MAX_CONNECTIONS 1000
2118 #endif
2119 
2120 /*
2121 ** Implement an HTTP server daemon listening on port iPort.
2122 **
2123 ** As new connections arrive, fork a child and let child return
2124 ** out of this procedure call.  The child will handle the request.
2125 ** The parent never returns from this procedure.
2126 **
2127 ** Return 0 to each child as it runs.  If unable to establish a
2128 ** listening socket, return non-zero.
2129 */
cgi_http_server(int mnPort,int mxPort,const char * zBrowser,const char * zIpAddr,int flags)2130 int cgi_http_server(
2131   int mnPort, int mxPort,   /* Range of TCP ports to try */
2132   const char *zBrowser,     /* Run this browser, if not NULL */
2133   const char *zIpAddr,      /* Bind to this IP address, if not null */
2134   int flags                 /* HTTP_SERVER_* flags */
2135 ){
2136 #if defined(_WIN32)
2137   /* Use win32_http_server() instead */
2138   fossil_exit(1);
2139 #else
2140   int listener = -1;           /* The server socket */
2141   int connection;              /* A socket for each individual connection */
2142   int nRequest = 0;            /* Number of requests handled so far */
2143   fd_set readfds;              /* Set of file descriptors for select() */
2144   socklen_t lenaddr;           /* Length of the inaddr structure */
2145   int child;                   /* PID of the child process */
2146   int nchildren = 0;           /* Number of child processes */
2147   struct timeval delay;        /* How long to wait inside select() */
2148   struct sockaddr_in inaddr;   /* The socket address */
2149   int opt = 1;                 /* setsockopt flag */
2150   int iPort = mnPort;
2151 
2152   while( iPort<=mxPort ){
2153     memset(&inaddr, 0, sizeof(inaddr));
2154     inaddr.sin_family = AF_INET;
2155     if( zIpAddr ){
2156       inaddr.sin_addr.s_addr = inet_addr(zIpAddr);
2157       if( inaddr.sin_addr.s_addr == (-1) ){
2158         fossil_fatal("not a valid IP address: %s", zIpAddr);
2159       }
2160     }else if( flags & HTTP_SERVER_LOCALHOST ){
2161       inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
2162     }else{
2163       inaddr.sin_addr.s_addr = htonl(INADDR_ANY);
2164     }
2165     inaddr.sin_port = htons(iPort);
2166     listener = socket(AF_INET, SOCK_STREAM, 0);
2167     if( listener<0 ){
2168       iPort++;
2169       continue;
2170     }
2171 
2172     /* if we can't terminate nicely, at least allow the socket to be reused */
2173     setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
2174 
2175     if( bind(listener, (struct sockaddr*)&inaddr, sizeof(inaddr))<0 ){
2176       close(listener);
2177       iPort++;
2178       continue;
2179     }
2180     break;
2181   }
2182   if( iPort>mxPort ){
2183     if( mnPort==mxPort ){
2184       fossil_fatal("unable to open listening socket on ports %d", mnPort);
2185     }else{
2186       fossil_fatal("unable to open listening socket on any"
2187                    " port in the range %d..%d", mnPort, mxPort);
2188     }
2189   }
2190   if( iPort>mxPort ) return 1;
2191   listen(listener,10);
2192   fossil_print("Listening for %s requests on TCP port %d\n",
2193      (flags & HTTP_SERVER_SCGI)!=0?"SCGI":"HTTP",  iPort);
2194   fflush(stdout);
2195   if( zBrowser ){
2196     assert( strstr(zBrowser,"%d")!=0 );
2197     zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort);
2198 #if defined(__CYGWIN__)
2199     /* On Cygwin, we can do better than "echo" */
2200     if( strncmp(zBrowser, "echo ", 5)==0 ){
2201       wchar_t *wUrl = fossil_utf8_to_unicode(zBrowser+5);
2202       wUrl[wcslen(wUrl)-2] = 0; /* Strip terminating " &" */
2203       if( (size_t)ShellExecuteW(0, L"open", wUrl, 0, 0, 1)<33 ){
2204         fossil_warning("cannot start browser\n");
2205       }
2206     }else
2207 #endif
2208     if( fossil_system(zBrowser)<0 ){
2209       fossil_warning("cannot start browser: %s\n", zBrowser);
2210     }
2211   }
2212   while( 1 ){
2213 #if FOSSIL_MAX_CONNECTIONS>0
2214     while( nchildren>=FOSSIL_MAX_CONNECTIONS ){
2215       if( wait(0)>=0 ) nchildren--;
2216     }
2217 #endif
2218     delay.tv_sec = 0;
2219     delay.tv_usec = 100000;
2220     FD_ZERO(&readfds);
2221     assert( listener>=0 );
2222     FD_SET( listener, &readfds);
2223     select( listener+1, &readfds, 0, 0, &delay);
2224     if( FD_ISSET(listener, &readfds) ){
2225       lenaddr = sizeof(inaddr);
2226       connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr);
2227       if( connection>=0 ){
2228         child = fork();
2229         if( child!=0 ){
2230           if( child>0 ){
2231             nchildren++;
2232             nRequest++;
2233           }
2234           close(connection);
2235         }else{
2236           int nErr = 0, fd;
2237           close(0);
2238           fd = dup(connection);
2239           if( fd!=0 ) nErr++;
2240           close(1);
2241           fd = dup(connection);
2242           if( fd!=1 ) nErr++;
2243           if( 0 && !g.fAnyTrace ){
2244             close(2);
2245             fd = dup(connection);
2246             if( fd!=2 ) nErr++;
2247           }
2248           close(connection);
2249           g.nPendingRequest = nchildren+1;
2250           g.nRequest = nRequest+1;
2251           return nErr;
2252         }
2253       }
2254     }
2255     /* Bury dead children */
2256     if( nchildren ){
2257       while(1){
2258         int iStatus = 0;
2259         pid_t x = waitpid(-1, &iStatus, WNOHANG);
2260         if( x<=0 ) break;
2261         if( WIFSIGNALED(iStatus) && g.fAnyTrace ){
2262           fprintf(stderr, "/***** Child %d exited on signal %d (%s) *****/\n",
2263                   x, WTERMSIG(iStatus), strsignal(WTERMSIG(iStatus)));
2264         }
2265         nchildren--;
2266       }
2267     }
2268   }
2269   /* NOT REACHED */
2270   fossil_exit(1);
2271 #endif
2272   /* NOT REACHED */
2273   return 0;
2274 }
2275 
2276 
2277 /*
2278 ** Name of days and months.
2279 */
2280 static const char *const azDays[] =
2281     {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 0};
2282 static const char *const azMonths[] =
2283     {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
2284      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0};
2285 
2286 
2287 /*
2288 ** Returns an RFC822-formatted time string suitable for HTTP headers.
2289 ** The timezone is always GMT.  The value returned is always a
2290 ** string obtained from mprintf() and must be freed using fossil_free()
2291 ** to avoid a memory leak.
2292 **
2293 ** See http://www.faqs.org/rfcs/rfc822.html, section 5
2294 ** and http://www.faqs.org/rfcs/rfc2616.html, section 3.3.
2295 */
cgi_rfc822_datestamp(time_t now)2296 char *cgi_rfc822_datestamp(time_t now){
2297   struct tm *pTm;
2298   pTm = gmtime(&now);
2299   if( pTm==0 ){
2300     return mprintf("");
2301   }else{
2302     return mprintf("%s, %d %s %02d %02d:%02d:%02d +0000",
2303                    azDays[pTm->tm_wday], pTm->tm_mday, azMonths[pTm->tm_mon],
2304                    pTm->tm_year+1900, pTm->tm_hour, pTm->tm_min, pTm->tm_sec);
2305   }
2306 }
2307 
2308 /*
2309 ** Returns an ISO8601-formatted time string suitable for debugging
2310 ** purposes.
2311 **
2312 ** The value returned is always a string obtained from mprintf() and must
2313 ** be freed using fossil_free() to avoid a memory leak.
2314 */
cgi_iso8601_datestamp(void)2315 char *cgi_iso8601_datestamp(void){
2316   struct tm *pTm;
2317   time_t now = time(0);
2318   pTm = gmtime(&now);
2319   if( pTm==0 ){
2320     return mprintf("");
2321   }else{
2322     return mprintf("%04d-%02d-%02d %02d:%02d:%02d",
2323                    pTm->tm_year+1900, pTm->tm_mon, pTm->tm_mday,
2324                    pTm->tm_hour, pTm->tm_min, pTm->tm_sec);
2325   }
2326 }
2327 
2328 /*
2329 ** Parse an RFC822-formatted timestamp as we'd expect from HTTP and return
2330 ** a Unix epoch time. <= zero is returned on failure.
2331 **
2332 ** Note that this won't handle all the _allowed_ HTTP formats, just the
2333 ** most popular one (the one generated by cgi_rfc822_datestamp(), actually).
2334 */
cgi_rfc822_parsedate(const char * zDate)2335 time_t cgi_rfc822_parsedate(const char *zDate){
2336   int mday, mon, year, yday, hour, min, sec;
2337   char zIgnore[4];
2338   char zMonth[4];
2339   static const char *const azMonths[] =
2340     {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
2341      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0};
2342   if( 7==sscanf(zDate, "%3[A-Za-z], %d %3[A-Za-z] %d %d:%d:%d", zIgnore,
2343                        &mday, zMonth, &year, &hour, &min, &sec)){
2344     if( year > 1900 ) year -= 1900;
2345     for(mon=0; azMonths[mon]; mon++){
2346       if( !strncmp( azMonths[mon], zMonth, 3 )){
2347         int nDay;
2348         int isLeapYr;
2349         static int priorDays[] =
2350          {  0, 31, 59, 90,120,151,181,212,243,273,304,334 };
2351         if( mon<0 ){
2352           int nYear = (11 - mon)/12;
2353           year -= nYear;
2354           mon += nYear*12;
2355         }else if( mon>11 ){
2356           year += mon/12;
2357           mon %= 12;
2358         }
2359         isLeapYr = year%4==0 && (year%100!=0 || (year+300)%400==0);
2360         yday = priorDays[mon] + mday - 1;
2361         if( isLeapYr && mon>1 ) yday++;
2362         nDay = (year-70)*365 + (year-69)/4 - year/100 + (year+300)/400 + yday;
2363         return ((time_t)(nDay*24 + hour)*60 + min)*60 + sec;
2364       }
2365     }
2366   }
2367   return 0;
2368 }
2369 
2370 /*
2371 ** Check the objectTime against the If-Modified-Since request header. If the
2372 ** object time isn't any newer than the header, we immediately send back
2373 ** a 304 reply and exit.
2374 */
cgi_modified_since(time_t objectTime)2375 void cgi_modified_since(time_t objectTime){
2376   const char *zIf = P("HTTP_IF_MODIFIED_SINCE");
2377   if( zIf==0 ) return;
2378   if( objectTime > cgi_rfc822_parsedate(zIf) ) return;
2379   cgi_set_status(304,"Not Modified");
2380   cgi_reset_content();
2381   cgi_reply();
2382   fossil_exit(0);
2383 }
2384 
2385 /*
2386 ** Check to see if the remote client is SSH and return
2387 ** its IP or return default
2388 */
cgi_ssh_remote_addr(const char * zDefault)2389 const char *cgi_ssh_remote_addr(const char *zDefault){
2390   char *zIndex;
2391   const char *zSshConn = fossil_getenv("SSH_CONNECTION");
2392 
2393   if( zSshConn && zSshConn[0] ){
2394     char *zSshClient = fossil_strdup(zSshConn);
2395     if( (zIndex = strchr(zSshClient,' '))!=0 ){
2396       zSshClient[zIndex-zSshClient] = '\0';
2397       return zSshClient;
2398     }
2399   }
2400   return zDefault;
2401 }
2402 
2403 /*
2404 ** Return true if information is coming from the loopback network.
2405 */
cgi_is_loopback(const char * zIpAddr)2406 int cgi_is_loopback(const char *zIpAddr){
2407   return fossil_strcmp(zIpAddr, "127.0.0.1")==0 ||
2408          fossil_strcmp(zIpAddr, "::ffff:127.0.0.1")==0 ||
2409          fossil_strcmp(zIpAddr, "::1")==0;
2410 }
2411 
2412 /*
2413 ** Return true if the HTTP request is likely to be from a small-screen
2414 ** mobile device.
2415 **
2416 ** The returned value is a guess.  Use it only for setting up defaults.
2417 */
cgi_from_mobile(void)2418 int cgi_from_mobile(void){
2419   const char *zAgent = P("HTTP_USER_AGENT");
2420   if( zAgent==0 ) return 0;
2421   if( sqlite3_strglob("*iPad*", zAgent)==0 ) return 0;
2422   return sqlite3_strlike("%mobile%", zAgent, 0)==0;
2423 }
2424