Changes On Branch ashish-ipv6
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch ashish-ipv6 Excluding Merge-Ins

This is equivalent to a diff from fe5cf37e66 to 7124f09f07

2012-03-10
16:47
minor internal refactoring of /json/wiki/get in prep for /json/wiki/preview. check-in: 73816973fb user: stephan tags: trunk
13:17
Merge latest changes from trunk. Leaf check-in: 7124f09f07 user: ashish tags: ashish-ipv6
03:59
Removed some dead code in /json/timeline/wiki. check-in: fe5cf37e66 user: stephan tags: trunk
2012-03-09
20:16
Fixed a logic bug which caused /json/wiki/create to not be able to create a new page. check-in: 64c2ec012c user: stephan tags: trunk
2012-01-15
18:06
Merge latest changes from trunk check-in: b3130baa06 user: ashish tags: ashish-ipv6

Changes to auto.def.

     7      7                            => {Look for openssl in the given path, or auto or none}
     8      8       with-zlib:path       => {Look for zlib in the given path}
     9      9       with-tcl:path        => {Enable Tcl integration, with Tcl in the specified path}
    10     10       internal-sqlite=1    => {Don't use the internal sqlite, use the system one}
    11     11       static=0             => {Link a static executable}
    12     12       lineedit=1           => {Disable line editing}
    13     13       fossil-debug=0       => {Build with fossil debugging enabled}
           14  +    ipv6=1		 => {Disable IPv6 support}
    14     15       json=0        => {Build with fossil JSON API enabled}
    15     16   }
    16     17   
    17     18   # sqlite wants these types if possible
    18     19   cc-with {-includes {stdint.h inttypes.h}} {
    19     20       cc-check-types uint32_t uint16_t int16_t uint8_t
    20     21   }
