Fossil

Artifact Content
Login

Artifact e75796be5338a81c0ed31879287935f07af90523:


     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 module codes the main() procedure that runs first when the
    19  ** program is invoked.
    20  */
    21  #include "VERSION.h"
    22  #include "config.h"
    23  #include "main.h"
    24  #include <string.h>
    25  #include <time.h>
    26  #include <fcntl.h>
    27  #include <sys/types.h>
    28  #include <sys/stat.h>
    29  #include <stdlib.h> /* atexit() */
    30  #if defined(_WIN32)
    31  #  include <windows.h>
    32  #else
    33  #  include <errno.h> /* errno global */
    34  #endif
    35  #ifdef FOSSIL_ENABLE_SSL
    36  #  include "openssl/crypto.h"
    37  #endif
    38  #if defined(FOSSIL_ENABLE_MINIZ)
    39  #  define MINIZ_HEADER_FILE_ONLY
    40  #  include "miniz.c"
    41  #else
    42  #  include <zlib.h>
    43  #endif
    44  #if INTERFACE
    45  #ifdef FOSSIL_ENABLE_TCL
    46  #  include "tcl.h"
    47  #endif
    48  #ifdef FOSSIL_ENABLE_JSON
    49  #  include "cson_amalgamation.h" /* JSON API. */
    50  #  include "json_detail.h"
    51  #endif
    52  
    53  /*
    54  ** Number of elements in an array
    55  */
    56  #define count(X)  (sizeof(X)/sizeof(X[0]))
    57  
    58  /*
    59  ** Size of a UUID in characters
    60  */
    61  #define UUID_SIZE 40
    62  
    63  /*
    64  ** Maximum number of auxiliary parameters on reports
    65  */
    66  #define MX_AUX  5
    67  
    68  /*
    69  ** Holds flags for fossil user permissions.
    70  */
    71  struct FossilUserPerms {
    72    char Setup;            /* s: use Setup screens on web interface */
    73    char Admin;            /* a: administrative permission */
    74    char Delete;           /* d: delete wiki or tickets */
    75    char Password;         /* p: change password */
    76    char Query;            /* q: create new reports */
    77    char Write;            /* i: xfer inbound. check-in */
    78    char Read;             /* o: xfer outbound. check-out */
    79    char Hyperlink;        /* h: enable the display of hyperlinks */
    80    char Clone;            /* g: clone */
    81    char RdWiki;           /* j: view wiki via web */
    82    char NewWiki;          /* f: create new wiki via web */
    83    char ApndWiki;         /* m: append to wiki via web */
    84    char WrWiki;           /* k: edit wiki via web */
    85    char ModWiki;          /* l: approve and publish wiki content (Moderator) */
    86    char RdTkt;            /* r: view tickets via web */
    87    char NewTkt;           /* n: create new tickets */
    88    char ApndTkt;          /* c: append to tickets via the web */
    89    char WrTkt;            /* w: make changes to tickets via web */
    90    char ModTkt;           /* q: approve and publish ticket changes (Moderator) */
    91    char Attach;           /* b: add attachments */
    92    char TktFmt;           /* t: create new ticket report formats */
    93    char RdAddr;           /* e: read email addresses or other private data */
    94    char Zip;              /* z: download zipped artifact via /zip URL */
    95    char Private;          /* x: can send and receive private content */
    96  };
    97  
    98  #ifdef FOSSIL_ENABLE_TCL
    99  /*
   100  ** All Tcl related context information is in this structure.  This structure
   101  ** definition has been copied from and should be kept in sync with the one in
   102  ** "th_tcl.c".
   103  */
   104  struct TclContext {
   105    int argc;              /* Number of original (expanded) arguments. */
   106    char **argv;           /* Full copy of the original (expanded) arguments. */
   107    void *hLibrary;        /* The Tcl library module handle. */
   108    void *xFindExecutable; /* See tcl_FindExecutableProc in th_tcl.c. */
   109    void *xCreateInterp;   /* See tcl_CreateInterpProc in th_tcl.c. */
   110    void *xDeleteInterp;   /* See tcl_DeleteInterpProc in th_tcl.c. */
   111    void *xFinalize;       /* See tcl_FinalizeProc in th_tcl.c. */
   112    Tcl_Interp *interp;    /* The on-demand created Tcl interpreter. */
   113    int useObjProc;        /* Non-zero if an objProc can be called directly. */
   114    int useTip285;         /* Non-zero if TIP #285 is available. */
   115    char *setup;           /* The optional Tcl setup script. */
   116    void *xPreEval;        /* Optional, called before Tcl_Eval*(). */
   117    void *pPreContext;     /* Optional, provided to xPreEval(). */
   118    void *xPostEval;       /* Optional, called after Tcl_Eval*(). */
   119    void *pPostContext;    /* Optional, provided to xPostEval(). */
   120  };
   121  #endif
   122  
   123  struct Global {
   124    int argc; char **argv;  /* Command-line arguments to the program */
   125    char *nameOfExe;        /* Full path of executable. */
   126    const char *zErrlog;    /* Log errors to this file, if not NULL */
   127    int isConst;            /* True if the output is unchanging & cacheable */
   128    const char *zVfsName;   /* The VFS to use for database connections */
   129    sqlite3 *db;            /* The connection to the databases */
   130    sqlite3 *dbConfig;      /* Separate connection for global_config table */
   131    char *zAuxSchema;       /* Main repository aux-schema */
   132    int useAttach;          /* True if global_config is attached to repository */
   133    const char *zConfigDbName;/* Path of the config database. NULL if not open */
   134    sqlite3_int64 now;      /* Seconds since 1970 */
   135    int repositoryOpen;     /* True if the main repository database is open */
   136    char *zRepositoryOption; /* Most recent cached repository option value */
   137    char *zRepositoryName;  /* Name of the repository database */
   138    char *zLocalDbName;     /* Name of the local database */
   139    const char *zMainDbType;/* "configdb", "localdb", or "repository" */
   140    const char *zConfigDbType;  /* "configdb", "localdb", or "repository" */
   141    char *zOpenRevision;    /* Check-in version to use during database open */
   142    int localOpen;          /* True if the local database is open */
   143    char *zLocalRoot;       /* The directory holding the  local database */
   144    int minPrefix;          /* Number of digits needed for a distinct UUID */
   145    int fSqlTrace;          /* True if --sqltrace flag is present */
   146    int fSqlStats;          /* True if --sqltrace or --sqlstats are present */
   147    int fSqlPrint;          /* True if -sqlprint flag is present */
   148    int fQuiet;             /* True if -quiet flag is present */
   149    int fHttpTrace;         /* Trace outbound HTTP requests */
   150    int fAnyTrace;          /* Any kind of tracing */
   151    char *zHttpAuth;        /* HTTP Authorization user:pass information */
   152    int fSystemTrace;       /* Trace calls to fossil_system(), --systemtrace */
   153    int fSshTrace;          /* Trace the SSH setup traffic */
   154    int fSshClient;         /* HTTP client flags for SSH client */
   155    char *zSshCmd;          /* SSH command string */
   156    int fNoSync;            /* Do not do an autosync ever.  --nosync */
   157    int fIPv4;              /* Use only IPv4, not IPv6. --ipv4 */
   158    char *zPath;            /* Name of webpage being served */
   159    char *zExtra;           /* Extra path information past the webpage name */
   160    char *zBaseURL;         /* Full text of the URL being served */
   161    char *zHttpsURL;        /* zBaseURL translated to https: */
   162    char *zTop;             /* Parent directory of zPath */
   163    const char *zContentType;  /* The content type of the input HTTP request */
   164    int iErrPriority;       /* Priority of current error message */
   165    char *zErrMsg;          /* Text of an error message */
   166    int sslNotAvailable;    /* SSL is not available.  Do not redirect to https: */
   167    Blob cgiIn;             /* Input to an xfer www method */
   168    int cgiOutput;          /* Write error and status messages to CGI */
   169    int xferPanic;          /* Write error messages in XFER protocol */
   170    int fullHttpReply;      /* True for full HTTP reply.  False for CGI reply */
   171    Th_Interp *interp;      /* The TH1 interpreter */
   172    char *th1Setup;         /* The TH1 post-creation setup script, if any */
   173    int th1Flags;           /* The TH1 integration state flags */
   174    FILE *httpIn;           /* Accept HTTP input from here */
   175    FILE *httpOut;          /* Send HTTP output here */
   176    int xlinkClusterOnly;   /* Set when cloning.  Only process clusters */
   177    int fTimeFormat;        /* 1 for UTC.  2 for localtime.  0 not yet selected */
   178    int *aCommitFile;       /* Array of files to be committed */
   179    int markPrivate;        /* All new artifacts are private if true */
   180    int clockSkewSeen;      /* True if clocks on client and server out of sync */
   181    int wikiFlags;          /* Wiki conversion flags applied to %W */
   182    char isHTTP;            /* True if server/CGI modes, else assume CLI. */
   183    char javascriptHyperlink; /* If true, set href= using script, not HTML */
   184    Blob httpHeader;        /* Complete text of the HTTP request header */
   185    UrlData url;            /* Information about current URL */
   186    const char *zLogin;     /* Login name.  NULL or "" if not logged in. */
   187    const char *zSSLIdentity;  /* Value of --ssl-identity option, filename of
   188                               ** SSL client identity */
   189    int useLocalauth;       /* No login required if from 127.0.0.1 */
   190    int noPswd;             /* Logged in without password (on 127.0.0.1) */
   191    int userUid;            /* Integer user id */
   192    int isHuman;            /* True if access by a human, not a spider or bot */
   193    int comFmtFlags;        /* Zero or more "COMMENT_PRINT_*" bit flags */
   194  
   195    /* Information used to populate the RCVFROM table */
   196    int rcvid;              /* The rcvid.  0 if not yet defined. */
   197    char *zIpAddr;          /* The remote IP address */
   198    char *zNonce;           /* The nonce used for login */
   199  
   200    /* permissions available to current user */
   201    struct FossilUserPerms perm;
   202  
   203    /* permissions available to current user or to "anonymous".
   204    ** This is the logical union of perm permissions above with
   205    ** the value that perm would take if g.zLogin were "anonymous". */
   206    struct FossilUserPerms anon;
   207  
   208  #ifdef FOSSIL_ENABLE_TCL
   209    /* all Tcl related context necessary for integration */
   210    struct TclContext tcl;
   211  #endif
   212  
   213    /* For defense against Cross-site Request Forgery attacks */
   214    char zCsrfToken[12];    /* Value of the anti-CSRF token */
   215    int okCsrf;             /* Anti-CSRF token is present and valid */
   216  
   217    int parseCnt[10];       /* Counts of artifacts parsed */
   218    FILE *fDebug;           /* Write debug information here, if the file exists */
   219  #ifdef FOSSIL_ENABLE_TH1_HOOKS
   220    int fNoThHook;          /* Disable all TH1 command/webpage hooks */
   221  #endif
   222    int thTrace;            /* True to enable TH1 debugging output */
   223    Blob thLog;             /* Text of the TH1 debugging output */
   224  
   225    int isHome;             /* True if rendering the "home" page */
   226  
   227    /* Storage for the aux() and/or option() SQL function arguments */
   228    int nAux;                    /* Number of distinct aux() or option() values */
   229    const char *azAuxName[MX_AUX]; /* Name of each aux() or option() value */
   230    char *azAuxParam[MX_AUX];      /* Param of each aux() or option() value */
   231    const char *azAuxVal[MX_AUX];  /* Value of each aux() or option() value */
   232    const char **azAuxOpt[MX_AUX]; /* Options of each option() value */
   233    int anAuxCols[MX_AUX];         /* Number of columns for option() values */
   234  
   235    int allowSymlinks;             /* Cached "allow-symlinks" option */
   236  
   237    int mainTimerId;               /* Set to fossil_timer_start() */
   238  #ifdef FOSSIL_ENABLE_JSON
   239    struct FossilJsonBits {
   240      int isJsonMode;            /* True if running in JSON mode, else
   241                                    false. This changes how errors are
   242                                    reported. In JSON mode we try to
   243                                    always output JSON-form error
   244                                    responses and always exit() with
   245                                    code 0 to avoid an HTTP 500 error.
   246                                 */
   247      int resultCode;            /* used for passing back specific codes
   248                                 ** from /json callbacks. */
   249      int errorDetailParanoia;   /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
   250      cson_output_opt outOpt;    /* formatting options for JSON mode. */
   251      cson_value *authToken;     /* authentication token */
   252      const char *jsonp;         /* Name of JSONP function wrapper. */
   253      unsigned char dispatchDepth /* Tells JSON command dispatching
   254                                     which argument we are currently
   255                                     working on. For this purpose, arg#0
   256                                     is the "json" path/CLI arg.
   257                                  */;
   258      struct {                   /* "garbage collector" */
   259        cson_value *v;
   260        cson_array *a;
   261      } gc;
   262      struct {                   /* JSON POST data. */
   263        cson_value *v;
   264        cson_array *a;
   265        int offset;              /* Tells us which PATH_INFO/CLI args
   266                                    part holds the "json" command, so
   267                                    that we can account for sub-repos
   268                                    and path prefixes.  This is handled
   269                                    differently for CLI and CGI modes.
   270                                 */
   271        const char *commandStr   /*"command" request param.*/;
   272      } cmd;
   273      struct {                   /* JSON POST data. */
   274        cson_value *v;
   275        cson_object *o;
   276      } post;
   277      struct {                   /* GET/COOKIE params in JSON mode. */
   278        cson_value *v;
   279        cson_object *o;
   280      } param;
   281      struct {
   282        cson_value *v;
   283        cson_object *o;
   284      } reqPayload;              /* request payload object (if any) */
   285      cson_array *warnings;      /* response warnings */
   286      int timerId;               /* fetched from fossil_timer_start() */
   287    } json;
   288  #endif /* FOSSIL_ENABLE_JSON */
   289  };
   290  
   291  /*
   292  ** Macro for debugging:
   293  */
   294  #define CGIDEBUG(X)  if( g.fDebug ) cgi_debug X
   295  
   296  #endif
   297  
   298  Global g;
   299  
   300  /*
   301  ** The table of web pages supported by this application is generated
   302  ** automatically by the "mkindex" program and written into a file
   303  ** named "page_index.h".  We include that file here to get access
   304  ** to the table.
   305  */
   306  #include "page_index.h"
   307  
   308  /*
   309  ** Search for a function whose name matches zName.  Write a pointer to
   310  ** that function into *pxFunc and return 0.  If no match is found,
   311  ** return 1.  If the command is ambiguous return 2;
   312  **
   313  ** The NameMap structure and the tables we are searching against are
   314  ** defined in the page_index.h header file which is automatically
   315  ** generated by mkindex.c program.
   316  */
   317  static int name_search(
   318    const char *zName,       /* The name we are looking for */
   319    const NameMap *aMap,     /* Search in this array */
   320    int nMap,                /* Number of slots in aMap[] */
   321    int iBegin,              /* Lower bound on the array search */
   322    int *pIndex              /* OUT: The index in aMap[] of the match */
   323  ){
   324    int upr, lwr, cnt, m, i;
   325    int n = strlen(zName);
   326    lwr = iBegin;
   327    upr = nMap-1;
   328    while( lwr<=upr ){
   329      int mid, c;
   330      mid = (upr+lwr)/2;
   331      c = fossil_strcmp(zName, aMap[mid].zName);
   332      if( c==0 ){
   333        *pIndex = mid;
   334        return 0;
   335      }else if( c<0 ){
   336        upr = mid - 1;
   337      }else{
   338        lwr = mid + 1;
   339      }
   340    }
   341    for(m=cnt=0, i=upr-2; cnt<2 && i<=upr+3 && i<nMap; i++){
   342      if( i<iBegin ) continue;
   343      if( strncmp(zName, aMap[i].zName, n)==0 ){
   344        m = i;
   345        cnt++;
   346      }
   347    }
   348    if( cnt==1 ){
   349      *pIndex = m;
   350      return 0;
   351    }
   352    return 1+(cnt>1);
   353  }
   354  
   355  /*
   356  ** atexit() handler which frees up "some" of the resources
   357  ** used by fossil.
   358  */
   359  static void fossil_atexit(void) {
   360  #if defined(_WIN32) && !defined(_WIN64) && defined(FOSSIL_ENABLE_TCL) && \
   361      defined(USE_TCL_STUBS)
   362    /*
   363    ** If Tcl is compiled on Windows using the latest MinGW, Fossil can crash
   364    ** when exiting while a stubs-enabled Tcl is still loaded.  This is due to
   365    ** a bug in MinGW, see:
   366    **
   367    **     http://comments.gmane.org/gmane.comp.gnu.mingw.user/41724
   368    **
   369    ** The workaround is to manually unload the loaded Tcl library prior to
   370    ** exiting the process.  This issue does not impact 64-bit Windows.
   371    */
   372    unloadTcl(g.interp, &g.tcl);
   373  #endif
   374  #ifdef FOSSIL_ENABLE_JSON
   375    cson_value_free(g.json.gc.v);
   376    memset(&g.json, 0, sizeof(g.json));
   377  #endif
   378    free(g.zErrMsg);
   379    if(g.db){
   380      db_close(0);
   381    }
   382    /*
   383    ** FIXME: The next two lines cannot always be enabled; however, they
   384    **        are very useful for tracking down TH1 memory leaks.
   385    */
386 if( fossil_getenv("TH1_DELETE_INTERP")!=0 ){ 387 if( g.interp ){ 388 Th_DeleteInterp(g.interp); g.interp = 0; 389 } 390 assert( Th_GetOutstandingMalloc()==0 ); 391 }
392 } 393 394 /* 395 ** Convert all arguments from mbcs (or unicode) to UTF-8. Then 396 ** search g.argv for arguments "--args FILENAME". If found, then 397 ** (1) remove the two arguments from g.argv 398 ** (2) Read the file FILENAME 399 ** (3) Use the contents of FILE to replace the two removed arguments: 400 ** (a) Ignore blank lines in the file 401 ** (b) Each non-empty line of the file is an argument, except 402 ** (c) If the line begins with "-" and contains a space, it is broken 403 ** into two arguments at the space. 404 */ 405 static void expand_args_option(int argc, void *argv){ 406 Blob file = empty_blob; /* Content of the file */ 407 Blob line = empty_blob; /* One line of the file */ 408 unsigned int nLine; /* Number of lines in the file*/ 409 unsigned int i, j, k; /* Loop counters */ 410 int n; /* Number of bytes in one line */ 411 char *z; /* General use string pointer */ 412 char **newArgv; /* New expanded g.argv under construction */ 413 const char *zFileName; /* input file name */ 414 FILE *inFile; /* input FILE */ 415 #if defined(_WIN32) 416 wchar_t buf[MAX_PATH]; 417 #endif 418 419 g.argc = argc; 420 g.argv = argv; 421 sqlite3_initialize(); 422 #if defined(_WIN32) && defined(BROKEN_MINGW_CMDLINE) 423 for(i=0; i<g.argc; i++) g.argv[i] = fossil_mbcs_to_utf8(g.argv[i]); 424 #else 425 for(i=0; i<g.argc; i++) g.argv[i] = fossil_path_to_utf8(g.argv[i]); 426 #endif 427 #if defined(_WIN32) 428 GetModuleFileNameW(NULL, buf, MAX_PATH); 429 g.nameOfExe = fossil_path_to_utf8(buf); 430 #else 431 g.nameOfExe = g.argv[0]; 432 #endif 433 for(i=1; i<g.argc-1; i++){ 434 z = g.argv[i]; 435 if( z[0]!='-' ) continue; 436 z++; 437 if( z[0]=='-' ) z++; 438 if( z[0]==0 ) return; /* Stop searching at "--" */ 439 if( fossil_strcmp(z, "args")==0 ) break; 440 } 441 if( i>=g.argc-1 ) return; 442 443 zFileName = g.argv[i+1]; 444 inFile = (0==strcmp("-",zFileName)) 445 ? stdin 446 : fossil_fopen(zFileName,"rb"); 447 if(!inFile){ 448 fossil_fatal("Cannot open -args file [%s]", zFileName); 449 }else{ 450 blob_read_from_channel(&file, inFile, -1); 451 if(stdin != inFile){ 452 fclose(inFile); 453 } 454 inFile = NULL; 455 } 456 blob_to_utf8_no_bom(&file, 1); 457 z = blob_str(&file); 458 for(k=0, nLine=1; z[k]; k++) if( z[k]=='\n' ) nLine++; 459 newArgv = fossil_malloc( sizeof(char*)*(g.argc + nLine*2) ); 460 for(j=0; j<i; j++) newArgv[j] = g.argv[j]; 461 462 blob_rewind(&file); 463 while( (n = blob_line(&file, &line))>0 ){ 464 if( n<1 ) continue 465 /** 466 ** Reminder: corner-case: a line with 1 byte and no newline. 467 */; 468 z = blob_buffer(&line); 469 if('\n'==z[n-1]){ 470 z[n-1] = 0; 471 } 472 473 if((n>1) && ('\r'==z[n-2])){ 474 if(n==2) continue /*empty line*/; 475 z[n-2] = 0; 476 } 477 if(!z[0]) continue; 478 newArgv[j++] = z; 479 if( z[0]=='-' ){ 480 for(k=1; z[k] && !fossil_isspace(z[k]); k++){} 481 if( z[k] ){ 482 z[k] = 0; 483 k++; 484 if( z[k] ) newArgv[j++] = &z[k]; 485 } 486 } 487 } 488 i += 2; 489 while( i<g.argc ) newArgv[j++] = g.argv[i++]; 490 newArgv[j] = 0; 491 g.argc = j; 492 g.argv = newArgv; 493 } 494 495 #ifdef FOSSIL_ENABLE_TCL 496 /* 497 ** Make a deep copy of the provided argument array and return it. 498 */ 499 static char **copy_args(int argc, char **argv){ 500 char **zNewArgv; 501 int i; 502 zNewArgv = fossil_malloc( sizeof(char*)*(argc+1) ); 503 memset(zNewArgv, 0, sizeof(char*)*(argc+1)); 504 for(i=0; i<argc; i++){ 505 zNewArgv[i] = fossil_strdup(argv[i]); 506 } 507 return zNewArgv; 508 } 509 #endif 510 511 /* 512 ** Returns a name for a SQLite return code. 513 */ 514 static const char *fossil_sqlite_return_code_name(int rc){ 515 static char zCode[30]; 516 switch( rc & 0xff ){ 517 case SQLITE_OK: return "SQLITE_OK"; 518 case SQLITE_ERROR: return "SQLITE_ERROR"; 519 case SQLITE_INTERNAL: return "SQLITE_INTERNAL"; 520 case SQLITE_PERM: return "SQLITE_PERM"; 521 case SQLITE_ABORT: return "SQLITE_ABORT"; 522 case SQLITE_BUSY: return "SQLITE_BUSY"; 523 case SQLITE_LOCKED: return "SQLITE_LOCKED"; 524 case SQLITE_NOMEM: return "SQLITE_NOMEM"; 525 case SQLITE_READONLY: return "SQLITE_READONLY"; 526 case SQLITE_INTERRUPT: return "SQLITE_INTERRUPT"; 527 case SQLITE_IOERR: return "SQLITE_IOERR"; 528 case SQLITE_CORRUPT: return "SQLITE_CORRUPT"; 529 case SQLITE_NOTFOUND: return "SQLITE_NOTFOUND"; 530 case SQLITE_FULL: return "SQLITE_FULL"; 531 case SQLITE_CANTOPEN: return "SQLITE_CANTOPEN"; 532 case SQLITE_PROTOCOL: return "SQLITE_PROTOCOL"; 533 case SQLITE_EMPTY: return "SQLITE_EMPTY"; 534 case SQLITE_SCHEMA: return "SQLITE_SCHEMA"; 535 case SQLITE_TOOBIG: return "SQLITE_TOOBIG"; 536 case SQLITE_CONSTRAINT: return "SQLITE_CONSTRAINT"; 537 case SQLITE_MISMATCH: return "SQLITE_MISMATCH"; 538 case SQLITE_MISUSE: return "SQLITE_MISUSE"; 539 case SQLITE_NOLFS: return "SQLITE_NOLFS"; 540 case SQLITE_AUTH: return "SQLITE_AUTH"; 541 case SQLITE_FORMAT: return "SQLITE_FORMAT"; 542 case SQLITE_RANGE: return "SQLITE_RANGE"; 543 case SQLITE_NOTADB: return "SQLITE_NOTADB"; 544 case SQLITE_NOTICE: return "SQLITE_NOTICE"; 545 case SQLITE_WARNING: return "SQLITE_WARNING"; 546 case SQLITE_ROW: return "SQLITE_ROW"; 547 case SQLITE_DONE: return "SQLITE_DONE"; 548 default: { 549 sqlite3_snprintf(sizeof(zCode), zCode, "SQLite return code %d", rc); 550 } 551 } 552 return zCode; 553 } 554 555 /* Error logs from SQLite */ 556 static void fossil_sqlite_log(void *notUsed, int iCode, const char *zErrmsg){ 557 #ifdef __APPLE__ 558 /* Disable the file alias warning on apple products because Time Machine 559 ** creates lots of aliases and the warning alarms people. */ 560 if( iCode==SQLITE_WARNING ) return; 561 #endif 562 if( iCode==SQLITE_SCHEMA ) return; 563 fossil_warning("%s: %s", fossil_sqlite_return_code_name(iCode), zErrmsg); 564 } 565 566 /* 567 ** This function attempts to find command line options known to contain 568 ** bitwise flags and initializes the associated global variables. After 569 ** this function executes, all global variables (i.e. in the "g" struct) 570 ** containing option-settable bitwise flag fields must be initialized. 571 */ 572 static void fossil_init_flags_from_options(void){ 573 const char *zValue = find_option("comfmtflags", 0, 1); 574 if( zValue ){ 575 g.comFmtFlags = atoi(zValue); 576 }else{ 577 g.comFmtFlags = COMMENT_PRINT_DEFAULT; 578 } 579 } 580 581 /* 582 ** This procedure runs first. 583 */ 584 #if defined(_WIN32) && !defined(BROKEN_MINGW_CMDLINE) 585 int _dowildcard = -1; /* This turns on command-line globbing in MinGW-w64 */ 586 int wmain(int argc, wchar_t **argv) 587 #else 588 #if defined(_WIN32) 589 int _CRT_glob = 0x0001; /* See MinGW bug #2062 */ 590 #endif 591 int main(int argc, char **argv) 592 #endif 593 { 594 const char *zCmdName = "unknown"; 595 int idx; 596 int rc; 597 if( sqlite3_libversion_number()<3010000 ){ 598 fossil_fatal("Unsuitable SQLite version %s, must be at least 3.10.0", 599 sqlite3_libversion()); 600 } 601 sqlite3_config(SQLITE_CONFIG_MULTITHREAD); 602 sqlite3_config(SQLITE_CONFIG_LOG, fossil_sqlite_log, 0); 603 memset(&g, 0, sizeof(g)); 604 g.now = time(0); 605 g.httpHeader = empty_blob; 606 #ifdef FOSSIL_ENABLE_JSON 607 #if defined(NDEBUG) 608 g.json.errorDetailParanoia = 2 /* FIXME: make configurable 609 One problem we have here is that this 610 code is needed before the db is opened, 611 so we can't sql for it.*/; 612 #else 613 g.json.errorDetailParanoia = 0; 614 #endif 615 g.json.outOpt = cson_output_opt_empty; 616 g.json.outOpt.addNewline = 1; 617 g.json.outOpt.indentation = 1 /* in CGI/server mode this can be configured */; 618 #endif /* FOSSIL_ENABLE_JSON */ 619 expand_args_option(argc, argv); 620 #ifdef FOSSIL_ENABLE_TCL 621 memset(&g.tcl, 0, sizeof(TclContext)); 622 g.tcl.argc = g.argc; 623 g.tcl.argv = copy_args(g.argc, g.argv); /* save full arguments */ 624 #endif 625 g.mainTimerId = fossil_timer_start(); 626 capture_case_sensitive_option(); 627 g.zVfsName = find_option("vfs",0,1); 628 if( g.zVfsName==0 ){ 629 g.zVfsName = fossil_getenv("FOSSIL_VFS"); 630 } 631 if( g.zVfsName ){ 632 sqlite3_vfs *pVfs = sqlite3_vfs_find(g.zVfsName); 633 if( pVfs ){ 634 sqlite3_vfs_register(pVfs, 1); 635 }else{ 636 fossil_fatal("no such VFS: \"%s\"", g.zVfsName); 637 } 638 } 639 if( fossil_getenv("GATEWAY_INTERFACE")!=0 && !find_option("nocgi", 0, 0)){ 640 zCmdName = "cgi"; 641 g.isHTTP = 1; 642 }else if( g.argc<2 ){ 643 fossil_print( 644 "Usage: %s COMMAND ...\n" 645 " or: %s help -- for a list of common commands\n" 646 " or: %s help COMMAND -- for help with the named command\n", 647 g.argv[0], g.argv[0], g.argv[0]); 648 fossil_print( 649 "\nCommands and filenames may be passed on to fossil from a file\n" 650 "by using:\n" 651 "\n %s --args FILENAME ...\n", 652 g.argv[0] 653 ); 654 fossil_print( 655 "\nEach line of the file is assumed to be a filename unless it starts\n" 656 "with '-' and contains a space, in which case it is assumed to be\n" 657 "another flag and is treated as such. --args FILENAME may be used\n" 658 "in conjunction with any other flags.\n"); 659 fossil_exit(1); 660 }else{ 661 const char *zChdir = find_option("chdir",0,1); 662 g.isHTTP = 0; 663 g.rcvid = 0; 664 g.fQuiet = find_option("quiet", 0, 0)!=0; 665 g.fSqlTrace = find_option("sqltrace", 0, 0)!=0; 666 g.fSqlStats = find_option("sqlstats", 0, 0)!=0; 667 g.fSystemTrace = find_option("systemtrace", 0, 0)!=0; 668 g.fSshTrace = find_option("sshtrace", 0, 0)!=0; 669 g.fSshClient = 0; 670 g.zSshCmd = 0; 671 if( g.fSqlTrace ) g.fSqlStats = 1; 672 g.fHttpTrace = find_option("httptrace", 0, 0)!=0; 673 #ifdef FOSSIL_ENABLE_TH1_HOOKS 674 g.fNoThHook = find_option("no-th-hook", 0, 0)!=0; 675 #endif 676 g.fAnyTrace = g.fSqlTrace|g.fSystemTrace|g.fSshTrace|g.fHttpTrace; 677 g.zHttpAuth = 0; 678 g.zLogin = find_option("user", "U", 1); 679 g.zSSLIdentity = find_option("ssl-identity", 0, 1); 680 g.zErrlog = find_option("errorlog", 0, 1); 681 fossil_init_flags_from_options(); 682 if( find_option("utc",0,0) ) g.fTimeFormat = 1; 683 if( find_option("localtime",0,0) ) g.fTimeFormat = 2; 684 if( zChdir && file_chdir(zChdir, 0) ){ 685 fossil_fatal("unable to change directories to %s", zChdir); 686 } 687 if( find_option("help",0,0)!=0 ){ 688 /* If --help is found anywhere on the command line, translate the command 689 * to "fossil help cmdname" where "cmdname" is the first argument that 690 * does not begin with a "-" character. If all arguments start with "-", 691 * translate to "fossil help argv[1] argv[2]...". */ 692 int i, nNewArgc; 693 char **zNewArgv = fossil_malloc( sizeof(char*)*(g.argc+2) ); 694 zNewArgv[0] = g.argv[0]; 695 zNewArgv[1] = "help"; 696 for(i=1; i<g.argc; i++){ 697 if( g.argv[i][0]!='-' ){ 698 nNewArgc = 3; 699 zNewArgv[2] = g.argv[i]; 700 zNewArgv[3] = 0; 701 break; 702 } 703 } 704 if( i==g.argc ){ 705 for(i=1; i<g.argc; i++) zNewArgv[i+1] = g.argv[i]; 706 nNewArgc = g.argc+1; 707 zNewArgv[i+1] = 0; 708 } 709 g.argc = nNewArgc; 710 g.argv = zNewArgv; 711 } 712 zCmdName = g.argv[1]; 713 } 714 #ifndef _WIN32 715 /* There is a bug in stunnel4 in which it sometimes starts up client 716 ** processes without first opening file descriptor 2 (standard error). 717 ** If this happens, and a subsequent open() of a database returns file 718 ** descriptor 2, and then an assert() fires and writes on fd 2, that 719 ** can corrupt the data file. To avoid this problem, make sure open() 720 ** will never return file descriptor 2 or less. */ 721 if( !is_valid_fd(2) ){ 722 int nTry = 0; 723 int fd = 0; 724 int x = 0; 725 do{ 726 fd = open("/dev/null",O_WRONLY); 727 if( fd>=2 ) break; 728 if( fd<0 ) x = errno; 729 }while( nTry++ < 2 ); 730 if( fd<2 ){ 731 g.cgiOutput = 1; 732 g.httpOut = stdout; 733 g.fullHttpReply = !g.isHTTP; 734 fossil_fatal("file descriptor 2 is not open. (fd=%d, errno=%d)", 735 fd, x); 736 } 737 } 738 #endif 739 rc = name_search(zCmdName, aCommand, count(aCommand), FOSSIL_FIRST_CMD, &idx); 740 if( rc==1 ){ 741 #ifdef FOSSIL_ENABLE_TH1_HOOKS 742 if( !g.isHTTP && !g.fNoThHook ){ 743 rc = Th_CommandHook(zCmdName, 0); 744 }else{ 745 rc = TH_OK; 746 } 747 if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ 748 if( rc==TH_OK || rc==TH_RETURN ){ 749 #endif 750 fossil_fatal("%s: unknown command: %s\n" 751 "%s: use \"help\" for more information\n", 752 g.argv[0], zCmdName, g.argv[0]); 753 #ifdef FOSSIL_ENABLE_TH1_HOOKS 754 } 755 if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ 756 Th_CommandNotify(zCmdName, 0); 757 } 758 } 759 fossil_exit(0); 760 #endif 761 }else if( rc==2 ){ 762 int i, n; 763 Blob couldbe; 764 blob_zero(&couldbe); 765 n = strlen(zCmdName); 766 for(i=0; i<count(aCommand); i++){ 767 if( memcmp(zCmdName, aCommand[i].zName, n)==0 ){ 768 blob_appendf(&couldbe, " %s", aCommand[i].zName); 769 } 770 } 771 fossil_print("%s: ambiguous command prefix: %s\n" 772 "%s: could be any of:%s\n" 773 "%s: use \"help\" for more information\n", 774 g.argv[0], zCmdName, g.argv[0], blob_str(&couldbe), g.argv[0]); 775 fossil_exit(1); 776 } 777 atexit( fossil_atexit ); 778 #ifdef FOSSIL_ENABLE_TH1_HOOKS 779 /* 780 ** The TH1 return codes from the hook will be handled as follows: 781 ** 782 ** TH_OK: The xFunc() and the TH1 notification will both be executed. 783 ** 784 ** TH_ERROR: The xFunc() will be executed, the TH1 notification will be 785 ** skipped. If the xFunc() is being hooked, the error message 786 ** will be emitted. 787 ** 788 ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped. 789 ** 790 ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be 791 ** skipped. 792 ** 793 ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be 794 ** executed. 795 */ 796 if( !g.isHTTP && !g.fNoThHook ){ 797 rc = Th_CommandHook(aCommand[idx].zName, aCommand[idx].cmdFlags); 798 }else{ 799 rc = TH_OK; 800 } 801 if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ 802 if( rc==TH_OK || rc==TH_RETURN ){ 803 #endif 804 aCommand[idx].xFunc(); 805 #ifdef FOSSIL_ENABLE_TH1_HOOKS 806 } 807 if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ 808 Th_CommandNotify(aCommand[idx].zName, aCommand[idx].cmdFlags); 809 } 810 } 811 #endif 812 fossil_exit(0); 813 /*NOT_REACHED*/ 814 return 0; 815 } 816 817 /* 818 ** Print a usage comment and quit 819 */ 820 void usage(const char *zFormat){ 821 fossil_fatal("Usage: %s %s %s", g.argv[0], g.argv[1], zFormat); 822 } 823 824 /* 825 ** Remove n elements from g.argv beginning with the i-th element. 826 */ 827 static void remove_from_argv(int i, int n){ 828 int j; 829 for(j=i+n; j<g.argc; i++, j++){ 830 g.argv[i] = g.argv[j]; 831 } 832 g.argc = i; 833 } 834 835 836 /* 837 ** Look for a command-line option. If present, return a pointer. 838 ** Return NULL if missing. 839 ** 840 ** hasArg==0 means the option is a flag. It is either present or not. 841 ** hasArg==1 means the option has an argument. Return a pointer to the 842 ** argument. 843 */ 844 const char *find_option(const char *zLong, const char *zShort, int hasArg){ 845 int i; 846 int nLong; 847 const char *zReturn = 0; 848 assert( hasArg==0 || hasArg==1 ); 849 nLong = strlen(zLong); 850 for(i=1; i<g.argc; i++){ 851 char *z; 852 if( i+hasArg >= g.argc ) break; 853 z = g.argv[i]; 854 if( z[0]!='-' ) continue; 855 z++; 856 if( z[0]=='-' ){ 857 if( z[1]==0 ){ 858 remove_from_argv(i, 1); 859 break; 860 } 861 z++; 862 } 863 if( strncmp(z,zLong,nLong)==0 ){ 864 if( hasArg && z[nLong]=='=' ){ 865 zReturn = &z[nLong+1]; 866 remove_from_argv(i, 1); 867 break; 868 }else if( z[nLong]==0 ){ 869 zReturn = g.argv[i+hasArg]; 870 remove_from_argv(i, 1+hasArg); 871 break; 872 } 873 }else if( fossil_strcmp(z,zShort)==0 ){ 874 zReturn = g.argv[i+hasArg]; 875 remove_from_argv(i, 1+hasArg); 876 break; 877 } 878 } 879 return zReturn; 880 } 881 882 /* 883 ** Look for multiple occurrences of a command-line option with the 884 ** corresponding argument. 885 ** 886 ** Return a malloc allocated array of pointers to the arguments. 887 ** 888 ** pnUsedArgs is used to store the number of matched arguments. 889 ** 890 ** Caller is responsible to free allocated memory. 891 */ 892 const char **find_repeatable_option( 893 const char *zLong, 894 const char *zShort, 895 int *pnUsedArgs 896 ){ 897 const char *zOption; 898 const char **pzArgs = 0; 899 int nAllocArgs = 0; 900 int nUsedArgs = 0; 901 902 while( (zOption = find_option(zLong, zShort, 1))!=0 ){ 903 if( pzArgs==0 && nAllocArgs==0 ){ 904 nAllocArgs = 1; 905 pzArgs = fossil_malloc( nAllocArgs*sizeof(pzArgs[0]) ); 906 }else if( nAllocArgs<=nUsedArgs ){ 907 nAllocArgs = nAllocArgs*2; 908 pzArgs = fossil_realloc( (void *)pzArgs, nAllocArgs*sizeof(pzArgs[0]) ); 909 } 910 pzArgs[nUsedArgs++] = zOption; 911 } 912 *pnUsedArgs = nUsedArgs; 913 return pzArgs; 914 } 915 916 /* 917 ** Look for a repository command-line option. If present, [re-]cache it in 918 ** the global state and return the new pointer, freeing any previous value. 919 ** If absent and there is no cached value, return NULL. 920 */ 921 const char *find_repository_option(){ 922 const char *zRepository = find_option("repository", "R", 1); 923 if( zRepository ){ 924 if( g.zRepositoryOption ) fossil_free(g.zRepositoryOption); 925 g.zRepositoryOption = mprintf("%s", zRepository); 926 } 927 return g.zRepositoryOption; 928 } 929 930 /* 931 ** Verify that there are no unprocessed command-line options. If 932 ** Any remaining command-line argument begins with "-" print 933 ** an error message and quit. 934 */ 935 void verify_all_options(void){ 936 int i; 937 for(i=1; i<g.argc; i++){ 938 if( g.argv[i][0]=='-' ){ 939 fossil_fatal( 940 "unrecognized command-line option, or missing argument: %s", 941 g.argv[i]); 942 } 943 } 944 } 945 946 /* 947 ** Print a list of words in multiple columns. 948 */ 949 static void multi_column_list(const char **azWord, int nWord){ 950 int i, j, len; 951 int mxLen = 0; 952 int nCol; 953 int nRow; 954 for(i=0; i<nWord; i++){ 955 len = strlen(azWord[i]); 956 if( len>mxLen ) mxLen = len; 957 } 958 nCol = 80/(mxLen+2); 959 if( nCol==0 ) nCol = 1; 960 nRow = (nWord + nCol - 1)/nCol; 961 for(i=0; i<nRow; i++){ 962 const char *zSpacer = ""; 963 for(j=i; j<nWord; j+=nRow){ 964 fossil_print("%s%-*s", zSpacer, mxLen, azWord[j]); 965 zSpacer = " "; 966 } 967 fossil_print("\n"); 968 } 969 } 970 971 /* 972 ** List of commands starting with zPrefix, or all commands if zPrefix is NULL. 973 */ 974 static void command_list(const char *zPrefix, int cmdMask){ 975 int i, nCmd; 976 int nPrefix = zPrefix ? strlen(zPrefix) : 0; 977 const char *aCmd[count(aCommand)]; 978 for(i=nCmd=0; i<count(aCommand); i++){ 979 const char *z = aCommand[i].zName; 980 if( (aCommand[i].cmdFlags & cmdMask)==0 ) continue; 981 if( zPrefix && memcmp(zPrefix, z, nPrefix)!=0 ) continue; 982 aCmd[nCmd++] = aCommand[i].zName; 983 } 984 multi_column_list(aCmd, nCmd); 985 } 986 987 /* 988 ** COMMAND: test-list-webpage 989 ** 990 ** List all web pages 991 */ 992 void cmd_test_webpage_list(void){ 993 int i, nCmd; 994 const char *aCmd[count(aCommand)]; 995 for(i=nCmd=0; i<count(aCommand); i++){ 996 if(0x08 & aCommand[i].cmdFlags){ 997 aCmd[nCmd++] = aWebpage[i].zName; 998 } 999 } 1000 assert(nCmd && "page list is empty?"); 1001 multi_column_list(aCmd, nCmd); 1002 } 1003 1004 1005 1006 /* 1007 ** This function returns a human readable version string. 1008 */ 1009 const char *get_version(){ 1010 static const char version[] = RELEASE_VERSION " " MANIFEST_VERSION " " 1011 MANIFEST_DATE " UTC"; 1012 return version; 1013 } 1014 1015 /* 1016 ** This function populates a blob with version information. It is used by 1017 ** the "version" command and "test-version" web page. It assumes the blob 1018 ** passed to it is uninitialized; otherwise, it will leak memory. 1019 */ 1020 static void get_version_blob( 1021 Blob *pOut, /* Write the manifest here */ 1022 int bVerbose /* Non-zero for full information. */ 1023 ){ 1024 #if defined(FOSSIL_ENABLE_TCL) 1025 int rc; 1026 const char *zRc; 1027 #endif 1028 blob_zero(pOut); 1029 blob_appendf(pOut, "This is fossil version %s\n", get_version()); 1030 if( !bVerbose ) return; 1031 blob_appendf(pOut, "Compiled on %s %s using %s (%d-bit)\n", 1032 __DATE__, __TIME__, COMPILER_NAME, sizeof(void*)*8); 1033 blob_appendf(pOut, "SQLite %s %.30s\n", sqlite3_libversion(), 1034 sqlite3_sourceid()); 1035 blob_appendf(pOut, "Schema version %s\n", AUX_SCHEMA_MAX); 1036 #if defined(FOSSIL_ENABLE_MINIZ) 1037 blob_appendf(pOut, "miniz %s, loaded %s\n", MZ_VERSION, mz_version()); 1038 #else 1039 blob_appendf(pOut, "zlib %s, loaded %s\n", ZLIB_VERSION, zlibVersion()); 1040 #endif 1041 #if defined(FOSSIL_ENABLE_SSL) 1042 blob_appendf(pOut, "SSL (%s)\n", SSLeay_version(SSLEAY_VERSION)); 1043 #endif 1044 #if defined(FOSSIL_ENABLE_LEGACY_MV_RM) 1045 blob_append(pOut, "LEGACY_MV_RM\n", -1); 1046 #endif 1047 #if defined(FOSSIL_ENABLE_EXEC_REL_PATHS) 1048 blob_append(pOut, "EXEC_REL_PATHS\n", -1); 1049 #endif 1050 #if defined(FOSSIL_ENABLE_TH1_DOCS) 1051 blob_append(pOut, "TH1_DOCS\n", -1); 1052 #endif 1053 #if defined(FOSSIL_ENABLE_TH1_HOOKS) 1054 blob_append(pOut, "TH1_HOOKS\n", -1); 1055 #endif 1056 #if defined(FOSSIL_ENABLE_TCL) 1057 Th_FossilInit(TH_INIT_DEFAULT | TH_INIT_FORCE_TCL); 1058 rc = Th_Eval(g.interp, 0, "tclInvoke info patchlevel", -1); 1059 zRc = Th_ReturnCodeName(rc, 0); 1060 blob_appendf(pOut, "TCL (Tcl %s, loaded %s: %s)\n", 1061 TCL_PATCH_LEVEL, zRc, Th_GetResult(g.interp, 0) 1062 ); 1063 #endif 1064 #if defined(USE_TCL_STUBS) 1065 blob_append(pOut, "USE_TCL_STUBS\n", -1); 1066 #endif 1067 #if defined(FOSSIL_ENABLE_TCL_STUBS) 1068 blob_append(pOut, "TCL_STUBS\n", -1); 1069 #endif 1070 #if defined(FOSSIL_ENABLE_TCL_PRIVATE_STUBS) 1071 blob_append(pOut, "TCL_PRIVATE_STUBS\n", -1); 1072 #endif 1073 #if defined(FOSSIL_ENABLE_JSON) 1074 blob_appendf(pOut, "JSON (API %s)\n", FOSSIL_JSON_API_VERSION); 1075 #endif 1076 #if defined(BROKEN_MINGW_CMDLINE) 1077 blob_append(pOut, "MBCS_COMMAND_LINE\n", -1); 1078 #else 1079 blob_append(pOut, "UNICODE_COMMAND_LINE\n", -1); 1080 #endif 1081 #if defined(FOSSIL_DYNAMIC_BUILD) 1082 blob_append(pOut, "DYNAMIC_BUILD\n", -1); 1083 #else 1084 blob_append(pOut, "STATIC_BUILD\n", -1); 1085 #endif 1086 } 1087 1088 /* 1089 ** This function returns the user-agent string for Fossil, for 1090 ** use in HTTP(S) requests. 1091 */ 1092 const char *get_user_agent(){ 1093 static const char version[] = "Fossil/" RELEASE_VERSION " (" MANIFEST_DATE 1094 " " MANIFEST_VERSION ")"; 1095 return version; 1096 } 1097 1098 1099 /* 1100 ** COMMAND: version 1101 ** 1102 ** Usage: %fossil version ?-verbose|-v? 1103 ** 1104 ** Print the source code version number for the fossil executable. 1105 ** If the verbose option is specified, additional details will 1106 ** be output about what optional features this binary was compiled 1107 ** with 1108 */ 1109 void version_cmd(void){ 1110 Blob versionInfo; 1111 int verboseFlag = find_option("verbose","v",0)!=0; 1112 1113 /* We should be done with options.. */ 1114 verify_all_options(); 1115 get_version_blob(&versionInfo, verboseFlag); 1116 fossil_print("%s", blob_str(&versionInfo)); 1117 } 1118 1119 1120 /* 1121 ** WEBPAGE: test-version 1122 ** 1123 ** Show the version information for Fossil. 1124 ** 1125 ** Query parameters: 1126 ** 1127 ** verbose Show all available details. 1128 */ 1129 void test_version_page(void){ 1130 Blob versionInfo; 1131 int verboseFlag; 1132 1133 login_check_credentials(); 1134 if( !g.perm.Read ){ login_needed(g.anon.Read); return; } 1135 verboseFlag = P("verbose")!=0; 1136 style_header("Version Information"); 1137 get_version_blob(&versionInfo, verboseFlag); 1138 @ <blockquote><pre> 1139 @ %h(blob_str(&versionInfo)) 1140 @ </pre></blockquote> 1141 style_footer(); 1142 } 1143 1144 1145 /* 1146 ** COMMAND: help 1147 ** 1148 ** Usage: %fossil help COMMAND 1149 ** or: %fossil COMMAND --help 1150 ** 1151 ** Display information on how to use COMMAND. To display a list of 1152 ** available commands use one of: 1153 ** 1154 ** %fossil help Show common commands 1155 ** %fossil help -a|--all Show both common and auxiliary commands 1156 ** %fossil help -t|--test Show test commands only 1157 ** %fossil help -x|--aux Show auxiliary commands only 1158 ** %fossil help -w|--www Show list of WWW pages 1159 */ 1160 void help_cmd(void){ 1161 int rc, idx, isPage = 0; 1162 const char *z; 1163 const char *zCmdOrPage; 1164 const char *zCmdOrPagePlural; 1165 if( g.argc<3 ){ 1166 z = g.argv[0]; 1167 fossil_print( 1168 "Usage: %s help COMMAND\n" 1169 "Common COMMANDs: (use \"%s help -a|--all\" for a complete list)\n", 1170 z, z); 1171 command_list(0, CMDFLAG_1ST_TIER); 1172 version_cmd(); 1173 return; 1174 } 1175 if( find_option("all","a",0) ){ 1176 command_list(0, CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER); 1177 return; 1178 } 1179 else if( find_option("www","w",0) ){ 1180 command_list(0, CMDFLAG_WEBPAGE); 1181 return; 1182 } 1183 else if( find_option("aux","x",0) ){ 1184 command_list(0, CMDFLAG_2ND_TIER); 1185 return; 1186 } 1187 else if( find_option("test","t",0) ){ 1188 command_list(0, CMDFLAG_TEST); 1189 return; 1190 } 1191 isPage = ('/' == *g.argv[2]) ? 1 : 0; 1192 if(isPage){ 1193 zCmdOrPage = "page"; 1194 zCmdOrPagePlural = "pages"; 1195 }else{ 1196 zCmdOrPage = "command"; 1197 zCmdOrPagePlural = "commands"; 1198 } 1199 rc = name_search(g.argv[2], aCommand, count(aCommand), 0, &idx); 1200 if( rc==1 ){ 1201 fossil_print("unknown %s: %s\nAvailable %s:\n", 1202 zCmdOrPage, g.argv[2], zCmdOrPagePlural); 1203 command_list(0, isPage ? CMDFLAG_WEBPAGE : (0xff & ~CMDFLAG_WEBPAGE)); 1204 fossil_exit(1); 1205 }else if( rc==2 ){ 1206 fossil_print("ambiguous %s prefix: %s\nMatching %s:\n", 1207 zCmdOrPage, g.argv[2], zCmdOrPagePlural); 1208 command_list(g.argv[2], 0xff); 1209 fossil_exit(1); 1210 } 1211 z = aCmdHelp[idx].zText; 1212 if( z==0 ){ 1213 fossil_fatal("no help available for the %s %s", 1214 aCommand[idx].zName, zCmdOrPage); 1215 } 1216 while( *z ){ 1217 if( *z=='%' && strncmp(z, "%fossil", 7)==0 ){ 1218 fossil_print("%s", g.argv[0]); 1219 z += 7; 1220 }else{ 1221 putchar(*z); 1222 z++; 1223 } 1224 } 1225 putchar('\n'); 1226 } 1227 1228 /* 1229 ** WEBPAGE: help 1230 ** URL: /help?name=CMD 1231 ** 1232 ** Show the built-in help text for CMD. CMD can be a command-line interface 1233 ** command or a page name from the web interface. 1234 */ 1235 void help_page(void){ 1236 const char *zCmd = P("cmd"); 1237 1238 if( zCmd==0 ) zCmd = P("name"); 1239 style_header("Command-line Help"); 1240 if( zCmd ){ 1241 int rc, idx; 1242 char *z, *s, *d; 1243 const char *zCmdOrPage = ('/'==*zCmd) ? "page" : "command"; 1244 style_submenu_element("Command-List", "Command-List", "%s/help", g.zTop); 1245 @ <h1>The "%s(zCmd)" %s(zCmdOrPage):</h1> 1246 rc = name_search(zCmd, aCommand, count(aCommand), 0, &idx); 1247 if( rc==1 ){ 1248 @ unknown command: %s(zCmd) 1249 }else if( rc==2 ){ 1250 @ ambiguous command prefix: %s(zCmd) 1251 }else{ 1252 z = (char*)aCmdHelp[idx].zText; 1253 if( z==0 ){ 1254 @ no help available for the %s(aCommand[idx].zName) command 1255 }else{ 1256 z=s=d=mprintf("%s",z); 1257 while( *s ){ 1258 if( *s=='%' && strncmp(s, "%fossil", 7)==0 ){ 1259 s++; 1260 }else{ 1261 *d++ = *s++; 1262 } 1263 } 1264 *d = 0; 1265 @ <blockquote><pre> 1266 @ %h(z) 1267 @ </pre></blockquote> 1268 fossil_free(z); 1269 } 1270 } 1271 }else{ 1272 int i, j, n; 1273 1274 @ <h1>Available commands:</h1> 1275 @ <table border="0"><tr> 1276 for(i=j=0; i<count(aCommand); i++){ 1277 const char *z = aCommand[i].zName; 1278 if( '/'==*z || strncmp(z,"test",4)==0 ) continue; 1279 j++; 1280 } 1281 n = (j+6)/7; 1282 for(i=j=0; i<count(aCommand); i++){ 1283 const char *z = aCommand[i].zName; 1284 if( '/'==*z || strncmp(z,"test",4)==0 ) continue; 1285 if( j==0 ){ 1286 @ <td valign="top"><ul> 1287 } 1288 @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li> 1289 j++; 1290 if( j>=n ){ 1291 @ </ul></td> 1292 j = 0; 1293 } 1294 } 1295 if( j>0 ){ 1296 @ </ul></td> 1297 } 1298 @ </tr></table> 1299 1300 @ <h1>Available web UI pages:</h1> 1301 @ <table border="0"><tr> 1302 for(i=j=0; i<count(aCommand); i++){ 1303 const char *z = aCommand[i].zName; 1304 if( '/'!=*z ) continue; 1305 j++; 1306 } 1307 n = (j+4)/5; 1308 for(i=j=0; i<count(aCommand); i++){ 1309 const char *z = aCommand[i].zName; 1310 if( '/'!=*z ) continue; 1311 if( j==0 ){ 1312 @ <td valign="top"><ul> 1313 } 1314 if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){ 1315 @ <li><a href="%R/help?cmd=%s(z)">%s(z+1)</a></li> 1316 }else{ 1317 @ <li>%s(z+1)</li> 1318 } 1319 j++; 1320 if( j>=n ){ 1321 @ </ul></td> 1322 j = 0; 1323 } 1324 } 1325 if( j>0 ){ 1326 @ </ul></td> 1327 } 1328 @ </tr></table> 1329 1330 @ <h1>Unsupported commands:</h1> 1331 @ <table border="0"><tr> 1332 for(i=j=0; i<count(aCommand); i++){ 1333 const char *z = aCommand[i].zName; 1334 if( strncmp(z,"test",4)!=0 ) continue; 1335 j++; 1336 } 1337 n = (j+3)/4; 1338 for(i=j=0; i<count(aCommand); i++){ 1339 const char *z = aCommand[i].zName; 1340 if( strncmp(z,"test",4)!=0 ) continue; 1341 if( j==0 ){ 1342 @ <td valign="top"><ul> 1343 } 1344 if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){ 1345 @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li> 1346 }else{ 1347 @ <li>%s(z)</li> 1348 } 1349 j++; 1350 if( j>=n ){ 1351 @ </ul></td> 1352 j = 0; 1353 } 1354 } 1355 if( j>0 ){ 1356 @ </ul></td> 1357 } 1358 @ </tr></table> 1359 1360 } 1361 style_footer(); 1362 } 1363 1364 /* 1365 ** WEBPAGE: test-all-help 1366 ** 1367 ** Show all help text on a single page. Useful for proof-reading. 1368 */ 1369 void test_all_help_page(void){ 1370 int i; 1371 style_header("Testpage: All Help Text"); 1372 for(i=0; i<count(aCommand); i++){ 1373 if( memcmp(aCommand[i].zName, "test", 4)==0 ) continue; 1374 @ <h2>%s(aCommand[i].zName):</h2> 1375 @ <blockquote><pre> 1376 @ %h(aCmdHelp[i].zText) 1377 @ </pre></blockquote> 1378 } 1379 style_footer(); 1380 } 1381 1382 /* 1383 ** Set the g.zBaseURL value to the full URL for the toplevel of 1384 ** the fossil tree. Set g.zTop to g.zBaseURL without the 1385 ** leading "http://" and the host and port. 1386 ** 1387 ** The g.zBaseURL is normally set based on HTTP_HOST and SCRIPT_NAME 1388 ** environment variables. However, if zAltBase is not NULL then it 1389 ** is the argument to the --baseurl option command-line option and 1390 ** g.zBaseURL and g.zTop is set from that instead. 1391 */ 1392 static void set_base_url(const char *zAltBase){ 1393 int i; 1394 const char *zHost; 1395 const char *zMode; 1396 const char *zCur; 1397 1398 if( g.zBaseURL!=0 ) return; 1399 if( zAltBase ){ 1400 int i, n, c; 1401 g.zTop = g.zBaseURL = mprintf("%s", zAltBase); 1402 if( strncmp(g.zTop, "http://", 7)==0 ){ 1403 /* it is HTTP, replace prefix with HTTPS. */ 1404 g.zHttpsURL = mprintf("https://%s", &g.zTop[7]); 1405 }else if( strncmp(g.zTop, "https://", 8)==0 ){ 1406 /* it is already HTTPS, use it. */ 1407 g.zHttpsURL = mprintf("%s", g.zTop); 1408 }else{ 1409 fossil_fatal("argument to --baseurl should be 'http://host/path'" 1410 " or 'https://host/path'"); 1411 } 1412 for(i=n=0; (c = g.zTop[i])!=0; i++){ 1413 if( c=='/' ){ 1414 n++; 1415 if( n==3 ){ 1416 g.zTop += i; 1417 break; 1418 } 1419 } 1420 } 1421 if( g.zTop==g.zBaseURL ){ 1422 fossil_fatal("argument to --baseurl should be 'http://host/path'" 1423 " or 'https://host/path'"); 1424 } 1425 if( g.zTop[1]==0 ) g.zTop++; 1426 }else{ 1427 zHost = PD("HTTP_HOST",""); 1428 zMode = PD("HTTPS","off"); 1429 zCur = PD("SCRIPT_NAME","/"); 1430 i = strlen(zCur); 1431 while( i>0 && zCur[i-1]=='/' ) i--; 1432 if( fossil_stricmp(zMode,"on")==0 ){ 1433 g.zBaseURL = mprintf("https://%s%.*s", zHost, i, zCur); 1434 g.zTop = &g.zBaseURL[8+strlen(zHost)]; 1435 g.zHttpsURL = g.zBaseURL; 1436 }else{ 1437 g.zBaseURL = mprintf("http://%s%.*s", zHost, i, zCur); 1438 g.zTop = &g.zBaseURL[7+strlen(zHost)]; 1439 g.zHttpsURL = mprintf("https://%s%.*s", zHost, i, zCur); 1440 } 1441 } 1442 if( db_is_writeable("repository") ){ 1443 if( !db_exists("SELECT 1 FROM config WHERE name='baseurl:%q'", g.zBaseURL)){ 1444 db_multi_exec("INSERT INTO config(name,value,mtime)" 1445 "VALUES('baseurl:%q',1,now())", g.zBaseURL); 1446 }else{ 1447 db_optional_sql("repository", 1448 "REPLACE INTO config(name,value,mtime)" 1449 "VALUES('baseurl:%q',1,now())", g.zBaseURL 1450 ); 1451 } 1452 } 1453 } 1454 1455 /* 1456 ** Send an HTTP redirect back to the designated Index Page. 1457 */ 1458 NORETURN void fossil_redirect_home(void){ 1459 cgi_redirectf("%s%s", g.zTop, db_get("index-page", "/index")); 1460 } 1461 1462 /* 1463 ** If running as root, chroot to the directory containing the 1464 ** repository zRepo and then drop root privileges. Return the 1465 ** new repository name. 1466 ** 1467 ** zRepo might be a directory itself. In that case chroot into 1468 ** the directory zRepo. 1469 ** 1470 ** Assume the user-id and group-id of the repository, or if zRepo 1471 ** is a directory, of that directory. 1472 ** 1473 ** The noJail flag means that the chroot jail is not entered. But 1474 ** privileges are still lowered to that of the user-id and group-id 1475 ** of the repository file. 1476 */ 1477 static char *enter_chroot_jail(char *zRepo, int noJail){ 1478 #if !defined(_WIN32) 1479 if( getuid()==0 ){ 1480 int i; 1481 struct stat sStat; 1482 Blob dir; 1483 char *zDir; 1484 if( g.db!=0 ){ 1485 db_close(1); 1486 } 1487 1488 file_canonical_name(zRepo, &dir, 0); 1489 zDir = blob_str(&dir); 1490 if( !noJail ){ 1491 if( file_isdir(zDir)==1 ){ 1492 if( file_chdir(zDir, 1) ){ 1493 fossil_fatal("unable to chroot into %s", zDir); 1494 } 1495 zRepo = "/"; 1496 }else{ 1497 for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){} 1498 if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo); 1499 if( i>0 ){ 1500 zDir[i] = 0; 1501 if( file_chdir(zDir, 1) ){ 1502 fossil_fatal("unable to chroot into %s", zDir); 1503 } 1504 zDir[i] = '/'; 1505 } 1506 zRepo = &zDir[i]; 1507 } 1508 } 1509 if( stat(zRepo, &sStat)!=0 ){ 1510 fossil_fatal("cannot stat() repository: %s", zRepo); 1511 } 1512 i = setgid(sStat.st_gid); 1513 i = i || setuid(sStat.st_uid); 1514 if(i){ 1515 fossil_fatal("setgid/uid() failed with errno %d", errno); 1516 } 1517 if( g.db==0 && file_isfile(zRepo) ){ 1518 db_open_repository(zRepo); 1519 } 1520 } 1521 #endif 1522 return zRepo; 1523 } 1524 1525 /* 1526 ** Generate a web-page that lists all repositories located under the 1527 ** g.zRepositoryName directory and return non-zero. 1528 ** 1529 ** Or, if no repositories can be located beneath g.zRepositoryName, 1530 ** return 0. 1531 */ 1532 static int repo_list_page(void){ 1533 Blob base; 1534 int n = 0; 1535 1536 assert( g.db==0 ); 1537 blob_init(&base, g.zRepositoryName, -1); 1538 sqlite3_open(":memory:", &g.db); 1539 db_multi_exec("CREATE TABLE sfile(x TEXT);"); 1540 db_multi_exec("CREATE TABLE vfile(pathname);"); 1541 vfile_scan(&base, blob_size(&base), 0, 0, 0); 1542 db_multi_exec("DELETE FROM sfile WHERE x NOT GLOB '*[^/].fossil'"); 1543 n = db_int(0, "SELECT count(*) FROM sfile"); 1544 if( n>0 ){ 1545 Stmt q; 1546 @ <html> 1547 @ <head> 1548 @ <title>Repository List</title> 1549 @ </head> 1550 @ <body> 1551 @ <h1>Available Repositories:</h1> 1552 @ <ol> 1553 db_prepare(&q, "SELECT x, substr(x,-7,-100000)||'/home'" 1554 " FROM sfile ORDER BY x COLLATE nocase;"); 1555 while( db_step(&q)==SQLITE_ROW ){ 1556 const char *zName = db_column_text(&q, 0); 1557 const char *zUrl = db_column_text(&q, 1); 1558 @ <li><a href="%h(zUrl)" target="_blank">%h(zName)</a></li> 1559 } 1560 @ </ol> 1561 @ </body> 1562 @ </html> 1563 cgi_reply(); 1564 } 1565 sqlite3_close(g.db); 1566 g.db = 0; 1567 return n; 1568 } 1569 1570 /* 1571 ** Preconditions: 1572 ** 1573 ** * Environment variables are set up according to the CGI standard. 1574 ** 1575 ** If the repository is known, it has already been opened. If unknown, 1576 ** then g.zRepositoryName holds the directory that contains the repository 1577 ** and the actual repository is taken from the first element of PATH_INFO. 1578 ** 1579 ** Process the webpage specified by the PATH_INFO or REQUEST_URI 1580 ** environment variable. 1581 ** 1582 ** If the repository is not known, then a search is done through the 1583 ** file hierarchy rooted at g.zRepositoryName for a suitable repository 1584 ** with a name of $prefix.fossil, where $prefix is any prefix of PATH_INFO. 1585 ** Or, if an ordinary file named $prefix is found, and $prefix matches 1586 ** pFileGlob and $prefix does not match "*.fossil*" and the mimetype of 1587 ** $prefix can be determined from its suffix, then the file $prefix is 1588 ** returned as static text. 1589 ** 1590 ** If no suitable webpage is found, try to redirect to zNotFound. 1591 */ 1592 static void process_one_web_page( 1593 const char *zNotFound, /* Redirect here on a 404 if not NULL */ 1594 Glob *pFileGlob, /* Deliver static files matching */ 1595 int allowRepoList /* Send repo list for "/" URL */ 1596 ){ 1597 const char *zPathInfo; 1598 char *zPath = NULL; 1599 int idx; 1600 int i; 1601 1602 /* Handle universal query parameters */ 1603 if( PB("utc") ){ 1604 g.fTimeFormat = 1; 1605 }else if( PB("localtime") ){ 1606 g.fTimeFormat = 2; 1607 } 1608 1609 /* If the repository has not been opened already, then find the 1610 ** repository based on the first element of PATH_INFO and open it. 1611 */ 1612 zPathInfo = PD("PATH_INFO",""); 1613 if( !g.repositoryOpen ){ 1614 char *zRepo, *zToFree; 1615 const char *zOldScript = PD("SCRIPT_NAME", ""); 1616 char *zNewScript; 1617 int j, k; 1618 i64 szFile; 1619 1620 i = zPathInfo[0]!=0; 1621 while( 1 ){ 1622 while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; } 1623 zRepo = zToFree = mprintf("%s%.*s.fossil",g.zRepositoryName,i,zPathInfo); 1624 1625 /* To avoid mischief, make sure the repository basename contains no 1626 ** characters other than alphanumerics, "/", "_", "-", and ".", and 1627 ** that "-" never occurs immediately after a "/" and that "." is always 1628 ** surrounded by two alphanumerics. Any character that does not 1629 ** satisfy these constraints is converted into "_". 1630 */ 1631 szFile = 0; 1632 for(j=strlen(g.zRepositoryName)+1, k=0; zRepo[j] && k<i-1; j++, k++){ 1633 char c = zRepo[j]; 1634 if( fossil_isalnum(c) ) continue; 1635 if( c=='/' ) continue; 1636 if( c=='_' ) continue; 1637 if( c=='-' && zRepo[j-1]!='/' ) continue; 1638 if( c=='.' && fossil_isalnum(zRepo[j-1]) && fossil_isalnum(zRepo[j+1])){ 1639 continue; 1640 } 1641 szFile = 1; 1642 break; 1643 } 1644 if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){ 1645 if( zRepo[0]=='/' && zRepo[1]=='/' ){ zRepo++; j--; } 1646 szFile = file_size(zRepo); 1647 /* this should only be set from the --baseurl option, not CGI */ 1648 if( g.zBaseURL && g.zBaseURL[0]!=0 && g.zTop && g.zTop[0]!=0 && 1649 file_isdir(g.zRepositoryName)==1 ){ 1650 g.zBaseURL = mprintf("%s%.*s", g.zBaseURL, i, zPathInfo); 1651 g.zTop = mprintf("%s%.*s", g.zTop, i, zPathInfo); 1652 } 1653 } 1654 if( szFile<0 && i>0 ){ 1655 const char *zMimetype; 1656 assert( fossil_strcmp(&zRepo[j], ".fossil")==0 ); 1657 zRepo[j] = 0; 1658 if( zPathInfo[i]=='/' && file_isdir(zRepo)==1 ){ 1659 fossil_free(zToFree); 1660 i++; 1661 continue; 1662 } 1663 if( pFileGlob!=0 1664 && file_isfile(zRepo) 1665 && glob_match(pFileGlob, zRepo) 1666 && sqlite3_strglob("*.fossil*",zRepo)!=0 1667 && (zMimetype = mimetype_from_name(zRepo))!=0 1668 && strcmp(zMimetype, "application/x-fossil-artifact")!=0 1669 ){ 1670 Blob content; 1671 blob_read_from_file(&content, zRepo); 1672 cgi_set_content_type(zMimetype); 1673 cgi_set_content(&content); 1674 cgi_reply(); 1675 return; 1676 } 1677 zRepo[j] = '.'; 1678 } 1679 1680 if( szFile<1024 ){ 1681 set_base_url(0); 1682 if( strcmp(zPathInfo,"/")==0 1683 && allowRepoList 1684 && repo_list_page() ){ 1685 /* Will return a list of repositories */ 1686 }else if( zNotFound ){ 1687 cgi_redirect(zNotFound); 1688 }else{ 1689 #ifdef FOSSIL_ENABLE_JSON 1690 if(g.json.isJsonMode){ 1691 json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1); 1692 return; 1693 } 1694 #endif 1695 @ <h1>Not Found</h1> 1696 cgi_set_status(404, "not found"); 1697 cgi_reply(); 1698 } 1699 return; 1700 } 1701 break; 1702 } 1703 zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo); 1704 cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]); 1705 zPathInfo += i; 1706 cgi_replace_parameter("SCRIPT_NAME", zNewScript); 1707 db_open_repository(zRepo); 1708 if( g.fHttpTrace ){ 1709 fprintf(stderr, 1710 "# repository: [%s]\n" 1711 "# new PATH_INFO = [%s]\n" 1712 "# new SCRIPT_NAME = [%s]\n", 1713 zRepo, zPathInfo, zNewScript); 1714 } 1715 } 1716 1717 /* Find the page that the user has requested, construct and deliver that 1718 ** page. 1719 */ 1720 if( g.zContentType && 1721 strncmp(g.zContentType, "application/x-fossil", 20)==0 ){ 1722 zPathInfo = "/xfer"; 1723 } 1724 set_base_url(0); 1725 if( zPathInfo==0 || zPathInfo[0]==0 1726 || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){ 1727 #ifdef FOSSIL_ENABLE_JSON 1728 if(g.json.isJsonMode){ 1729 json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1); 1730 fossil_exit(0); 1731 } 1732 #endif 1733 fossil_redirect_home() /*does not return*/; 1734 }else{ 1735 zPath = mprintf("%s", zPathInfo); 1736 } 1737 1738 /* Make g.zPath point to the first element of the path. Make 1739 ** g.zExtra point to everything past that point. 1740 */ 1741 while(1){ 1742 char *zAltRepo = 0; 1743 g.zPath = &zPath[1]; 1744 for(i=1; zPath[i] && zPath[i]!='/'; i++){} 1745 if( zPath[i]=='/' ){ 1746 zPath[i] = 0; 1747 g.zExtra = &zPath[i+1]; 1748 1749 /* Look for sub-repositories. A sub-repository is another repository 1750 ** that accepts the login credentials of the current repository. A 1751 ** subrepository is identified by a CONFIG table entry "subrepo:NAME" 1752 ** where NAME is the first component of the path. The value of the 1753 ** the CONFIG entries is the string "USER:FILENAME" where USER is the 1754 ** USER name to log in as in the subrepository and FILENAME is the 1755 ** repository filename. 1756 */ 1757 zAltRepo = db_text(0, "SELECT value FROM config WHERE name='subrepo:%q'", 1758 g.zPath); 1759 if( zAltRepo ){ 1760 int nHost; 1761 int jj; 1762 char *zUser = zAltRepo; 1763 login_check_credentials(); 1764 for(jj=0; zAltRepo[jj] && zAltRepo[jj]!=':'; jj++){} 1765 if( zAltRepo[jj]==':' ){ 1766 zAltRepo[jj] = 0; 1767 zAltRepo += jj+1; 1768 }else{ 1769 zUser = "nobody"; 1770 } 1771 if( g.zLogin==0 || g.zLogin[0]==0 ) zUser = "nobody"; 1772 if( zAltRepo[0]!='/' ){ 1773 zAltRepo = mprintf("%s/../%s", g.zRepositoryName, zAltRepo); 1774 file_simplify_name(zAltRepo, -1, 0); 1775 } 1776 db_close(1); 1777 db_open_repository(zAltRepo); 1778 login_as_user(zUser); 1779 g.perm.Password = 0; 1780 zPath += i; 1781 nHost = g.zTop - g.zBaseURL; 1782 g.zBaseURL = mprintf("%z/%s", g.zBaseURL, g.zPath); 1783 g.zTop = g.zBaseURL + nHost; 1784 continue; 1785 } 1786 }else{ 1787 g.zExtra = 0; 1788 } 1789 break; 1790 } 1791 #ifdef FOSSIL_ENABLE_JSON 1792 /* 1793 ** Workaround to allow us to customize some following behaviour for 1794 ** JSON mode. The problem is, we don't always know if we're in JSON 1795 ** mode at this point (namely, for GET mode we don't know but POST 1796 ** we do), so we snoop g.zPath and cheat a bit. 1797 */ 1798 if( !g.json.isJsonMode && g.zPath && (0==strncmp("json",g.zPath,4)) ){ 1799 g.json.isJsonMode = 1; 1800 } 1801 #endif 1802 if( g.zExtra ){ 1803 /* CGI parameters get this treatment elsewhere, but places like getfile 1804 ** will use g.zExtra directly. 1805 ** Reminder: the login mechanism uses 'name' differently, and may 1806 ** eventually have a problem/collision with this. 1807 ** 1808 ** Disabled by stephan when running in JSON mode because this 1809 ** particular parameter name is very common and i have had no end 1810 ** of grief with this handling. The JSON API never relies on the 1811 ** handling below, and by disabling it in JSON mode I can remove 1812 ** lots of special-case handling in several JSON handlers. 1813 */ 1814 #ifdef FOSSIL_ENABLE_JSON 1815 if(!g.json.isJsonMode){ 1816 #endif 1817 dehttpize(g.zExtra); 1818 cgi_set_parameter_nocopy("name", g.zExtra, 1); 1819 #ifdef FOSSIL_ENABLE_JSON 1820 } 1821 #endif 1822 } 1823 1824 /* Locate the method specified by the path and execute the function 1825 ** that implements that method. 1826 */ 1827 if( name_search(g.zPath, aWebpage, count(aWebpage), 0, &idx) ){ 1828 #ifdef FOSSIL_ENABLE_JSON 1829 if(g.json.isJsonMode){ 1830 json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,0); 1831 }else 1832 #endif 1833 { 1834 #ifdef FOSSIL_ENABLE_TH1_HOOKS 1835 int rc; 1836 if( !g.fNoThHook ){ 1837 rc = Th_WebpageHook(g.zPath, 0); 1838 }else{ 1839 rc = TH_OK; 1840 } 1841 if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ 1842 if( rc==TH_OK || rc==TH_RETURN ){ 1843 #endif 1844 cgi_set_status(404,"Not Found"); 1845 @ <h1>Not Found</h1> 1846 @ <p>Page not found: %h(g.zPath)</p> 1847 #ifdef FOSSIL_ENABLE_TH1_HOOKS 1848 } 1849 if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ 1850 Th_WebpageNotify(g.zPath, 0); 1851 } 1852 } 1853 #endif 1854 } 1855 }else if( aWebpage[idx].xFunc!=page_xfer && db_schema_is_outofdate() ){ 1856 #ifdef FOSSIL_ENABLE_JSON 1857 if(g.json.isJsonMode){ 1858 json_err(FSL_JSON_E_DB_NEEDS_REBUILD,NULL,0); 1859 }else 1860 #endif 1861 { 1862 @ <h1>Server Configuration Error</h1> 1863 @ <p>The database schema on the server is out-of-date. Please ask 1864 @ the administrator to run <b>fossil rebuild</b>.</p> 1865 } 1866 }else{ 1867 #ifdef FOSSIL_ENABLE_TH1_HOOKS 1868 /* 1869 ** The TH1 return codes from the hook will be handled as follows: 1870 ** 1871 ** TH_OK: The xFunc() and the TH1 notification will both be executed. 1872 ** 1873 ** TH_ERROR: The xFunc() will be executed, the TH1 notification will be 1874 ** skipped. If the xFunc() is being hooked, the error message 1875 ** will be emitted. 1876 ** 1877 ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped. 1878 ** 1879 ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be 1880 ** skipped. 1881 ** 1882 ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be 1883 ** executed. 1884 */ 1885 int rc; 1886 if( !g.fNoThHook ){ 1887 rc = Th_WebpageHook(aWebpage[idx].zName, aWebpage[idx].cmdFlags); 1888 }else{ 1889 rc = TH_OK; 1890 } 1891 if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ 1892 if( rc==TH_OK || rc==TH_RETURN ){ 1893 #endif 1894 aWebpage[idx].xFunc(); 1895 #ifdef FOSSIL_ENABLE_TH1_HOOKS 1896 } 1897 if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ 1898 Th_WebpageNotify(aWebpage[idx].zName, aWebpage[idx].cmdFlags); 1899 } 1900 } 1901 #endif 1902 } 1903 1904 /* Return the result. 1905 */ 1906 cgi_reply(); 1907 } 1908 1909 /* If the CGI program contains one or more lines of the form 1910 ** 1911 ** redirect: repository-filename http://hostname/path/%s 1912 ** 1913 ** then control jumps here. Search each repository for an artifact ID 1914 ** or ticket ID that matches the "name" CGI parameter and for the 1915 ** first match, redirect to the corresponding URL with the "name" CGI 1916 ** parameter inserted. Paint an error page if no match is found. 1917 ** 1918 ** If there is a line of the form: 1919 ** 1920 ** redirect: * URL 1921 ** 1922 ** Then a redirect is made to URL if no match is found. Otherwise a 1923 ** very primitive error message is returned. 1924 */ 1925 static void redirect_web_page(int nRedirect, char **azRedirect){ 1926 int i; /* Loop counter */ 1927 const char *zNotFound = 0; /* Not found URL */ 1928 const char *zName = P("name"); 1929 set_base_url(0); 1930 if( zName==0 ){ 1931 zName = P("SCRIPT_NAME"); 1932 if( zName && zName[0]=='/' ) zName++; 1933 } 1934 if( zName && validate16(zName, strlen(zName)) ){ 1935 for(i=0; i<nRedirect; i++){ 1936 if( fossil_strcmp(azRedirect[i*2],"*")==0 ){ 1937 zNotFound = azRedirect[i*2+1]; 1938 continue; 1939 } 1940 db_open_repository(azRedirect[i*2]); 1941 if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'", zName) || 1942 db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){ 1943 cgi_redirectf(azRedirect[i*2+1] /*works-like:"%s"*/, zName); 1944 return; 1945 } 1946 db_close(1); 1947 } 1948 } 1949 if( zNotFound ){ 1950 cgi_redirectf(zNotFound /*works-like:"%s"*/, zName); 1951 }else{ 1952 @ <html> 1953 @ <head><title>No Such Object</title></head> 1954 @ <body> 1955 @ <p>No such object: <b>%h(zName)</b></p> 1956 @ </body> 1957 cgi_reply(); 1958 } 1959 } 1960 1961 /* 1962 ** COMMAND: cgi* 1963 ** 1964 ** Usage: %fossil ?cgi? SCRIPT 1965 ** 1966 ** The SCRIPT argument is the name of a file that is the CGI script 1967 ** that is being run. The command name, "cgi", may be omitted if 1968 ** the GATEWAY_INTERFACE environment variable is set to "CGI" (which 1969 ** should always be the case for CGI scripts run by a webserver.) The 1970 ** SCRIPT file should look something like this: 1971 ** 1972 ** #!/usr/bin/fossil 1973 ** repository: /home/somebody/project.db 1974 ** 1975 ** The second line defines the name of the repository. After locating 1976 ** the repository, fossil will generate a webpage on stdout based on 1977 ** the values of standard CGI environment variables. 1978 ** 1979 ** See also: http, server, winsrv 1980 */ 1981 void cmd_cgi(void){ 1982 const char *zFile; 1983 const char *zNotFound = 0; 1984 char **azRedirect = 0; /* List of repositories to redirect to */ 1985 int nRedirect = 0; /* Number of entries in azRedirect */ 1986 Glob *pFileGlob = 0; /* Pattern for files */ 1987 int allowRepoList = 0; /* Allow lists of repository files */ 1988 Blob config, line, key, value, value2; 1989 if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){ 1990 zFile = g.argv[2]; 1991 }else{ 1992 zFile = g.argv[1]; 1993 } 1994 g.httpOut = stdout; 1995 g.httpIn = stdin; 1996 fossil_binary_mode(g.httpOut); 1997 fossil_binary_mode(g.httpIn); 1998 g.cgiOutput = 1; 1999 blob_read_from_file(&config, zFile); 2000 while( blob_line(&config, &line) ){ 2001 if( !blob_token(&line, &key) ) continue; 2002 if( blob_buffer(&key)[0]=='#' ) continue; 2003 if( blob_eq(&key, "repository:") && blob_tail(&line, &value) ){ 2004 /* repository: FILENAME 2005 ** 2006 ** The name of the Fossil repository to be served via CGI. Most 2007 ** fossil CGI scripts have a single non-comment line that contains 2008 ** this one entry. 2009 */ 2010 blob_trim(&value); 2011 db_open_repository(blob_str(&value)); 2012 blob_reset(&value); 2013 continue; 2014 } 2015 if( blob_eq(&key, "directory:") && blob_token(&line, &value) ){ 2016 /* directory: DIRECTORY 2017 ** 2018 ** If repository: is omitted, then terms of the PATH_INFO cgi parameter 2019 ** are appended to DIRECTORY looking for a repository (whose name ends 2020 ** in ".fossil") or a file in "files:". 2021 */ 2022 db_close(1); 2023 g.zRepositoryName = mprintf("%s", blob_str(&value)); 2024 blob_reset(&value); 2025 continue; 2026 } 2027 if( blob_eq(&key, "notfound:") && blob_token(&line, &value) ){ 2028 /* notfound: URL 2029 ** 2030 ** If using directory: and no suitable repository or file is found, 2031 ** then redirect to URL. 2032 */ 2033 zNotFound = mprintf("%s", blob_str(&value)); 2034 blob_reset(&value); 2035 continue; 2036 } 2037 if( blob_eq(&key, "localauth") ){ 2038 /* localauth 2039 ** 2040 ** Grant "administrator" privileges to users connecting with HTTP 2041 ** from IP address 127.0.0.1. Do not bother checking credentials. 2042 */ 2043 g.useLocalauth = 1; 2044 continue; 2045 } 2046 if( blob_eq(&key, "repolist") ){ 2047 /* repolist 2048 ** 2049 ** If using "directory:" and the URL is "/" then generate a page 2050 ** showing a list of available repositories. 2051 */ 2052 allowRepoList = 1; 2053 continue; 2054 } 2055 if( blob_eq(&key, "redirect:") && blob_token(&line, &value) 2056 && blob_token(&line, &value2) ){ 2057 /* See the header comment on the redirect_web_page() function 2058 ** above for details. */ 2059 nRedirect++; 2060 azRedirect = fossil_realloc(azRedirect, 2*nRedirect*sizeof(char*)); 2061 azRedirect[nRedirect*2-2] = mprintf("%s", blob_str(&value)); 2062 azRedirect[nRedirect*2-1] = mprintf("%s", blob_str(&value2)); 2063 blob_reset(&value); 2064 blob_reset(&value2); 2065 continue; 2066 } 2067 if( blob_eq(&key, "files:") && blob_token(&line, &value) ){ 2068 /* files: GLOBLIST 2069 ** 2070 ** GLOBLIST is a comma-separated list of filename globs. For 2071 ** example: *.html,*.css,*.js 2072 ** 2073 ** If the repository: line is omitted and then PATH_INFO is searched 2074 ** for files that match any of these GLOBs and if any such file is 2075 ** found it is returned verbatim. This feature allows "fossil server" 2076 ** to function as a primitive web-server delivering arbitrary content. 2077 */ 2078 pFileGlob = glob_create(blob_str(&value)); 2079 blob_reset(&value); 2080 continue; 2081 } 2082 if( blob_eq(&key, "setenv:") && blob_token(&line, &value) 2083 && blob_token(&line, &value2) ){ 2084 /* setenv: NAME VALUE 2085 ** 2086 ** Sets environment variable NAME to VALUE 2087 */ 2088 fossil_setenv(blob_str(&value), blob_str(&value2)); 2089 blob_reset(&value); 2090 blob_reset(&value2); 2091 continue; 2092 } 2093 if( blob_eq(&key, "debug:") && blob_token(&line, &value) ){ 2094 /* debug: FILENAME 2095 ** 2096 ** Causes output from cgi_debug() and CGIDEBUG(()) calls to go 2097 ** into FILENAME. 2098 */ 2099 g.fDebug = fossil_fopen(blob_str(&value), "ab"); 2100 blob_reset(&value); 2101 continue; 2102 } 2103 if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){ 2104 /* errorlog: FILENAME 2105 ** 2106 ** Causes messages from warnings, errors, and panics to be appended 2107 ** to FILENAME. 2108 */ 2109 g.zErrlog = mprintf("%s", blob_str(&value)); 2110 blob_reset(&value); 2111 continue; 2112 } 2113 if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){ 2114 /* HOME: VALUE 2115 ** 2116 ** Set CGI parameter "HOME" to VALUE. This is legacy. Use 2117 ** setenv: instead. 2118 */ 2119 cgi_setenv("HOME", blob_str(&value)); 2120 blob_reset(&value); 2121 continue; 2122 } 2123 if( blob_eq(&key, "skin:") && blob_token(&line, &value) ){ 2124 /* skin: LABEL 2125 ** 2126 ** Use one of the built-in skins defined by LABEL. LABEL is the 2127 ** name of the subdirectory under the skins/ directory that holds 2128 ** the elements of the built-in skin. If LABEL does not match, 2129 ** this directive is a silent no-op. 2130 */ 2131 skin_use_alternative(blob_str(&value)); 2132 blob_reset(&value); 2133 continue; 2134 } 2135 } 2136 blob_reset(&config); 2137 if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){ 2138 cgi_panic("Unable to find or open the project repository"); 2139 } 2140 cgi_init(); 2141 if( nRedirect ){ 2142 redirect_web_page(nRedirect, azRedirect); 2143 }else{ 2144 process_one_web_page(zNotFound, pFileGlob, allowRepoList); 2145 } 2146 } 2147 2148 /* 2149 ** If g.argv[arg] exists then it is either the name of a repository 2150 ** that will be used by a server, or else it is a directory that 2151 ** contains multiple repositories that can be served. If g.argv[arg] 2152 ** is a directory, the repositories it contains must be named 2153 ** "*.fossil". If g.argv[arg] does not exist, then we must be within 2154 ** an open check-out and the repository serve is the repository of 2155 ** that check-out. 2156 ** 2157 ** Open the repository to be served if it is known. If g.argv[arg] is 2158 ** a directory full of repositories, then set g.zRepositoryName to 2159 ** the name of that directory and the specific repository will be 2160 ** opened later by process_one_web_page() based on the content of 2161 ** the PATH_INFO variable. 2162 ** 2163 ** If the fCreate flag is set, then create the repository if it 2164 ** does not already exist. 2165 */ 2166 static void find_server_repository(int arg, int fCreate){ 2167 if( g.argc<=arg ){ 2168 db_must_be_within_tree(); 2169 }else{ 2170 const char *zRepo = g.argv[arg]; 2171 int isDir = file_isdir(zRepo); 2172 if( isDir==1 ){ 2173 g.zRepositoryName = mprintf("%s", zRepo); 2174 file_simplify_name(g.zRepositoryName, -1, 0); 2175 }else{ 2176 if( isDir==0 && fCreate ){ 2177 const char *zPassword; 2178 db_create_repository(zRepo); 2179 db_open_repository(zRepo); 2180 db_begin_transaction(); 2181 db_initial_setup(0, "now", g.zLogin); 2182 db_end_transaction(0); 2183 fossil_print("project-id: %s\n", db_get("project-code", 0)); 2184 fossil_print("server-id: %s\n", db_get("server-code", 0)); 2185 zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin); 2186 fossil_print("admin-user: %s (initial password is \"%s\")\n", 2187 g.zLogin, zPassword); 2188 cache_initialize(); 2189 g.zLogin = 0; 2190 g.userUid = 0; 2191 }else{ 2192 db_open_repository(zRepo); 2193 } 2194 } 2195 } 2196 } 2197 2198 /* 2199 ** undocumented format: 2200 ** 2201 ** fossil http INFILE OUTFILE IPADDR ?REPOSITORY? 2202 ** 2203 ** The argv==6 form (with no options) is used by the win32 server only. 2204 ** 2205 ** COMMAND: http* 2206 ** 2207 ** Usage: %fossil http ?REPOSITORY? ?OPTIONS? 2208 ** 2209 ** Handle a single HTTP request appearing on stdin. The resulting webpage 2210 ** is delivered on stdout. This method is used to launch an HTTP request 2211 ** handler from inetd, for example. The argument is the name of the 2212 ** repository. 2213 ** 2214 ** If REPOSITORY is a directory that contains one or more repositories, 2215 ** either directly in REPOSITORY itself or in subdirectories, and 2216 ** with names of the form "*.fossil" then a prefix of the URL pathname 2217 ** selects from among the various repositories. If the pathname does 2218 ** not select a valid repository and the --notfound option is available, 2219 ** then the server redirects (HTTP code 302) to the URL of --notfound. 2220 ** When REPOSITORY is a directory, the pathname must contain only 2221 ** alphanumerics, "_", "/", "-" and "." and no "-" may occur after a "/" 2222 ** and every "." must be surrounded on both sides by alphanumerics or else 2223 ** a 404 error is returned. Static content files in the directory are 2224 ** returned if they match comma-separate GLOB pattern specified by --files 2225 ** and do not match "*.fossil*" and have a well-known suffix. 2226 ** 2227 ** The --host option can be used to specify the hostname for the server. 2228 ** The --https option indicates that the request came from HTTPS rather 2229 ** than HTTP. If --nossl is given, then SSL connections will not be available, 2230 ** thus also no redirecting from http: to https: will take place. 2231 ** 2232 ** If the --localauth option is given, then automatic login is performed 2233 ** for requests coming from localhost, if the "localauth" setting is not 2234 ** enabled. 2235 ** 2236 ** Options: 2237 ** --baseurl URL base URL (useful with reverse proxies) 2238 ** --files GLOB comma-separate glob patterns for static file to serve 2239 ** --localauth enable automatic login for local connections 2240 ** --host NAME specify hostname of the server 2241 ** --https signal a request coming in via https 2242 ** --nojail drop root privilege but do not enter the chroot jail 2243 ** --nossl signal that no SSL connections are available 2244 ** --notfound URL use URL as "HTTP 404, object not found" page. 2245 ** --repolist If REPOSITORY is directory, URL "/" lists all repos 2246 ** --scgi Interpret input as SCGI rather than HTTP 2247 ** --skin LABEL Use override skin LABEL 2248 ** 2249 ** See also: cgi, server, winsrv 2250 */ 2251 void cmd_http(void){ 2252 const char *zIpAddr = 0; 2253 const char *zNotFound; 2254 const char *zHost; 2255 const char *zAltBase; 2256 const char *zFileGlob; 2257 int useSCGI; 2258 int noJail; 2259 int allowRepoList; 2260 2261 /* The winhttp module passes the --files option as --files-urlenc with 2262 ** the argument being URL encoded, to avoid wildcard expansion in the 2263 ** shell. This option is for internal use and is undocumented. 2264 */ 2265 zFileGlob = find_option("files-urlenc",0,1); 2266 if( zFileGlob ){ 2267 char *z = mprintf("%s", zFileGlob); 2268 dehttpize(z); 2269 zFileGlob = z; 2270 }else{ 2271 zFileGlob = find_option("files",0,1); 2272 } 2273 skin_override(); 2274 zNotFound = find_option("notfound", 0, 1); 2275 noJail = find_option("nojail",0,0)!=0; 2276 allowRepoList = find_option("repolist",0,0)!=0; 2277 g.useLocalauth = find_option("localauth", 0, 0)!=0; 2278 g.sslNotAvailable = find_option("nossl", 0, 0)!=0; 2279 useSCGI = find_option("scgi", 0, 0)!=0; 2280 zAltBase = find_option("baseurl", 0, 1); 2281 if( zAltBase ) set_base_url(zAltBase); 2282 if( find_option("https",0,0)!=0 ){ 2283 zIpAddr = fossil_getenv("REMOTE_HOST"); /* From stunnel */ 2284 cgi_replace_parameter("HTTPS","on"); 2285 } 2286 zHost = find_option("host", 0, 1); 2287 if( zHost ) cgi_replace_parameter("HTTP_HOST",zHost); 2288 2289 /* We should be done with options.. */ 2290 verify_all_options(); 2291 2292 if( g.argc!=2 && g.argc!=3 && g.argc!=5 && g.argc!=6 ){ 2293 fossil_fatal("no repository specified"); 2294 } 2295 g.cgiOutput = 1; 2296 g.fullHttpReply = 1; 2297 if( g.argc>=5 ){ 2298 g.httpIn = fossil_fopen(g.argv[2], "rb"); 2299 g.httpOut = fossil_fopen(g.argv[3], "wb"); 2300 zIpAddr = g.argv[4]; 2301 find_server_repository(5, 0); 2302 }else{ 2303 g.httpIn = stdin; 2304 g.httpOut = stdout; 2305 find_server_repository(2, 0); 2306 } 2307 if( zIpAddr==0 ){ 2308 zIpAddr = cgi_ssh_remote_addr(0); 2309 if( zIpAddr && zIpAddr[0] ){ 2310 g.fSshClient |= CGI_SSH_CLIENT; 2311 } 2312 } 2313 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); 2314 if( useSCGI ){ 2315 cgi_handle_scgi_request(); 2316 }else if( g.fSshClient & CGI_SSH_CLIENT ){ 2317 ssh_request_loop(zIpAddr, glob_create(zFileGlob)); 2318 }else{ 2319 cgi_handle_http_request(zIpAddr); 2320 } 2321 process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList); 2322 } 2323 2324 /* 2325 ** Process all requests in a single SSH connection if possible. 2326 */ 2327 void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){ 2328 blob_zero(&g.cgiIn); 2329 do{ 2330 cgi_handle_ssh_http_request(zIpAddr); 2331 process_one_web_page(0, FileGlob, 0); 2332 blob_reset(&g.cgiIn); 2333 } while ( g.fSshClient & CGI_SSH_FOSSIL || 2334 g.fSshClient & CGI_SSH_COMPAT ); 2335 } 2336 2337 /* 2338 ** Note that the following command is used by ssh:// processing. 2339 ** 2340 ** COMMAND: test-http 2341 ** 2342 ** Works like the http command but gives setup permission to all users. 2343 ** 2344 ** Options: 2345 ** --th-trace trace TH1 execution (for debugging purposes) 2346 ** 2347 */ 2348 void cmd_test_http(void){ 2349 const char *zIpAddr; /* IP address of remote client */ 2350 2351 Th_InitTraceLog(); 2352 login_set_capabilities("sx", 0); 2353 g.useLocalauth = 1; 2354 g.httpIn = stdin; 2355 g.httpOut = stdout; 2356 find_server_repository(2, 0); 2357 g.cgiOutput = 1; 2358 g.fullHttpReply = 1; 2359 zIpAddr = cgi_ssh_remote_addr(0); 2360 if( zIpAddr && zIpAddr[0] ){ 2361 g.fSshClient |= CGI_SSH_CLIENT; 2362 ssh_request_loop(zIpAddr, 0); 2363 }else{ 2364 cgi_set_parameter("REMOTE_ADDR", "127.0.0.1"); 2365 cgi_handle_http_request(0); 2366 process_one_web_page(0, 0, 0); 2367 } 2368 } 2369 2370 #if !defined(_WIN32) 2371 #if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__) 2372 /* 2373 ** Search for an executable on the PATH environment variable. 2374 ** Return true (1) if found and false (0) if not found. 2375 */ 2376 static int binaryOnPath(const char *zBinary){ 2377 const char *zPath = fossil_getenv("PATH"); 2378 char *zFull; 2379 int i; 2380 int bExists; 2381 while( zPath && zPath[0] ){ 2382 while( zPath[0]==':' ) zPath++; 2383 for(i=0; zPath[i] && zPath[i]!=':'; i++){} 2384 zFull = mprintf("%.*s/%s", i, zPath, zBinary); 2385 bExists = file_access(zFull, X_OK); 2386 fossil_free(zFull); 2387 if( bExists==0 ) return 1; 2388 zPath += i; 2389 } 2390 return 0; 2391 } 2392 #endif 2393 #endif 2394 2395 /* 2396 ** COMMAND: server* 2397 ** COMMAND: ui 2398 ** 2399 ** Usage: %fossil server ?OPTIONS? ?REPOSITORY? 2400 ** Or: %fossil ui ?OPTIONS? ?REPOSITORY? 2401 ** 2402 ** Open a socket and begin listening and responding to HTTP requests on 2403 ** TCP port 8080, or on any other TCP port defined by the -P or 2404 ** --port option. The optional argument is the name of the repository. 2405 ** The repository argument may be omitted if the working directory is 2406 ** within an open checkout. 2407 ** 2408 ** The "ui" command automatically starts a web browser after initializing 2409 ** the web server. The "ui" command also binds to 127.0.0.1 and so will 2410 ** only process HTTP traffic from the local machine. 2411 ** 2412 ** The REPOSITORY can be a directory (aka folder) that contains one or 2413 ** more repositories with names ending in ".fossil". In this case, a 2414 ** prefix of the URL pathname is used to search the directory for an 2415 ** appropriate repository. To thwart mischief, the pathname in the URL must 2416 ** contain only alphanumerics, "_", "/", "-", and ".", and no "-" may 2417 ** occur after "/", and every "." must be surrounded on both sides by 2418 ** alphanumerics. Any pathname that does not satisfy these constraints 2419 ** results in a 404 error. Files in REPOSITORY that match the comma-separated 2420 ** list of glob patterns given by --files and that have known suffixes 2421 ** such as ".txt" or ".html" or ".jpeg" and do not match the pattern 2422 ** "*.fossil*" will be served as static content. With the "ui" command, 2423 ** the REPOSITORY can only be a directory if the --notfound option is 2424 ** also present. 2425 ** 2426 ** By default, the "ui" command provides full administrative access without 2427 ** having to log in. This can be disabled by turning off the "localauth" 2428 ** setting. Automatic login for the "server" command is available if the 2429 ** --localauth option is present and the "localauth" setting is off and the 2430 ** connection is from localhost. The "ui" command also enables --repolist 2431 ** by default. 2432 ** 2433 ** Options: 2434 ** --baseurl URL Use URL as the base (useful for reverse proxies) 2435 ** --create Create a new REPOSITORY if it does not already exist 2436 ** --page PAGE Start "ui" on PAGE. ex: --page "timeline?y=ci" 2437 ** --files GLOBLIST Comma-separated list of glob patterns for static files 2438 ** --localauth enable automatic login for requests from localhost 2439 ** --localhost listen on 127.0.0.1 only (always true for "ui") 2440 ** --https signal a request coming in via https 2441 ** --nojail Drop root privileges but do not enter the chroot jail 2442 ** --nossl signal that no SSL connections are available 2443 ** --notfound URL Redirect 2444 ** -P|--port TCPPORT listen to request on port TCPPORT 2445 ** --th-trace trace TH1 execution (for debugging purposes) 2446 ** --repolist If REPOSITORY is dir, URL "/" lists repos. 2447 ** --scgi Accept SCGI rather than HTTP 2448 ** --skin LABEL Use override skin LABEL 2449 2450 ** 2451 ** See also: cgi, http, winsrv 2452 */ 2453 void cmd_webserver(void){ 2454 int iPort, mxPort; /* Range of TCP ports allowed */ 2455 const char *zPort; /* Value of the --port option */ 2456 const char *zBrowser; /* Name of web browser program */ 2457 char *zBrowserCmd = 0; /* Command to launch the web browser */ 2458 int isUiCmd; /* True if command is "ui", not "server' */ 2459 const char *zNotFound; /* The --notfound option or NULL */ 2460 int flags = 0; /* Server flags */ 2461 #if !defined(_WIN32) 2462 int noJail; /* Do not enter the chroot jail */ 2463 #endif 2464 int allowRepoList; /* List repositories on URL "/" */ 2465 const char *zAltBase; /* Argument to the --baseurl option */ 2466 const char *zFileGlob; /* Static content must match this */ 2467 char *zIpAddr = 0; /* Bind to this IP address */ 2468 int fCreate = 0; /* The --create flag */ 2469 const char *zInitPage = 0; /* Start on this page. --page option */ 2470 2471 #if defined(_WIN32) 2472 const char *zStopperFile; /* Name of file used to terminate server */ 2473 zStopperFile = find_option("stopper", 0, 1); 2474 #endif 2475 2476 zFileGlob = find_option("files-urlenc",0,1); 2477 if( zFileGlob ){ 2478 char *z = mprintf("%s", zFileGlob); 2479 dehttpize(z); 2480 zFileGlob = z; 2481 }else{ 2482 zFileGlob = find_option("files",0,1); 2483 } 2484 skin_override(); 2485 #if !defined(_WIN32) 2486 noJail = find_option("nojail",0,0)!=0; 2487 #endif 2488 g.useLocalauth = find_option("localauth", 0, 0)!=0; 2489 Th_InitTraceLog(); 2490 zPort = find_option("port", "P", 1); 2491 isUiCmd = g.argv[1][0]=='u'; 2492 if( isUiCmd ){ 2493 zInitPage = find_option("page", 0, 1); 2494 } 2495 if( zInitPage==0 ) zInitPage = ""; 2496 zNotFound = find_option("notfound", 0, 1); 2497 allowRepoList = find_option("repolist",0,0)!=0; 2498 zAltBase = find_option("baseurl", 0, 1); 2499 fCreate = find_option("create",0,0)!=0; 2500 if( find_option("scgi", 0, 0)!=0 ) flags |= HTTP_SERVER_SCGI; 2501 if( zAltBase ){ 2502 set_base_url(zAltBase); 2503 } 2504 g.sslNotAvailable = find_option("nossl", 0, 0)!=0; 2505 if( find_option("https",0,0)!=0 ){ 2506 cgi_replace_parameter("HTTPS","on"); 2507 }else{ 2508 /* without --https, defaults to not available. */ 2509 g.sslNotAvailable = 1; 2510 } 2511 if( find_option("localhost", 0, 0)!=0 ){ 2512 flags |= HTTP_SERVER_LOCALHOST; 2513 } 2514 2515 /* We should be done with options.. */ 2516 verify_all_options(); 2517 2518 if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?"); 2519 if( isUiCmd ){ 2520 flags |= HTTP_SERVER_LOCALHOST|HTTP_SERVER_REPOLIST; 2521 g.useLocalauth = 1; 2522 allowRepoList = 1; 2523 } 2524 find_server_repository(2, fCreate); 2525 if( zPort ){ 2526 int i; 2527 for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){} 2528 if( i>0 ){ 2529 zIpAddr = mprintf("%.*s", i, zPort); 2530 zPort += i+1; 2531 } 2532 iPort = mxPort = atoi(zPort); 2533 }else{ 2534 iPort = db_get_int("http-port", 8080); 2535 mxPort = iPort+100; 2536 } 2537 #if !defined(_WIN32) 2538 /* Unix implementation */ 2539 if( isUiCmd ){ 2540 #if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__) 2541 zBrowser = db_get("web-browser", 0); 2542 if( zBrowser==0 ){ 2543 static const char *const azBrowserProg[] = 2544 { "xdg-open", "gnome-open", "firefox", "google-chrome" }; 2545 int i; 2546 zBrowser = "echo"; 2547 for(i=0; i<sizeof(azBrowserProg)/sizeof(azBrowserProg[0]); i++){ 2548 if( binaryOnPath(azBrowserProg[i]) ){ 2549 zBrowser = azBrowserProg[i]; 2550 break; 2551 } 2552 } 2553 } 2554 #else 2555 zBrowser = db_get("web-browser", "open"); 2556 #endif 2557 if( zIpAddr ){ 2558 zBrowserCmd = mprintf("%s http://%s:%%d/%s &", 2559 zBrowser, zIpAddr, zInitPage); 2560 }else{ 2561 zBrowserCmd = mprintf("%s http://localhost:%%d/%s &", 2562 zBrowser, zInitPage); 2563 } 2564 } 2565 if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY; 2566 if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT; 2567 db_close(1); 2568 if( cgi_http_server(iPort, mxPort, zBrowserCmd, zIpAddr, flags) ){ 2569 fossil_fatal("unable to listen on TCP socket %d", iPort); 2570 } 2571 g.httpIn = stdin; 2572 g.httpOut = stdout; 2573 if( g.fHttpTrace || g.fSqlTrace ){ 2574 fprintf(stderr, "====== SERVER pid %d =======\n", getpid()); 2575 } 2576 g.cgiOutput = 1; 2577 find_server_repository(2, 0); 2578 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); 2579 if( flags & HTTP_SERVER_SCGI ){ 2580 cgi_handle_scgi_request(); 2581 }else{ 2582 cgi_handle_http_request(0); 2583 } 2584 process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList); 2585 #else 2586 /* Win32 implementation */ 2587 if( isUiCmd ){ 2588 zBrowser = db_get("web-browser", "start"); 2589 if( zIpAddr ){ 2590 zBrowserCmd = mprintf("%s http://%s:%%d/%s &", 2591 zBrowser, zIpAddr, zInitPage); 2592 }else{ 2593 zBrowserCmd = mprintf("%s http://localhost:%%d/%s &", 2594 zBrowser, zInitPage); 2595 } 2596 } 2597 if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY; 2598 if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT; 2599 db_close(1); 2600 if( allowRepoList ){ 2601 flags |= HTTP_SERVER_REPOLIST; 2602 } 2603 if( win32_http_service(iPort, zNotFound, zFileGlob, flags) ){ 2604 win32_http_server(iPort, mxPort, zBrowserCmd, 2605 zStopperFile, zNotFound, zFileGlob, zIpAddr, flags); 2606 } 2607 #endif 2608 } 2609 2610 /* 2611 ** COMMAND: test-echo 2612 ** 2613 ** Usage: %fossil test-echo [--hex] ARGS... 2614 ** 2615 ** Echo all command-line arguments (enclosed in [...]) to the screen so that 2616 ** wildcard expansion behavior of the host shell can be investigated. 2617 ** 2618 ** With the --hex option, show the output as hexadecimal. This can be used 2619 ** to verify the fossil_path_to_utf8() routine on Windows and Mac. 2620 */ 2621 void test_echo_cmd(void){ 2622 int i, j; 2623 if( find_option("hex",0,0)==0 ){ 2624 fossil_print("g.nameOfExe = [%s]\n", g.nameOfExe); 2625 for(i=0; i<g.argc; i++){ 2626 fossil_print("argv[%d] = [%s]\n", i, g.argv[i]); 2627 } 2628 }else{ 2629 unsigned char *z, c; 2630 for(i=0; i<g.argc; i++){ 2631 fossil_print("argv[%d] = [", i); 2632 z = (unsigned char*)g.argv[i]; 2633 for(j=0; (c = z[j])!=0; j++){ 2634 fossil_print("%02x", c); 2635 } 2636 fossil_print("]\n"); 2637 } 2638 } 2639 }