Fossil

Check-in [0a51263a23]
Login

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

Overview
Comment:Merge the experimental ssh:// changes into the trunk.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 0a51263a23b658ab98a7ddb064e81422b7ab02bc
User & Date: drh 2010-08-26 13:18:35.000
Context
2010-08-26
13:29
Close pipes before killing off the child process. ... (check-in: 360b047759 user: drh tags: trunk)
13:18
Merge the experimental ssh:// changes into the trunk. ... (check-in: 0a51263a23 user: drh tags: trunk)
13:17
Untested implementation of popen2() for windows. ... (Closed-Leaf check-in: 34ea1e4abb user: drh tags: experimental)
11:31
Enable proxy handling for "config push" and "config pull" Tickets [0d1a4fe14f1fb6ee2082] and [311fbfd6b696142a4b2c86dc]. ... (check-in: 1a9fe88b66 user: drh tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/cgi.c.
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
  cgi_parameter("","");  /* Force the parameters into sorted order */
  for(i=0; i<nUsedQP; i++){
    cgi_printf("%s = %s  <br />\n",
       htmlize(aParamQP[i].zName, -1), htmlize(aParamQP[i].zValue, -1));
  }
}

/*
** Write HTML text for an option menu to standard output.  zParam
** is the query parameter that the option menu sets.  zDflt is the
** initial value of the option menu.  Addition arguments are name/value
** pairs that define values on the menu.  The list is terminated with
** a single NULL argument.
*/
void cgi_optionmenu(int in, const char *zP, const char *zD, ...){
  va_list ap;
  char *zName, *zVal;
  int dfltSeen = 0;
  cgi_printf("%*s<select size=1 name=\"%s\">\n", in, "", zP);
  va_start(ap, zD);
  while( (zName = va_arg(ap, char*))!=0 && (zVal = va_arg(ap, char*))!=0 ){
    if( strcmp(zVal,zD)==0 ){ dfltSeen = 1; break; }
  }
  va_end(ap);
  if( !dfltSeen ){
    if( zD[0] ){
      cgi_printf("%*s<option value=\"%h\" selected>%h</option>\n",
        in+2, "", zD, zD);
    }else{
      cgi_printf("%*s<option value=\"\" selected>&nbsp;</option>\n", in+2, "");
    }
  }
  va_start(ap, zD);
  while( (zName = va_arg(ap, char*))!=0 && (zVal = va_arg(ap, char*))!=0 ){
    if( zName[0] ){
      cgi_printf("%*s<option value=\"%h\"%s>%h</option>\n",
        in+2, "",
        zVal,
        strcmp(zVal, zD) ? "" : " selected",
        zName
      );
    }else{
      cgi_printf("%*s<option value=\"\"%s>&nbsp;</option>\n",
        in+2, "",
        strcmp(zVal, zD) ? "" : " selected"
      );
    }
  }
  va_end(ap);
  cgi_printf("%*s</select>\n", in, "");
}

/*
** This routine works a lot like cgi_optionmenu() except that the list of
** values is contained in an array.  Also, the values are just values, not
** name/value pairs as in cgi_optionmenu.
*/
void cgi_v_optionmenu(
  int in,              /* Indent by this amount */
  const char *zP,      /* The query parameter name */
  const char *zD,      /* Default value */
  const char **az      /* NULL-terminated list of allowed values */
){
  const char *zVal;
  int i;
  cgi_printf("%*s<select size=1 name=\"%s\">\n", in, "", zP);
  for(i=0; az[i]; i++){
    if( strcmp(az[i],zD)==0 ) break;
  }
  if( az[i]==0 ){
    if( zD[0]==0 ){
      cgi_printf("%*s<option value=\"\" selected>&nbsp;</option>\n",
       in+2, "");
    }else{
      cgi_printf("%*s<option value=\"%h\" selected>%h</option>\n",
       in+2, "", zD, zD);
    }
  }
  while( (zVal = *(az++))!=0  ){
    if( zVal[0] ){
      cgi_printf("%*s<option value=\"%h\"%s>%h</option>\n",
        in+2, "",
        zVal,
        strcmp(zVal, zD) ? "" : " selected",
        zVal
      );
    }else{
      cgi_printf("%*s<option value=\"\"%s>&nbsp;</option>\n",
        in+2, "",
        strcmp(zVal, zD) ? "" : " selected"
      );
    }
  }
  cgi_printf("%*s</select>\n", in, "");
}

/*
** This routine works a lot like cgi_v_optionmenu() except that the list
** is a list of pairs.  The first element of each pair is the value used
** internally and the second element is the value displayed to the user.
*/
void cgi_v_optionmenu2(
  int in,              /* Indent by this amount */
  const char *zP,      /* The query parameter name */
  const char *zD,      /* Default value */
  const char **az      /* NULL-terminated list of allowed values */
){
  const char *zVal;
  int i;
  cgi_printf("%*s<select size=1 name=\"%s\">\n", in, "", zP);
  for(i=0; az[i]; i+=2){
    if( strcmp(az[i],zD)==0 ) break;
  }
  if( az[i]==0 ){
    if( zD[0]==0 ){
      cgi_printf("%*s<option value=\"\" selected>&nbsp;</option>\n",
       in+2, "");
    }else{
      cgi_printf("%*s<option value=\"%h\" selected>%h</option>\n",
       in+2, "", zD, zD);
    }
  }
  while( (zVal = *(az++))!=0  ){
    const char *zName = *(az++);
    if( zName[0] ){
      cgi_printf("%*s<option value=\"%h\"%s>%h</option>\n",
        in+2, "",
        zVal,
        strcmp(zVal, zD) ? "" : " selected",
        zName
      );
    }else{
      cgi_printf("%*s<option value=\"%h\"%s>&nbsp;</option>\n",
        in+2, "",
        zVal,
        strcmp(zVal, zD) ? "" : " selected"
      );
    }
  }
  cgi_printf("%*s</select>\n", in, "");
}

/*
** This routine works like "printf" except that it has the
** extra formatting capabilities such as %h and %t.
*/
void cgi_printf(const char *zFormat, ...){
  va_list ap;
  va_start(ap,zFormat);







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







849
850
851
852
853
854
855







































































































































856
857
858
859
860
861
862
  cgi_parameter("","");  /* Force the parameters into sorted order */
  for(i=0; i<nUsedQP; i++){
    cgi_printf("%s = %s  <br />\n",
       htmlize(aParamQP[i].zName, -1), htmlize(aParamQP[i].zValue, -1));
  }
}








































































































































/*
** This routine works like "printf" except that it has the
** extra formatting capabilities such as %h and %t.
*/
void cgi_printf(const char *zFormat, ...){
  va_list ap;
  va_start(ap,zFormat);
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
** Send a reply indicating that the HTTP request was malformed
*/
static void malformed_request(void){
  cgi_set_status(501, "Not Implemented");
  cgi_printf(
    "<html><body>Unrecognized HTTP Request</body></html>\n"
  );
  cgi_reply();
  fossil_exit(0);
}

/*
** Send a reply indicating that the HTTP request is forbidden
*/
static void forbidden_request(void){
  cgi_set_status(403, "Forbidden");
  cgi_printf(
    "<html><body>Access Denied</body></html>\n"
  );
  cgi_reply();
  fossil_exit(0);
}

/*
** Panic and die while processing a webpage.
*/







<
<
<
<
<
<
<
<
<
<
<
<







877
878
879
880
881
882
883












884
885
886
887
888
889
890
** Send a reply indicating that the HTTP request was malformed
*/
static void malformed_request(void){
  cgi_set_status(501, "Not Implemented");
  cgi_printf(
    "<html><body>Unrecognized HTTP Request</body></html>\n"
  );












  cgi_reply();
  fossil_exit(0);
}

/*
** Panic and die while processing a webpage.
*/
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
** and subsequent code handles the actual generation of the webpage.
*/
void cgi_handle_http_request(const char *zIpAddr){
  char *z, *zToken;
  int i;
  struct sockaddr_in remoteName;
  size_t size = sizeof(struct sockaddr_in);
  int accessTokenSeen = 0;
  char zLine[2000];     /* A single line of input. */

  g.fullHttpReply = 1;
  if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
    malformed_request();
  }
  zToken = extract_token(zLine, &z);







<







936
937
938
939
940
941
942

943
944
945
946
947
948
949
** and subsequent code handles the actual generation of the webpage.
*/
void cgi_handle_http_request(const char *zIpAddr){
  char *z, *zToken;
  int i;
  struct sockaddr_in remoteName;
  size_t size = sizeof(struct sockaddr_in);

  char zLine[2000];     /* A single line of input. */

  g.fullHttpReply = 1;
  if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
    malformed_request();
  }
  zToken = extract_token(zLine, &z);
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
      cgi_setenv("HTTPS", zVal);
    }else if( strcmp(zFieldName,"host:")==0 ){
      cgi_setenv("HTTP_HOST", zVal);
    }else if( strcmp(zFieldName,"if-none-match:")==0 ){
      cgi_setenv("HTTP_IF_NONE_MATCH", zVal);
    }else if( strcmp(zFieldName,"if-modified-since:")==0 ){
      cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal);
    }else if( strcmp(zFieldName,"x-fossil-access-token:")==0 ){
      if( g.zAccessToken ){
        if( strcmp(zVal,g.zAccessToken)==0 ){
          accessTokenSeen = 1;
        }
      }
    }
