Fossil

Artifact Content
Login

Artifact 663246084eded6c69a9c4ecdf17d6f30ae43df7e:


     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  #if defined(USE_SEE)
  1087    blob_append(pOut, "USE_SEE\n", -1);
  1088  #endif
  1089  }
  1090  
  1091  /*
  1092  ** This function returns the user-agent string for Fossil, for
  1093  ** use in HTTP(S) requests.
  1094  */
  1095  const char *get_user_agent(){
  1096    static const char version[] = "Fossil/" RELEASE_VERSION " (" MANIFEST_DATE
  1097                                  " " MANIFEST_VERSION ")";
  1098    return version;
  1099  }
  1100  
  1101  
  1102  /*
  1103  ** COMMAND: version
  1104  **
  1105  ** Usage: %fossil version ?-verbose|-v?
  1106  **
  1107  ** Print the source code version number for the fossil executable.
  1108  ** If the verbose option is specified, additional details will
  1109  ** be output about what optional features this binary was compiled
  1110  ** with
  1111  */
  1112  void version_cmd(void){
  1113    Blob versionInfo;
  1114    int verboseFlag = find_option("verbose","v",0)!=0;
  1115  
  1116    /* We should be done with options.. */
  1117    verify_all_options();
  1118    get_version_blob(&versionInfo, verboseFlag);
  1119    fossil_print("%s", blob_str(&versionInfo));
  1120  }
  1121  
  1122  
  1123  /*
  1124  ** WEBPAGE: test-version
  1125  **
  1126  ** Show the version information for Fossil.
  1127  **
  1128  ** Query parameters:
  1129  **
  1130  **    verbose       Show all available details.
  1131  */
  1132  void test_version_page(void){
  1133    Blob versionInfo;
  1134    int verboseFlag;
  1135  
  1136    login_check_credentials();
  1137    if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  1138    verboseFlag = P("verbose")!=0;
  1139    style_header("Version Information");
  1140    get_version_blob(&versionInfo, verboseFlag);
  1141    @ <blockquote><pre>
  1142    @ %h(blob_str(&versionInfo))
  1143    @ </pre></blockquote>
  1144    style_footer();
  1145  }
  1146  
  1147  
  1148  /*
  1149  ** COMMAND: help
  1150  **
  1151  ** Usage: %fossil help COMMAND
  1152  **    or: %fossil COMMAND --help
  1153  **
  1154  ** Display information on how to use COMMAND.  To display a list of
  1155  ** available commands use one of:
  1156  **
  1157  **    %fossil help              Show common commands
  1158  **    %fossil help -a|--all     Show both common and auxiliary commands
  1159  **    %fossil help -t|--test    Show test commands only
  1160  **    %fossil help -x|--aux     Show auxiliary commands only
  1161  **    %fossil help -w|--www     Show list of WWW pages
  1162  */
  1163  void help_cmd(void){
  1164    int rc, idx, isPage = 0;
  1165    const char *z;
  1166    const char *zCmdOrPage;
  1167    const char *zCmdOrPagePlural;
  1168    if( g.argc<3 ){
  1169      z = g.argv[0];
  1170      fossil_print(
  1171        "Usage: %s help COMMAND\n"
  1172        "Common COMMANDs:  (use \"%s help -a|--all\" for a complete list)\n",
  1173        z, z);
  1174      command_list(0, CMDFLAG_1ST_TIER);
  1175      version_cmd();
  1176      return;
  1177    }
  1178    if( find_option("all","a",0) ){
  1179      command_list(0, CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER);
  1180      return;
  1181    }
  1182    else if( find_option("www","w",0) ){
  1183      command_list(0, CMDFLAG_WEBPAGE);
  1184      return;
  1185    }
  1186    else if( find_option("aux","x",0) ){
  1187      command_list(0, CMDFLAG_2ND_TIER);
  1188      return;
  1189    }
  1190    else if( find_option("test","t",0) ){
  1191      command_list(0, CMDFLAG_TEST);
  1192      return;
  1193    }
  1194    isPage = ('/' == *g.argv[2]) ? 1 : 0;
  1195    if(isPage){
  1196      zCmdOrPage = "page";
  1197      zCmdOrPagePlural = "pages";
  1198    }else{
  1199      zCmdOrPage = "command";
  1200      zCmdOrPagePlural = "commands";
  1201    }
  1202    rc = name_search(g.argv[2], aCommand, count(aCommand), 0, &idx);
  1203    if( rc==1 ){
  1204      fossil_print("unknown %s: %s\nAvailable %s:\n",
  1205                   zCmdOrPage, g.argv[2], zCmdOrPagePlural);
  1206      command_list(0, isPage ? CMDFLAG_WEBPAGE : (0xff & ~CMDFLAG_WEBPAGE));
  1207      fossil_exit(1);
  1208    }else if( rc==2 ){
  1209      fossil_print("ambiguous %s prefix: %s\nMatching %s:\n",
  1210                   zCmdOrPage, g.argv[2], zCmdOrPagePlural);
  1211      command_list(g.argv[2], 0xff);
  1212      fossil_exit(1);
  1213    }
  1214    z = aCmdHelp[idx].zText;
  1215    if( z==0 ){
  1216      fossil_fatal("no help available for the %s %s",
  1217                   aCommand[idx].zName, zCmdOrPage);
  1218    }
  1219    while( *z ){
  1220      if( *z=='%' && strncmp(z, "%fossil", 7)==0 ){
  1221        fossil_print("%s", g.argv[0]);
  1222        z += 7;
  1223      }else{
  1224        putchar(*z);
  1225        z++;
  1226      }
  1227    }
  1228    putchar('\n');
  1229  }
  1230  
  1231  /*
  1232  ** COMMAND: test-all-help
  1233  ** 
  1234  ** Usage: %fossil test-all-help ?OPTIONS?
  1235  **
  1236  ** Show help text for commands and pages.  Useful for proof-reading.
  1237  ** Defaults to just the CLI commands.  Specify --www to see only the
  1238  ** web pages, or --everything to see both commands and pages.
  1239  **
  1240  ** Options:
  1241  **    -e|--everything   Show all commands and pages.
  1242  **    -t|--test         Include test- commands
  1243  **    -w|--www          Show WWW pages.
  1244  */
  1245  void test_all_help_cmd(void){
  1246    int i;
  1247    int mask = CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER;
  1248  
  1249    if( find_option("www","w",0) ){
  1250      mask = CMDFLAG_WEBPAGE;
  1251    }
  1252    if( find_option("everything","e",0) ){
  1253      mask = CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER | CMDFLAG_WEBPAGE;
  1254    }
  1255    if( find_option("test","t",0) ){
  1256      mask |= CMDFLAG_TEST;
  1257    }
  1258    fossil_print("Help text for:\n");
  1259    if( mask & CMDFLAG_1ST_TIER ) fossil_print(" * Commands\n");
  1260    if( mask & CMDFLAG_2ND_TIER ) fossil_print(" * Auxiliary commands\n");
  1261    if( mask & CMDFLAG_TEST )     fossil_print(" * Test commands\n");
  1262    if( mask & CMDFLAG_WEBPAGE )  fossil_print(" * Web pages\n");
  1263    fossil_print("---\n");
  1264    for(i=0; i<count(aCommand); i++){
  1265      if( (aCommand[i].cmdFlags & mask)==0 ) continue;
  1266      fossil_print("# %s\n", aCommand[i].zName);
  1267      fossil_print("%s\n\n", aCmdHelp[i].zText);
  1268    }
  1269    fossil_print("---\n");
  1270    version_cmd();
  1271  }
  1272  
  1273  /*
  1274  ** WEBPAGE: help
  1275  ** URL: /help?name=CMD
  1276  **
  1277  ** Show the built-in help text for CMD.  CMD can be a command-line interface
  1278  ** command or a page name from the web interface.
  1279  */
  1280  void help_page(void){
  1281    const char *zCmd = P("cmd");
  1282  
  1283    if( zCmd==0 ) zCmd = P("name");
  1284    style_header("Command-line Help");
  1285    if( zCmd ){
  1286      int rc, idx;
  1287      char *z, *s, *d;
  1288      style_submenu_element("Command-List", "Command-List", "%s/help", g.zTop);
  1289      if( *zCmd=='/' ){
  1290        /* Some of the webpages require query parameters in order to work.
  1291        ** @ <h1>The "<a href='%R%s(zCmd)'>%s(zCmd)</a>" page:</h1> */
  1292        @ <h1>The "%s(zCmd)" page:</h1>
  1293      }else{
  1294        @ <h1>The "%s(zCmd)" command:</h1>
  1295      }
  1296      rc = name_search(zCmd, aCommand, count(aCommand), 0, &idx);
  1297      if( rc==1 ){
  1298        @ unknown command: %s(zCmd)
  1299      }else if( rc==2 ){
  1300        @ ambiguous command prefix: %s(zCmd)
  1301      }else{
  1302        z = (char*)aCmdHelp[idx].zText;
  1303        if( z==0 ){
  1304          @ no help available for the %s(aCommand[idx].zName) command
  1305        }else{
  1306          z=s=d=mprintf("%s",z);
  1307          while( *s ){
  1308            if( *s=='%' && strncmp(s, "%fossil", 7)==0 ){
  1309              s++;
  1310            }else{
  1311              *d++ = *s++;
  1312            }
  1313          }
  1314          *d = 0;
  1315          @ <blockquote><pre>
  1316          @ %h(z)
  1317          @ </pre></blockquote>
  1318          fossil_free(z);
  1319        }
  1320      }
  1321    }else{
  1322      int i, j, n;
  1323  
  1324      @ <h1>Available commands:</h1>
  1325      @ <table border="0"><tr>
  1326      for(i=j=0; i<count(aCommand); i++){
  1327        const char *z = aCommand[i].zName;
  1328        if( '/'==*z || strncmp(z,"test",4)==0 ) continue;
  1329        j++;
  1330      }
  1331      n = (j+6)/7;
  1332      for(i=j=0; i<count(aCommand); i++){
  1333        const char *z = aCommand[i].zName;
  1334        if( '/'==*z || strncmp(z,"test",4)==0 ) continue;
  1335        if( j==0 ){
  1336          @ <td valign="top"><ul>
  1337        }
  1338        @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
  1339        j++;
  1340        if( j>=n ){
  1341          @ </ul></td>
  1342          j = 0;
  1343        }
  1344      }
  1345      if( j>0 ){
  1346        @ </ul></td>
  1347      }
  1348      @ </tr></table>
  1349  
  1350      @ <h1>Available web UI pages:</h1>
  1351      @ <table border="0"><tr>
  1352      for(i=j=0; i<count(aCommand); i++){
  1353        const char *z = aCommand[i].zName;
  1354        if( '/'!=*z ) continue;
  1355        j++;
  1356      }
  1357      n = (j+4)/5;
  1358      for(i=j=0; i<count(aCommand); i++){
  1359        const char *z = aCommand[i].zName;
  1360        if( '/'!=*z ) continue;
  1361        if( j==0 ){
  1362          @ <td valign="top"><ul>
  1363        }
  1364        if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){
  1365          @ <li><a href="%R/help?cmd=%s(z)">%s(z+1)</a></li>
  1366        }else{
  1367          @ <li>%s(z+1)</li>
  1368        }
  1369        j++;
  1370        if( j>=n ){
  1371          @ </ul></td>
  1372          j = 0;
  1373        }
  1374      }
  1375      if( j>0 ){
  1376        @ </ul></td>
  1377      }
  1378      @ </tr></table>
  1379  
  1380      @ <h1>Unsupported commands:</h1>
  1381      @ <table border="0"><tr>
  1382      for(i=j=0; i<count(aCommand); i++){
  1383        const char *z = aCommand[i].zName;
  1384        if( strncmp(z,"test",4)!=0 ) continue;
  1385        j++;
  1386      }
  1387      n = (j+3)/4;
  1388      for(i=j=0; i<count(aCommand); i++){
  1389        const char *z = aCommand[i].zName;
  1390        if( strncmp(z,"test",4)!=0 ) continue;
  1391        if( j==0 ){
  1392          @ <td valign="top"><ul>
  1393        }
  1394        if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){
  1395          @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
  1396        }else{
  1397          @ <li>%s(z)</li>
  1398        }
  1399        j++;
  1400        if( j>=n ){
  1401          @ </ul></td>
  1402          j = 0;
  1403        }
  1404      }
  1405      if( j>0 ){
  1406        @ </ul></td>
  1407      }
  1408      @ </tr></table>
  1409  
  1410    }
  1411    style_footer();
  1412  }
  1413  
  1414  /*
  1415  ** WEBPAGE: test-all-help
  1416  **
  1417  ** Show all help text on a single page.  Useful for proof-reading.
  1418  */
  1419  void test_all_help_page(void){
  1420    int i;
  1421    style_header("Testpage: All Help Text");
  1422    for(i=0; i<count(aCommand); i++){
  1423      if( memcmp(aCommand[i].zName, "test", 4)==0 ) continue;
  1424      @ <h2>%s(aCommand[i].zName):</h2>
  1425      @ <blockquote><pre>
  1426      @ %h(aCmdHelp[i].zText)
  1427      @ </pre></blockquote>
  1428    }
  1429    style_footer();
  1430  }
  1431  
  1432  /*
  1433  ** Set the g.zBaseURL value to the full URL for the toplevel of
  1434  ** the fossil tree.  Set g.zTop to g.zBaseURL without the
  1435  ** leading "http://" and the host and port.
  1436  **
  1437  ** The g.zBaseURL is normally set based on HTTP_HOST and SCRIPT_NAME
  1438  ** environment variables.  However, if zAltBase is not NULL then it
  1439  ** is the argument to the --baseurl option command-line option and
  1440  ** g.zBaseURL and g.zTop is set from that instead.
  1441  */
  1442  static void set_base_url(const char *zAltBase){
  1443    int i;
  1444    const char *zHost;
  1445    const char *zMode;
  1446    const char *zCur;
  1447  
  1448    if( g.zBaseURL!=0 ) return;
  1449    if( zAltBase ){
  1450      int i, n, c;
  1451      g.zTop = g.zBaseURL = mprintf("%s", zAltBase);
  1452      if( strncmp(g.zTop, "http://", 7)==0 ){
  1453        /* it is HTTP, replace prefix with HTTPS. */
  1454        g.zHttpsURL = mprintf("https://%s", &g.zTop[7]);
  1455      }else if( strncmp(g.zTop, "https://", 8)==0 ){
  1456        /* it is already HTTPS, use it. */
  1457        g.zHttpsURL = mprintf("%s", g.zTop);
  1458      }else{
  1459        fossil_fatal("argument to --baseurl should be 'http://host/path'"
  1460                     " or 'https://host/path'");
  1461      }
  1462      for(i=n=0; (c = g.zTop[i])!=0; i++){
  1463        if( c=='/' ){
  1464          n++;
  1465          if( n==3 ){
  1466            g.zTop += i;
  1467            break;
  1468          }
  1469        }
  1470      }
  1471      if( g.zTop==g.zBaseURL ){
  1472        fossil_fatal("argument to --baseurl should be 'http://host/path'"
  1473                     " or 'https://host/path'");
  1474      }
  1475      if( g.zTop[1]==0 ) g.zTop++;
  1476    }else{
  1477      zHost = PD("HTTP_HOST","");
  1478      zMode = PD("HTTPS","off");
  1479      zCur = PD("SCRIPT_NAME","/");
  1480      i = strlen(zCur);
  1481      while( i>0 && zCur[i-1]=='/' ) i--;
  1482      if( fossil_stricmp(zMode,"on")==0 ){
  1483        g.zBaseURL = mprintf("https://%s%.*s", zHost, i, zCur);
  1484        g.zTop = &g.zBaseURL[8+strlen(zHost)];
  1485        g.zHttpsURL = g.zBaseURL;
  1486      }else{
  1487        g.zBaseURL = mprintf("http://%s%.*s", zHost, i, zCur);
  1488        g.zTop = &g.zBaseURL[7+strlen(zHost)];
  1489        g.zHttpsURL = mprintf("https://%s%.*s", zHost, i, zCur);
  1490      }
  1491    }
  1492    if( db_is_writeable("repository") ){
  1493      if( !db_exists("SELECT 1 FROM config WHERE name='baseurl:%q'", g.zBaseURL)){
  1494        db_multi_exec("INSERT INTO config(name,value,mtime)"
  1495                      "VALUES('baseurl:%q',1,now())", g.zBaseURL);
  1496      }else{
  1497        db_optional_sql("repository",
  1498             "REPLACE INTO config(name,value,mtime)"
  1499             "VALUES('baseurl:%q',1,now())", g.zBaseURL
  1500        );
  1501      }
  1502    }
  1503  }
  1504  
  1505  /*
  1506  ** Send an HTTP redirect back to the designated Index Page.
  1507  */
  1508  NORETURN void fossil_redirect_home(void){
  1509    cgi_redirectf("%s%s", g.zTop, db_get("index-page", "/index"));
  1510  }
  1511  
  1512  /*
  1513  ** If running as root, chroot to the directory containing the
  1514  ** repository zRepo and then drop root privileges.  Return the
  1515  ** new repository name.
  1516  **
  1517  ** zRepo might be a directory itself.  In that case chroot into
  1518  ** the directory zRepo.
  1519  **
  1520  ** Assume the user-id and group-id of the repository, or if zRepo
  1521  ** is a directory, of that directory.
  1522  **
  1523  ** The noJail flag means that the chroot jail is not entered.  But
  1524  ** privileges are still lowered to that of the user-id and group-id
  1525  ** of the repository file.
  1526  */
  1527  static char *enter_chroot_jail(char *zRepo, int noJail){
  1528  #if !defined(_WIN32)
  1529    if( getuid()==0 ){
  1530      int i;
  1531      struct stat sStat;
  1532      Blob dir;
  1533      char *zDir;
  1534      if( g.db!=0 ){
  1535        db_close(1);
  1536      }
  1537  
  1538      file_canonical_name(zRepo, &dir, 0);
  1539      zDir = blob_str(&dir);
  1540      if( !noJail ){
  1541        if( file_isdir(zDir)==1 ){
  1542          if( file_chdir(zDir, 1) ){
  1543            fossil_fatal("unable to chroot into %s", zDir);
  1544          }
  1545          zRepo = "/";
  1546        }else{
  1547          for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
  1548          if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo);
  1549          if( i>0 ){
  1550            zDir[i] = 0;
  1551            if( file_chdir(zDir, 1) ){
  1552              fossil_fatal("unable to chroot into %s", zDir);
  1553            }
  1554            zDir[i] = '/';
  1555          }
  1556          zRepo = &zDir[i];
  1557        }
  1558      }
  1559      if( stat(zRepo, &sStat)!=0 ){
  1560        fossil_fatal("cannot stat() repository: %s", zRepo);
  1561      }
  1562      i = setgid(sStat.st_gid);
  1563      i = i || setuid(sStat.st_uid);
  1564      if(i){
  1565        fossil_fatal("setgid/uid() failed with errno %d", errno);
  1566      }
  1567      if( g.db==0 && file_isfile(zRepo) ){
  1568        db_open_repository(zRepo);
  1569      }
  1570    }
  1571  #endif
  1572    return zRepo;
  1573  }
  1574  
  1575  /*
  1576  ** Generate a web-page that lists all repositories located under the
  1577  ** g.zRepositoryName directory and return non-zero.
  1578  **
  1579  ** Or, if no repositories can be located beneath g.zRepositoryName,
  1580  ** return 0.
  1581  */
  1582  static int repo_list_page(void){
  1583    Blob base;
  1584    int n = 0;
  1585  
  1586    assert( g.db==0 );
  1587    blob_init(&base, g.zRepositoryName, -1);
  1588    sqlite3_open(":memory:", &g.db);
  1589    db_multi_exec("CREATE TABLE sfile(x TEXT);");
  1590    db_multi_exec("CREATE TABLE vfile(pathname);");
  1591    vfile_scan(&base, blob_size(&base), 0, 0, 0);
  1592    db_multi_exec("DELETE FROM sfile WHERE x NOT GLOB '*[^/].fossil'");
  1593    n = db_int(0, "SELECT count(*) FROM sfile");
  1594    if( n>0 ){
  1595      Stmt q;
  1596      @ <html>
  1597      @ <head>
  1598      @ <base href="%s(g.zBaseURL)/" />
  1599      @ <title>Repository List</title>
  1600      @ </head>
  1601      @ <body>
  1602      @ <h1>Available Repositories:</h1>
  1603      @ <ol>
  1604      db_prepare(&q, "SELECT x, substr(x,-7,-100000)||'/home'"
  1605                     " FROM sfile ORDER BY x COLLATE nocase;");
  1606      while( db_step(&q)==SQLITE_ROW ){
  1607        const char *zName = db_column_text(&q, 0);
  1608        const char *zUrl = db_column_text(&q, 1);
  1609        @ <li><a href="%R/%h(zUrl)" target="_blank">%h(zName)</a></li>
  1610      }
  1611      @ </ol>
  1612      @ </body>
  1613      @ </html>
  1614      cgi_reply();
  1615    }
  1616    sqlite3_close(g.db);
  1617    g.db = 0;
  1618    return n;
  1619  }
  1620  
  1621  /*
  1622  ** Preconditions:
  1623  **
  1624  **  * Environment variables are set up according to the CGI standard.
  1625  **
  1626  ** If the repository is known, it has already been opened.  If unknown,
  1627  ** then g.zRepositoryName holds the directory that contains the repository
  1628  ** and the actual repository is taken from the first element of PATH_INFO.
  1629  **
  1630  ** Process the webpage specified by the PATH_INFO or REQUEST_URI
  1631  ** environment variable.
  1632  **
  1633  ** If the repository is not known, then a search is done through the
  1634  ** file hierarchy rooted at g.zRepositoryName for a suitable repository
  1635  ** with a name of $prefix.fossil, where $prefix is any prefix of PATH_INFO.
  1636  ** Or, if an ordinary file named $prefix is found, and $prefix matches
  1637  ** pFileGlob and $prefix does not match "*.fossil*" and the mimetype of
  1638  ** $prefix can be determined from its suffix, then the file $prefix is
  1639  ** returned as static text.
  1640  **
  1641  ** If no suitable webpage is found, try to redirect to zNotFound.
  1642  */
  1643  static void process_one_web_page(
  1644    const char *zNotFound,      /* Redirect here on a 404 if not NULL */
  1645    Glob *pFileGlob,            /* Deliver static files matching */
  1646    int allowRepoList           /* Send repo list for "/" URL */
  1647  ){
  1648    const char *zPathInfo;
  1649    const char *zDirPathInfo;
  1650    char *zPath = NULL;
  1651    int idx;
  1652    int i;
  1653  
  1654    /* Handle universal query parameters */
  1655    if( PB("utc") ){
  1656      g.fTimeFormat = 1;
  1657    }else if( PB("localtime") ){
  1658      g.fTimeFormat = 2;
  1659    }
  1660  
  1661    /* If the repository has not been opened already, then find the
  1662    ** repository based on the first element of PATH_INFO and open it.
  1663    */
  1664    zDirPathInfo = zPathInfo = PD("PATH_INFO","");
  1665    /* For the PATH_INFO that will be used to help build the final
  1666    ** g.zBaseURL and g.zTop (only), skip over the initial directory
  1667    ** portion of PATH_INFO; otherwise, it may be duplicated.
  1668    */
  1669    if( g.zTop ){
  1670      int nTop = strlen(g.zTop);
  1671      if ( strncmp(zDirPathInfo, g.zTop, nTop)==0 ){
  1672        zDirPathInfo += nTop;
  1673      }
  1674    }
  1675    if( !g.repositoryOpen ){
  1676      char *zRepo, *zToFree;
  1677      const char *zOldScript = PD("SCRIPT_NAME", "");
  1678      char *zNewScript;
  1679      int j, k;
  1680      i64 szFile;
  1681  
  1682      i = zPathInfo[0]!=0;
  1683      while( 1 ){
  1684        while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; }
  1685        zRepo = zToFree = mprintf("%s%.*s.fossil",g.zRepositoryName,i,zPathInfo);
  1686  
  1687        /* To avoid mischief, make sure the repository basename contains no
  1688        ** characters other than alphanumerics, "/", "_", "-", and ".", and
  1689        ** that "-" never occurs immediately after a "/" and that "." is always
  1690        ** surrounded by two alphanumerics.  Any character that does not
  1691        ** satisfy these constraints is converted into "_".
  1692        */
  1693        szFile = 0;
  1694        for(j=strlen(g.zRepositoryName)+1, k=0; zRepo[j] && k<i-1; j++, k++){
  1695          char c = zRepo[j];
  1696          if( fossil_isalnum(c) ) continue;
  1697          if( c=='/' ) continue;
  1698          if( c=='_' ) continue;
  1699          if( c=='-' && zRepo[j-1]!='/' ) continue;
  1700          if( c=='.' && fossil_isalnum(zRepo[j-1]) && fossil_isalnum(zRepo[j+1])){
  1701            continue;
  1702          }
  1703          szFile = 1;
  1704          break;
  1705        }
  1706        if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){
  1707          if( zRepo[0]=='/' && zRepo[1]=='/' ){ zRepo++; j--; }
  1708          szFile = file_size(zRepo);
  1709          /* this should only be set from the --baseurl option, not CGI  */
  1710          if( g.zBaseURL && g.zBaseURL[0]!=0 && g.zTop && g.zTop[0]!=0 &&
  1711              file_isdir(g.zRepositoryName)==1 ){
  1712            if( zPathInfo==zDirPathInfo ){
  1713              g.zBaseURL = mprintf("%s%.*s", g.zBaseURL, i, zPathInfo);
  1714              g.zTop = mprintf("%s%.*s", g.zTop, i, zPathInfo);
  1715            }
  1716          }
  1717        }
  1718        if( szFile<0 && i>0 ){
  1719          const char *zMimetype;
  1720          assert( fossil_strcmp(&zRepo[j], ".fossil")==0 );
  1721          zRepo[j] = 0;
  1722          if( zPathInfo[i]=='/' && file_isdir(zRepo)==1 ){
  1723            fossil_free(zToFree);
  1724            i++;
  1725            continue;
  1726          }
  1727          if( pFileGlob!=0
  1728           && file_isfile(zRepo)
  1729           && glob_match(pFileGlob, zRepo)
  1730           && sqlite3_strglob("*.fossil*",zRepo)!=0
  1731           && (zMimetype = mimetype_from_name(zRepo))!=0
  1732           && strcmp(zMimetype, "application/x-fossil-artifact")!=0
  1733          ){
  1734            Blob content;
  1735            blob_read_from_file(&content, zRepo);
  1736            cgi_set_content_type(zMimetype);
  1737            cgi_set_content(&content);
  1738            cgi_reply();
  1739            return;
  1740          }
  1741          zRepo[j] = '.';
  1742        }
  1743  
  1744        if( szFile<1024 ){
  1745          set_base_url(0);
  1746          if( strcmp(zPathInfo,"/")==0
  1747                    && allowRepoList
  1748                    && repo_list_page() ){
  1749            /* Will return a list of repositories */
  1750          }else if( zNotFound ){
  1751            cgi_redirect(zNotFound);
  1752          }else{
  1753  #ifdef FOSSIL_ENABLE_JSON
  1754            if(g.json.isJsonMode){
  1755              json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
  1756              return;
  1757            }
  1758  #endif
  1759            @ <h1>Not Found</h1>
  1760            cgi_set_status(404, "not found");
  1761            cgi_reply();
  1762          }
  1763          return;
  1764        }
  1765        break;
  1766      }
  1767      zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo);
  1768      cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]);
  1769      zPathInfo += i;
  1770      cgi_replace_parameter("SCRIPT_NAME", zNewScript);
  1771      db_open_repository(zRepo);
  1772      if( g.fHttpTrace ){
  1773        fprintf(stderr,
  1774            "# repository: [%s]\n"
  1775            "# new PATH_INFO = [%s]\n"
  1776            "# new SCRIPT_NAME = [%s]\n",
  1777            zRepo, zPathInfo, zNewScript);
  1778      }
  1779    }
  1780  
  1781    /* Find the page that the user has requested, construct and deliver that
  1782    ** page.
  1783    */
  1784    if( g.zContentType &&
  1785        strncmp(g.zContentType, "application/x-fossil", 20)==0 ){
  1786      zPathInfo = "/xfer";
  1787    }
  1788    set_base_url(0);
  1789    if( zPathInfo==0 || zPathInfo[0]==0
  1790        || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
  1791  #ifdef FOSSIL_ENABLE_JSON
  1792      if(g.json.isJsonMode){
  1793        json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
  1794        fossil_exit(0);
  1795      }
  1796  #endif
  1797      fossil_redirect_home() /*does not return*/;
  1798    }else{
  1799      zPath = mprintf("%s", zPathInfo);
  1800    }
  1801  
  1802    /* Make g.zPath point to the first element of the path.  Make
  1803    ** g.zExtra point to everything past that point.
  1804    */
  1805    while(1){
  1806      char *zAltRepo = 0;
  1807      g.zPath = &zPath[1];
  1808      for(i=1; zPath[i] && zPath[i]!='/'; i++){}
  1809      if( zPath[i]=='/' ){
  1810        zPath[i] = 0;
  1811        g.zExtra = &zPath[i+1];
  1812  
  1813        /* Look for sub-repositories.  A sub-repository is another repository
  1814        ** that accepts the login credentials of the current repository.  A
  1815        ** subrepository is identified by a CONFIG table entry "subrepo:NAME"
  1816        ** where NAME is the first component of the path.  The value of the
  1817        ** the CONFIG entries is the string "USER:FILENAME" where USER is the
  1818        ** USER name to log in as in the subrepository and FILENAME is the
  1819        ** repository filename.
  1820        */
  1821        zAltRepo = db_text(0, "SELECT value FROM config WHERE name='subrepo:%q'",
  1822                           g.zPath);
  1823        if( zAltRepo ){
  1824          int nHost;
  1825          int jj;
  1826          char *zUser = zAltRepo;
  1827          login_check_credentials();
  1828          for(jj=0; zAltRepo[jj] && zAltRepo[jj]!=':'; jj++){}
  1829          if( zAltRepo[jj]==':' ){
  1830            zAltRepo[jj] = 0;
  1831            zAltRepo += jj+1;
  1832          }else{
  1833            zUser = "nobody";
  1834          }
  1835          if( g.zLogin==0 || g.zLogin[0]==0 ) zUser = "nobody";
  1836          if( zAltRepo[0]!='/' ){
  1837            zAltRepo = mprintf("%s/../%s", g.zRepositoryName, zAltRepo);
  1838            file_simplify_name(zAltRepo, -1, 0);
  1839          }
  1840          db_close(1);
  1841          db_open_repository(zAltRepo);
  1842          login_as_user(zUser);
  1843          g.perm.Password = 0;
  1844          zPath += i;
  1845          nHost = g.zTop - g.zBaseURL;
  1846          g.zBaseURL = mprintf("%z/%s", g.zBaseURL, g.zPath);
  1847          g.zTop = g.zBaseURL + nHost;
  1848          continue;
  1849        }
  1850      }else{
  1851        g.zExtra = 0;
  1852      }
  1853      break;
  1854    }
  1855  #ifdef FOSSIL_ENABLE_JSON
  1856    /*
  1857    ** Workaround to allow us to customize some following behaviour for
  1858    ** JSON mode.  The problem is, we don't always know if we're in JSON
  1859    ** mode at this point (namely, for GET mode we don't know but POST
  1860    ** we do), so we snoop g.zPath and cheat a bit.
  1861    */
  1862    if( !g.json.isJsonMode && g.zPath && (0==strncmp("json",g.zPath,4)) ){
  1863      g.json.isJsonMode = 1;
  1864    }
  1865  #endif
  1866    if( g.zExtra ){
  1867      /* CGI parameters get this treatment elsewhere, but places like getfile
  1868      ** will use g.zExtra directly.
  1869      ** Reminder: the login mechanism uses 'name' differently, and may
  1870      ** eventually have a problem/collision with this.
  1871      **
  1872      ** Disabled by stephan when running in JSON mode because this
  1873      ** particular parameter name is very common and i have had no end
  1874      ** of grief with this handling. The JSON API never relies on the
  1875      ** handling below, and by disabling it in JSON mode I can remove
  1876      ** lots of special-case handling in several JSON handlers.
  1877      */
  1878  #ifdef FOSSIL_ENABLE_JSON
  1879      if(!g.json.isJsonMode){
  1880  #endif
  1881        dehttpize(g.zExtra);
  1882        cgi_set_parameter_nocopy("name", g.zExtra, 1);
  1883  #ifdef FOSSIL_ENABLE_JSON
  1884      }
  1885  #endif
  1886    }
  1887  
  1888    /* Locate the method specified by the path and execute the function
  1889    ** that implements that method.
  1890    */
  1891    if( name_search(g.zPath, aWebpage, count(aWebpage), 0, &idx) ){
  1892  #ifdef FOSSIL_ENABLE_JSON
  1893      if(g.json.isJsonMode){
  1894        json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,0);
  1895      }else
  1896  #endif
  1897      {
  1898  #ifdef FOSSIL_ENABLE_TH1_HOOKS
  1899        int rc;
  1900        if( !g.fNoThHook ){
  1901          rc = Th_WebpageHook(g.zPath, 0);
  1902        }else{
  1903          rc = TH_OK;
  1904        }
  1905        if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
  1906          if( rc==TH_OK || rc==TH_RETURN ){
  1907  #endif
  1908            cgi_set_status(404,"Not Found");
  1909            @ <h1>Not Found</h1>
  1910            @ <p>Page not found: %h(g.zPath)</p>
  1911  #ifdef FOSSIL_ENABLE_TH1_HOOKS
  1912          }
  1913          if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
  1914            Th_WebpageNotify(g.zPath, 0);
  1915          }
  1916        }
  1917  #endif
  1918      }
  1919    }else if( aWebpage[idx].xFunc!=page_xfer && db_schema_is_outofdate() ){
  1920  #ifdef FOSSIL_ENABLE_JSON
  1921      if(g.json.isJsonMode){
  1922        json_err(FSL_JSON_E_DB_NEEDS_REBUILD,NULL,0);
  1923      }else
  1924  #endif
  1925      {
  1926        @ <h1>Server Configuration Error</h1>
  1927        @ <p>The database schema on the server is out-of-date.  Please ask
  1928        @ the administrator to run <b>fossil rebuild</b>.</p>
  1929      }
  1930    }else{
  1931  #ifdef FOSSIL_ENABLE_TH1_HOOKS
  1932      /*
  1933      ** The TH1 return codes from the hook will be handled as follows:
  1934      **
  1935      ** TH_OK: The xFunc() and the TH1 notification will both be executed.
  1936      **
  1937      ** TH_ERROR: The xFunc() will be executed, the TH1 notification will be
  1938      **           skipped.  If the xFunc() is being hooked, the error message
  1939      **           will be emitted.
  1940      **
  1941      ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped.
  1942      **
  1943      ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be
  1944      **            skipped.
  1945      **
  1946      ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be
  1947      **              executed.
  1948      */
  1949      int rc;
  1950      if( !g.fNoThHook ){
  1951        rc = Th_WebpageHook(aWebpage[idx].zName, aWebpage[idx].cmdFlags);
  1952      }else{
  1953        rc = TH_OK;
  1954      }
  1955      if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){
  1956        if( rc==TH_OK || rc==TH_RETURN ){
  1957  #endif
  1958          aWebpage[idx].xFunc();
  1959  #ifdef FOSSIL_ENABLE_TH1_HOOKS
  1960        }
  1961        if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
  1962          Th_WebpageNotify(aWebpage[idx].zName, aWebpage[idx].cmdFlags);
  1963        }
  1964      }
  1965  #endif
  1966    }
  1967  
  1968    /* Return the result.
  1969    */
  1970    cgi_reply();
  1971  }
  1972  
  1973  /* If the CGI program contains one or more lines of the form
  1974  **
  1975  **    redirect:  repository-filename  http://hostname/path/%s
  1976  **
  1977  ** then control jumps here.  Search each repository for an artifact ID
  1978  ** or ticket ID that matches the "name" CGI parameter and for the
  1979  ** first match, redirect to the corresponding URL with the "name" CGI
  1980  ** parameter inserted.  Paint an error page if no match is found.
  1981  **
  1982  ** If there is a line of the form:
  1983  **
  1984  **    redirect: * URL
  1985  **
  1986  ** Then a redirect is made to URL if no match is found.  Otherwise a
  1987  ** very primitive error message is returned.
  1988  */
  1989  static void redirect_web_page(int nRedirect, char **azRedirect){
  1990    int i;                             /* Loop counter */
  1991    const char *zNotFound = 0;         /* Not found URL */
  1992    const char *zName = P("name");
  1993    set_base_url(0);
  1994    if( zName==0 ){
  1995      zName = P("SCRIPT_NAME");
  1996      if( zName && zName[0]=='/' ) zName++;
  1997    }
  1998    if( zName && validate16(zName, strlen(zName)) ){
  1999      for(i=0; i<nRedirect; i++){
  2000        if( fossil_strcmp(azRedirect[i*2],"*")==0 ){
  2001          zNotFound = azRedirect[i*2+1];
  2002          continue;
  2003        }
  2004        db_open_repository(azRedirect[i*2]);
  2005        if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'", zName) ||
  2006  	  db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){
  2007          cgi_redirectf(azRedirect[i*2+1] /*works-like:"%s"*/, zName);
  2008          return;
  2009        }
  2010        db_close(1);
  2011      }
  2012    }
  2013    if( zNotFound ){
  2014      cgi_redirectf(zNotFound /*works-like:"%s"*/, zName);
  2015    }else{
  2016      @ <html>
  2017      @ <head><title>No Such Object</title></head>
  2018      @ <body>
  2019      @ <p>No such object: <b>%h(zName)</b></p>
  2020      @ </body>
  2021      cgi_reply();
  2022    }
  2023  }
  2024  
  2025  /*
  2026  ** COMMAND: cgi*
  2027  **
  2028  ** Usage: %fossil ?cgi? SCRIPT
  2029  **
  2030  ** The SCRIPT argument is the name of a file that is the CGI script
  2031  ** that is being run.  The command name, "cgi", may be omitted if
  2032  ** the GATEWAY_INTERFACE environment variable is set to "CGI" (which
  2033  ** should always be the case for CGI scripts run by a webserver.)  The
  2034  ** SCRIPT file should look something like this:
  2035  **
  2036  **      #!/usr/bin/fossil
  2037  **      repository: /home/somebody/project.db
  2038  **
  2039  ** The second line defines the name of the repository.  After locating
  2040  ** the repository, fossil will generate a webpage on stdout based on
  2041  ** the values of standard CGI environment variables.
  2042  **
  2043  ** See also: http, server, winsrv
  2044  */
  2045  void cmd_cgi(void){
  2046    const char *zFile;
  2047    const char *zNotFound = 0;
  2048    char **azRedirect = 0;             /* List of repositories to redirect to */
  2049    int nRedirect = 0;                 /* Number of entries in azRedirect */
  2050    Glob *pFileGlob = 0;               /* Pattern for files */
  2051    int allowRepoList = 0;             /* Allow lists of repository files */
  2052    Blob config, line, key, value, value2;
  2053    if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){
  2054      zFile = g.argv[2];
  2055    }else{
  2056      zFile = g.argv[1];
  2057    }
  2058    g.httpOut = stdout;
  2059    g.httpIn = stdin;
  2060    fossil_binary_mode(g.httpOut);
  2061    fossil_binary_mode(g.httpIn);
  2062    g.cgiOutput = 1;
  2063    blob_read_from_file(&config, zFile);
  2064    while( blob_line(&config, &line) ){
  2065      if( !blob_token(&line, &key) ) continue;
  2066      if( blob_buffer(&key)[0]=='#' ) continue;
  2067      if( blob_eq(&key, "repository:") && blob_tail(&line, &value) ){
  2068        /* repository: FILENAME
  2069        **
  2070        ** The name of the Fossil repository to be served via CGI.  Most
  2071        ** fossil CGI scripts have a single non-comment line that contains
  2072        ** this one entry.
  2073        */
  2074        blob_trim(&value);
  2075        db_open_repository(blob_str(&value));
  2076        blob_reset(&value);
  2077        continue;
  2078      }
  2079      if( blob_eq(&key, "directory:") && blob_token(&line, &value) ){
  2080        /* directory: DIRECTORY
  2081        **
  2082        ** If repository: is omitted, then terms of the PATH_INFO cgi parameter
  2083        ** are appended to DIRECTORY looking for a repository (whose name ends
  2084        ** in ".fossil") or a file in "files:".
  2085        */
  2086        db_close(1);
  2087        g.zRepositoryName = mprintf("%s", blob_str(&value));
  2088        blob_reset(&value);
  2089        continue;
  2090      }
  2091      if( blob_eq(&key, "notfound:") && blob_token(&line, &value) ){
  2092        /* notfound: URL
  2093        **
  2094        ** If using directory: and no suitable repository or file is found,
  2095        ** then redirect to URL.
  2096        */
  2097        zNotFound = mprintf("%s", blob_str(&value));
  2098        blob_reset(&value);
  2099        continue;
  2100      }
  2101      if( blob_eq(&key, "localauth") ){
  2102        /* localauth
  2103        **
  2104        ** Grant "administrator" privileges to users connecting with HTTP
  2105        ** from IP address 127.0.0.1.  Do not bother checking credentials.
  2106        */
  2107        g.useLocalauth = 1;
  2108        continue;
  2109      }
2110 if( blob_eq(&key, "repolist") ){ 2111 /* repolist 2112 ** 2113 ** If using "directory:" and the URL is "/" then generate a page 2114 ** showing a list of available repositories. 2115 */ 2116 allowRepoList = 1; 2117 continue; 2118 }
2119 if( blob_eq(&key, "redirect:") && blob_token(&line, &value) 2120 && blob_token(&line, &value2) ){ 2121 /* See the header comment on the redirect_web_page() function 2122 ** above for details. */ 2123 nRedirect++; 2124 azRedirect = fossil_realloc(azRedirect, 2*nRedirect*sizeof(char*)); 2125 azRedirect[nRedirect*2-2] = mprintf("%s", blob_str(&value)); 2126 azRedirect[nRedirect*2-1] = mprintf("%s", blob_str(&value2)); 2127 blob_reset(&value); 2128 blob_reset(&value2); 2129 continue; 2130 } 2131 if( blob_eq(&key, "files:") && blob_token(&line, &value) ){ 2132 /* files: GLOBLIST 2133 ** 2134 ** GLOBLIST is a comma-separated list of filename globs. For 2135 ** example: *.html,*.css,*.js 2136 ** 2137 ** If the repository: line is omitted and then PATH_INFO is searched 2138 ** for files that match any of these GLOBs and if any such file is 2139 ** found it is returned verbatim. This feature allows "fossil server" 2140 ** to function as a primitive web-server delivering arbitrary content. 2141 */ 2142 pFileGlob = glob_create(blob_str(&value)); 2143 blob_reset(&value); 2144 continue; 2145 } 2146 if( blob_eq(&key, "setenv:") && blob_token(&line, &value) ){ 2147 /* setenv: NAME VALUE 2148 ** setenv: NAME 2149 ** 2150 ** Sets environment variable NAME to VALUE. If VALUE is omitted, then 2151 ** the environment variable is unset. 2152 */ 2153 blob_token(&line,&value2); 2154 fossil_setenv(blob_str(&value), blob_str(&value2)); 2155 blob_reset(&value); 2156 blob_reset(&value2); 2157 continue; 2158 } 2159 if( blob_eq(&key, "debug:") && blob_token(&line, &value) ){ 2160 /* debug: FILENAME 2161 ** 2162 ** Causes output from cgi_debug() and CGIDEBUG(()) calls to go 2163 ** into FILENAME. 2164 */ 2165 g.fDebug = fossil_fopen(blob_str(&value), "ab"); 2166 blob_reset(&value); 2167 continue; 2168 } 2169 if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){ 2170 /* errorlog: FILENAME 2171 ** 2172 ** Causes messages from warnings, errors, and panics to be appended 2173 ** to FILENAME. 2174 */ 2175 g.zErrlog = mprintf("%s", blob_str(&value)); 2176 blob_reset(&value); 2177 continue; 2178 } 2179 if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){ 2180 /* HOME: VALUE 2181 ** 2182 ** Set CGI parameter "HOME" to VALUE. This is legacy. Use 2183 ** setenv: instead. 2184 */ 2185 cgi_setenv("HOME", blob_str(&value)); 2186 blob_reset(&value); 2187 continue; 2188 } 2189 if( blob_eq(&key, "skin:") && blob_token(&line, &value) ){ 2190 /* skin: LABEL 2191 ** 2192 ** Use one of the built-in skins defined by LABEL. LABEL is the 2193 ** name of the subdirectory under the skins/ directory that holds 2194 ** the elements of the built-in skin. If LABEL does not match, 2195 ** this directive is a silent no-op. 2196 */ 2197 skin_use_alternative(blob_str(&value)); 2198 blob_reset(&value); 2199 continue; 2200 } 2201 } 2202 blob_reset(&config); 2203 if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){ 2204 cgi_panic("Unable to find or open the project repository"); 2205 } 2206 cgi_init(); 2207 if( nRedirect ){ 2208 redirect_web_page(nRedirect, azRedirect); 2209 }else{ 2210 process_one_web_page(zNotFound, pFileGlob, allowRepoList); 2211 } 2212 } 2213 2214 /* 2215 ** If g.argv[arg] exists then it is either the name of a repository 2216 ** that will be used by a server, or else it is a directory that 2217 ** contains multiple repositories that can be served. If g.argv[arg] 2218 ** is a directory, the repositories it contains must be named 2219 ** "*.fossil". If g.argv[arg] does not exist, then we must be within 2220 ** an open check-out and the repository serve is the repository of 2221 ** that check-out. 2222 ** 2223 ** Open the repository to be served if it is known. If g.argv[arg] is 2224 ** a directory full of repositories, then set g.zRepositoryName to 2225 ** the name of that directory and the specific repository will be 2226 ** opened later by process_one_web_page() based on the content of 2227 ** the PATH_INFO variable. 2228 ** 2229 ** If the fCreate flag is set, then create the repository if it 2230 ** does not already exist. 2231 */ 2232 static void find_server_repository(int arg, int fCreate){ 2233 if( g.argc<=arg ){ 2234 db_must_be_within_tree(); 2235 }else{ 2236 const char *zRepo = g.argv[arg]; 2237 int isDir = file_isdir(zRepo); 2238 if( isDir==1 ){ 2239 g.zRepositoryName = mprintf("%s", zRepo); 2240 file_simplify_name(g.zRepositoryName, -1, 0); 2241 }else{ 2242 if( isDir==0 && fCreate ){ 2243 const char *zPassword; 2244 db_create_repository(zRepo); 2245 db_open_repository(zRepo); 2246 db_begin_transaction(); 2247 db_initial_setup(0, "now", g.zLogin); 2248 db_end_transaction(0); 2249 fossil_print("project-id: %s\n", db_get("project-code", 0)); 2250 fossil_print("server-id: %s\n", db_get("server-code", 0)); 2251 zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin); 2252 fossil_print("admin-user: %s (initial password is \"%s\")\n", 2253 g.zLogin, zPassword); 2254 cache_initialize(); 2255 g.zLogin = 0; 2256 g.userUid = 0; 2257 }else{ 2258 db_open_repository(zRepo); 2259 } 2260 } 2261 } 2262 } 2263 2264 /* 2265 ** undocumented format: 2266 ** 2267 ** fossil http INFILE OUTFILE IPADDR ?REPOSITORY? 2268 ** 2269 ** The argv==6 form (with no options) is used by the win32 server only. 2270 ** 2271 ** COMMAND: http* 2272 ** 2273 ** Usage: %fossil http ?REPOSITORY? ?OPTIONS? 2274 ** 2275 ** Handle a single HTTP request appearing on stdin. The resulting webpage 2276 ** is delivered on stdout. This method is used to launch an HTTP request 2277 ** handler from inetd, for example. The argument is the name of the 2278 ** repository. 2279 ** 2280 ** If REPOSITORY is a directory that contains one or more repositories, 2281 ** either directly in REPOSITORY itself or in subdirectories, and 2282 ** with names of the form "*.fossil" then a prefix of the URL pathname 2283 ** selects from among the various repositories. If the pathname does 2284 ** not select a valid repository and the --notfound option is available, 2285 ** then the server redirects (HTTP code 302) to the URL of --notfound. 2286 ** When REPOSITORY is a directory, the pathname must contain only 2287 ** alphanumerics, "_", "/", "-" and "." and no "-" may occur after a "/" 2288 ** and every "." must be surrounded on both sides by alphanumerics or else 2289 ** a 404 error is returned. Static content files in the directory are 2290 ** returned if they match comma-separate GLOB pattern specified by --files 2291 ** and do not match "*.fossil*" and have a well-known suffix. 2292 ** 2293 ** The --host option can be used to specify the hostname for the server. 2294 ** The --https option indicates that the request came from HTTPS rather 2295 ** than HTTP. If --nossl is given, then SSL connections will not be available, 2296 ** thus also no redirecting from http: to https: will take place. 2297 ** 2298 ** If the --localauth option is given, then automatic login is performed 2299 ** for requests coming from localhost, if the "localauth" setting is not 2300 ** enabled. 2301 ** 2302 ** Options: 2303 ** --baseurl URL base URL (useful with reverse proxies) 2304 ** --files GLOB comma-separate glob patterns for static file to serve 2305 ** --localauth enable automatic login for local connections 2306 ** --host NAME specify hostname of the server 2307 ** --https signal a request coming in via https 2308 ** --nojail drop root privilege but do not enter the chroot jail 2309 ** --nossl signal that no SSL connections are available 2310 ** --notfound URL use URL as "HTTP 404, object not found" page. 2311 ** --repolist If REPOSITORY is directory, URL "/" lists all repos 2312 ** --scgi Interpret input as SCGI rather than HTTP 2313 ** --skin LABEL Use override skin LABEL 2314 ** 2315 ** See also: cgi, server, winsrv 2316 */ 2317 void cmd_http(void){ 2318 const char *zIpAddr = 0; 2319 const char *zNotFound; 2320 const char *zHost; 2321 const char *zAltBase; 2322 const char *zFileGlob; 2323 int useSCGI; 2324 int noJail; 2325 int allowRepoList; 2326 2327 /* The winhttp module passes the --files option as --files-urlenc with 2328 ** the argument being URL encoded, to avoid wildcard expansion in the 2329 ** shell. This option is for internal use and is undocumented. 2330 */ 2331 zFileGlob = find_option("files-urlenc",0,1); 2332 if( zFileGlob ){ 2333 char *z = mprintf("%s", zFileGlob); 2334 dehttpize(z); 2335 zFileGlob = z; 2336 }else{ 2337 zFileGlob = find_option("files",0,1); 2338 } 2339 skin_override(); 2340 zNotFound = find_option("notfound", 0, 1); 2341 noJail = find_option("nojail",0,0)!=0; 2342 allowRepoList = find_option("repolist",0,0)!=0; 2343 g.useLocalauth = find_option("localauth", 0, 0)!=0; 2344 g.sslNotAvailable = find_option("nossl", 0, 0)!=0; 2345 useSCGI = find_option("scgi", 0, 0)!=0; 2346 zAltBase = find_option("baseurl", 0, 1); 2347 if( zAltBase ) set_base_url(zAltBase); 2348 if( find_option("https",0,0)!=0 ){ 2349 zIpAddr = fossil_getenv("REMOTE_HOST"); /* From stunnel */ 2350 cgi_replace_parameter("HTTPS","on"); 2351 } 2352 zHost = find_option("host", 0, 1); 2353 if( zHost ) cgi_replace_parameter("HTTP_HOST",zHost); 2354 2355 /* We should be done with options.. */ 2356 verify_all_options(); 2357 2358 if( g.argc!=2 && g.argc!=3 && g.argc!=5 && g.argc!=6 ){ 2359 fossil_fatal("no repository specified"); 2360 } 2361 g.cgiOutput = 1; 2362 g.fullHttpReply = 1; 2363 if( g.argc>=5 ){ 2364 g.httpIn = fossil_fopen(g.argv[2], "rb"); 2365 g.httpOut = fossil_fopen(g.argv[3], "wb"); 2366 zIpAddr = g.argv[4]; 2367 find_server_repository(5, 0); 2368 }else{ 2369 g.httpIn = stdin; 2370 g.httpOut = stdout; 2371 find_server_repository(2, 0); 2372 } 2373 if( zIpAddr==0 ){ 2374 zIpAddr = cgi_ssh_remote_addr(0); 2375 if( zIpAddr && zIpAddr[0] ){ 2376 g.fSshClient |= CGI_SSH_CLIENT; 2377 } 2378 } 2379 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); 2380 if( useSCGI ){ 2381 cgi_handle_scgi_request(); 2382 }else if( g.fSshClient & CGI_SSH_CLIENT ){ 2383 ssh_request_loop(zIpAddr, glob_create(zFileGlob)); 2384 }else{ 2385 cgi_handle_http_request(zIpAddr); 2386 } 2387 process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList); 2388 } 2389 2390 /* 2391 ** Process all requests in a single SSH connection if possible. 2392 */ 2393 void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){ 2394 blob_zero(&g.cgiIn); 2395 do{ 2396 cgi_handle_ssh_http_request(zIpAddr); 2397 process_one_web_page(0, FileGlob, 0); 2398 blob_reset(&g.cgiIn); 2399 } while ( g.fSshClient & CGI_SSH_FOSSIL || 2400 g.fSshClient & CGI_SSH_COMPAT ); 2401 } 2402 2403 /* 2404 ** Note that the following command is used by ssh:// processing. 2405 ** 2406 ** COMMAND: test-http 2407 ** 2408 ** Works like the http command but gives setup permission to all users. 2409 ** 2410 ** Options: 2411 ** --th-trace trace TH1 execution (for debugging purposes) 2412 ** 2413 */ 2414 void cmd_test_http(void){ 2415 const char *zIpAddr; /* IP address of remote client */ 2416 2417 Th_InitTraceLog(); 2418 login_set_capabilities("sx", 0); 2419 g.useLocalauth = 1; 2420 g.httpIn = stdin; 2421 g.httpOut = stdout; 2422 find_server_repository(2, 0); 2423 g.cgiOutput = 1; 2424 g.fullHttpReply = 1; 2425 zIpAddr = cgi_ssh_remote_addr(0); 2426 if( zIpAddr && zIpAddr[0] ){ 2427 g.fSshClient |= CGI_SSH_CLIENT; 2428 ssh_request_loop(zIpAddr, 0); 2429 }else{ 2430 cgi_set_parameter("REMOTE_ADDR", "127.0.0.1"); 2431 cgi_handle_http_request(0); 2432 process_one_web_page(0, 0, 0); 2433 } 2434 } 2435 2436 #if !defined(_WIN32) 2437 #if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__) 2438 /* 2439 ** Search for an executable on the PATH environment variable. 2440 ** Return true (1) if found and false (0) if not found. 2441 */ 2442 static int binaryOnPath(const char *zBinary){ 2443 const char *zPath = fossil_getenv("PATH"); 2444 char *zFull; 2445 int i; 2446 int bExists; 2447 while( zPath && zPath[0] ){ 2448 while( zPath[0]==':' ) zPath++; 2449 for(i=0; zPath[i] && zPath[i]!=':'; i++){} 2450 zFull = mprintf("%.*s/%s", i, zPath, zBinary); 2451 bExists = file_access(zFull, X_OK); 2452 fossil_free(zFull); 2453 if( bExists==0 ) return 1; 2454 zPath += i; 2455 } 2456 return 0; 2457 } 2458 #endif 2459 #endif 2460 2461 /* 2462 ** COMMAND: server* 2463 ** COMMAND: ui 2464 ** 2465 ** Usage: %fossil server ?OPTIONS? ?REPOSITORY? 2466 ** or: %fossil ui ?OPTIONS? ?REPOSITORY? 2467 ** 2468 ** Open a socket and begin listening and responding to HTTP requests on 2469 ** TCP port 8080, or on any other TCP port defined by the -P or 2470 ** --port option. The optional argument is the name of the repository. 2471 ** The repository argument may be omitted if the working directory is 2472 ** within an open checkout. 2473 ** 2474 ** The "ui" command automatically starts a web browser after initializing 2475 ** the web server. The "ui" command also binds to 127.0.0.1 and so will 2476 ** only process HTTP traffic from the local machine. 2477 ** 2478 ** The REPOSITORY can be a directory (aka folder) that contains one or 2479 ** more repositories with names ending in ".fossil". In this case, a 2480 ** prefix of the URL pathname is used to search the directory for an 2481 ** appropriate repository. To thwart mischief, the pathname in the URL must 2482 ** contain only alphanumerics, "_", "/", "-", and ".", and no "-" may 2483 ** occur after "/", and every "." must be surrounded on both sides by 2484 ** alphanumerics. Any pathname that does not satisfy these constraints 2485 ** results in a 404 error. Files in REPOSITORY that match the comma-separated 2486 ** list of glob patterns given by --files and that have known suffixes 2487 ** such as ".txt" or ".html" or ".jpeg" and do not match the pattern 2488 ** "*.fossil*" will be served as static content. With the "ui" command, 2489 ** the REPOSITORY can only be a directory if the --notfound option is 2490 ** also present. 2491 ** 2492 ** By default, the "ui" command provides full administrative access without 2493 ** having to log in. This can be disabled by turning off the "localauth" 2494 ** setting. Automatic login for the "server" command is available if the 2495 ** --localauth option is present and the "localauth" setting is off and the 2496 ** connection is from localhost. The "ui" command also enables --repolist 2497 ** by default. 2498 ** 2499 ** Options: 2500 ** --baseurl URL Use URL as the base (useful for reverse proxies) 2501 ** --create Create a new REPOSITORY if it does not already exist 2502 ** --page PAGE Start "ui" on PAGE. ex: --page "timeline?y=ci" 2503 ** --files GLOBLIST Comma-separated list of glob patterns for static files 2504 ** --localauth enable automatic login for requests from localhost 2505 ** --localhost listen on 127.0.0.1 only (always true for "ui") 2506 ** --https signal a request coming in via https 2507 ** --nojail Drop root privileges but do not enter the chroot jail 2508 ** --nossl signal that no SSL connections are available 2509 ** --notfound URL Redirect 2510 ** -P|--port TCPPORT listen to request on port TCPPORT 2511 ** --th-trace trace TH1 execution (for debugging purposes) 2512 ** --repolist If REPOSITORY is dir, URL "/" lists repos. 2513 ** --scgi Accept SCGI rather than HTTP 2514 ** --skin LABEL Use override skin LABEL 2515 2516 ** 2517 ** See also: cgi, http, winsrv 2518 */ 2519 void cmd_webserver(void){ 2520 int iPort, mxPort; /* Range of TCP ports allowed */ 2521 const char *zPort; /* Value of the --port option */ 2522 const char *zBrowser; /* Name of web browser program */ 2523 char *zBrowserCmd = 0; /* Command to launch the web browser */ 2524 int isUiCmd; /* True if command is "ui", not "server' */ 2525 const char *zNotFound; /* The --notfound option or NULL */ 2526 int flags = 0; /* Server flags */ 2527 #if !defined(_WIN32) 2528 int noJail; /* Do not enter the chroot jail */ 2529 #endif 2530 int allowRepoList; /* List repositories on URL "/" */ 2531 const char *zAltBase; /* Argument to the --baseurl option */ 2532 const char *zFileGlob; /* Static content must match this */ 2533 char *zIpAddr = 0; /* Bind to this IP address */ 2534 int fCreate = 0; /* The --create flag */ 2535 const char *zInitPage = 0; /* Start on this page. --page option */ 2536 2537 #if defined(_WIN32) 2538 const char *zStopperFile; /* Name of file used to terminate server */ 2539 zStopperFile = find_option("stopper", 0, 1); 2540 #endif 2541 2542 zFileGlob = find_option("files-urlenc",0,1); 2543 if( zFileGlob ){ 2544 char *z = mprintf("%s", zFileGlob); 2545 dehttpize(z); 2546 zFileGlob = z; 2547 }else{ 2548 zFileGlob = find_option("files",0,1); 2549 } 2550 skin_override(); 2551 #if !defined(_WIN32) 2552 noJail = find_option("nojail",0,0)!=0; 2553 #endif 2554 g.useLocalauth = find_option("localauth", 0, 0)!=0; 2555 Th_InitTraceLog(); 2556 zPort = find_option("port", "P", 1); 2557 isUiCmd = g.argv[1][0]=='u'; 2558 if( isUiCmd ){ 2559 zInitPage = find_option("page", 0, 1); 2560 } 2561 zNotFound = find_option("notfound", 0, 1); 2562 allowRepoList = find_option("repolist",0,0)!=0; 2563 zAltBase = find_option("baseurl", 0, 1); 2564 fCreate = find_option("create",0,0)!=0; 2565 if( find_option("scgi", 0, 0)!=0 ) flags |= HTTP_SERVER_SCGI; 2566 if( zAltBase ){ 2567 set_base_url(zAltBase); 2568 } 2569 g.sslNotAvailable = find_option("nossl", 0, 0)!=0; 2570 if( find_option("https",0,0)!=0 ){ 2571 cgi_replace_parameter("HTTPS","on"); 2572 }else{ 2573 /* without --https, defaults to not available. */ 2574 g.sslNotAvailable = 1; 2575 } 2576 if( find_option("localhost", 0, 0)!=0 ){ 2577 flags |= HTTP_SERVER_LOCALHOST; 2578 } 2579 2580 /* We should be done with options.. */ 2581 verify_all_options(); 2582 2583 if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?"); 2584 if( isUiCmd ){ 2585 flags |= HTTP_SERVER_LOCALHOST|HTTP_SERVER_REPOLIST; 2586 g.useLocalauth = 1; 2587 allowRepoList = 1; 2588 } 2589 find_server_repository(2, fCreate); 2590 if( zInitPage==0 ){ 2591 if( isUiCmd && g.localOpen ){ 2592 zInitPage = "timeline?c=current"; 2593 }else{ 2594 zInitPage = ""; 2595 } 2596 } 2597 if( zPort ){ 2598 int i; 2599 for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){} 2600 if( i>0 ){ 2601 zIpAddr = mprintf("%.*s", i, zPort); 2602 zPort += i+1; 2603 } 2604 iPort = mxPort = atoi(zPort); 2605 }else{ 2606 iPort = db_get_int("http-port", 8080); 2607 mxPort = iPort+100; 2608 } 2609 #if !defined(_WIN32) 2610 /* Unix implementation */ 2611 if( isUiCmd ){ 2612 #if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__) 2613 zBrowser = db_get("web-browser", 0); 2614 if( zBrowser==0 ){ 2615 static const char *const azBrowserProg[] = 2616 { "xdg-open", "gnome-open", "firefox", "google-chrome" }; 2617 int i; 2618 zBrowser = "echo"; 2619 for(i=0; i<sizeof(azBrowserProg)/sizeof(azBrowserProg[0]); i++){ 2620 if( binaryOnPath(azBrowserProg[i]) ){ 2621 zBrowser = azBrowserProg[i]; 2622 break; 2623 } 2624 } 2625 } 2626 #else 2627 zBrowser = db_get("web-browser", "open"); 2628 #endif 2629 if( zIpAddr ){ 2630 zBrowserCmd = mprintf("%s http://%s:%%d/%s &", 2631 zBrowser, zIpAddr, zInitPage); 2632 }else{ 2633 zBrowserCmd = mprintf("%s http://localhost:%%d/%s &", 2634 zBrowser, zInitPage); 2635 } 2636 } 2637 if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY; 2638 if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT; 2639 db_close(1); 2640 if( cgi_http_server(iPort, mxPort, zBrowserCmd, zIpAddr, flags) ){ 2641 fossil_fatal("unable to listen on TCP socket %d", iPort); 2642 } 2643 g.httpIn = stdin; 2644 g.httpOut = stdout; 2645 if( g.fHttpTrace || g.fSqlTrace ){ 2646 fprintf(stderr, "====== SERVER pid %d =======\n", getpid()); 2647 } 2648 g.cgiOutput = 1; 2649 find_server_repository(2, 0); 2650 g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); 2651 if( flags & HTTP_SERVER_SCGI ){ 2652 cgi_handle_scgi_request(); 2653 }else{ 2654 cgi_handle_http_request(0); 2655 } 2656 process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList); 2657 #else 2658 /* Win32 implementation */ 2659 if( isUiCmd ){ 2660 zBrowser = db_get("web-browser", "start"); 2661 if( zIpAddr ){ 2662 zBrowserCmd = mprintf("%s http://%s:%%d/%s &", 2663 zBrowser, zIpAddr, zInitPage); 2664 }else{ 2665 zBrowserCmd = mprintf("%s http://localhost:%%d/%s &", 2666 zBrowser, zInitPage); 2667 } 2668 } 2669 if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY; 2670 if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT; 2671 db_close(1); 2672 if( allowRepoList ){ 2673 flags |= HTTP_SERVER_REPOLIST; 2674 } 2675 if( win32_http_service(iPort, zAltBase, zNotFound, zFileGlob, flags) ){ 2676 win32_http_server(iPort, mxPort, zBrowserCmd, zStopperFile, 2677 zAltBase, zNotFound, zFileGlob, zIpAddr, flags); 2678 } 2679 #endif 2680 } 2681 2682 /* 2683 ** COMMAND: test-echo 2684 ** 2685 ** Usage: %fossil test-echo [--hex] ARGS... 2686 ** 2687 ** Echo all command-line arguments (enclosed in [...]) to the screen so that 2688 ** wildcard expansion behavior of the host shell can be investigated. 2689 ** 2690 ** With the --hex option, show the output as hexadecimal. This can be used 2691 ** to verify the fossil_path_to_utf8() routine on Windows and Mac. 2692 */ 2693 void test_echo_cmd(void){ 2694 int i, j; 2695 if( find_option("hex",0,0)==0 ){ 2696 fossil_print("g.nameOfExe = [%s]\n", g.nameOfExe); 2697 for(i=0; i<g.argc; i++){ 2698 fossil_print("argv[%d] = [%s]\n", i, g.argv[i]); 2699 } 2700 }else{ 2701 unsigned char *z, c; 2702 for(i=0; i<g.argc; i++){ 2703 fossil_print("argv[%d] = [", i); 2704 z = (unsigned char*)g.argv[i]; 2705 for(j=0; (c = z[j])!=0; j++){ 2706 fossil_print("%02x", c); 2707 } 2708 fossil_print("]\n"); 2709 } 2710 } 2711 }