................................................................................
    70     71       define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_JSON
    71     72   }
    72     73   
    73     74   if {[opt-bool static]} {
    74     75       # XXX: This will not work on all systems.
    75     76       define-append EXTRA_LDFLAGS -static
    76     77   }
           78  +
           79  +if {[opt-bool ipv6]} {
           80  +   define-append EXTRA_CFLAGS -DWITH_IPV6
           81  +   msg-result "IPv6 support enabled"
           82  +   if {[cc-check-functions getaddrinfo]} {
           83  +      define-append EXTRA_CFLAGS -DHAVE_GETADDRINFO
           84  +      msg-result "getaddrinfo() enabled"
           85  +   }
           86  +}
    77     87   
    78     88   
    79     89   # Check for zlib, using the given location if specified
    80     90   set zlibpath [opt-val with-zlib]
    81     91   if {$zlibpath ne ""} {
    82     92       cc-with [list -cflags "-I$zlibpath -L$zlibpath"]
    83     93       define-append EXTRA_CFLAGS -I$zlibpath

Changes to src/cgi.c.

    32     32   # include <sys/socket.h>
    33     33   # include <netinet/in.h>
    34     34   # include <arpa/inet.h>
    35     35   # include <sys/times.h>
    36     36   # include <sys/time.h>
    37     37   # include <sys/wait.h>
    38     38   # include <sys/select.h>
           39  +# include <netdb.h>             /* for NI_NUMERICHOST */
    39     40   #endif
    40     41   #ifdef __EMX__
    41     42     typedef int socklen_t;
    42     43   #endif
    43     44   #include <time.h>
    44     45   #include <stdio.h>
    45     46   #include <stdlib.h>
................................................................................
  1114   1115   ** environment variables as per CGI.  The cgi_init() routine to complete
  1115   1116   ** the setup.  Once all the setup is finished, this procedure returns
  1116   1117   ** and subsequent code handles the actual generation of the webpage.
  1117   1118   */
  1118   1119   void cgi_handle_http_request(const char *zIpAddr){
  1119   1120     char *z, *zToken;
  1120   1121     int i;
  1121         -  struct sockaddr_in remoteName;
  1122         -  socklen_t size = sizeof(struct sockaddr_in);
         1122  +  struct sockaddr_storage remoteName;
         1123  +  socklen_t size = sizeof(remoteName);
  1123   1124     char zLine[2000];     /* A single line of input. */
  1124   1125     g.fullHttpReply = 1;
  1125   1126     if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
  1126   1127       malformed_request();
  1127   1128     }
  1128   1129     zToken = extract_token(zLine, &z);
  1129   1130     if( zToken==0 ){
................................................................................
  1140   1141       malformed_request();
  1141   1142     }
  1142   1143     cgi_setenv("REQUEST_URI", zToken);
  1143   1144     for(i=0; zToken[i] && zToken[i]!='?'; i++){}
  1144   1145     if( zToken[i] ) zToken[i++] = 0;
  1145   1146     cgi_setenv("PATH_INFO", zToken);
  1146   1147     cgi_setenv("QUERY_STRING", &zToken[i]);
  1147         -  if( zIpAddr==0 &&
  1148         -        getpeername(fileno(g.httpIn), (struct sockaddr*)&remoteName, 
  1149         -                                &size)>=0
  1150         -  ){
  1151         -    zIpAddr = inet_ntoa(remoteName.sin_addr);
  1152         -  }
  1153         -  if( zIpAddr ){   
  1154         -    cgi_setenv("REMOTE_ADDR", zIpAddr);
  1155         -    g.zIpAddr = mprintf("%s", zIpAddr);
  1156         -  }
  1157   1148    
  1158   1149     /* Get all the optional fields that follow the first line.
  1159   1150     */
  1160   1151     while( fgets(zLine,sizeof(zLine),g.httpIn) ){
  1161   1152       char *zFieldName;
  1162   1153       char *zVal;
  1163   1154   
................................................................................
  1180   1171         cgi_setenv("HTTPS", zVal);
  1181   1172       }else if( fossil_strcmp(zFieldName,"host:")==0 ){
  1182   1173         cgi_setenv("HTTP_HOST", zVal);
  1183   1174       }else if( fossil_strcmp(zFieldName,"if-none-match:")==0 ){
  1184   1175         cgi_setenv("HTTP_IF_NONE_MATCH", zVal);
  1185   1176       }else if( fossil_strcmp(zFieldName,"if-modified-since:")==0 ){
  1186   1177         cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal);
         1178  +    }else if( fossil_strcmp(zFieldName,"x-forwarded-for:")==0 ){
         1179  +      char* p = zVal;
         1180  +      /*
         1181  +      ** x-forwarded-for header is a list of comma-separated addresses, 
         1182  +      ** with leftmost address corresponding to the client
         1183  +      */
         1184  +      while(*p && *p != ',') p++;
         1185  +      *p = '\0';
         1186  +      zIpAddr = mprintf( "%s", zVal );
  1187   1187   #if 0
  1188   1188       }else if( fossil_strcmp(zFieldName,"referer:")==0 ){
  1189   1189         cgi_setenv("HTTP_REFERER", zVal);
  1190   1190   #endif
  1191   1191       }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){
  1192   1192         cgi_setenv("HTTP_USER_AGENT", zVal);
  1193   1193       }
  1194   1194     }
         1195  +
         1196  +  if( zIpAddr==0 &&
         1197  +      getsockname(fileno(g.httpIn), (struct sockaddr*)&remoteName, 
         1198  +                                &size)>=0
         1199  +  ){
         1200  +    sa_family_t family;
         1201  +    int v4mapped=0;
         1202  +    if( remoteName.ss_family == AF_INET6 && 
         1203  +        IN6_IS_ADDR_V4MAPPED(&(((struct sockaddr_in6*)&remoteName)->sin6_addr)) ){
         1204  +        v4mapped = 1;
         1205  +    }
         1206  +    if(!getnameinfo((struct sockaddr*)&remoteName, size, zLine, sizeof(zLine),
         1207  +                    NULL, 0, NI_NUMERICHOST)){
         1208  +      zIpAddr = zLine;
         1209  +    } else {
         1210  +      zIpAddr = NULL;
         1211  +    }
         1212  +    if(zIpAddr && v4mapped) {
         1213  +      /* ::ffff:172.16.0.2 */
         1214  +      zIpAddr += 7; 
         1215  +    }
         1216  +  }
         1217  +  if( zIpAddr ){
         1218  +    cgi_setenv("REMOTE_ADDR", zIpAddr);
         1219  +    g.zIpAddr = mprintf("%s", zIpAddr);
         1220  +  }
  1195   1221   
  1196   1222     cgi_init();
  1197   1223   }
  1198   1224   
  1199   1225   #if INTERFACE
  1200   1226   /* 
  1201   1227   ** Bitmap values for the flags parameter to cgi_http_server().
................................................................................
  1228   1254     int listener = -1;           /* The server socket */
  1229   1255     int connection;              /* A socket for each individual connection */
  1230   1256     fd_set readfds;              /* Set of file descriptors for select() */
  1231   1257     socklen_t lenaddr;           /* Length of the inaddr structure */
  1232   1258     int child;                   /* PID of the child process */
  1233   1259     int nchildren = 0;           /* Number of child processes */
  1234   1260     struct timeval delay;        /* How long to wait inside select() */
         1261  +#ifdef HAVE_GETADDRINFO
         1262  +  struct addrinfo hints;
         1263  +  struct addrinfo* res;
         1264  +  struct addrinfo* i;
         1265  +  struct sockaddr_storage inaddr;   /* The socket address */
         1266  +  char* sPort;
         1267  +  int iRet;
         1268  +#else
         1269  +#ifdef WITH_IPV6
         1270  +  struct sockaddr_in6 inaddr;   /* The socket address */
         1271  +#else
  1235   1272     struct sockaddr_in inaddr;   /* The socket address */
         1273  +#endif
         1274  +#endif
  1236   1275     int opt = 1;                 /* setsockopt flag */
  1237   1276     int iPort = mnPort;
  1238   1277   
  1239   1278     while( iPort<=mxPort ){
  1240         -    memset(&inaddr, 0, sizeof(inaddr));
  1241         -    inaddr.sin_family = AF_INET;
  1242         -    if( flags & HTTP_SERVER_LOCALHOST ){
  1243         -      inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
  1244         -    }else{
  1245         -      inaddr.sin_addr.s_addr = htonl(INADDR_ANY);
         1279  +#ifdef HAVE_GETADDRINFO
         1280  +    memset(&hints, 0, sizeof(struct addrinfo));
         1281  +#ifdef WITH_IPV6
         1282  +    hints.ai_family = PF_UNSPEC;
         1283  +#else
         1284  +    hints.ai_family = PF_INET;
         1285  +#endif
         1286  +    hints.ai_socktype = SOCK_STREAM;
         1287  +    hints.ai_protocol = IPPROTO_TCP;
         1288  +    if(!(flags & HTTP_SERVER_LOCALHOST)) hints.ai_flags |= AI_PASSIVE;
         1289  +
         1290  +    sPort = mprintf("%d", iPort);
         1291  +
         1292  +    if(iRet = getaddrinfo(NULL, sPort, &hints, &res)) {
         1293  +      fossil_fatal("Unable to obtain address: %s", gai_strerror(iRet));
         1294  +    }
         1295  +
         1296  +    for(i = res; i; i = i->ai_next) {
         1297  +      listener = socket(i->ai_family, i->ai_socktype, i->ai_protocol);
         1298  +      if(listener < 0) {
         1299  +        fossil_fatal("Unable to create socket");
         1300  +      }
         1301  +	  opt=1;
         1302  +      setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
         1303  +      if(i->ai_family == AF_INET6) {
         1304  +        opt=0;
         1305  +        setsockopt(listener, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
         1306  +      }
         1307  +      if( bind(listener, i->ai_addr, i->ai_addrlen)<0 ){
         1308  +        close(listener);
         1309  +        listener = -1;
         1310  +      }
         1311  +	  break;
  1246   1312       }
  1247         -    inaddr.sin_port = htons(iPort);
  1248         -    listener = socket(AF_INET, SOCK_STREAM, 0);
  1249         -    if( listener<0 ){
         1313  +
         1314  +    free(sPort);
         1315  +    freeaddrinfo(res);
         1316  +
         1317  +    if(listener == -1) {
  1250   1318         iPort++;
  1251   1319         continue;
  1252   1320       }
         1321  +#else
         1322  +    memset(&inaddr, 0, sizeof(inaddr));
         1323  +
         1324  +#ifdef WITH_IPV6
         1325  +    inaddr.sin6_family = AF_INET6;
         1326  +#else
         1327  +    inaddr.sin_family = AF_INET;
         1328  +#endif
         1329  +    if( flags & HTTP_SERVER_LOCALHOST ){
         1330  +#ifdef WITH_IPV6
         1331  +      memcpy(&inaddr.sin6_addr, &in6addr_loopback, sizeof(inaddr.sin6_addr));
         1332  +#else
         1333  +      inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
         1334  +#endif
         1335  +    }else{
         1336  +#ifdef WITH_IPV6
         1337  +      memcpy(&inaddr.sin6_addr, &in6addr_any, sizeof(inaddr.sin6_addr));
         1338  +#else
         1339  +      inaddr.sin_addr.s_addr = htonl(INADDR_ANY);
         1340  +#endif
         1341  +    }
         1342  +#ifdef WITH_IPV6
         1343  +    inaddr.sin6_port = htons(iPort);
         1344  +    listener = socket(AF_INET6, SOCK_STREAM, 0);
         1345  +#else
         1346  +    inaddr.sin_port = htons(iPort);
         1347  +    listener = socket(AF_INET, SOCK_STREAM, 0);
         1348  +#endif
         1349  +    if( listener<0 ){
         1350  +      fossil_fatal("Unable to create socket");
         1351  +    }
  1253   1352   
  1254   1353       /* if we can't terminate nicely, at least allow the socket to be reused */
  1255   1354       setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
         1355  +
         1356  +#ifdef WITH_IPV6
         1357  +    opt=0;
         1358  +    setsockopt(listener, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
         1359  +#endif
  1256   1360   
  1257   1361       if( bind(listener, (struct sockaddr*)&inaddr, sizeof(inaddr))<0 ){
  1258   1362         close(listener);
  1259   1363         iPort++;
  1260   1364         continue;
  1261   1365       }
         1366  +#endif
  1262   1367       break;
  1263   1368     }
  1264   1369     if( iPort>mxPort ){
  1265   1370       if( mnPort==mxPort ){
  1266   1371         fossil_fatal("unable to open listening socket on ports %d", mnPort);
  1267   1372       }else{
  1268   1373         fossil_fatal("unable to open listening socket on any"

Changes to src/http_socket.c.

   133    133   **
   134    134   **    g.urlName       Name of the server.  Ex: www.fossil-scm.org
   135    135   **    g.urlPort       TCP/IP port to use.  Ex: 80
   136    136   **
   137    137   ** Return the number of errors.
   138    138   */
   139    139   int socket_open(void){
          140  +  int error = 0;
          141  +#ifdef HAVE_GETADDRINFO
          142  +  struct addrinfo hints;
          143  +  struct addrinfo* res;
          144  +  struct addrinfo* i;
          145  +  char ip[INET6_ADDRSTRLEN];
          146  +  void* addr;
          147  +  char* sPort;
          148  +
          149  +  memset(&hints, 0, sizeof(struct addrinfo));
          150  +  hints.ai_flags = AI_ADDRCONFIG;
          151  +#ifdef WITH_IPV6
          152  +  hints.ai_family = PF_UNSPEC;
          153  +#else
          154  +  hints.ai_family = PF_INET;
          155  +#endif
          156  +  hints.ai_socktype = SOCK_STREAM;
          157  +  hints.ai_protocol = IPPROTO_TCP;
          158  +
          159  +  sPort = mprintf("%d", g.urlPort);
          160  +
          161  +  if(getaddrinfo(g.urlName, sPort, &hints, &res)) {
          162  +    socket_set_errmsg("can't resolve host name: %s", g.urlName);
          163  +    free(sPort);
          164  +    return 1;
          165  +  }
          166  +  for(i = res; i; i = i->ai_next) {
          167  +    iSocket = socket(i->ai_family, i->ai_socktype, i->ai_protocol);
          168  +    if(iSocket < 0) {
          169  +      continue;
          170  +    }
          171  +    if(connect(iSocket, i->ai_addr, i->ai_addrlen) < 0) {
          172  +      close(iSocket);
          173  +      iSocket = -1;
          174  +      continue;
          175  +    }
          176  +    if(!getnameinfo(i->ai_addr, i->ai_addrlen, ip, sizeof(ip),
          177  +                    NULL, 0, NI_NUMERICHOST))
          178  +        g.zIpAddr = mprintf("%s", ip);
          179  +    break;
          180  +  }
          181  +  if(iSocket == -1) {
          182  +    socket_set_errmsg("cannot connect to host %s:%s", g.urlName, sPort);
          183  +    error = 1;
          184  +  }
          185  +  free(sPort);
          186  +  freeaddrinfo(res);
          187  +#else
   140    188     static struct sockaddr_in addr;  /* The server address */
   141    189     static int addrIsInit = 0;       /* True once addr is initialized */
   142    190   
   143    191     socket_global_init();
   144    192     if( !addrIsInit ){
   145    193       addr.sin_family = AF_INET;
   146    194       addr.sin_port = htons(g.urlPort);
................................................................................
   170    218     if( iSocket<0 ){
   171    219       socket_set_errmsg("cannot create a socket");
   172    220       return 1;
   173    221     }
   174    222     if( connect(iSocket,(struct sockaddr*)&addr,sizeof(addr))<0 ){
   175    223       socket_set_errmsg("cannot connect to host %s:%d", g.urlName, g.urlPort);
   176    224       socket_close();
   177         -    return 1;
          225  +    error = 1;
   178    226     }
          227  +#endif
   179    228   #if !defined(_WIN32)
   180         -  signal(SIGPIPE, SIG_IGN);
          229  +  if(!error)
          230  +    signal(SIGPIPE, SIG_IGN);
   181    231   #endif
   182         -  return 0;
          232  +  return error;
   183    233   }
   184    234   
   185    235   /*
   186    236   ** Send content out over the open socket connection.
   187    237   */
   188    238   size_t socket_send(void *NotUsed, void *pContent, size_t N){
   189    239     size_t sent;

Changes to src/login.c.

   773    773     ** local login is disabled and if we are using HTTP and not HTTPS, 
   774    774     ** then there is no need to check user credentials.
   775    775     **
   776    776     ** This feature allows the "fossil ui" command to give the user
   777    777     ** full access rights without having to log in.
   778    778     */
   779    779     zRemoteAddr = ipPrefix(zIpAddr = PD("REMOTE_ADDR","nil"));
   780         -  if( fossil_strcmp(zIpAddr, "127.0.0.1")==0
          780  +  if( ( fossil_strcmp(zIpAddr, "127.0.0.1")==0 ||
          781  +        fossil_strcmp(zIpAddr, "::1")==0 )
   781    782      && g.useLocalauth
   782    783      && db_get_int("localauth",0)==0
   783    784      && P("HTTPS")==0
   784    785     ){
   785    786       uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
   786    787       g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);
   787    788       zCap = "sx";
................................................................................
  1112   1113   void login_anonymous_available(void){
  1113   1114     if( !g.perm.History &&
  1114   1115         db_exists("SELECT 1 FROM user"
  1115   1116                   " WHERE login='anonymous'"
  1116   1117                   "   AND cap LIKE '%%h%%'") ){
  1117   1118       const char *zUrl = PD("REQUEST_URI", "index");
  1118   1119       @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br />
  1119         -    @ Use <a href="%s(g.zTop)/login?anon=1&amp;g=%T(zUrl)">anonymous login</a>
         1120  +    @ Use <a href="%s(g.zTop)/login?anon=1&amp;g=%T(g.zRoot)%T(zUrl)">anonymous login</a>
  1120   1121       @ to enable hyperlinks.</p>
  1121   1122     }
  1122   1123   }
  1123   1124   
  1124   1125   /*
  1125   1126   ** While rendering a form, call this routine to add the Anti-CSRF token
  1126   1127   ** as a hidden element of the form.

Changes to src/main.c.

   116    116     int fQuiet;             /* True if -quiet flag is present */
   117    117     int fHttpTrace;         /* Trace outbound HTTP requests */
   118    118     int fSystemTrace;       /* Trace calls to fossil_system(), --systemtrace */
   119    119     int fNoSync;            /* Do not do an autosync even.  --nosync */
   120    120     char *zPath;            /* Name of webpage being served */
   121    121     char *zExtra;           /* Extra path information past the webpage name */
   122    122     char *zBaseURL;         /* Full text of the URL being served */
          123  +  char *zRedirectBaseURL; /* Full text of the URL being served to be used in redirect */
          124  +  char *zRoot;            /* Repository web root */
   123    125     char *zTop;             /* Parent directory of zPath */
   124    126     const char *zContentType;  /* The content type of the input HTTP request */
   125    127     int iErrPriority;       /* Priority of current error message */
   126    128     char *zErrMsg;          /* Text of an error message */
   127    129     int sslNotAvailable;    /* SSL is not available.  Do not redirect to https: */
   128    130     Blob cgiIn;             /* Input to an xfer www method */
   129    131     int cgiOutput;          /* Write error and status messages to CGI */
................................................................................
  1071   1073   void set_base_url(void){
  1072   1074     int i;
  1073   1075     const char *zHost;
  1074   1076     const char *zMode;
  1075   1077     const char *zCur;
  1076   1078   
  1077   1079     if( g.zBaseURL!=0 ) return;
         1080  +  if( g.zRoot==0 ) g.zRoot="";
  1078   1081     zHost = PD("HTTP_HOST","");
  1079   1082     zMode = PD("HTTPS","off");
  1080   1083     zCur = PD("SCRIPT_NAME","/");
  1081   1084     i = strlen(zCur);
  1082   1085     while( i>0 && zCur[i-1]=='/' ) i--;
  1083   1086     if( fossil_stricmp(zMode,"on")==0 ){
  1084         -    g.zBaseURL = mprintf("https://%s%.*s", zHost, i, zCur);
         1087  +    g.zBaseURL = mprintf("https://%s%s%.*s", zHost, g.zRoot, i, zCur);
  1085   1088       g.zTop = &g.zBaseURL[8+strlen(zHost)];
  1086   1089     }else{
  1087         -    g.zBaseURL = mprintf("http://%s%.*s", zHost, i, zCur);
         1090  +    g.zBaseURL = mprintf("http://%s%s%.*s", zHost, g.zRoot, i, zCur);
  1088   1091       g.zTop = &g.zBaseURL[7+strlen(zHost)];
  1089   1092     }
  1090   1093   }
  1091   1094   
  1092   1095   /*
  1093   1096   ** Send an HTTP redirect back to the designated Index Page.
  1094   1097   */
................................................................................
  1659   1662   **
  1660   1663   ** Open a socket and begin listening and responding to HTTP requests on
  1661   1664   ** TCP port 8080, or on any other TCP port defined by the -P or
  1662   1665   ** --port option.  The optional argument is the name of the repository.
  1663   1666   ** The repository argument may be omitted if the working directory is
  1664   1667   ** within an open checkout.
  1665   1668   **
         1669  +** If HTTP requests are being reverse proxied to the fossil server,
         1670  +** and in proxy server fossil is mapped at a virtual directory, then
         1671  +** virtual directory can be specified using optional -R or --root option.
         1672  +**
  1666   1673   ** The "ui" command automatically starts a web browser after initializing
  1667   1674   ** the web server.  The "ui" command also binds to 127.0.0.1 and so will
  1668   1675   ** only process HTTP traffic from the local machine.
  1669   1676   **
  1670   1677   ** In the "server" command, the REPOSITORY can be a directory (aka folder)
  1671   1678   ** that contains one or more respositories with names ending in ".fossil".
  1672   1679   ** In that case, the first element of the URL is used to select among the
................................................................................
  1673   1680   ** various repositories.
  1674   1681   **
  1675   1682   ** By default, the "ui" command provides full administrative access without
  1676   1683   ** having to log in.  This can be disabled by setting turning off the
  1677   1684   ** "localauth" setting.  Automatic login for the "server" command is available
  1678   1685   ** if the --localauth option is present and the "localauth" setting is off
  1679   1686   ** and the connection is from localhost.
  1680         -**
  1681   1687   ** Options:
  1682   1688   **   --localauth         enable automatic login for requests from localhost
  1683   1689   **   -P|--port TCPPORT   listen to request on port TCPPORT
         1690  +**   -R|--root ROOT      map fossil at virtual directory ROOT in reverse 
         1691  +**                       proxying
  1684   1692   **   --th-trace          trace TH1 execution (for debugging purposes)
  1685   1693   **
  1686   1694   ** See also: cgi, http, winsrv
  1687   1695   */
  1688   1696   void cmd_webserver(void){
  1689   1697     int iPort, mxPort;        /* Range of TCP ports allowed */
  1690   1698     const char *zPort;        /* Value of the --port option */
................................................................................
  1697   1705   #if defined(_WIN32)
  1698   1706     const char *zStopperFile;    /* Name of file used to terminate server */
  1699   1707     zStopperFile = find_option("stopper", 0, 1);
  1700   1708   #endif
  1701   1709   
  1702   1710     g.thTrace = find_option("th-trace", 0, 0)!=0;
  1703   1711     g.useLocalauth = find_option("localauth", 0, 0)!=0;
         1712  +  g.zRoot = find_option("root", "R", 1);
  1704   1713     if( g.thTrace ){
  1705   1714       blob_zero(&g.thLog);
  1706   1715     }
  1707   1716     zPort = find_option("port", "P", 1);
  1708   1717     zNotFound = find_option("notfound", 0, 1);
  1709   1718     if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
  1710   1719     isUiCmd = g.argv[1][0]=='u';
................................................................................
  1734   1743             break;
  1735   1744           }
  1736   1745         }
  1737   1746       }
  1738   1747   #else
  1739   1748       zBrowser = db_get("web-browser", "open");
  1740   1749   #endif
  1741         -    zBrowserCmd = mprintf("%s http://localhost:%%d/ &", zBrowser);
         1750  +    zBrowserCmd = mprintf("%s http://localhost:%%d/%s &", zBrowser, (g.zRoot==0?"":g.zRoot));
  1742   1751     }
  1743   1752     db_close(1);
  1744   1753     if( cgi_http_server(iPort, mxPort, zBrowserCmd, flags) ){
  1745   1754       fossil_fatal("unable to listen on TCP socket %d", iPort);
  1746   1755     }
  1747   1756     g.sslNotAvailable = 1;
  1748   1757     g.httpIn = stdin;