#if 0
    else if( strcmp(zFieldName,"referer:")==0 ){
      cgi_setenv("HTTP_REFERER", zVal);
    }else if( strcmp(zFieldName,"user-agent:")==0 ){
      cgi_setenv("HTTP_USER_AGENT", zVal);
    }
#endif
  }

  if( g.zAccessToken && !accessTokenSeen ){
    forbidden_request();
  }

  cgi_init();
}

#if INTERFACE
/* 
** Bitmap values for the flags parameter to cgi_http_server().
*/
#define HTTP_SERVER_LOCALHOST      0x0001     /* Bind to 127.0.0.1 only */
#define HTTP_SERVER_STDIN          0x0002     /* Monitor stdin for "quit" */

#endif /* INTERFACE */

/*
** Maximum number of child processes that we can have running
** at one time before we start slowing things down.
*/







<
<
<
<
<
<










<
<
<
<








<







999
1000
1001
1002
1003
1004
1005






1006
1007
1008
1009
1010
1011
1012
1013
1014
1015




1016
1017
1018
1019
1020
1021
1022
1023

1024
1025
1026
1027
1028
1029
1030
      cgi_setenv("HTTPS", zVal);
    }else if( strcmp(zFieldName,"host:")==0 ){
      cgi_setenv("HTTP_HOST", zVal);
    }else if( strcmp(zFieldName,"if-none-match:")==0 ){
      cgi_setenv("HTTP_IF_NONE_MATCH", zVal);
    }else if( strcmp(zFieldName,"if-modified-since:")==0 ){
      cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal);






    }
#if 0
    else if( strcmp(zFieldName,"referer:")==0 ){
      cgi_setenv("HTTP_REFERER", zVal);
    }else if( strcmp(zFieldName,"user-agent:")==0 ){
      cgi_setenv("HTTP_USER_AGENT", zVal);
    }
#endif
  }





  cgi_init();
}

#if INTERFACE
/* 
** Bitmap values for the flags parameter to cgi_http_server().
*/
#define HTTP_SERVER_LOCALHOST      0x0001     /* Bind to 127.0.0.1 only */


#endif /* INTERFACE */

/*
** Maximum number of child processes that we can have running
** at one time before we start slowing things down.
*/
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
      /* Slow down if connections are arriving too fast */
      sleep( nchildren-MAX_PARALLEL );
    }
    delay.tv_sec = 60;
    delay.tv_usec = 0;
    FD_ZERO(&readfds);
    FD_SET( listener, &readfds);
    if( flags & HTTP_SERVER_STDIN ){
      FD_SET( 0, &readfds);
    }
    select( listener+1, &readfds, 0, 0, &delay);
    if( FD_ISSET(0, &readfds) ){
      int i;
      char zIn[200];
      assert( flags & HTTP_SERVER_STDIN );
      zIn[0] = 0;
      fgets(zIn, sizeof(zIn), stdin);
      for(i=0; zIn[i] && zIn[i]!='\n'; i++){}
      zIn[i] = 0;
      if( strcmp(zIn, "quit")==0 || feof(stdin) ) fossil_exit(0);
    }
    if( FD_ISSET(listener, &readfds) ){
      lenaddr = sizeof(inaddr);
      connection = accept(listener, (struct sockaddr*)&inaddr,
                                    (socklen_t*) &lenaddr);
      if( connection>=0 ){
        child = fork();
        if( child!=0 ){







<
<
<

<
<
<
<
<
<
<
<
<
<







1104
1105
1106
1107
1108
1109
1110



1111










1112
1113
1114
1115
1116
1117
1118
      /* Slow down if connections are arriving too fast */
      sleep( nchildren-MAX_PARALLEL );
    }
    delay.tv_sec = 60;
    delay.tv_usec = 0;
    FD_ZERO(&readfds);
    FD_SET( listener, &readfds);



    select( listener+1, &readfds, 0, 0, &delay);










    if( FD_ISSET(listener, &readfds) ){
      lenaddr = sizeof(inaddr);
      connection = accept(listener, (struct sockaddr*)&inaddr,
                                    (socklen_t*) &lenaddr);
      if( connection>=0 ){
        child = fork();
        if( child!=0 ){
Changes to src/diffcmd.c.
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include "config.h"
#include "diffcmd.h"
#include <assert.h>

/*
** Shell-escape the given string.  Append the result to a blob.
*/
static void shell_escape(Blob *pBlob, const char *zIn){
  int n = blob_size(pBlob);
  int k = strlen(zIn);
  int i, c;
  char *z;
  for(i=0; (c = zIn[i])!=0; i++){
    if( isspace(c) || c=='"' || (c=='\\' && zIn[i+1]!=0) ){
      blob_appendf(pBlob, "\"%s\"", zIn);







|







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include "config.h"
#include "diffcmd.h"
#include <assert.h>

/*
** Shell-escape the given string.  Append the result to a blob.
*/
void shell_escape(Blob *pBlob, const char *zIn){
  int n = blob_size(pBlob);
  int k = strlen(zIn);
  int i, c;
  char *z;
  for(i=0; (c = zIn[i])!=0; i++){
    if( isspace(c) || c=='"' || (c=='\\' && zIn[i+1]!=0) ){
      blob_appendf(pBlob, "\"%s\"", zIn);
Changes to src/http.c.
40
41
42
43
44
45
46



47
48
49
50
51
52
53
  Blob pw;             /* The nonce with user password appended */
  Blob sig;            /* The signature field */

  blob_zero(pLogin);
  if( g.urlUser==0 || strcmp(g.urlUser, "anonymous")==0 ){
     return;  /* If no login card for users "nobody" and "anonymous" */
  }



  blob_zero(&nonce);
  blob_zero(&pw);
  sha1sum_blob(pPayload, &nonce);
  blob_copy(&pw, &nonce);
  zLogin = g.urlUser;
  if( g.urlPasswd ){
    zPw = g.urlPasswd;







>
>
>







40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
  Blob pw;             /* The nonce with user password appended */
  Blob sig;            /* The signature field */

  blob_zero(pLogin);
  if( g.urlUser==0 || strcmp(g.urlUser, "anonymous")==0 ){
     return;  /* If no login card for users "nobody" and "anonymous" */
  }
  if( g.urlIsSsh ){
     return;  /* If no login card for SSH: */
  }
  blob_zero(&nonce);
  blob_zero(&pw);
  sha1sum_blob(pPayload, &nonce);
  blob_copy(&pw, &nonce);
  zLogin = g.urlUser;
  if( g.urlPasswd ){
    zPw = g.urlPasswd;
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
  blob_appendf(pHdr, "Host: %s\r\n", g.urlHostname);
  blob_appendf(pHdr, "User-Agent: Fossil/" MANIFEST_VERSION "\r\n");
  if( g.fHttpTrace ){
    blob_appendf(pHdr, "Content-Type: application/x-fossil-debug\r\n");
  }else{
    blob_appendf(pHdr, "Content-Type: application/x-fossil\r\n");
  }
  blob_appendf(pHdr, "Content-Length: %d\r\n", blob_size(pPayload));
  if( g.zAccessToken ){
    blob_appendf(pHdr, "X-Fossil-Access-Token: %s\r\n", g.zAccessToken);
  }
  blob_appendf(pHdr, "\r\n");
}

/*
** Sign the content in pSend, compress it, and send it to the server
** via HTTP or HTTPS.  Get a reply, uncompress the reply, and store the reply
** in pRecv.  pRecv is assumed to be uninitialized when
** this routine is called - this routine will initialize it.







|
<
<
<
<







112
113
114
115
116
117
118
119




120
121
122
123
124
125
126
  blob_appendf(pHdr, "Host: %s\r\n", g.urlHostname);
  blob_appendf(pHdr, "User-Agent: Fossil/" MANIFEST_VERSION "\r\n");
  if( g.fHttpTrace ){
    blob_appendf(pHdr, "Content-Type: application/x-fossil-debug\r\n");
  }else{
    blob_appendf(pHdr, "Content-Type: application/x-fossil\r\n");
  }
  blob_appendf(pHdr, "Content-Length: %d\r\n\r\n", blob_size(pPayload));




}

/*
** Sign the content in pSend, compress it, and send it to the server
** via HTTP or HTTPS.  Get a reply, uncompress the reply, and store the reply
** in pRecv.  pRecv is assumed to be uninitialized when
** this routine is called - this routine will initialize it.
136
137
138
139
140
141
142

143
144
145
146
147
148
149
  Blob hdr;             /* The HTTP request header */
  int closeConnection;  /* True to close the connection when done */
  int iLength;          /* Length of the reply payload */
  int rc;               /* Result code */
  int iHttpVersion;     /* Which version of HTTP protocol server uses */
  char *zLine;          /* A single line of the reply header */
  int i;                /* Loop counter */


  if( transport_open() ){
    fossil_fatal(transport_errmsg());
  }

  /* Construct the login card and prepare the complete payload */
  blob_zero(&login);







>







135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
  Blob hdr;             /* The HTTP request header */
  int closeConnection;  /* True to close the connection when done */
  int iLength;          /* Length of the reply payload */
  int rc;               /* Result code */
  int iHttpVersion;     /* Which version of HTTP protocol server uses */
  char *zLine;          /* A single line of the reply header */
  int i;                /* Loop counter */
  int isError = 0;      /* True if the reply is an error message */

  if( transport_open() ){
    fossil_fatal(transport_errmsg());
  }

  /* Construct the login card and prepare the complete payload */
  blob_zero(&login);
192
193
194
195
196
197
198

199
200
201
202
203
204
205
  
  /*
  ** Read and interpret the server reply
  */
  closeConnection = 1;
  iLength = -1;
  while( (zLine = transport_receive_line())!=0 && zLine[0]!=0 ){

    if( strncasecmp(zLine, "http/1.", 7)==0 ){
      if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err;
      if( rc!=200 && rc!=302 ){
        int ii;
        for(ii=7; zLine[ii] && zLine[ii]!=' '; ii++){}
        while( zLine[ii]==' ' ) ii++;
        fossil_fatal("server says: %s\n", &zLine[ii]);







>







192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
  
  /*
  ** Read and interpret the server reply
  */
  closeConnection = 1;
  iLength = -1;
  while( (zLine = transport_receive_line())!=0 && zLine[0]!=0 ){
    /* printf("[%s]\n", zLine); fflush(stdout); */
    if( strncasecmp(zLine, "http/1.", 7)==0 ){
      if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err;
      if( rc!=200 && rc!=302 ){
        int ii;
        for(ii=7; zLine[ii] && zLine[ii]!=' '; ii++){}
        while( zLine[ii]==' ' ) ii++;
        fossil_fatal("server says: %s\n", &zLine[ii]);
229
230
231
232
233
234
235


236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253














254
255
256
257
258
259
260
      j = strlen(zLine) - 1; 
      if( j>4 && strcmp(&zLine[j-4],"/xfer")==0 ) zLine[j-4] = 0;
      fossil_print("redirect to %s\n", &zLine[i]);
      url_parse(&zLine[i]);
      transport_close();
      http_exchange(pSend, pReply, useLogin);
      return;


    }
  }
  if( rc!=200 ){
    fossil_fatal("\"location:\" missing from 302 redirect reply");
    goto write_err;
  }

  /*
  ** Extract the reply payload that follows the header
  */
  if( iLength<0 ){
    fossil_fatal("server did not reply");
    goto write_err;
  }
  blob_zero(pReply);
  blob_resize(pReply, iLength);
  iLength = transport_receive(blob_buffer(pReply), iLength);
  blob_resize(pReply, iLength);














  if( g.fHttpTrace ){
    printf("HTTP RECEIVE:\n%s\n=======================\n", blob_str(pReply));
  }else{
    blob_uncompress(pReply, pReply);
  }

  /*







>
>


















>
>
>
>
>
>
>
>
>
>
>
>
>
>







230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
      j = strlen(zLine) - 1; 
      if( j>4 && strcmp(&zLine[j-4],"/xfer")==0 ) zLine[j-4] = 0;
      fossil_print("redirect to %s\n", &zLine[i]);
      url_parse(&zLine[i]);
      transport_close();
      http_exchange(pSend, pReply, useLogin);
      return;
    }else if( strncasecmp(zLine, "content-type: text/html", 23)==0 ){
      isError = 1;
    }
  }
  if( rc!=200 ){
    fossil_fatal("\"location:\" missing from 302 redirect reply");
    goto write_err;
  }

  /*
  ** Extract the reply payload that follows the header
  */
  if( iLength<0 ){
    fossil_fatal("server did not reply");
    goto write_err;
  }
  blob_zero(pReply);
  blob_resize(pReply, iLength);
  iLength = transport_receive(blob_buffer(pReply), iLength);
  blob_resize(pReply, iLength);
  if( isError ){
    char *z;
    int i, j;
    z = blob_str(pReply);
    for(i=j=0; z[i]; i++, j++){
      if( z[i]=='<' ){
        while( z[i] && z[i]!='>' ) i++;
        if( z[i]==0 ) break;
      }
      z[j] = z[i];
    }
    z[j] = 0;
    fossil_fatal("server sends error: %s", z);
  }
  if( g.fHttpTrace ){
    printf("HTTP RECEIVE:\n%s\n=======================\n", blob_str(pReply));
  }else{
    blob_uncompress(pReply, pReply);
  }

  /*
Changes to src/http_transport.c.
36
37
38
39
40
41
42









43
44
45
46
47
48
49
  int nRcvd;              /* Number of bytes received */
  FILE *pFile;            /* File I/O for FILE: */
  char *zOutFile;         /* Name of outbound file for FILE: */
  char *zInFile;          /* Name of inbound file for FILE: */
} transport = {
  0, 0, 0, 0, 0, 0, 0
};










/*
** Return the current transport error message.
*/
const char *transport_errmsg(void){
  #ifdef FOSSIL_ENABLE_SSL
  if( g.urlIsHttps ){







>
>
>
>
>
>
>
>
>







36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
  int nRcvd;              /* Number of bytes received */
  FILE *pFile;            /* File I/O for FILE: */
  char *zOutFile;         /* Name of outbound file for FILE: */
  char *zInFile;          /* Name of inbound file for FILE: */
} transport = {
  0, 0, 0, 0, 0, 0, 0
};

/*
** Information about the connection to the SSH subprocess when
** using the ssh:// sync method.
*/
static int sshPid;             /* Process id of ssh subprocess */
static int sshIn;              /* From ssh subprocess to this process */
static FILE *sshOut;           /* From this to ssh subprocess */


/*
** Return the current transport error message.
*/
const char *transport_errmsg(void){
  #ifdef FOSSIL_ENABLE_SSL
  if( g.urlIsHttps ){
61
62
63
64
65
66
67















68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98


99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123










124
125
126
127
128
129
130
  if( pnSent ) *pnSent = transport.nSent;
  if( pnRcvd ) *pnRcvd = transport.nRcvd;
  if( resetFlag ){
    transport.nSent = 0;
    transport.nRcvd = 0;
  }
}
















/*
** Global initialization of the transport layer
*/
void transport_global_startup(void){
  if( g.urlIsSsh ){
    char *zCmd;
    int i;
    char zIn[200];
#ifdef __MINGW32__
    fossil_fatal("the ssh:// sync method is currently only supported on unix");
#endif
    if( g.urlUser && g.urlUser[0] ){
      zCmd = mprintf(
         "ssh -L127.0.0.1:%d:127.0.0.1:%d %s@%s "
               "\"fossil server -P %d '%s'\"", 
         g.urlPort, g.urlPort, g.urlUser, g.urlSshHost, g.urlPort, g.urlPath
      );
    }else{
      zCmd = mprintf(
         "ssh -L127.0.0.1:%d:127.0.0.1:%d %s \"fossil sshd -P %d '%s'\"", 
         g.urlPort, g.urlPort, g.urlSshHost, g.urlPort, g.urlPath
      );
    }
    printf("%s\n", zCmd);
    popen2(zCmd, &g.sshIn, &g.sshOut, &g.sshPid);
    if( g.sshPid==0 ){
      fossil_fatal("cannot start ssh tunnel using [%s]", zCmd);
    }
    free(zCmd);
    zIn[0] = 0;


    fgets(zIn, sizeof(zIn), g.sshIn);
    for(i=0; zIn[i] && zIn[i]!='\n'; i++){}
    zIn[i] = 0;
    if( memcmp(zIn, "Access-Token: ", 14)!=0 ){
      pclose2(g.sshIn, g.sshOut, g.sshPid);
      fossil_fatal("failed to start ssh tunnel");
    }
    g.zAccessToken = mprintf("%s", &zIn[14]);
  }
}

/*
** Open a connection to the server.  The server is defined by the following
** global variables:
**
**   g.urlName        Name of the server.  Ex: www.fossil-scm.org
**   g.urlPort        TCP/IP port.  Ex: 80
**   g.urlIsHttps     Use TLS for the connection
**
** Return the number of errors.
*/
int transport_open(void){
  int rc = 0;
  if( transport.isOpen==0 ){
    if( g.urlIsHttps ){










      #ifdef FOSSIL_ENABLE_SSL
      rc = ssl_open();
      if( rc==0 ) transport.isOpen = 1;
      #else
      socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support");
      rc = 1;
      #endif







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







<
|




|
<
<
<
<

|
<
<
<

|
|
|



<
>
>
|
<
<
|
<
|

<
















|
>
>
>
>
>
>
>
>
>
>







70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98

99
100
101
102
103
104




105
106



107
108
109
110
111
112
113

114
115
116


117

118
119

120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
  if( pnSent ) *pnSent = transport.nSent;
  if( pnRcvd ) *pnRcvd = transport.nRcvd;
  if( resetFlag ){
    transport.nSent = 0;
    transport.nRcvd = 0;
  }
}

/*
** Read text from sshIn.  Zero-terminate and remove trailing
** whitespace.
*/
static void sshin_read(char *zBuf, int szBuf){
  int got;
  zBuf[0] = 0;
  got = read(sshIn, zBuf, szBuf-1);
  while( got>=0 ){
    zBuf[got] = 0;
    if( got==0 || !isspace(zBuf[got-1]) ) break;
    got--;
  }
}

/*
** Global initialization of the transport layer
*/
void transport_global_startup(void){
  if( g.urlIsSsh ){
    char *zCmd;

    char zIn[20];
#ifdef __MINGW32__
    fossil_fatal("the ssh:// sync method is currently only supported on unix");
#endif
    if( g.urlUser && g.urlUser[0] ){
      zCmd = mprintf("ssh -e none %s@%s", g.urlUser, g.urlName);




    }else{
      zCmd = mprintf("ssh -e none %s", g.urlName);



    }
    /* printf("%s\n", zCmd); */
    popen2(zCmd, &sshIn, &sshOut, &sshPid);
    if( sshPid==0 ){
      fossil_fatal("cannot start ssh tunnel using [%s]", zCmd);
    }
    free(zCmd);

    fprintf(sshOut, "echo test\n");
    fflush(sshOut);
    sshin_read(zIn, sizeof(zIn));


    if( memcmp(zIn, "test", 4)!=0 ){

      fossil_fatal("ssh connection failed");
    }

  }
}

/*
** Open a connection to the server.  The server is defined by the following
** global variables:
**
**   g.urlName        Name of the server.  Ex: www.fossil-scm.org
**   g.urlPort        TCP/IP port.  Ex: 80
**   g.urlIsHttps     Use TLS for the connection
**
** Return the number of errors.
*/
int transport_open(void){
  int rc = 0;
  if( transport.isOpen==0 ){
    if( g.urlIsSsh ){
      Blob cmd;
      blob_zero(&cmd);
      shell_escape(&cmd, g.urlFossil);
      blob_append(&cmd, " test-http ", -1);
      shell_escape(&cmd, g.urlPath);
      /* fprintf(stdout, "%s\n", blob_str(&cmd)); */
      fprintf(sshOut, "%s\n", blob_str(&cmd));
      fflush(sshOut);
      blob_reset(&cmd);
    }else if( g.urlIsHttps ){
      #ifdef FOSSIL_ENABLE_SSL
      rc = ssl_open();
      if( rc==0 ) transport.isOpen = 1;
      #else
      socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support");
      rc = 1;
      #endif
154
155
156
157
158
159
160
161


162
163
164
165
166
167
168
void transport_close(void){
  if( transport.isOpen ){
    free(transport.pBuf);
    transport.pBuf = 0;
    transport.nAlloc = 0;
    transport.nUsed = 0;
    transport.iCursor = 0;
    if( g.urlIsHttps ){


      #ifdef FOSSIL_ENABLE_SSL
      ssl_close();
      #endif
    }else if( g.urlIsFile ){
      if( transport.pFile ){ 
        fclose(transport.pFile);
        transport.pFile = 0;







|
>
>







177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
void transport_close(void){
  if( transport.isOpen ){
    free(transport.pBuf);
    transport.pBuf = 0;
    transport.nAlloc = 0;
    transport.nUsed = 0;
    transport.iCursor = 0;
    if( g.urlIsSsh ){
      /* No-op */
    }else if( g.urlIsHttps ){
      #ifdef FOSSIL_ENABLE_SSL
      ssl_close();
      #endif
    }else if( g.urlIsFile ){
      if( transport.pFile ){ 
        fclose(transport.pFile);
        transport.pFile = 0;
181
182
183
184
185
186
187
188





189
190
191
192
193
194
195
/*
** Send content over the wire.
*/
void transport_send(Blob *toSend){
  char *z = blob_buffer(toSend);
  int n = blob_size(toSend);
  transport.nSent += n;
  if( g.urlIsHttps ){





    #ifdef FOSSIL_ENABLE_SSL
    int sent;
    while( n>0 ){
      sent = ssl_send(0, z, n);
      /* printf("Sent %d of %d bytes\n", sent, n); fflush(stdout); */
      if( sent<=0 ) break;
      n -= sent;







|
>
>
>
>
>







206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/*
** Send content over the wire.
*/
void transport_send(Blob *toSend){
  char *z = blob_buffer(toSend);
  int n = blob_size(toSend);
  transport.nSent += n;
  if( g.urlIsSsh ){
    int sent;
    sent = fwrite(z, 1, n, sshOut);
    fflush(sshOut);
    /* printf("sent %d of %d bytes\n", sent, n); fflush(stdout); */
  }else if( g.urlIsHttps ){
    #ifdef FOSSIL_ENABLE_SSL
    int sent;
    while( n>0 ){
      sent = ssl_send(0, z, n);
      /* printf("Sent %d of %d bytes\n", sent, n); fflush(stdout); */
      if( sent<=0 ) break;
      n -= sent;
209
210
211
212
213
214
215
216


217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236































237
238
239
240
241
242
243
244
245
246

247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
}

/*
** This routine is called when the outbound message is complete and
** it is time to being recieving a reply.
*/
void transport_flip(void){
  if( g.urlIsFile ){


    char *zCmd;
    fclose(transport.pFile);
    zCmd = mprintf("\"%s\" http \"%s\" \"%s\" \"%s\" 127.0.0.1",
       g.argv[0], g.urlName, transport.zOutFile, transport.zInFile
    );
    portable_system(zCmd);
    free(zCmd);
    transport.pFile = fopen(transport.zInFile, "rb");
  }
}

/*
** This routine is called when the inbound message has been received
** and it is time to start sending again.
*/
void transport_rewind(void){
  if( g.urlIsFile ){
    transport_close();
  }
}
































/*
** Read N bytes of content from the wire and store in the supplied buffer.
** Return the number of bytes actually received.
*/
int transport_receive(char *zBuf, int N){
  int onHand;       /* Bytes current held in the transport buffer */
  int nByte = 0;    /* Bytes of content received */

  onHand = transport.nUsed - transport.iCursor;

  if( onHand>0 ){
    int toMove = onHand;
    if( toMove>N ) toMove = N;
    /* printf("bytes on hand: %d of %d\n", toMove, N); fflush(stdout); */
    memcpy(zBuf, &transport.pBuf[transport.iCursor], toMove);
    transport.iCursor += toMove;
    if( transport.iCursor>=transport.nUsed ){
      transport.nUsed = 0;
      transport.iCursor = 0;
    }
    N -= toMove;
    zBuf += toMove;
    nByte += toMove;
  }
  if( N>0 ){
    int got;
    if( g.urlIsHttps ){
      #ifdef FOSSIL_ENABLE_SSL
      got = ssl_receive(0, zBuf, N);
      /* printf("received %d of %d bytes\n", got, N); fflush(stdout); */
      #else
      got = 0;
      #endif
    }else if( g.urlIsFile ){
      got = fread(zBuf, 1, N, transport.pFile);
    }else{
      got = socket_receive(0, zBuf, N);
      /* printf("received %d of %d bytes\n", got, N); fflush(stdout); */
    }
    if( got>0 ){
      nByte += got;
      transport.nRcvd += got;
    }
  }
  return nByte;
}







|
>
>




















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>










>















|
<
<
<
<
<
<
<
<
<
<
<
<
<







239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326













327
328
329
330
331
332
333
}

/*
** This routine is called when the outbound message is complete and
** it is time to being recieving a reply.
*/
void transport_flip(void){
  if( g.urlIsSsh ){
    fprintf(sshOut, "\n\n");
  }else if( g.urlIsFile ){
    char *zCmd;
    fclose(transport.pFile);
    zCmd = mprintf("\"%s\" http \"%s\" \"%s\" \"%s\" 127.0.0.1",
       g.argv[0], g.urlName, transport.zOutFile, transport.zInFile
    );
    portable_system(zCmd);
    free(zCmd);
    transport.pFile = fopen(transport.zInFile, "rb");
  }
}

/*
** This routine is called when the inbound message has been received
** and it is time to start sending again.
*/
void transport_rewind(void){
  if( g.urlIsFile ){
    transport_close();
  }
}

/*
** Read N bytes of content directly from the wire and write into
** the buffer.
*/
static int transport_fetch(char *zBuf, int N){
  int got;
  if( sshIn ){
    int x;
    int wanted = N;
    got = 0;
    while( wanted>0 ){
      x = read(sshIn, &zBuf[got], wanted);
      if( x<=0 ) break;
      got += x;
      wanted -= x;
    }
  }else if( g.urlIsHttps ){
    #ifdef FOSSIL_ENABLE_SSL
    got = ssl_receive(0, zBuf, N);
    #else
    got = 0;
    #endif
  }else if( g.urlIsFile ){
    got = fread(zBuf, 1, N, transport.pFile);
  }else{
    got = socket_receive(0, zBuf, N);
  }
  /* printf("received %d of %d bytes\n", got, N); fflush(stdout);  */
  return got;
}

/*
** Read N bytes of content from the wire and store in the supplied buffer.
** Return the number of bytes actually received.
*/
int transport_receive(char *zBuf, int N){
  int onHand;       /* Bytes current held in the transport buffer */
  int nByte = 0;    /* Bytes of content received */

  onHand = transport.nUsed - transport.iCursor;
  /* printf("request %d with %d on hand\n", N, onHand); fflush(stdout);  */
  if( onHand>0 ){
    int toMove = onHand;
    if( toMove>N ) toMove = N;
    /* printf("bytes on hand: %d of %d\n", toMove, N); fflush(stdout); */
    memcpy(zBuf, &transport.pBuf[transport.iCursor], toMove);
    transport.iCursor += toMove;
    if( transport.iCursor>=transport.nUsed ){
      transport.nUsed = 0;
      transport.iCursor = 0;
    }
    N -= toMove;
    zBuf += toMove;
    nByte += toMove;
  }
  if( N>0 ){
    int got = transport_fetch(zBuf, N);













    if( got>0 ){
      nByte += got;
      transport.nRcvd += got;
    }
  }
  return nByte;
}
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
    char *pNew;
    transport.nAlloc = transport.nUsed + N;
    pNew = realloc(transport.pBuf, transport.nAlloc);
    if( pNew==0 ) fossil_panic("out of memory");
    transport.pBuf = pNew;
  }
  if( N>0 ){
    i = transport_receive(&transport.pBuf[transport.nUsed], N);
    if( i>0 ){
      transport.nUsed += i;
    }
  }
}

/*







|







357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
    char *pNew;
    transport.nAlloc = transport.nUsed + N;
    pNew = realloc(transport.pBuf, transport.nAlloc);
    if( pNew==0 ) fossil_panic("out of memory");
    transport.pBuf = pNew;
  }
  if( N>0 ){
    i = transport_fetch(&transport.pBuf[transport.nUsed], N);
    if( i>0 ){
      transport.nUsed += i;
    }
  }
}

/*
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
    i++;
  }
  /* printf("Got line: [%s]\n", &transport.pBuf[iStart]); */
  return &transport.pBuf[iStart];
}

void transport_global_shutdown(void){
  if( g.urlIsSsh && g.sshPid ){
    printf("Closing SSH tunnel: ");
    fflush(stdout);
    pclose2(g.sshIn, g.sshOut, g.sshPid);
    g.sshPid = 0;
  }
  if( g.urlIsHttps ){
    #ifdef FOSSIL_ENABLE_SSL
    ssl_global_shutdown();
    #endif
  }else{
    socket_global_shutdown();
  }
}







|


|
|









403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
    i++;
  }
  /* printf("Got line: [%s]\n", &transport.pBuf[iStart]); */
  return &transport.pBuf[iStart];
}

void transport_global_shutdown(void){
  if( g.urlIsSsh && sshPid ){
    printf("Closing SSH tunnel: ");
    fflush(stdout);
    pclose2(sshIn, sshOut, sshPid);
    sshPid = 0;
  }
  if( g.urlIsHttps ){
    #ifdef FOSSIL_ENABLE_SSL
    ssl_global_shutdown();
    #endif
  }else{
    socket_global_shutdown();
  }
}
Changes to src/main.c.
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107

108
109
110
111
112
113
114
  Th_Interp *interp;      /* The TH1 interpreter */
  FILE *httpIn;           /* Accept HTTP input from here */
  FILE *httpOut;          /* Send HTTP output here */
  int xlinkClusterOnly;   /* Set when cloning.  Only process clusters */
  int fTimeFormat;        /* 1 for UTC.  2 for localtime.  0 not yet selected */
  int *aCommitFile;       /* Array of files to be committed */
  int markPrivate;        /* All new artifacts are private if true */
  char *zAccessToken;     /* X-Fossil-Access-Token HTTP header field */
  int sshPid;             /* Process id of ssh subprocess */
  FILE *sshIn;            /* From ssh subprocess to this */
  FILE *sshOut;           /* From this to ssh subprocess */

  int urlIsFile;          /* True if a "file:" url */
  int urlIsHttps;         /* True if a "https:" url */
  int urlIsSsh;           /* True if an "ssh:" url */
  char *urlName;          /* Hostname for http: or filename for file: */
  char *urlSshHost;       /* Hostname for ssh: tunnels */
  char *urlHostname;      /* The HOST: parameter on http headers */
  char *urlProtocol;      /* "http" or "https" */
  int urlPort;            /* TCP port number for http: or https: */
  int urlDfltPort;        /* The default port for the given protocol */
  int urlSshPort;         /* TCP port for SSH */
  char *urlPath;          /* Pathname for http: */
  char *urlUser;          /* User id for http: */
  char *urlPasswd;        /* Password for http: */
  char *urlCanonical;     /* Canonical representation of the URL */
  char *urlProxyAuth;     /* Proxy-Authorizer: string */

  int dontKeepUrl;        /* Do not persist the URL */

  const char *zLogin;     /* Login name.  "" if not logged in. */
  int noPswd;             /* Logged in without password (on 127.0.0.1) */
  int userUid;            /* Integer user id */

  /* Information used to populate the RCVFROM table */







<
<
<
<





<




<





>







81
82
83
84
85
86
87




88
89
90
91
92

93
94
95
96

97
98
99
100
101
102
103
104
105
106
107
108
109
  Th_Interp *interp;      /* The TH1 interpreter */
  FILE *httpIn;           /* Accept HTTP input from here */
  FILE *httpOut;          /* Send HTTP output here */
  int xlinkClusterOnly;   /* Set when cloning.  Only process clusters */
  int fTimeFormat;        /* 1 for UTC.  2 for localtime.  0 not yet selected */
  int *aCommitFile;       /* Array of files to be committed */
  int markPrivate;        /* All new artifacts are private if true */





  int urlIsFile;          /* True if a "file:" url */
  int urlIsHttps;         /* True if a "https:" url */
  int urlIsSsh;           /* True if an "ssh:" url */
  char *urlName;          /* Hostname for http: or filename for file: */

  char *urlHostname;      /* The HOST: parameter on http headers */
  char *urlProtocol;      /* "http" or "https" */
  int urlPort;            /* TCP port number for http: or https: */
  int urlDfltPort;        /* The default port for the given protocol */

  char *urlPath;          /* Pathname for http: */
  char *urlUser;          /* User id for http: */
  char *urlPasswd;        /* Password for http: */
  char *urlCanonical;     /* Canonical representation of the URL */
  char *urlProxyAuth;     /* Proxy-Authorizer: string */
  char *urlFossil;        /* The path of the ?fossil=path suffix on ssh: */
  int dontKeepUrl;        /* Do not persist the URL */

  const char *zLogin;     /* Login name.  "" if not logged in. */
  int noPswd;             /* Logged in without password (on 127.0.0.1) */
  int userUid;            /* Integer user id */

  /* Information used to populate the RCVFROM table */
919
920
921
922
923
924
925

926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946


947
948
949
950
951
952






953
954
955
956
957
958
959
** not select a valid repository and the --notfound option is available,
** then the server redirects (HTTP code 302) to the URL of --notfound.
*/
void cmd_http(void){
  const char *zIpAddr;
  const char *zNotFound;
  zNotFound = find_option("notfound", 0, 1);

  if( g.argc!=2 && g.argc!=3 && g.argc!=6 ){
    cgi_panic("no repository specified");
  }
  g.cgiOutput = 1;
  g.fullHttpReply = 1;
  if( g.argc==6 ){
    g.httpIn = fopen(g.argv[3], "rb");
    g.httpOut = fopen(g.argv[4], "wb");
    zIpAddr = g.argv[5];
  }else{
    g.httpIn = stdin;
    g.httpOut = stdout;
    zIpAddr = 0;
  }
  find_server_repository(0);
  g.zRepositoryName = enter_chroot_jail(g.zRepositoryName);
  cgi_handle_http_request(zIpAddr);
  process_one_web_page(zNotFound);
}

/*


** COMMAND: test-http
** Works like the http command but gives setup permission to all users.
*/
void cmd_test_http(void){
  login_set_capabilities("s");
  cmd_http();






}

#ifndef __MINGW32__
#if !defined(__DARWIN__) && !defined(__APPLE__)
/*
** Search for an executable on the PATH environment variable.
** Return true (1) if found and false (0) if not found.







>

|

<

















>
>





|
>
>
>
>
>
>







914
915
916
917
918
919
920
921
922
923
924

925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
** not select a valid repository and the --notfound option is available,
** then the server redirects (HTTP code 302) to the URL of --notfound.
*/
void cmd_http(void){
  const char *zIpAddr;
  const char *zNotFound;
  zNotFound = find_option("notfound", 0, 1);
  g.cgiOutput = 1;
  if( g.argc!=2 && g.argc!=3 && g.argc!=6 ){
    fossil_fatal("no repository specified");
  }

  g.fullHttpReply = 1;
  if( g.argc==6 ){
    g.httpIn = fopen(g.argv[3], "rb");
    g.httpOut = fopen(g.argv[4], "wb");
    zIpAddr = g.argv[5];
  }else{
    g.httpIn = stdin;
    g.httpOut = stdout;
    zIpAddr = 0;
  }
  find_server_repository(0);
  g.zRepositoryName = enter_chroot_jail(g.zRepositoryName);
  cgi_handle_http_request(zIpAddr);
  process_one_web_page(zNotFound);
}

/*
** Note that the following command is used by ssh:// processing.
**
** COMMAND: test-http
** Works like the http command but gives setup permission to all users.
*/
void cmd_test_http(void){
  login_set_capabilities("s");
  g.httpIn = stdin;
  g.httpOut = stdout;
  find_server_repository(0);
  g.cgiOutput = 1;
  g.fullHttpReply = 1;
  cgi_handle_http_request(0);
  process_one_web_page(0);
}

#ifndef __MINGW32__
#if !defined(__DARWIN__) && !defined(__APPLE__)
/*
** Search for an executable on the PATH environment variable.
** Return true (1) if found and false (0) if not found.
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
  }
  return 0;
}
#endif
#endif

/*
** COMMAND: sshd
** COMMAND: server
** COMMAND: ui
**
** Usage: %fossil server ?-P|--port TCPPORT? ?REPOSITORY?
**    Or: %fossil ui ?-P|--port TCPPORT? ?REPOSITORY?
**
** Open a socket and begin listening and responding to HTTP requests on
** TCP port 8080, or on any other TCP port defined by the -P or
** --port option.  The optional argument is the name of the repository.
** The repository argument may be omitted if the working directory is
** within an open checkout.
**
** The "ui" command automatically starts a web browser after initializing
** the web server.  The "ui" command also binds to 127.0.0.1 and so will
** only process HTTP traffic from the local machine.
**
** In the "server" command, the REPOSITORY can be a directory (aka folder)
** that contains one or more respositories with names ending in ".fossil".
** In that case, the first element of the URL is used to select among the
** various repositories.
**
** The "ui" or "server" verb can also be "sshd".  This is used internally
** by the ssh:// sync method.
*/
void cmd_webserver(void){
  int iPort, mxPort;        /* Range of TCP ports allowed */
  const char *zPort;        /* Value of the --port option */
  char *zBrowser;           /* Name of web browser program */
  char *zBrowserCmd = 0;    /* Command to launch the web browser */
  int isUiCmd;              /* True if command is "ui", not "server' */







<




















<
<
<







977
978
979
980
981
982
983

984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003



1004
1005
1006
1007
1008
1009
1010
  }
  return 0;
}
#endif
#endif

/*

** COMMAND: server
** COMMAND: ui
**
** Usage: %fossil server ?-P|--port TCPPORT? ?REPOSITORY?
**    Or: %fossil ui ?-P|--port TCPPORT? ?REPOSITORY?
**
** Open a socket and begin listening and responding to HTTP requests on
** TCP port 8080, or on any other TCP port defined by the -P or
** --port option.  The optional argument is the name of the repository.
** The repository argument may be omitted if the working directory is
** within an open checkout.
**
** The "ui" command automatically starts a web browser after initializing
** the web server.  The "ui" command also binds to 127.0.0.1 and so will
** only process HTTP traffic from the local machine.
**
** In the "server" command, the REPOSITORY can be a directory (aka folder)
** that contains one or more respositories with names ending in ".fossil".
** In that case, the first element of the URL is used to select among the
** various repositories.



*/
void cmd_webserver(void){
  int iPort, mxPort;        /* Range of TCP ports allowed */
  const char *zPort;        /* Value of the --port option */
  char *zBrowser;           /* Name of web browser program */
  char *zBrowserCmd = 0;    /* Command to launch the web browser */
  int isUiCmd;              /* True if command is "ui", not "server' */
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
  find_server_repository(isUiCmd);
  if( zPort ){
    iPort = mxPort = atoi(zPort);
  }else{
    iPort = db_get_int("http-port", 8080);
    mxPort = iPort+100;
  }
  if( g.argv[1][0]=='s' && g.argv[1][1]=='s' ){
    /* For ssh://, output a random "access token" that must appear in
    ** the header of every HTTP request.  HTTP requests without the
    ** correct access token reply with 403 Forbidden.  The access token
    ** prevents any clients other than the one client that launched the
    ** remote server via SSH from accessing the remote server.
    */
    g.zAccessToken = db_text(0, "SELECT lower(hex(randomblob(20)))");
    printf("Access-Token: %s\n", g.zAccessToken);
    fflush(stdout);
    flags |= HTTP_SERVER_LOCALHOST | HTTP_SERVER_STDIN;
  }
#ifndef __MINGW32__
  /* Unix implementation */
  if( isUiCmd ){
#if !defined(__DARWIN__) && !defined(__APPLE__)
    zBrowser = db_get("web-browser", 0);
    if( zBrowser==0 ){
      static char *azBrowserProg[] = { "xdg-open", "gnome-open", "firefox" };







<
<
<
<
<
<
<
<
<
<
<
<







1028
1029
1030
1031
1032
1033
1034












1035
1036
1037
1038
1039
1040
1041
  find_server_repository(isUiCmd);
  if( zPort ){
    iPort = mxPort = atoi(zPort);
  }else{
    iPort = db_get_int("http-port", 8080);
    mxPort = iPort+100;
  }












#ifndef __MINGW32__
  /* Unix implementation */
  if( isUiCmd ){
#if !defined(__DARWIN__) && !defined(__APPLE__)
    zBrowser = db_get("web-browser", 0);
    if( zBrowser==0 ){
      static char *azBrowserProg[] = { "xdg-open", "gnome-open", "firefox" };
Changes to src/popen.c.
16
17
18
19
20
21
22

23




























































24
25
26
27
28

29
30
31
32
33
34



























35
36
37
38
39
40
41
42
43
44
45
*******************************************************************************
**
** This file contains an implementation of a bi-directional popen().
*/
#include "config.h"
#include "popen.h"


/*




























































** Create a child process running shell command "zCmd".  *ppOut is
** a FILE that becomes the standard input of the child process.  
** (The caller writes to *ppOut in order to send text to the child.)
** *ppIn is stdout from the child process.  (The caller
** reads from *ppIn in order to receive input from the child.)

** The process ID of the child is written into *pChildPid.
**
** Return the number of errors.
*/
int popen2(const char *zCmd, FILE **ppIn, FILE **ppOut, int *pChildPid){
#ifdef __MINGW32__



























  return 1;   /* Not implemented on windows, yet */
#else
  int pin[2], pout[2];
  *ppIn = 0;
  *ppOut = 0;
  *pChildPid = 0;

  if( pipe(pin)<0 ){
    return 1;
  }
  if( pipe(pout)<0 ){







>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





>




|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|


|







16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
*******************************************************************************
**
** This file contains an implementation of a bi-directional popen().
*/
#include "config.h"
#include "popen.h"

#ifdef __MINGW32__
/*
** Print a fatal error and quit.
*/
static void win32_fatal_error(const char *zMsg){
  fossil_fatal("%s");
}
#endif



#ifdef __MINGW32__
/*
** On windows, create a child process and specify the stdin, stdout,
** and stderr channels for that process to use.
**
** Return the number of errors.
*/
static int win32_create_child_process(
  char *zCmd,          /* The command that the child process will run */
  HANDLE hIn,          /* Standard input */
  HANDLE hOut,         /* Standard output */
  HANDLE hErr,         /* Standard error */
  DWORD *pChildPid     /* OUT: Child process handle */
){
  STARTUPINFO si;
  PROCESS_INFORMATION pi;
  BOOL rc;

  memset(&si, 0, sizeof(si));
  si.cb = sizeof(si);
  si.dwFlags = STARTF_USESTDHANDLES;
  SetHandleInformation(hIn, HANDLE_FLAG_INHERIT, TRUE);
  si.hStdInput  = hIn;
  SetHandleInformation(hOut, HANDLE_FLAG_INHERIT, TRUE);
  si.hStdOutput = hOut;
  SetHandleInformation(hErr, HANDLE_FLAG_INHERIT, TRUE);
  si.hStdError  = hErr;
  rc = CreateProcess(
     NULL,  /* Application Name */
     zCmd,  /* Command-line */
     NULL,  /* Process attributes */
     NULL,  /* Thread attributes */
     TRUE,  /* Inherit Handles */
     0,     /* Create flags  */
     NULL,  /* Environment */
     NULL,  /* Current directory */
     &si,   /* Startup Info */
     &pi    /* Process Info */
  );
  if( rc ){
    CloseHandle( pi.hProcess );
    CloseHandle( pi.hThread );
    *pChildPid = pi.dwProcessId;
  }else{
    win32_fatal_error("cannot create child process");
  }
  return rc!=0;
}
#endif

/*
** Create a child process running shell command "zCmd".  *ppOut is
** a FILE that becomes the standard input of the child process.  
** (The caller writes to *ppOut in order to send text to the child.)
** *ppIn is stdout from the child process.  (The caller
** reads from *ppIn in order to receive input from the child.)
** Note that *ppIn is an unbuffered file descriptor, not a FILE.
** The process ID of the child is written into *pChildPid.
**
** Return the number of errors.
*/
int popen2(const char *zCmd, int *pfdIn, FILE **ppOut, int *pChildPid){
#ifdef __MINGW32__
  HANDLE hStdinRd, hStdinWr, hStdoutRd, hStdoutWr, hStderr;
  SECURITY_ATTRIBUTES saAttr;    
  DWORD childPid;
  int fd;

  saAttr.nLength = sizeof(saAttr);
  saAttr.bInheritHandle = TRUE;
  saAttr.lpSecurityDescriptor = NULL; 
  hStderr = GetStdHandle(STD_ERROR_HANDLE);
  if( !CreatePipe(&hStdoutRd, &hStdoutWr, &saAttr, 4096) ){
    win32_fatal_error("cannot create pipe for stdout");
  }
  SetHandleInformation( hStdoutRd, HANDLE_FLAG_INHERIT, FALSE);

  if( !CreatePipe(&hStdinRd, &hStdinWr, &saAttr, 4096) ){
    win32_fatal_error("cannot create pipe for stdin");
  }
  SetHandleInformation( hStdinWr, HANDLE_FLAG_INHERIT, FALSE);
  
  win32_create_child_process((char*)zCmd, 
                             hStdinRd, hStdoutWr, hStderr,&childPid);
  *pChildPid = childPid;
  *pfdIn = _open_osfhandle(hStdoutRd, 0);
  fd = _open_osfhandle(hStdoutWr, 0);
  *ppOut = _fdopen(fd, "w");
  CloseHandle(hStdinRd); 
  CloseHandle(hStdoutWr);
  return 0;
#else
  int pin[2], pout[2];
  *pfdIn = 0;
  *ppOut = 0;
  *pChildPid = 0;

  if( pipe(pin)<0 ){
    return 1;
  }
  if( pipe(pout)<0 ){
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
    close(pin[0]);
    close(pin[1]);
    execl("/bin/sh", "/bin/sh", "-c", zCmd, (char*)0);
    return 1;
  }else{
    /* This is the parent process */
    close(pin[1]);
    *ppIn = fdopen(pin[0], "r");
    close(pout[0]);
    *ppOut = fdopen(pout[1], "w");
    return 0;
  }
#endif
}

/*
** Close the connection to a child process previously created using
** popen2().  Kill off the child process, then close the pipes.
*/
void pclose2(FILE *pIn, FILE *pOut, int childPid){
#ifdef __MINGW32__
  /* Not implemented, yet */
#else
  kill(childPid, SIGINT);
  fclose(pIn);
  fclose(pOut);
#endif
}







|











|




|



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
    close(pin[0]);
    close(pin[1]);
    execl("/bin/sh", "/bin/sh", "-c", zCmd, (char*)0);
    return 1;
  }else{
    /* This is the parent process */
    close(pin[1]);
    *pfdIn = pin[0];
    close(pout[0]);
    *ppOut = fdopen(pout[1], "w");
    return 0;
  }
#endif
}

/*
** Close the connection to a child process previously created using
** popen2().  Kill off the child process, then close the pipes.
*/
void pclose2(int fdIn, FILE *pOut, int childPid){
#ifdef __MINGW32__
  /* Not implemented, yet */
#else
  kill(childPid, SIGINT);
  close(fdIn);
  fclose(pOut);
#endif
}
Changes to src/url.c.
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/*
** Parse the given URL.  Populate variables in the global "g" structure.
**
**      g.urlIsFile      True if FILE:
**      g.urlIsHttps     True if HTTPS: 
**      g.urlIsSsh       True if SSH:
**      g.urlProtocol    "http" or "https" or "file"
**      g.urlName        Hostname for HTTP: or HTTPS:.  Filename for FILE:
**      g.urlSshHost     Hostname for SSH: tunnel
**      g.urlPort        TCP port number for HTTP or HTTPS.
**      g.urlDfltPort    Default TCP port number (80 or 443).
**      g.urlSshPort     TCP port for SSH: tunnel
**      g.urlPath        Path name for HTTP or HTTPS.
**      g.urlUser        Userid.
**      g.urlPasswd      Password.
**      g.urlHostname    HOST:PORT or just HOST if port is the default.
**      g.urlCanonical   The URL in canonical form, omitting the password
**
** HTTP url format is:
**
**     http://userid:password@host:port/path?query#fragment
**
** SSH url format is:
**
**     ssh://userid@host:port/fullpath
**
*/
void url_parse(const char *zUrl){
  int i, j, c;
  char *zFile = 0;
  if( strncmp(zUrl, "http://", 7)==0 || strncmp(zUrl, "https://", 8)==0 ){
    int iStart;







|
<


<












|







33
34
35
36
37
38
39
40

41
42

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/*
** Parse the given URL.  Populate variables in the global "g" structure.
**
**      g.urlIsFile      True if FILE:
**      g.urlIsHttps     True if HTTPS: 
**      g.urlIsSsh       True if SSH:
**      g.urlProtocol    "http" or "https" or "file"
**      g.urlName        Hostname for HTTP:, HTTPS:, SSH:.  Filename for FILE:

**      g.urlPort        TCP port number for HTTP or HTTPS.
**      g.urlDfltPort    Default TCP port number (80 or 443).

**      g.urlPath        Path name for HTTP or HTTPS.
**      g.urlUser        Userid.
**      g.urlPasswd      Password.
**      g.urlHostname    HOST:PORT or just HOST if port is the default.
**      g.urlCanonical   The URL in canonical form, omitting the password
**
** HTTP url format is:
**
**     http://userid:password@host:port/path?query#fragment
**
** SSH url format is:
**
**     ssh://userid@host:port/path?fossil=path/to/fossil.exe
**
*/
void url_parse(const char *zUrl){
  int i, j, c;
  char *zFile = 0;
  if( strncmp(zUrl, "http://", 7)==0 || strncmp(zUrl, "https://", 8)==0 ){
    int iStart;
118
119
120
121
122
123
124

125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151

152



























153
154
155
156
157
158

159
160
161
162
163
164
165
        "%s://%s%T:%d%T",
        g.urlProtocol, zLogin, g.urlName, g.urlPort, g.urlPath
      );
    }
    free(zLogin);
  }else if( strncmp(zUrl, "ssh://", 6)==0 ){
    char *zLogin;

    int r;
    g.urlIsFile = 0;
    g.urlIsSsh = 1;
    g.urlProtocol = "ssh";
    sqlite3_randomness(sizeof(r), &r);
    g.urlPort = 18800 + (r & 0x7fffff)%2000;
    g.urlDfltPort = 80;
    g.urlName = "127.0.0.1";
    g.urlHostname = g.urlName;
    for(i=6; (c=zUrl[i])!=0 && c!='/' && c!='@'; i++){}
    if( c=='@' ){
      for(j=6; j<i && zUrl[j]!=':'; j++){}
      g.urlUser = mprintf("%.*s", j-6, &zUrl[6]);
      dehttpize(g.urlUser);
      if( j<i ){
        g.urlPasswd = mprintf("%.*s", i-j-1, &zUrl[j+1]);
        dehttpize(g.urlPasswd);
      }
      for(j=i+1; (c=zUrl[j])!=0 && c!='/'; j++){}
      g.urlSshHost = mprintf("%.*s", j-i-1, &zUrl[i+1]);
      i = j;
      zLogin = mprintf("%t@", g.urlUser);
    }else{
      g.urlSshHost = mprintf("%.*s", i-6, &zUrl[6]);
      zLogin = mprintf("");
    }
    url_tolower(g.urlSshHost);

    g.urlPath = mprintf(&zUrl[i+1]);



























    dehttpize(g.urlPath);
    g.urlCanonical = mprintf(
        "ssh://%s%T/%T", 
        zLogin, g.urlSshHost, g.urlPath
    );
    free(zLogin);

  }else if( strncmp(zUrl, "file:", 5)==0 ){
    g.urlIsFile = 1;
    if( zUrl[5]=='/' && zUrl[6]=='/' ){
      i = 7;
    }else{
      i = 5;
    }







>
|



<
|
|
|
|










|



|


|
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


|
|


>







116
117
118
119
120
121
122
123
124
125
126
127

128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
        "%s://%s%T:%d%T",
        g.urlProtocol, zLogin, g.urlName, g.urlPort, g.urlPath
      );
    }
    free(zLogin);
  }else if( strncmp(zUrl, "ssh://", 6)==0 ){
    char *zLogin;
    char *zExe;
    int i, j;
    g.urlIsFile = 0;
    g.urlIsSsh = 1;
    g.urlProtocol = "ssh";

    g.urlPort = 22;
    g.urlDfltPort = 22;
    g.urlPasswd = "(not-used)";
    g.urlFossil = "fossil";
    for(i=6; (c=zUrl[i])!=0 && c!='/' && c!='@'; i++){}
    if( c=='@' ){
      for(j=6; j<i && zUrl[j]!=':'; j++){}
      g.urlUser = mprintf("%.*s", j-6, &zUrl[6]);
      dehttpize(g.urlUser);
      if( j<i ){
        g.urlPasswd = mprintf("%.*s", i-j-1, &zUrl[j+1]);
        dehttpize(g.urlPasswd);
      }
      for(j=i+1; (c=zUrl[j])!=0 && c!='/'; j++){}
      g.urlName = mprintf("%.*s", j-i-1, &zUrl[i+1]);
      i = j;
      zLogin = mprintf("%t@", g.urlUser);
    }else{
      g.urlName = mprintf("%.*s", i-6, &zUrl[6]);
      zLogin = mprintf("");
    }
    url_tolower(g.urlName);
    g.urlHostname = g.urlName;
    g.urlPath = mprintf(&zUrl[i+1]);
    for(i=0; g.urlPath[i] && g.urlPath[i]!='?'; i++){}
    if( g.urlPath[i] ){
      g.urlPath[i] = 0;
      i++;
    }
    zExe = mprintf("");
    while( g.urlPath[i]!=0 ){
      char *zName, *zValue;
      zName = &g.urlPath[i];
      zValue = zName;
      while( g.urlPath[i] && g.urlPath[i]!='=' ){ i++; }
      if( g.urlPath[i]=='=' ){
        g.urlPath[i] = 0;
        i++;
        zValue = &g.urlPath[i];
        while( g.urlPath[i] && g.urlPath[i]!='&' ){ i++; }
      }
      if( g.urlPath[i] ){
        g.urlPath[i] = 0;
        i++;
      }
      if( strcmp(zName,"fossil")==0 ){
        g.urlFossil = zValue;
        dehttpize(g.urlFossil);
        zExe = mprintf("?fossil=%T", g.urlFossil);
      }
    }
    dehttpize(g.urlPath);
    g.urlCanonical = mprintf(
        "ssh://%s%T/%T%s", 
        zLogin, g.urlName, g.urlPath, zExe
    );
    free(zLogin);
    free(zExe);
  }else if( strncmp(zUrl, "file:", 5)==0 ){
    g.urlIsFile = 1;
    if( zUrl[5]=='/' && zUrl[6]=='/' ){
      i = 7;
    }else{
      i = 5;
    }
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217

218
219
220
221
222
223
224
  url_parse(g.argv[2]);
  for(i=0; i<2; i++){
    printf("g.urlIsFile    = %d\n", g.urlIsFile);
    printf("g.urlIsHttps   = %d\n", g.urlIsHttps);
    printf("g.urlIsSsh     = %d\n", g.urlIsSsh);
    printf("g.urlProtocol  = %s\n", g.urlProtocol);
    printf("g.urlName      = %s\n", g.urlName);
    printf("g.urlSshHost   = %s\n", g.urlSshHost);
    printf("g.urlPort      = %d\n", g.urlPort);
    printf("g.urlDfltPort  = %d\n", g.urlDfltPort);
    printf("g.urlHostname  = %s\n", g.urlHostname);
    printf("g.urlPath      = %s\n", g.urlPath);
    printf("g.urlUser      = %s\n", g.urlUser);
    printf("g.urlPasswd    = %s\n", g.urlPasswd);
    printf("g.urlCanonical = %s\n", g.urlCanonical);

    if( g.urlIsFile || g.urlIsSsh ) break;
    if( i==0 ){
      printf("********\n");
      url_enable_proxy("Using proxy: ");
    }
  }
}







<







>







230
231
232
233
234
235
236

237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
  url_parse(g.argv[2]);
  for(i=0; i<2; i++){
    printf("g.urlIsFile    = %d\n", g.urlIsFile);
    printf("g.urlIsHttps   = %d\n", g.urlIsHttps);
    printf("g.urlIsSsh     = %d\n", g.urlIsSsh);
    printf("g.urlProtocol  = %s\n", g.urlProtocol);
    printf("g.urlName      = %s\n", g.urlName);

    printf("g.urlPort      = %d\n", g.urlPort);
    printf("g.urlDfltPort  = %d\n", g.urlDfltPort);
    printf("g.urlHostname  = %s\n", g.urlHostname);
    printf("g.urlPath      = %s\n", g.urlPath);
    printf("g.urlUser      = %s\n", g.urlUser);
    printf("g.urlPasswd    = %s\n", g.urlPasswd);
    printf("g.urlCanonical = %s\n", g.urlCanonical);
    printf("g.urlFossil    = %s\n", g.urlFossil);
    if( g.urlIsFile || g.urlIsSsh ) break;
    if( i==0 ){
      printf("********\n");
      url_enable_proxy("Using proxy: ");
    }
  }
}