Fossil SCM

Add a new TH1 "http" command, a new "th1-uri-regexp" setting and two new hook scripts "xfer-commit-script" and "xfer-ticket-script". They can be used together to implement more advanced commit and ticket change notifications.

jan.nijtmans 2013-12-20 12:35 trunk merge
Commit 1311841a3c201a669bc0e433e24c6caf3fd3ab1d
+2 -2
--- src/attach.c
+++ src/attach.c
@@ -214,11 +214,11 @@
214214
}else{
215215
rid = content_put(pAttach);
216216
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d);", rid);
217217
db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", rid);
218218
}
219
- manifest_crosslink(rid, pAttach);
219
+ manifest_crosslink(rid, pAttach, MC_NONE);
220220
}
221221
222222
223223
/*
224224
** WEBPAGE: attachadd
@@ -431,11 +431,11 @@
431431
blob_appendf(&manifest, "D %s\n", zDate);
432432
blob_appendf(&manifest, "U %F\n", g.zLogin ? g.zLogin : "nobody");
433433
md5sum_blob(&manifest, &cksum);
434434
blob_appendf(&manifest, "Z %b\n", &cksum);
435435
rid = content_put(&manifest);
436
- manifest_crosslink(rid, &manifest);
436
+ manifest_crosslink(rid, &manifest, MC_NONE);
437437
db_end_transaction(0);
438438
@ <p>The attachment below has been deleted.</p>
439439
}
440440
441441
if( P("del")
442442
--- src/attach.c
+++ src/attach.c
@@ -214,11 +214,11 @@
214 }else{
215 rid = content_put(pAttach);
216 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d);", rid);
217 db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", rid);
218 }
219 manifest_crosslink(rid, pAttach);
220 }
221
222
223 /*
224 ** WEBPAGE: attachadd
@@ -431,11 +431,11 @@
431 blob_appendf(&manifest, "D %s\n", zDate);
432 blob_appendf(&manifest, "U %F\n", g.zLogin ? g.zLogin : "nobody");
433 md5sum_blob(&manifest, &cksum);
434 blob_appendf(&manifest, "Z %b\n", &cksum);
435 rid = content_put(&manifest);
436 manifest_crosslink(rid, &manifest);
437 db_end_transaction(0);
438 @ <p>The attachment below has been deleted.</p>
439 }
440
441 if( P("del")
442
--- src/attach.c
+++ src/attach.c
@@ -214,11 +214,11 @@
214 }else{
215 rid = content_put(pAttach);
216 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d);", rid);
217 db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", rid);
218 }
219 manifest_crosslink(rid, pAttach, MC_NONE);
220 }
221
222
223 /*
224 ** WEBPAGE: attachadd
@@ -431,11 +431,11 @@
431 blob_appendf(&manifest, "D %s\n", zDate);
432 blob_appendf(&manifest, "U %F\n", g.zLogin ? g.zLogin : "nobody");
433 md5sum_blob(&manifest, &cksum);
434 blob_appendf(&manifest, "Z %b\n", &cksum);
435 rid = content_put(&manifest);
436 manifest_crosslink(rid, &manifest, MC_NONE);
437 db_end_transaction(0);
438 @ <p>The attachment below has been deleted.</p>
439 }
440
441 if( P("del")
442
+2 -2
--- src/branch.c
+++ src/branch.c
@@ -153,12 +153,12 @@
153153
brid = content_put_ex(&branch, 0, 0, 0, isPrivate);
154154
if( brid==0 ){
155155
fossil_fatal("trouble committing manifest: %s", g.zErrMsg);
156156
}
157157
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid);
158
- if( manifest_crosslink(brid, &branch)==0 ){
159
- fossil_fatal("unable to install new manifest");
158
+ if( manifest_crosslink(brid, &branch, MC_PERMIT_HOOKS)==0 ){
159
+ fossil_fatal("%s\n", g.zErrMsg);
160160
}
161161
assert( blob_is_reset(&branch) );
162162
content_deltify(rootid, brid, 0);
163163
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", brid);
164164
fossil_print("New branch: %s\n", zUuid);
165165
--- src/branch.c
+++ src/branch.c
@@ -153,12 +153,12 @@
153 brid = content_put_ex(&branch, 0, 0, 0, isPrivate);
154 if( brid==0 ){
155 fossil_fatal("trouble committing manifest: %s", g.zErrMsg);
156 }
157 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid);
158 if( manifest_crosslink(brid, &branch)==0 ){
159 fossil_fatal("unable to install new manifest");
160 }
161 assert( blob_is_reset(&branch) );
162 content_deltify(rootid, brid, 0);
163 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", brid);
164 fossil_print("New branch: %s\n", zUuid);
165
--- src/branch.c
+++ src/branch.c
@@ -153,12 +153,12 @@
153 brid = content_put_ex(&branch, 0, 0, 0, isPrivate);
154 if( brid==0 ){
155 fossil_fatal("trouble committing manifest: %s", g.zErrMsg);
156 }
157 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid);
158 if( manifest_crosslink(brid, &branch, MC_PERMIT_HOOKS)==0 ){
159 fossil_fatal("%s\n", g.zErrMsg);
160 }
161 assert( blob_is_reset(&branch) );
162 content_deltify(rootid, brid, 0);
163 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", brid);
164 fossil_print("New branch: %s\n", zUuid);
165
+4 -1
--- src/checkin.c
+++ src/checkin.c
@@ -1816,11 +1816,14 @@
18161816
nvid = content_put(&manifest);
18171817
if( nvid==0 ){
18181818
fossil_fatal("trouble committing manifest: %s", g.zErrMsg);
18191819
}
18201820
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nvid);
1821
- manifest_crosslink(nvid, &manifest);
1821
+ if( manifest_crosslink(nvid, &manifest,
1822
+ dryRunFlag ? MC_NONE : MC_PERMIT_HOOKS)==0 ){
1823
+ fossil_fatal("%s\n", g.zErrMsg);
1824
+ }
18221825
assert( blob_is_reset(&manifest) );
18231826
content_deltify(vid, nvid, 0);
18241827
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid);
18251828
18261829
db_prepare(&q, "SELECT uuid,merge FROM vmerge JOIN blob ON merge=rid"
18271830
--- src/checkin.c
+++ src/checkin.c
@@ -1816,11 +1816,14 @@
1816 nvid = content_put(&manifest);
1817 if( nvid==0 ){
1818 fossil_fatal("trouble committing manifest: %s", g.zErrMsg);
1819 }
1820 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nvid);
1821 manifest_crosslink(nvid, &manifest);
 
 
 
1822 assert( blob_is_reset(&manifest) );
1823 content_deltify(vid, nvid, 0);
1824 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid);
1825
1826 db_prepare(&q, "SELECT uuid,merge FROM vmerge JOIN blob ON merge=rid"
1827
--- src/checkin.c
+++ src/checkin.c
@@ -1816,11 +1816,14 @@
1816 nvid = content_put(&manifest);
1817 if( nvid==0 ){
1818 fossil_fatal("trouble committing manifest: %s", g.zErrMsg);
1819 }
1820 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nvid);
1821 if( manifest_crosslink(nvid, &manifest,
1822 dryRunFlag ? MC_NONE : MC_PERMIT_HOOKS)==0 ){
1823 fossil_fatal("%s\n", g.zErrMsg);
1824 }
1825 assert( blob_is_reset(&manifest) );
1826 content_deltify(vid, nvid, 0);
1827 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid);
1828
1829 db_prepare(&q, "SELECT uuid,merge FROM vmerge JOIN blob ON merge=rid"
1830
--- src/configure.c
+++ src/configure.c
@@ -98,10 +98,11 @@
9898
{ "timeline-plaintext", CONFIGSET_SKIN },
9999
{ "adunit", CONFIGSET_SKIN },
100100
{ "adunit-omit-if-admin", CONFIGSET_SKIN },
101101
{ "adunit-omit-if-user", CONFIGSET_SKIN },
102102
{ "th1-setup", CONFIGSET_TH1 },
103
+ { "th1-uri-regexp", CONFIGSET_TH1 },
103104
104105
#ifdef FOSSIL_ENABLE_TCL
105106
{ "tcl", CONFIGSET_TH1 },
106107
{ "tcl-setup", CONFIGSET_TH1 },
107108
#endif
@@ -138,10 +139,12 @@
138139
139140
{ "@shun", CONFIGSET_SHUN },
140141
141142
{ "xfer-common-script", CONFIGSET_XFER },
142143
{ "xfer-push-script", CONFIGSET_XFER },
144
+ { "xfer-commit-script", CONFIGSET_XFER },
145
+ { "xfer-ticket-script", CONFIGSET_XFER },
143146
144147
};
145148
static int iConfig = 0;
146149
147150
/*
148151
--- src/configure.c
+++ src/configure.c
@@ -98,10 +98,11 @@
98 { "timeline-plaintext", CONFIGSET_SKIN },
99 { "adunit", CONFIGSET_SKIN },
100 { "adunit-omit-if-admin", CONFIGSET_SKIN },
101 { "adunit-omit-if-user", CONFIGSET_SKIN },
102 { "th1-setup", CONFIGSET_TH1 },
 
103
104 #ifdef FOSSIL_ENABLE_TCL
105 { "tcl", CONFIGSET_TH1 },
106 { "tcl-setup", CONFIGSET_TH1 },
107 #endif
@@ -138,10 +139,12 @@
138
139 { "@shun", CONFIGSET_SHUN },
140
141 { "xfer-common-script", CONFIGSET_XFER },
142 { "xfer-push-script", CONFIGSET_XFER },
 
 
143
144 };
145 static int iConfig = 0;
146
147 /*
148
--- src/configure.c
+++ src/configure.c
@@ -98,10 +98,11 @@
98 { "timeline-plaintext", CONFIGSET_SKIN },
99 { "adunit", CONFIGSET_SKIN },
100 { "adunit-omit-if-admin", CONFIGSET_SKIN },
101 { "adunit-omit-if-user", CONFIGSET_SKIN },
102 { "th1-setup", CONFIGSET_TH1 },
103 { "th1-uri-regexp", CONFIGSET_TH1 },
104
105 #ifdef FOSSIL_ENABLE_TCL
106 { "tcl", CONFIGSET_TH1 },
107 { "tcl-setup", CONFIGSET_TH1 },
108 #endif
@@ -138,10 +139,12 @@
139
140 { "@shun", CONFIGSET_SHUN },
141
142 { "xfer-common-script", CONFIGSET_XFER },
143 { "xfer-push-script", CONFIGSET_XFER },
144 { "xfer-commit-script", CONFIGSET_XFER },
145 { "xfer-ticket-script", CONFIGSET_XFER },
146
147 };
148 static int iConfig = 0;
149
150 /*
151
+2 -2
--- src/content.c
+++ src/content.c
@@ -388,11 +388,11 @@
388388
int i;
389389
390390
/* Parse the object rid itself */
391391
if( linkFlag ){
392392
content_get(rid, &content);
393
- manifest_crosslink(rid, &content);
393
+ manifest_crosslink(rid, &content, MC_NONE);
394394
assert( blob_is_reset(&content) );
395395
}
396396
397397
/* Parse all delta-manifests that depend on baseline-manifest rid */
398398
db_prepare(&q, "SELECT rid FROM orphan WHERE baseline=%d", rid);
@@ -405,11 +405,11 @@
405405
aChild[nChildUsed++] = child;
406406
}
407407
db_finalize(&q);
408408
for(i=0; i<nChildUsed; i++){
409409
content_get(aChild[i], &content);
410
- manifest_crosslink(aChild[i], &content);
410
+ manifest_crosslink(aChild[i], &content, MC_NONE);
411411
assert( blob_is_reset(&content) );
412412
}
413413
if( nChildUsed ){
414414
db_multi_exec("DELETE FROM orphan WHERE baseline=%d", rid);
415415
}
416416
--- src/content.c
+++ src/content.c
@@ -388,11 +388,11 @@
388 int i;
389
390 /* Parse the object rid itself */
391 if( linkFlag ){
392 content_get(rid, &content);
393 manifest_crosslink(rid, &content);
394 assert( blob_is_reset(&content) );
395 }
396
397 /* Parse all delta-manifests that depend on baseline-manifest rid */
398 db_prepare(&q, "SELECT rid FROM orphan WHERE baseline=%d", rid);
@@ -405,11 +405,11 @@
405 aChild[nChildUsed++] = child;
406 }
407 db_finalize(&q);
408 for(i=0; i<nChildUsed; i++){
409 content_get(aChild[i], &content);
410 manifest_crosslink(aChild[i], &content);
411 assert( blob_is_reset(&content) );
412 }
413 if( nChildUsed ){
414 db_multi_exec("DELETE FROM orphan WHERE baseline=%d", rid);
415 }
416
--- src/content.c
+++ src/content.c
@@ -388,11 +388,11 @@
388 int i;
389
390 /* Parse the object rid itself */
391 if( linkFlag ){
392 content_get(rid, &content);
393 manifest_crosslink(rid, &content, MC_NONE);
394 assert( blob_is_reset(&content) );
395 }
396
397 /* Parse all delta-manifests that depend on baseline-manifest rid */
398 db_prepare(&q, "SELECT rid FROM orphan WHERE baseline=%d", rid);
@@ -405,11 +405,11 @@
405 aChild[nChildUsed++] = child;
406 }
407 db_finalize(&q);
408 for(i=0; i<nChildUsed; i++){
409 content_get(aChild[i], &content);
410 manifest_crosslink(aChild[i], &content, MC_NONE);
411 assert( blob_is_reset(&content) );
412 }
413 if( nChildUsed ){
414 db_multi_exec("DELETE FROM orphan WHERE baseline=%d", rid);
415 }
416
+6 -1
--- src/db.c
+++ src/db.c
@@ -1412,11 +1412,11 @@
14121412
blob_appendf(&manifest, "U %F\n", g.zLogin);
14131413
md5sum_blob(&manifest, &hash);
14141414
blob_appendf(&manifest, "Z %b\n", &hash);
14151415
blob_reset(&hash);
14161416
rid = content_put(&manifest);
1417
- manifest_crosslink(rid, &manifest);
1417
+ manifest_crosslink(rid, &manifest, MC_NONE);
14181418
}
14191419
}
14201420
14211421
/*
14221422
** COMMAND: new*
@@ -2152,10 +2152,11 @@
21522152
#ifdef FOSSIL_ENABLE_TCL
21532153
{ "tcl", 0, 0, 0, "off" },
21542154
{ "tcl-setup", 0, 40, 0, "" },
21552155
#endif
21562156
{ "th1-setup", 0, 40, 0, "" },
2157
+ { "th1-uri-regexp",0, 40, 0, "" },
21572158
{ "web-browser", 0, 32, 0, "" },
21582159
{ "white-foreground", 0, 0, 0, "off" },
21592160
{ 0,0,0,0,0 }
21602161
};
21612162
@@ -2348,10 +2349,14 @@
23482349
** is empty and no extra setup is performed.
23492350
**
23502351
** th1-setup This is the setup script to be evaluated after creating
23512352
** and initializing the TH1 interpreter. By default, this
23522353
** is empty and no extra setup is performed.
2354
+**
2355
+** th1-uri-regexp Specify which URI's are allowed in HTTP requests from
2356
+** TH1 scripts. If empty, no HTTP requests are allowed
2357
+** whatsoever. The default is an empty string.
23532358
**
23542359
** web-browser A shell command used to launch your preferred
23552360
** web browser when given a URL as an argument.
23562361
** Defaults to "start" on windows, "open" on Mac,
23572362
** and "firefox" on Unix.
23582363
--- src/db.c
+++ src/db.c
@@ -1412,11 +1412,11 @@
1412 blob_appendf(&manifest, "U %F\n", g.zLogin);
1413 md5sum_blob(&manifest, &hash);
1414 blob_appendf(&manifest, "Z %b\n", &hash);
1415 blob_reset(&hash);
1416 rid = content_put(&manifest);
1417 manifest_crosslink(rid, &manifest);
1418 }
1419 }
1420
1421 /*
1422 ** COMMAND: new*
@@ -2152,10 +2152,11 @@
2152 #ifdef FOSSIL_ENABLE_TCL
2153 { "tcl", 0, 0, 0, "off" },
2154 { "tcl-setup", 0, 40, 0, "" },
2155 #endif
2156 { "th1-setup", 0, 40, 0, "" },
 
2157 { "web-browser", 0, 32, 0, "" },
2158 { "white-foreground", 0, 0, 0, "off" },
2159 { 0,0,0,0,0 }
2160 };
2161
@@ -2348,10 +2349,14 @@
2348 ** is empty and no extra setup is performed.
2349 **
2350 ** th1-setup This is the setup script to be evaluated after creating
2351 ** and initializing the TH1 interpreter. By default, this
2352 ** is empty and no extra setup is performed.
 
 
 
 
2353 **
2354 ** web-browser A shell command used to launch your preferred
2355 ** web browser when given a URL as an argument.
2356 ** Defaults to "start" on windows, "open" on Mac,
2357 ** and "firefox" on Unix.
2358
--- src/db.c
+++ src/db.c
@@ -1412,11 +1412,11 @@
1412 blob_appendf(&manifest, "U %F\n", g.zLogin);
1413 md5sum_blob(&manifest, &hash);
1414 blob_appendf(&manifest, "Z %b\n", &hash);
1415 blob_reset(&hash);
1416 rid = content_put(&manifest);
1417 manifest_crosslink(rid, &manifest, MC_NONE);
1418 }
1419 }
1420
1421 /*
1422 ** COMMAND: new*
@@ -2152,10 +2152,11 @@
2152 #ifdef FOSSIL_ENABLE_TCL
2153 { "tcl", 0, 0, 0, "off" },
2154 { "tcl-setup", 0, 40, 0, "" },
2155 #endif
2156 { "th1-setup", 0, 40, 0, "" },
2157 { "th1-uri-regexp",0, 40, 0, "" },
2158 { "web-browser", 0, 32, 0, "" },
2159 { "white-foreground", 0, 0, 0, "off" },
2160 { 0,0,0,0,0 }
2161 };
2162
@@ -2348,10 +2349,14 @@
2349 ** is empty and no extra setup is performed.
2350 **
2351 ** th1-setup This is the setup script to be evaluated after creating
2352 ** and initializing the TH1 interpreter. By default, this
2353 ** is empty and no extra setup is performed.
2354 **
2355 ** th1-uri-regexp Specify which URI's are allowed in HTTP requests from
2356 ** TH1 scripts. If empty, no HTTP requests are allowed
2357 ** whatsoever. The default is an empty string.
2358 **
2359 ** web-browser A shell command used to launch your preferred
2360 ** web browser when given a URL as an argument.
2361 ** Defaults to "start" on windows, "open" on Mac,
2362 ** and "firefox" on Unix.
2363
+1 -1
--- src/event.c
+++ src/event.c
@@ -351,11 +351,11 @@
351351
md5sum_blob(&event, &cksum);
352352
blob_appendf(&event, "Z %b\n", &cksum);
353353
blob_reset(&cksum);
354354
nrid = content_put(&event);
355355
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
356
- manifest_crosslink(nrid, &event);
356
+ manifest_crosslink(nrid, &event, MC_NONE);
357357
assert( blob_is_reset(&event) );
358358
content_deltify(rid, nrid, 0);
359359
db_end_transaction(0);
360360
cgi_redirectf("event?name=%T", zEventId);
361361
}
362362
--- src/event.c
+++ src/event.c
@@ -351,11 +351,11 @@
351 md5sum_blob(&event, &cksum);
352 blob_appendf(&event, "Z %b\n", &cksum);
353 blob_reset(&cksum);
354 nrid = content_put(&event);
355 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
356 manifest_crosslink(nrid, &event);
357 assert( blob_is_reset(&event) );
358 content_deltify(rid, nrid, 0);
359 db_end_transaction(0);
360 cgi_redirectf("event?name=%T", zEventId);
361 }
362
--- src/event.c
+++ src/event.c
@@ -351,11 +351,11 @@
351 md5sum_blob(&event, &cksum);
352 blob_appendf(&event, "Z %b\n", &cksum);
353 blob_reset(&cksum);
354 nrid = content_put(&event);
355 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
356 manifest_crosslink(nrid, &event, MC_NONE);
357 assert( blob_is_reset(&event) );
358 content_deltify(rid, nrid, 0);
359 db_end_transaction(0);
360 cgi_redirectf("event?name=%T", zEventId);
361 }
362
+12 -13
--- src/http.c
+++ src/http.c
@@ -110,12 +110,11 @@
110110
blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded);
111111
fossil_free(zEncoded);
112112
fossil_free(zCredentials);
113113
}
114114
blob_appendf(pHdr, "Host: %s\r\n", g.urlHostname);
115
- blob_appendf(pHdr, "User-Agent: Fossil/" RELEASE_VERSION
116
- " (" MANIFEST_DATE " " MANIFEST_VERSION ")\r\n");
115
+ blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent());
117116
if( g.urlIsSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n");
118117
if( g.fHttpTrace ){
119118
blob_appendf(pHdr, "Content-Type: application/x-fossil-debug\r\n");
120119
}else{
121120
blob_appendf(pHdr, "Content-Type: application/x-fossil\r\n");
@@ -144,12 +143,12 @@
144143
char *zLine; /* A single line of the reply header */
145144
int i; /* Loop counter */
146145
int isError = 0; /* True if the reply is an error message */
147146
int isCompressed = 1; /* True if the reply is compressed */
148147
149
- if( transport_open() ){
150
- fossil_warning(transport_errmsg());
148
+ if( transport_open(GLOBAL_URL()) ){
149
+ fossil_warning(transport_errmsg(GLOBAL_URL()));
151150
return 1;
152151
}
153152
154153
/* Construct the login card and prepare the complete payload */
155154
blob_zero(&login);
@@ -191,22 +190,22 @@
191190
}
192191
193192
/*
194193
** Send the request to the server.
195194
*/
196
- transport_send(&hdr);
197
- transport_send(&payload);
195
+ transport_send(GLOBAL_URL(), &hdr);
196
+ transport_send(GLOBAL_URL(), &payload);
198197
blob_reset(&hdr);
199198
blob_reset(&payload);
200
- transport_flip();
199
+ transport_flip(GLOBAL_URL());
201200
202201
/*
203202
** Read and interpret the server reply
204203
*/
205204
closeConnection = 1;
206205
iLength = -1;
207
- while( (zLine = transport_receive_line())!=0 && zLine[0]!=0 ){
206
+ while( (zLine = transport_receive_line(GLOBAL_URL()))!=0 && zLine[0]!=0 ){
208207
/* printf("[%s]\n", zLine); fflush(stdout); */
209208
if( fossil_strnicmp(zLine, "http/1.", 7)==0 ){
210209
if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err;
211210
if( rc!=200 && rc!=302 ){
212211
int ii;
@@ -255,11 +254,11 @@
255254
j -= 4;
256255
zLine[j] = 0;
257256
}
258257
fossil_print("redirect to %s\n", &zLine[i]);
259258
url_parse(&zLine[i], 0);
260
- transport_close();
259
+ transport_close(GLOBAL_URL());
261260
return http_exchange(pSend, pReply, useLogin, maxRedirect);
262261
}else if( fossil_strnicmp(zLine, "content-type: ", 14)==0 ){
263262
if( fossil_strnicmp(&zLine[14], "application/x-fossil-debug", -1)==0 ){
264263
isCompressed = 0;
265264
}else if( fossil_strnicmp(&zLine[14],
@@ -282,11 +281,11 @@
282281
/*
283282
** Extract the reply payload that follows the header
284283
*/
285284
blob_zero(pReply);
286285
blob_resize(pReply, iLength);
287
- iLength = transport_receive(blob_buffer(pReply), iLength);
286
+ iLength = transport_receive(GLOBAL_URL(), blob_buffer(pReply), iLength);
288287
blob_resize(pReply, iLength);
289288
if( isError ){
290289
char *z;
291290
int i, j;
292291
z = blob_str(pReply);
@@ -311,18 +310,18 @@
311310
**
312311
** For SSH we will leave the connection open.
313312
*/
314313
if( ! g.urlIsSsh ) closeConnection = 1; /* FIX ME */
315314
if( closeConnection ){
316
- transport_close();
315
+ transport_close(GLOBAL_URL());
317316
}else{
318
- transport_rewind();
317
+ transport_rewind(GLOBAL_URL());
319318
}
320319
return 0;
321320
322321
/*
323322
** Jump to here if an error is seen.
324323
*/
325324
write_err:
326
- transport_close();
325
+ transport_close(GLOBAL_URL());
327326
return 1;
328327
}
329328
--- src/http.c
+++ src/http.c
@@ -110,12 +110,11 @@
110 blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded);
111 fossil_free(zEncoded);
112 fossil_free(zCredentials);
113 }
114 blob_appendf(pHdr, "Host: %s\r\n", g.urlHostname);
115 blob_appendf(pHdr, "User-Agent: Fossil/" RELEASE_VERSION
116 " (" MANIFEST_DATE " " MANIFEST_VERSION ")\r\n");
117 if( g.urlIsSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n");
118 if( g.fHttpTrace ){
119 blob_appendf(pHdr, "Content-Type: application/x-fossil-debug\r\n");
120 }else{
121 blob_appendf(pHdr, "Content-Type: application/x-fossil\r\n");
@@ -144,12 +143,12 @@
144 char *zLine; /* A single line of the reply header */
145 int i; /* Loop counter */
146 int isError = 0; /* True if the reply is an error message */
147 int isCompressed = 1; /* True if the reply is compressed */
148
149 if( transport_open() ){
150 fossil_warning(transport_errmsg());
151 return 1;
152 }
153
154 /* Construct the login card and prepare the complete payload */
155 blob_zero(&login);
@@ -191,22 +190,22 @@
191 }
192
193 /*
194 ** Send the request to the server.
195 */
196 transport_send(&hdr);
197 transport_send(&payload);
198 blob_reset(&hdr);
199 blob_reset(&payload);
200 transport_flip();
201
202 /*
203 ** Read and interpret the server reply
204 */
205 closeConnection = 1;
206 iLength = -1;
207 while( (zLine = transport_receive_line())!=0 && zLine[0]!=0 ){
208 /* printf("[%s]\n", zLine); fflush(stdout); */
209 if( fossil_strnicmp(zLine, "http/1.", 7)==0 ){
210 if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err;
211 if( rc!=200 && rc!=302 ){
212 int ii;
@@ -255,11 +254,11 @@
255 j -= 4;
256 zLine[j] = 0;
257 }
258 fossil_print("redirect to %s\n", &zLine[i]);
259 url_parse(&zLine[i], 0);
260 transport_close();
261 return http_exchange(pSend, pReply, useLogin, maxRedirect);
262 }else if( fossil_strnicmp(zLine, "content-type: ", 14)==0 ){
263 if( fossil_strnicmp(&zLine[14], "application/x-fossil-debug", -1)==0 ){
264 isCompressed = 0;
265 }else if( fossil_strnicmp(&zLine[14],
@@ -282,11 +281,11 @@
282 /*
283 ** Extract the reply payload that follows the header
284 */
285 blob_zero(pReply);
286 blob_resize(pReply, iLength);
287 iLength = transport_receive(blob_buffer(pReply), iLength);
288 blob_resize(pReply, iLength);
289 if( isError ){
290 char *z;
291 int i, j;
292 z = blob_str(pReply);
@@ -311,18 +310,18 @@
311 **
312 ** For SSH we will leave the connection open.
313 */
314 if( ! g.urlIsSsh ) closeConnection = 1; /* FIX ME */
315 if( closeConnection ){
316 transport_close();
317 }else{
318 transport_rewind();
319 }
320 return 0;
321
322 /*
323 ** Jump to here if an error is seen.
324 */
325 write_err:
326 transport_close();
327 return 1;
328 }
329
--- src/http.c
+++ src/http.c
@@ -110,12 +110,11 @@
110 blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded);
111 fossil_free(zEncoded);
112 fossil_free(zCredentials);
113 }
114 blob_appendf(pHdr, "Host: %s\r\n", g.urlHostname);
115 blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent());
 
116 if( g.urlIsSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n");
117 if( g.fHttpTrace ){
118 blob_appendf(pHdr, "Content-Type: application/x-fossil-debug\r\n");
119 }else{
120 blob_appendf(pHdr, "Content-Type: application/x-fossil\r\n");
@@ -144,12 +143,12 @@
143 char *zLine; /* A single line of the reply header */
144 int i; /* Loop counter */
145 int isError = 0; /* True if the reply is an error message */
146 int isCompressed = 1; /* True if the reply is compressed */
147
148 if( transport_open(GLOBAL_URL()) ){
149 fossil_warning(transport_errmsg(GLOBAL_URL()));
150 return 1;
151 }
152
153 /* Construct the login card and prepare the complete payload */
154 blob_zero(&login);
@@ -191,22 +190,22 @@
190 }
191
192 /*
193 ** Send the request to the server.
194 */
195 transport_send(GLOBAL_URL(), &hdr);
196 transport_send(GLOBAL_URL(), &payload);
197 blob_reset(&hdr);
198 blob_reset(&payload);
199 transport_flip(GLOBAL_URL());
200
201 /*
202 ** Read and interpret the server reply
203 */
204 closeConnection = 1;
205 iLength = -1;
206 while( (zLine = transport_receive_line(GLOBAL_URL()))!=0 && zLine[0]!=0 ){
207 /* printf("[%s]\n", zLine); fflush(stdout); */
208 if( fossil_strnicmp(zLine, "http/1.", 7)==0 ){
209 if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err;
210 if( rc!=200 && rc!=302 ){
211 int ii;
@@ -255,11 +254,11 @@
254 j -= 4;
255 zLine[j] = 0;
256 }
257 fossil_print("redirect to %s\n", &zLine[i]);
258 url_parse(&zLine[i], 0);
259 transport_close(GLOBAL_URL());
260 return http_exchange(pSend, pReply, useLogin, maxRedirect);
261 }else if( fossil_strnicmp(zLine, "content-type: ", 14)==0 ){
262 if( fossil_strnicmp(&zLine[14], "application/x-fossil-debug", -1)==0 ){
263 isCompressed = 0;
264 }else if( fossil_strnicmp(&zLine[14],
@@ -282,11 +281,11 @@
281 /*
282 ** Extract the reply payload that follows the header
283 */
284 blob_zero(pReply);
285 blob_resize(pReply, iLength);
286 iLength = transport_receive(GLOBAL_URL(), blob_buffer(pReply), iLength);
287 blob_resize(pReply, iLength);
288 if( isError ){
289 char *z;
290 int i, j;
291 z = blob_str(pReply);
@@ -311,18 +310,18 @@
310 **
311 ** For SSH we will leave the connection open.
312 */
313 if( ! g.urlIsSsh ) closeConnection = 1; /* FIX ME */
314 if( closeConnection ){
315 transport_close(GLOBAL_URL());
316 }else{
317 transport_rewind(GLOBAL_URL());
318 }
319 return 0;
320
321 /*
322 ** Jump to here if an error is seen.
323 */
324 write_err:
325 transport_close(GLOBAL_URL());
326 return 1;
327 }
328
--- src/http_socket.c
+++ src/http_socket.c
@@ -131,29 +131,29 @@
131131
** g.urlName Name of the server. Ex: www.fossil-scm.org
132132
** g.urlPort TCP/IP port to use. Ex: 80
133133
**
134134
** Return the number of errors.
135135
*/
136
-int socket_open(void){
136
+int socket_open(UrlData *pUrlData){
137137
static struct sockaddr_in addr; /* The server address */
138138
static int addrIsInit = 0; /* True once addr is initialized */
139139
140140
socket_global_init();
141141
if( !addrIsInit ){
142142
addr.sin_family = AF_INET;
143
- addr.sin_port = htons(g.urlPort);
144
- *(int*)&addr.sin_addr = inet_addr(g.urlName);
143
+ addr.sin_port = htons(pUrlData->port);
144
+ *(int*)&addr.sin_addr = inet_addr(pUrlData->name);
145145
if( -1 == *(int*)&addr.sin_addr ){
146146
#ifndef FOSSIL_STATIC_LINK
147147
struct hostent *pHost;
148
- pHost = gethostbyname(g.urlName);
148
+ pHost = gethostbyname(pUrlData->name);
149149
if( pHost!=0 ){
150150
memcpy(&addr.sin_addr,pHost->h_addr_list[0],pHost->h_length);
151151
}else
152152
#endif
153153
{
154
- socket_set_errmsg("can't resolve host name: %s", g.urlName);
154
+ socket_set_errmsg("can't resolve host name: %s", pUrlData->name);
155155
return 1;
156156
}
157157
}
158158
addrIsInit = 1;
159159
@@ -167,11 +167,12 @@
167167
if( iSocket<0 ){
168168
socket_set_errmsg("cannot create a socket");
169169
return 1;
170170
}
171171
if( connect(iSocket,(struct sockaddr*)&addr,sizeof(addr))<0 ){
172
- socket_set_errmsg("cannot connect to host %s:%d", g.urlName, g.urlPort);
172
+ socket_set_errmsg("cannot connect to host %s:%d", pUrlData->name,
173
+ pUrlData->port);
173174
socket_close();
174175
return 1;
175176
}
176177
#if !defined(_WIN32)
177178
signal(SIGPIPE, SIG_IGN);
@@ -215,16 +216,16 @@
215216
/*
216217
** Attempt to resolve g.urlName to IP and setup g.zIpAddr so rcvfrom gets
217218
** populated. For hostnames with more than one IP (or if overridden in
218219
** ~/.ssh/config) the rcvfrom may not match the host to which we connect.
219220
*/
220
-void socket_ssh_resolve_addr(void){
221
+void socket_ssh_resolve_addr(UrlData *pUrlData){
221222
struct hostent *pHost; /* Used to make best effort for rcvfrom */
222223
struct sockaddr_in addr;
223224
224225
memset(&addr, 0, sizeof(addr));
225
- pHost = gethostbyname(g.urlName);
226
+ pHost = gethostbyname(pUrlData->name);
226227
if( pHost!=0 ){
227228
memcpy(&addr.sin_addr,pHost->h_addr_list[0],pHost->h_length);
228229
g.zIpAddr = mprintf("%s", inet_ntoa(addr.sin_addr));
229230
}
230231
}
231232
--- src/http_socket.c
+++ src/http_socket.c
@@ -131,29 +131,29 @@
131 ** g.urlName Name of the server. Ex: www.fossil-scm.org
132 ** g.urlPort TCP/IP port to use. Ex: 80
133 **
134 ** Return the number of errors.
135 */
136 int socket_open(void){
137 static struct sockaddr_in addr; /* The server address */
138 static int addrIsInit = 0; /* True once addr is initialized */
139
140 socket_global_init();
141 if( !addrIsInit ){
142 addr.sin_family = AF_INET;
143 addr.sin_port = htons(g.urlPort);
144 *(int*)&addr.sin_addr = inet_addr(g.urlName);
145 if( -1 == *(int*)&addr.sin_addr ){
146 #ifndef FOSSIL_STATIC_LINK
147 struct hostent *pHost;
148 pHost = gethostbyname(g.urlName);
149 if( pHost!=0 ){
150 memcpy(&addr.sin_addr,pHost->h_addr_list[0],pHost->h_length);
151 }else
152 #endif
153 {
154 socket_set_errmsg("can't resolve host name: %s", g.urlName);
155 return 1;
156 }
157 }
158 addrIsInit = 1;
159
@@ -167,11 +167,12 @@
167 if( iSocket<0 ){
168 socket_set_errmsg("cannot create a socket");
169 return 1;
170 }
171 if( connect(iSocket,(struct sockaddr*)&addr,sizeof(addr))<0 ){
172 socket_set_errmsg("cannot connect to host %s:%d", g.urlName, g.urlPort);
 
173 socket_close();
174 return 1;
175 }
176 #if !defined(_WIN32)
177 signal(SIGPIPE, SIG_IGN);
@@ -215,16 +216,16 @@
215 /*
216 ** Attempt to resolve g.urlName to IP and setup g.zIpAddr so rcvfrom gets
217 ** populated. For hostnames with more than one IP (or if overridden in
218 ** ~/.ssh/config) the rcvfrom may not match the host to which we connect.
219 */
220 void socket_ssh_resolve_addr(void){
221 struct hostent *pHost; /* Used to make best effort for rcvfrom */
222 struct sockaddr_in addr;
223
224 memset(&addr, 0, sizeof(addr));
225 pHost = gethostbyname(g.urlName);
226 if( pHost!=0 ){
227 memcpy(&addr.sin_addr,pHost->h_addr_list[0],pHost->h_length);
228 g.zIpAddr = mprintf("%s", inet_ntoa(addr.sin_addr));
229 }
230 }
231
--- src/http_socket.c
+++ src/http_socket.c
@@ -131,29 +131,29 @@
131 ** g.urlName Name of the server. Ex: www.fossil-scm.org
132 ** g.urlPort TCP/IP port to use. Ex: 80
133 **
134 ** Return the number of errors.
135 */
136 int socket_open(UrlData *pUrlData){
137 static struct sockaddr_in addr; /* The server address */
138 static int addrIsInit = 0; /* True once addr is initialized */
139
140 socket_global_init();
141 if( !addrIsInit ){
142 addr.sin_family = AF_INET;
143 addr.sin_port = htons(pUrlData->port);
144 *(int*)&addr.sin_addr = inet_addr(pUrlData->name);
145 if( -1 == *(int*)&addr.sin_addr ){
146 #ifndef FOSSIL_STATIC_LINK
147 struct hostent *pHost;
148 pHost = gethostbyname(pUrlData->name);
149 if( pHost!=0 ){
150 memcpy(&addr.sin_addr,pHost->h_addr_list[0],pHost->h_length);
151 }else
152 #endif
153 {
154 socket_set_errmsg("can't resolve host name: %s", pUrlData->name);
155 return 1;
156 }
157 }
158 addrIsInit = 1;
159
@@ -167,11 +167,12 @@
167 if( iSocket<0 ){
168 socket_set_errmsg("cannot create a socket");
169 return 1;
170 }
171 if( connect(iSocket,(struct sockaddr*)&addr,sizeof(addr))<0 ){
172 socket_set_errmsg("cannot connect to host %s:%d", pUrlData->name,
173 pUrlData->port);
174 socket_close();
175 return 1;
176 }
177 #if !defined(_WIN32)
178 signal(SIGPIPE, SIG_IGN);
@@ -215,16 +216,16 @@
216 /*
217 ** Attempt to resolve g.urlName to IP and setup g.zIpAddr so rcvfrom gets
218 ** populated. For hostnames with more than one IP (or if overridden in
219 ** ~/.ssh/config) the rcvfrom may not match the host to which we connect.
220 */
221 void socket_ssh_resolve_addr(UrlData *pUrlData){
222 struct hostent *pHost; /* Used to make best effort for rcvfrom */
223 struct sockaddr_in addr;
224
225 memset(&addr, 0, sizeof(addr));
226 pHost = gethostbyname(pUrlData->name);
227 if( pHost!=0 ){
228 memcpy(&addr.sin_addr,pHost->h_addr_list[0],pHost->h_length);
229 g.zIpAddr = mprintf("%s", inet_ntoa(addr.sin_addr));
230 }
231 }
232
+17 -15
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -183,11 +183,11 @@
183183
** g.urlName Name of the server. Ex: www.fossil-scm.org
184184
** g.urlPort TCP/IP port to use. Ex: 80
185185
**
186186
** Return the number of errors.
187187
*/
188
-int ssl_open(void){
188
+int ssl_open(UrlData *pUrlData){
189189
X509 *cert;
190190
int hasSavedCertificate = 0;
191191
int trusted = 0;
192192
unsigned long e;
193193
@@ -194,11 +194,11 @@
194194
ssl_global_init();
195195
196196
/* Get certificate for current server from global config and
197197
* (if we have it in config) add it to certificate store.
198198
*/
199
- cert = ssl_get_certificate(&trusted);
199
+ cert = ssl_get_certificate(pUrlData, &trusted);
200200
if ( cert!=NULL ){
201201
X509_STORE_add_cert(SSL_CTX_get_cert_store(sslCtx), cert);
202202
X509_free(cert);
203203
hasSavedCertificate = 1;
204204
}
@@ -205,11 +205,11 @@
205205
206206
iBio = BIO_new_ssl_connect(sslCtx);
207207
BIO_get_ssl(iBio, &ssl);
208208
209209
#if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT)
210
- if( !SSL_set_tlsext_host_name(ssl, g.urlName) ){
210
+ if( !SSL_set_tlsext_host_name(ssl, pUrlData->name) ){
211211
fossil_warning("WARNING: failed to set server name indication (SNI), "
212212
"continuing without it.\n");
213213
}
214214
#endif
215215
@@ -218,23 +218,25 @@
218218
ssl_set_errmsg("SSL: cannot open SSL (%s)",
219219
ERR_reason_error_string(ERR_get_error()));
220220
return 1;
221221
}
222222
223
- BIO_set_conn_hostname(iBio, g.urlName);
224
- BIO_set_conn_int_port(iBio, &g.urlPort);
223
+ BIO_set_conn_hostname(iBio, pUrlData->name);
224
+ BIO_set_conn_int_port(iBio, &pUrlData->port);
225225
226226
if( BIO_do_connect(iBio)<=0 ){
227227
ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)",
228
- g.urlName, g.urlPort, ERR_reason_error_string(ERR_get_error()));
228
+ pUrlData->name, pUrlData->port,
229
+ ERR_reason_error_string(ERR_get_error()));
229230
ssl_close();
230231
return 1;
231232
}
232233
233234
if( BIO_do_handshake(iBio)<=0 ) {
234235
ssl_set_errmsg("Error establishing SSL connection %s:%d (%s)",
235
- g.urlName, g.urlPort, ERR_reason_error_string(ERR_get_error()));
236
+ pUrlData->name, pUrlData->port,
237
+ ERR_reason_error_string(ERR_get_error()));
236238
ssl_close();
237239
return 1;
238240
}
239241
/* Check if certificate is valid */
240242
cert = SSL_get_peer_certificate(ssl);
@@ -281,11 +283,11 @@
281283
" certificates list\n\n"
282284
"If you are not expecting this message, answer no and "
283285
"contact your server\nadministrator.\n\n"
284286
"Accept certificate for host %s (a=always/y/N)? ",
285287
X509_verify_cert_error_string(e), desc, warning,
286
- g.urlName);
288
+ pUrlData->name);
287289
BIO_free(mem);
288290
289291
prompt_user(prompt, &ans);
290292
free(prompt);
291293
cReply = blob_str(&ans)[0];
@@ -302,11 +304,11 @@
302304
&ans);
303305
cReply = blob_str(&ans)[0];
304306
trusted = ( cReply=='a' || cReply=='A' );
305307
blob_reset(&ans);
306308
}
307
- ssl_save_certificate(cert, trusted);
309
+ ssl_save_certificate(pUrlData, cert, trusted);
308310
}
309311
}
310312
311313
/* Set the Global.zIpAddr variable to the server we are talking to.
312314
** This is used to populate the ipaddr column of the rcvfrom table,
@@ -323,44 +325,44 @@
323325
}
324326
325327
/*
326328
** Save certificate to global config.
327329
*/
328
-void ssl_save_certificate(X509 *cert, int trusted){
330
+void ssl_save_certificate(UrlData *pUrlData, X509 *cert, int trusted){
329331
BIO *mem;
330332
char *zCert, *zHost;
331333
332334
mem = BIO_new(BIO_s_mem());
333335
PEM_write_bio_X509(mem, cert);
334336
BIO_write(mem, "", 1); /* nul-terminate mem buffer */
335337
BIO_get_mem_data(mem, &zCert);
336
- zHost = mprintf("cert:%s", g.urlName);
338
+ zHost = mprintf("cert:%s", pUrlData->name);
337339
db_set(zHost, zCert, 1);
338340
free(zHost);
339
- zHost = mprintf("trusted:%s", g.urlName);
341
+ zHost = mprintf("trusted:%s", pUrlData->name);
340342
db_set_int(zHost, trusted, 1);
341343
free(zHost);
342344
BIO_free(mem);
343345
}
344346
345347
/*
346348
** Get certificate for g.urlName from global config.
347349
** Return NULL if no certificate found.
348350
*/
349
-X509 *ssl_get_certificate(int *pTrusted){
351
+X509 *ssl_get_certificate(UrlData *pUrlData, int *pTrusted){
350352
char *zHost, *zCert;
351353
BIO *mem;
352354
X509 *cert;
353355
354
- zHost = mprintf("cert:%s", g.urlName);
356
+ zHost = mprintf("cert:%s", pUrlData->name);
355357
zCert = db_get(zHost, NULL);
356358
free(zHost);
357359
if ( zCert==NULL )
358360
return NULL;
359361
360362
if ( pTrusted!=0 ){
361
- zHost = mprintf("trusted:%s", g.urlName);
363
+ zHost = mprintf("trusted:%s", pUrlData->name);
362364
*pTrusted = db_get_int(zHost, 0);
363365
free(zHost);
364366
}
365367
366368
mem = BIO_new(BIO_s_mem());
367369
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -183,11 +183,11 @@
183 ** g.urlName Name of the server. Ex: www.fossil-scm.org
184 ** g.urlPort TCP/IP port to use. Ex: 80
185 **
186 ** Return the number of errors.
187 */
188 int ssl_open(void){
189 X509 *cert;
190 int hasSavedCertificate = 0;
191 int trusted = 0;
192 unsigned long e;
193
@@ -194,11 +194,11 @@
194 ssl_global_init();
195
196 /* Get certificate for current server from global config and
197 * (if we have it in config) add it to certificate store.
198 */
199 cert = ssl_get_certificate(&trusted);
200 if ( cert!=NULL ){
201 X509_STORE_add_cert(SSL_CTX_get_cert_store(sslCtx), cert);
202 X509_free(cert);
203 hasSavedCertificate = 1;
204 }
@@ -205,11 +205,11 @@
205
206 iBio = BIO_new_ssl_connect(sslCtx);
207 BIO_get_ssl(iBio, &ssl);
208
209 #if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT)
210 if( !SSL_set_tlsext_host_name(ssl, g.urlName) ){
211 fossil_warning("WARNING: failed to set server name indication (SNI), "
212 "continuing without it.\n");
213 }
214 #endif
215
@@ -218,23 +218,25 @@
218 ssl_set_errmsg("SSL: cannot open SSL (%s)",
219 ERR_reason_error_string(ERR_get_error()));
220 return 1;
221 }
222
223 BIO_set_conn_hostname(iBio, g.urlName);
224 BIO_set_conn_int_port(iBio, &g.urlPort);
225
226 if( BIO_do_connect(iBio)<=0 ){
227 ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)",
228 g.urlName, g.urlPort, ERR_reason_error_string(ERR_get_error()));
 
229 ssl_close();
230 return 1;
231 }
232
233 if( BIO_do_handshake(iBio)<=0 ) {
234 ssl_set_errmsg("Error establishing SSL connection %s:%d (%s)",
235 g.urlName, g.urlPort, ERR_reason_error_string(ERR_get_error()));
 
236 ssl_close();
237 return 1;
238 }
239 /* Check if certificate is valid */
240 cert = SSL_get_peer_certificate(ssl);
@@ -281,11 +283,11 @@
281 " certificates list\n\n"
282 "If you are not expecting this message, answer no and "
283 "contact your server\nadministrator.\n\n"
284 "Accept certificate for host %s (a=always/y/N)? ",
285 X509_verify_cert_error_string(e), desc, warning,
286 g.urlName);
287 BIO_free(mem);
288
289 prompt_user(prompt, &ans);
290 free(prompt);
291 cReply = blob_str(&ans)[0];
@@ -302,11 +304,11 @@
302 &ans);
303 cReply = blob_str(&ans)[0];
304 trusted = ( cReply=='a' || cReply=='A' );
305 blob_reset(&ans);
306 }
307 ssl_save_certificate(cert, trusted);
308 }
309 }
310
311 /* Set the Global.zIpAddr variable to the server we are talking to.
312 ** This is used to populate the ipaddr column of the rcvfrom table,
@@ -323,44 +325,44 @@
323 }
324
325 /*
326 ** Save certificate to global config.
327 */
328 void ssl_save_certificate(X509 *cert, int trusted){
329 BIO *mem;
330 char *zCert, *zHost;
331
332 mem = BIO_new(BIO_s_mem());
333 PEM_write_bio_X509(mem, cert);
334 BIO_write(mem, "", 1); /* nul-terminate mem buffer */
335 BIO_get_mem_data(mem, &zCert);
336 zHost = mprintf("cert:%s", g.urlName);
337 db_set(zHost, zCert, 1);
338 free(zHost);
339 zHost = mprintf("trusted:%s", g.urlName);
340 db_set_int(zHost, trusted, 1);
341 free(zHost);
342 BIO_free(mem);
343 }
344
345 /*
346 ** Get certificate for g.urlName from global config.
347 ** Return NULL if no certificate found.
348 */
349 X509 *ssl_get_certificate(int *pTrusted){
350 char *zHost, *zCert;
351 BIO *mem;
352 X509 *cert;
353
354 zHost = mprintf("cert:%s", g.urlName);
355 zCert = db_get(zHost, NULL);
356 free(zHost);
357 if ( zCert==NULL )
358 return NULL;
359
360 if ( pTrusted!=0 ){
361 zHost = mprintf("trusted:%s", g.urlName);
362 *pTrusted = db_get_int(zHost, 0);
363 free(zHost);
364 }
365
366 mem = BIO_new(BIO_s_mem());
367
--- src/http_ssl.c
+++ src/http_ssl.c
@@ -183,11 +183,11 @@
183 ** g.urlName Name of the server. Ex: www.fossil-scm.org
184 ** g.urlPort TCP/IP port to use. Ex: 80
185 **
186 ** Return the number of errors.
187 */
188 int ssl_open(UrlData *pUrlData){
189 X509 *cert;
190 int hasSavedCertificate = 0;
191 int trusted = 0;
192 unsigned long e;
193
@@ -194,11 +194,11 @@
194 ssl_global_init();
195
196 /* Get certificate for current server from global config and
197 * (if we have it in config) add it to certificate store.
198 */
199 cert = ssl_get_certificate(pUrlData, &trusted);
200 if ( cert!=NULL ){
201 X509_STORE_add_cert(SSL_CTX_get_cert_store(sslCtx), cert);
202 X509_free(cert);
203 hasSavedCertificate = 1;
204 }
@@ -205,11 +205,11 @@
205
206 iBio = BIO_new_ssl_connect(sslCtx);
207 BIO_get_ssl(iBio, &ssl);
208
209 #if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT)
210 if( !SSL_set_tlsext_host_name(ssl, pUrlData->name) ){
211 fossil_warning("WARNING: failed to set server name indication (SNI), "
212 "continuing without it.\n");
213 }
214 #endif
215
@@ -218,23 +218,25 @@
218 ssl_set_errmsg("SSL: cannot open SSL (%s)",
219 ERR_reason_error_string(ERR_get_error()));
220 return 1;
221 }
222
223 BIO_set_conn_hostname(iBio, pUrlData->name);
224 BIO_set_conn_int_port(iBio, &pUrlData->port);
225
226 if( BIO_do_connect(iBio)<=0 ){
227 ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)",
228 pUrlData->name, pUrlData->port,
229 ERR_reason_error_string(ERR_get_error()));
230 ssl_close();
231 return 1;
232 }
233
234 if( BIO_do_handshake(iBio)<=0 ) {
235 ssl_set_errmsg("Error establishing SSL connection %s:%d (%s)",
236 pUrlData->name, pUrlData->port,
237 ERR_reason_error_string(ERR_get_error()));
238 ssl_close();
239 return 1;
240 }
241 /* Check if certificate is valid */
242 cert = SSL_get_peer_certificate(ssl);
@@ -281,11 +283,11 @@
283 " certificates list\n\n"
284 "If you are not expecting this message, answer no and "
285 "contact your server\nadministrator.\n\n"
286 "Accept certificate for host %s (a=always/y/N)? ",
287 X509_verify_cert_error_string(e), desc, warning,
288 pUrlData->name);
289 BIO_free(mem);
290
291 prompt_user(prompt, &ans);
292 free(prompt);
293 cReply = blob_str(&ans)[0];
@@ -302,11 +304,11 @@
304 &ans);
305 cReply = blob_str(&ans)[0];
306 trusted = ( cReply=='a' || cReply=='A' );
307 blob_reset(&ans);
308 }
309 ssl_save_certificate(pUrlData, cert, trusted);
310 }
311 }
312
313 /* Set the Global.zIpAddr variable to the server we are talking to.
314 ** This is used to populate the ipaddr column of the rcvfrom table,
@@ -323,44 +325,44 @@
325 }
326
327 /*
328 ** Save certificate to global config.
329 */
330 void ssl_save_certificate(UrlData *pUrlData, X509 *cert, int trusted){
331 BIO *mem;
332 char *zCert, *zHost;
333
334 mem = BIO_new(BIO_s_mem());
335 PEM_write_bio_X509(mem, cert);
336 BIO_write(mem, "", 1); /* nul-terminate mem buffer */
337 BIO_get_mem_data(mem, &zCert);
338 zHost = mprintf("cert:%s", pUrlData->name);
339 db_set(zHost, zCert, 1);
340 free(zHost);
341 zHost = mprintf("trusted:%s", pUrlData->name);
342 db_set_int(zHost, trusted, 1);
343 free(zHost);
344 BIO_free(mem);
345 }
346
347 /*
348 ** Get certificate for g.urlName from global config.
349 ** Return NULL if no certificate found.
350 */
351 X509 *ssl_get_certificate(UrlData *pUrlData, int *pTrusted){
352 char *zHost, *zCert;
353 BIO *mem;
354 X509 *cert;
355
356 zHost = mprintf("cert:%s", pUrlData->name);
357 zCert = db_get(zHost, NULL);
358 free(zHost);
359 if ( zCert==NULL )
360 return NULL;
361
362 if ( pTrusted!=0 ){
363 zHost = mprintf("trusted:%s", pUrlData->name);
364 *pTrusted = db_get_int(zHost, 0);
365 free(zHost);
366 }
367
368 mem = BIO_new(BIO_s_mem());
369
--- src/http_transport.c
+++ src/http_transport.c
@@ -52,13 +52,13 @@
5252
5353
5454
/*
5555
** Return the current transport error message.
5656
*/
57
-const char *transport_errmsg(void){
57
+const char *transport_errmsg(UrlData *pUrlData){
5858
#ifdef FOSSIL_ENABLE_SSL
59
- if( g.urlIsHttps ){
59
+ if( pUrlData->isHttps ){
6060
return ssl_errmsg();
6161
}
6262
#endif
6363
return socket_errmsg();
6464
}
@@ -86,47 +86,47 @@
8686
#endif
8787
8888
/*
8989
** SSH initialization of the transport layer
9090
*/
91
-int transport_ssh_open(void){
91
+int transport_ssh_open(UrlData *pUrlData){
9292
/* For SSH we need to create and run SSH fossil http
9393
** to talk to the remote machine.
9494
*/
9595
const char *zSsh; /* The base SSH command */
9696
Blob zCmd; /* The SSH command */
9797
char *zHost; /* The host name to contact */
9898
int n; /* Size of prefix string */
9999
100
- socket_ssh_resolve_addr();
100
+ socket_ssh_resolve_addr(pUrlData);
101101
zSsh = db_get("ssh-command", zDefaultSshCmd);
102102
blob_init(&zCmd, zSsh, -1);
103
- if( g.urlPort!=g.urlDfltPort && g.urlPort ){
103
+ if( pUrlData->port!=pUrlData->dfltPort && pUrlData->port ){
104104
#ifdef __MINGW32__
105
- blob_appendf(&zCmd, " -P %d", g.urlPort);
105
+ blob_appendf(&zCmd, " -P %d", pUrlData->port);
106106
#else
107
- blob_appendf(&zCmd, " -p %d", g.urlPort);
107
+ blob_appendf(&zCmd, " -p %d", pUrlData->port);
108108
#endif
109109
}
110110
if( g.fSshTrace ){
111111
fossil_force_newline();
112112
fossil_print("%s", blob_str(&zCmd)); /* Show the base of the SSH command */
113113
}
114
- if( g.urlUser && g.urlUser[0] ){
115
- zHost = mprintf("%s@%s", g.urlUser, g.urlName);
114
+ if( pUrlData->user && pUrlData->user[0] ){
115
+ zHost = mprintf("%s@%s", pUrlData->user, pUrlData->name);
116116
}else{
117
- zHost = mprintf("%s", g.urlName);
117
+ zHost = mprintf("%s", pUrlData->name);
118118
}
119119
n = blob_size(&zCmd);
120120
blob_append(&zCmd, " ", 1);
121121
shell_escape(&zCmd, zHost);
122122
blob_append(&zCmd, " ", 1);
123
- shell_escape(&zCmd, mprintf("%s", g.urlFossil));
123
+ shell_escape(&zCmd, mprintf("%s", pUrlData->fossil));
124124
blob_append(&zCmd, " test-http", 10);
125
- if( g.urlPath && g.urlPath[0] ){
125
+ if( pUrlData->path && pUrlData->path[0] ){
126126
blob_append(&zCmd, " ", 1);
127
- shell_escape(&zCmd, mprintf("%s", g.urlPath));
127
+ shell_escape(&zCmd, mprintf("%s", pUrlData->path));
128128
}
129129
if( g.fSshTrace ){
130130
fossil_print("%s\n", blob_str(&zCmd)+n); /* Show tail of SSH command */
131131
}
132132
free(zHost);
@@ -146,25 +146,25 @@
146146
** g.urlPort TCP/IP port. Ex: 80
147147
** g.urlIsHttps Use TLS for the connection
148148
**
149149
** Return the number of errors.
150150
*/
151
-int transport_open(void){
151
+int transport_open(UrlData *pUrlData){
152152
int rc = 0;
153153
if( transport.isOpen==0 ){
154
- if( g.urlIsSsh ){
155
- rc = transport_ssh_open();
154
+ if( pUrlData->isSsh ){
155
+ rc = transport_ssh_open(pUrlData);
156156
if( rc==0 ) transport.isOpen = 1;
157
- }else if( g.urlIsHttps ){
157
+ }else if( pUrlData->isHttps ){
158158
#ifdef FOSSIL_ENABLE_SSL
159
- rc = ssl_open();
159
+ rc = ssl_open(pUrlData);
160160
if( rc==0 ) transport.isOpen = 1;
161161
#else
162162
socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support");
163163
rc = 1;
164164
#endif
165
- }else if( g.urlIsFile ){
165
+ }else if( pUrlData->isFile ){
166166
sqlite3_uint64 iRandId;
167167
sqlite3_randomness(sizeof(iRandId), &iRandId);
168168
transport.zOutFile = mprintf("%s-%llu-out.http",
169169
g.zRepositoryName, iRandId);
170170
transport.zInFile = mprintf("%s-%llu-in.http",
@@ -173,21 +173,21 @@
173173
if( transport.pFile==0 ){
174174
fossil_fatal("cannot output temporary file: %s", transport.zOutFile);
175175
}
176176
transport.isOpen = 1;
177177
}else{
178
- rc = socket_open();
178
+ rc = socket_open(pUrlData);
179179
if( rc==0 ) transport.isOpen = 1;
180180
}
181181
}
182182
return rc;
183183
}
184184
185185
/*
186186
** Close the current connection
187187
*/
188
-void transport_close(void){
188
+void transport_close(UrlData *pUrlData){
189189
if( transport.isOpen ){
190190
free(transport.pBuf);
191191
transport.pBuf = 0;
192192
transport.nAlloc = 0;
193193
transport.nUsed = 0;
@@ -194,17 +194,17 @@
194194
transport.iCursor = 0;
195195
if( transport.pLog ){
196196
fclose(transport.pLog);
197197
transport.pLog = 0;
198198
}
199
- if( g.urlIsSsh ){
199
+ if( pUrlData->isSsh ){
200200
transport_ssh_close();
201
- }else if( g.urlIsHttps ){
201
+ }else if( pUrlData->isHttps ){
202202
#ifdef FOSSIL_ENABLE_SSL
203203
ssl_close();
204204
#endif
205
- }else if( g.urlIsFile ){
205
+ }else if( pUrlData->isFile ){
206206
if( transport.pFile ){
207207
fclose(transport.pFile);
208208
transport.pFile = 0;
209209
}
210210
file_delete(transport.zInFile);
@@ -219,28 +219,28 @@
219219
}
220220
221221
/*
222222
** Send content over the wire.
223223
*/
224
-void transport_send(Blob *toSend){
224
+void transport_send(UrlData *pUrlData, Blob *toSend){
225225
char *z = blob_buffer(toSend);
226226
int n = blob_size(toSend);
227227
transport.nSent += n;
228
- if( g.urlIsSsh ){
228
+ if( pUrlData->isSsh ){
229229
fwrite(z, 1, n, sshOut);
230230
fflush(sshOut);
231
- }else if( g.urlIsHttps ){
231
+ }else if( pUrlData->isHttps ){
232232
#ifdef FOSSIL_ENABLE_SSL
233233
int sent;
234234
while( n>0 ){
235235
sent = ssl_send(0, z, n);
236236
/* printf("Sent %d of %d bytes\n", sent, n); fflush(stdout); */
237237
if( sent<=0 ) break;
238238
n -= sent;
239239
}
240240
#endif
241
- }else if( g.urlIsFile ){
241
+ }else if( pUrlData->isFile ){
242242
fwrite(z, 1, n, transport.pFile);
243243
}else{
244244
int sent;
245245
while( n>0 ){
246246
sent = socket_send(0, z, n);
@@ -253,16 +253,16 @@
253253
254254
/*
255255
** This routine is called when the outbound message is complete and
256256
** it is time to being receiving a reply.
257257
*/
258
-void transport_flip(void){
259
- if( g.urlIsFile ){
258
+void transport_flip(UrlData *pUrlData){
259
+ if( pUrlData->isFile ){
260260
char *zCmd;
261261
fclose(transport.pFile);
262262
zCmd = mprintf("\"%s\" http \"%s\" \"%s\" \"%s\" 127.0.0.1 --localauth",
263
- g.nameOfExe, g.urlName, transport.zOutFile, transport.zInFile
263
+ g.nameOfExe, pUrlData->name, transport.zOutFile, transport.zInFile
264264
);
265265
fossil_system(zCmd);
266266
free(zCmd);
267267
transport.pFile = fossil_fopen(transport.zInFile, "rb");
268268
}
@@ -282,21 +282,21 @@
282282
283283
/*
284284
** This routine is called when the inbound message has been received
285285
** and it is time to start sending again.
286286
*/
287
-void transport_rewind(void){
288
- if( g.urlIsFile ){
289
- transport_close();
287
+void transport_rewind(UrlData *pUrlData){
288
+ if( pUrlData->isFile ){
289
+ transport_close(pUrlData);
290290
}
291291
}
292292
293293
/*
294294
** Read N bytes of content directly from the wire and write into
295295
** the buffer.
296296
*/
297
-static int transport_fetch(char *zBuf, int N){
297
+static int transport_fetch(UrlData *pUrlData, char *zBuf, int N){
298298
int got;
299299
if( sshIn ){
300300
int x;
301301
int wanted = N;
302302
got = 0;
@@ -304,17 +304,17 @@
304304
x = read(sshIn, &zBuf[got], wanted);
305305
if( x<=0 ) break;
306306
got += x;
307307
wanted -= x;
308308
}
309
- }else if( g.urlIsHttps ){
309
+ }else if( pUrlData->isHttps ){
310310
#ifdef FOSSIL_ENABLE_SSL
311311
got = ssl_receive(0, zBuf, N);
312312
#else
313313
got = 0;
314314
#endif
315
- }else if( g.urlIsFile ){
315
+ }else if( pUrlData->isFile ){
316316
got = fread(zBuf, 1, N, transport.pFile);
317317
}else{
318318
got = socket_receive(0, zBuf, N);
319319
}
320320
/* printf("received %d of %d bytes\n", got, N); fflush(stdout); */
@@ -327,11 +327,11 @@
327327
328328
/*
329329
** Read N bytes of content from the wire and store in the supplied buffer.
330330
** Return the number of bytes actually received.
331331
*/
332
-int transport_receive(char *zBuf, int N){
332
+int transport_receive(UrlData *pUrlData, char *zBuf, int N){
333333
int onHand; /* Bytes current held in the transport buffer */
334334
int nByte = 0; /* Bytes of content received */
335335
336336
onHand = transport.nUsed - transport.iCursor;
337337
if( g.fSshTrace){
@@ -351,11 +351,11 @@
351351
N -= toMove;
352352
zBuf += toMove;
353353
nByte += toMove;
354354
}
355355
if( N>0 ){
356
- int got = transport_fetch(zBuf, N);
356
+ int got = transport_fetch(pUrlData, zBuf, N);
357357
if( got>0 ){
358358
nByte += got;
359359
transport.nRcvd += got;
360360
}
361361
}
@@ -366,11 +366,11 @@
366366
/*
367367
** Load up to N new bytes of content into the transport.pBuf buffer.
368368
** The buffer itself might be moved. And the transport.iCursor value
369369
** might be reset to 0.
370370
*/
371
-static void transport_load_buffer(int N){
371
+static void transport_load_buffer(UrlData *pUrlData, int N){
372372
int i, j;
373373
if( transport.nAlloc==0 ){
374374
transport.nAlloc = N;
375375
transport.pBuf = fossil_malloc( N );
376376
transport.iCursor = 0;
@@ -388,11 +388,11 @@
388388
transport.nAlloc = transport.nUsed + N;
389389
pNew = fossil_realloc(transport.pBuf, transport.nAlloc);
390390
transport.pBuf = pNew;
391391
}
392392
if( N>0 ){
393
- i = transport_fetch(&transport.pBuf[transport.nUsed], N);
393
+ i = transport_fetch(pUrlData, &transport.pBuf[transport.nUsed], N);
394394
if( i>0 ){
395395
transport.nRcvd += i;
396396
transport.nUsed += i;
397397
}
398398
}
@@ -404,18 +404,18 @@
404404
** from the received line and zero-terminate the result. Return a pointer
405405
** to the line.
406406
**
407407
** Each call to this routine potentially overwrites the returned buffer.
408408
*/
409
-char *transport_receive_line(void){
409
+char *transport_receive_line(UrlData *pUrlData){
410410
int i;
411411
int iStart;
412412
413413
i = iStart = transport.iCursor;
414414
while(1){
415415
if( i >= transport.nUsed ){
416
- transport_load_buffer(g.urlIsSsh ? 2 : 1000);
416
+ transport_load_buffer(pUrlData, pUrlData->isSsh ? 2 : 1000);
417417
i -= iStart;
418418
iStart = 0;
419419
if( i >= transport.nUsed ){
420420
transport.pBuf[i] = 0;
421421
transport.iCursor = i;
@@ -437,15 +437,15 @@
437437
}
438438
439439
/*
440440
** Global transport shutdown
441441
*/
442
-void transport_global_shutdown(void){
443
- if( g.urlIsSsh ){
442
+void transport_global_shutdown(UrlData *pUrlData){
443
+ if( pUrlData->isSsh ){
444444
transport_ssh_close();
445445
}
446
- if( g.urlIsHttps ){
446
+ if( pUrlData->isHttps ){
447447
#ifdef FOSSIL_ENABLE_SSL
448448
ssl_global_shutdown();
449449
#endif
450450
}else{
451451
socket_global_shutdown();
452452
--- src/http_transport.c
+++ src/http_transport.c
@@ -52,13 +52,13 @@
52
53
54 /*
55 ** Return the current transport error message.
56 */
57 const char *transport_errmsg(void){
58 #ifdef FOSSIL_ENABLE_SSL
59 if( g.urlIsHttps ){
60 return ssl_errmsg();
61 }
62 #endif
63 return socket_errmsg();
64 }
@@ -86,47 +86,47 @@
86 #endif
87
88 /*
89 ** SSH initialization of the transport layer
90 */
91 int transport_ssh_open(void){
92 /* For SSH we need to create and run SSH fossil http
93 ** to talk to the remote machine.
94 */
95 const char *zSsh; /* The base SSH command */
96 Blob zCmd; /* The SSH command */
97 char *zHost; /* The host name to contact */
98 int n; /* Size of prefix string */
99
100 socket_ssh_resolve_addr();
101 zSsh = db_get("ssh-command", zDefaultSshCmd);
102 blob_init(&zCmd, zSsh, -1);
103 if( g.urlPort!=g.urlDfltPort && g.urlPort ){
104 #ifdef __MINGW32__
105 blob_appendf(&zCmd, " -P %d", g.urlPort);
106 #else
107 blob_appendf(&zCmd, " -p %d", g.urlPort);
108 #endif
109 }
110 if( g.fSshTrace ){
111 fossil_force_newline();
112 fossil_print("%s", blob_str(&zCmd)); /* Show the base of the SSH command */
113 }
114 if( g.urlUser && g.urlUser[0] ){
115 zHost = mprintf("%s@%s", g.urlUser, g.urlName);
116 }else{
117 zHost = mprintf("%s", g.urlName);
118 }
119 n = blob_size(&zCmd);
120 blob_append(&zCmd, " ", 1);
121 shell_escape(&zCmd, zHost);
122 blob_append(&zCmd, " ", 1);
123 shell_escape(&zCmd, mprintf("%s", g.urlFossil));
124 blob_append(&zCmd, " test-http", 10);
125 if( g.urlPath && g.urlPath[0] ){
126 blob_append(&zCmd, " ", 1);
127 shell_escape(&zCmd, mprintf("%s", g.urlPath));
128 }
129 if( g.fSshTrace ){
130 fossil_print("%s\n", blob_str(&zCmd)+n); /* Show tail of SSH command */
131 }
132 free(zHost);
@@ -146,25 +146,25 @@
146 ** g.urlPort TCP/IP port. Ex: 80
147 ** g.urlIsHttps Use TLS for the connection
148 **
149 ** Return the number of errors.
150 */
151 int transport_open(void){
152 int rc = 0;
153 if( transport.isOpen==0 ){
154 if( g.urlIsSsh ){
155 rc = transport_ssh_open();
156 if( rc==0 ) transport.isOpen = 1;
157 }else if( g.urlIsHttps ){
158 #ifdef FOSSIL_ENABLE_SSL
159 rc = ssl_open();
160 if( rc==0 ) transport.isOpen = 1;
161 #else
162 socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support");
163 rc = 1;
164 #endif
165 }else if( g.urlIsFile ){
166 sqlite3_uint64 iRandId;
167 sqlite3_randomness(sizeof(iRandId), &iRandId);
168 transport.zOutFile = mprintf("%s-%llu-out.http",
169 g.zRepositoryName, iRandId);
170 transport.zInFile = mprintf("%s-%llu-in.http",
@@ -173,21 +173,21 @@
173 if( transport.pFile==0 ){
174 fossil_fatal("cannot output temporary file: %s", transport.zOutFile);
175 }
176 transport.isOpen = 1;
177 }else{
178 rc = socket_open();
179 if( rc==0 ) transport.isOpen = 1;
180 }
181 }
182 return rc;
183 }
184
185 /*
186 ** Close the current connection
187 */
188 void transport_close(void){
189 if( transport.isOpen ){
190 free(transport.pBuf);
191 transport.pBuf = 0;
192 transport.nAlloc = 0;
193 transport.nUsed = 0;
@@ -194,17 +194,17 @@
194 transport.iCursor = 0;
195 if( transport.pLog ){
196 fclose(transport.pLog);
197 transport.pLog = 0;
198 }
199 if( g.urlIsSsh ){
200 transport_ssh_close();
201 }else if( g.urlIsHttps ){
202 #ifdef FOSSIL_ENABLE_SSL
203 ssl_close();
204 #endif
205 }else if( g.urlIsFile ){
206 if( transport.pFile ){
207 fclose(transport.pFile);
208 transport.pFile = 0;
209 }
210 file_delete(transport.zInFile);
@@ -219,28 +219,28 @@
219 }
220
221 /*
222 ** Send content over the wire.
223 */
224 void transport_send(Blob *toSend){
225 char *z = blob_buffer(toSend);
226 int n = blob_size(toSend);
227 transport.nSent += n;
228 if( g.urlIsSsh ){
229 fwrite(z, 1, n, sshOut);
230 fflush(sshOut);
231 }else if( g.urlIsHttps ){
232 #ifdef FOSSIL_ENABLE_SSL
233 int sent;
234 while( n>0 ){
235 sent = ssl_send(0, z, n);
236 /* printf("Sent %d of %d bytes\n", sent, n); fflush(stdout); */
237 if( sent<=0 ) break;
238 n -= sent;
239 }
240 #endif
241 }else if( g.urlIsFile ){
242 fwrite(z, 1, n, transport.pFile);
243 }else{
244 int sent;
245 while( n>0 ){
246 sent = socket_send(0, z, n);
@@ -253,16 +253,16 @@
253
254 /*
255 ** This routine is called when the outbound message is complete and
256 ** it is time to being receiving a reply.
257 */
258 void transport_flip(void){
259 if( g.urlIsFile ){
260 char *zCmd;
261 fclose(transport.pFile);
262 zCmd = mprintf("\"%s\" http \"%s\" \"%s\" \"%s\" 127.0.0.1 --localauth",
263 g.nameOfExe, g.urlName, transport.zOutFile, transport.zInFile
264 );
265 fossil_system(zCmd);
266 free(zCmd);
267 transport.pFile = fossil_fopen(transport.zInFile, "rb");
268 }
@@ -282,21 +282,21 @@
282
283 /*
284 ** This routine is called when the inbound message has been received
285 ** and it is time to start sending again.
286 */
287 void transport_rewind(void){
288 if( g.urlIsFile ){
289 transport_close();
290 }
291 }
292
293 /*
294 ** Read N bytes of content directly from the wire and write into
295 ** the buffer.
296 */
297 static int transport_fetch(char *zBuf, int N){
298 int got;
299 if( sshIn ){
300 int x;
301 int wanted = N;
302 got = 0;
@@ -304,17 +304,17 @@
304 x = read(sshIn, &zBuf[got], wanted);
305 if( x<=0 ) break;
306 got += x;
307 wanted -= x;
308 }
309 }else if( g.urlIsHttps ){
310 #ifdef FOSSIL_ENABLE_SSL
311 got = ssl_receive(0, zBuf, N);
312 #else
313 got = 0;
314 #endif
315 }else if( g.urlIsFile ){
316 got = fread(zBuf, 1, N, transport.pFile);
317 }else{
318 got = socket_receive(0, zBuf, N);
319 }
320 /* printf("received %d of %d bytes\n", got, N); fflush(stdout); */
@@ -327,11 +327,11 @@
327
328 /*
329 ** Read N bytes of content from the wire and store in the supplied buffer.
330 ** Return the number of bytes actually received.
331 */
332 int transport_receive(char *zBuf, int N){
333 int onHand; /* Bytes current held in the transport buffer */
334 int nByte = 0; /* Bytes of content received */
335
336 onHand = transport.nUsed - transport.iCursor;
337 if( g.fSshTrace){
@@ -351,11 +351,11 @@
351 N -= toMove;
352 zBuf += toMove;
353 nByte += toMove;
354 }
355 if( N>0 ){
356 int got = transport_fetch(zBuf, N);
357 if( got>0 ){
358 nByte += got;
359 transport.nRcvd += got;
360 }
361 }
@@ -366,11 +366,11 @@
366 /*
367 ** Load up to N new bytes of content into the transport.pBuf buffer.
368 ** The buffer itself might be moved. And the transport.iCursor value
369 ** might be reset to 0.
370 */
371 static void transport_load_buffer(int N){
372 int i, j;
373 if( transport.nAlloc==0 ){
374 transport.nAlloc = N;
375 transport.pBuf = fossil_malloc( N );
376 transport.iCursor = 0;
@@ -388,11 +388,11 @@
388 transport.nAlloc = transport.nUsed + N;
389 pNew = fossil_realloc(transport.pBuf, transport.nAlloc);
390 transport.pBuf = pNew;
391 }
392 if( N>0 ){
393 i = transport_fetch(&transport.pBuf[transport.nUsed], N);
394 if( i>0 ){
395 transport.nRcvd += i;
396 transport.nUsed += i;
397 }
398 }
@@ -404,18 +404,18 @@
404 ** from the received line and zero-terminate the result. Return a pointer
405 ** to the line.
406 **
407 ** Each call to this routine potentially overwrites the returned buffer.
408 */
409 char *transport_receive_line(void){
410 int i;
411 int iStart;
412
413 i = iStart = transport.iCursor;
414 while(1){
415 if( i >= transport.nUsed ){
416 transport_load_buffer(g.urlIsSsh ? 2 : 1000);
417 i -= iStart;
418 iStart = 0;
419 if( i >= transport.nUsed ){
420 transport.pBuf[i] = 0;
421 transport.iCursor = i;
@@ -437,15 +437,15 @@
437 }
438
439 /*
440 ** Global transport shutdown
441 */
442 void transport_global_shutdown(void){
443 if( g.urlIsSsh ){
444 transport_ssh_close();
445 }
446 if( g.urlIsHttps ){
447 #ifdef FOSSIL_ENABLE_SSL
448 ssl_global_shutdown();
449 #endif
450 }else{
451 socket_global_shutdown();
452
--- src/http_transport.c
+++ src/http_transport.c
@@ -52,13 +52,13 @@
52
53
54 /*
55 ** Return the current transport error message.
56 */
57 const char *transport_errmsg(UrlData *pUrlData){
58 #ifdef FOSSIL_ENABLE_SSL
59 if( pUrlData->isHttps ){
60 return ssl_errmsg();
61 }
62 #endif
63 return socket_errmsg();
64 }
@@ -86,47 +86,47 @@
86 #endif
87
88 /*
89 ** SSH initialization of the transport layer
90 */
91 int transport_ssh_open(UrlData *pUrlData){
92 /* For SSH we need to create and run SSH fossil http
93 ** to talk to the remote machine.
94 */
95 const char *zSsh; /* The base SSH command */
96 Blob zCmd; /* The SSH command */
97 char *zHost; /* The host name to contact */
98 int n; /* Size of prefix string */
99
100 socket_ssh_resolve_addr(pUrlData);
101 zSsh = db_get("ssh-command", zDefaultSshCmd);
102 blob_init(&zCmd, zSsh, -1);
103 if( pUrlData->port!=pUrlData->dfltPort && pUrlData->port ){
104 #ifdef __MINGW32__
105 blob_appendf(&zCmd, " -P %d", pUrlData->port);
106 #else
107 blob_appendf(&zCmd, " -p %d", pUrlData->port);
108 #endif
109 }
110 if( g.fSshTrace ){
111 fossil_force_newline();
112 fossil_print("%s", blob_str(&zCmd)); /* Show the base of the SSH command */
113 }
114 if( pUrlData->user && pUrlData->user[0] ){
115 zHost = mprintf("%s@%s", pUrlData->user, pUrlData->name);
116 }else{
117 zHost = mprintf("%s", pUrlData->name);
118 }
119 n = blob_size(&zCmd);
120 blob_append(&zCmd, " ", 1);
121 shell_escape(&zCmd, zHost);
122 blob_append(&zCmd, " ", 1);
123 shell_escape(&zCmd, mprintf("%s", pUrlData->fossil));
124 blob_append(&zCmd, " test-http", 10);
125 if( pUrlData->path && pUrlData->path[0] ){
126 blob_append(&zCmd, " ", 1);
127 shell_escape(&zCmd, mprintf("%s", pUrlData->path));
128 }
129 if( g.fSshTrace ){
130 fossil_print("%s\n", blob_str(&zCmd)+n); /* Show tail of SSH command */
131 }
132 free(zHost);
@@ -146,25 +146,25 @@
146 ** g.urlPort TCP/IP port. Ex: 80
147 ** g.urlIsHttps Use TLS for the connection
148 **
149 ** Return the number of errors.
150 */
151 int transport_open(UrlData *pUrlData){
152 int rc = 0;
153 if( transport.isOpen==0 ){
154 if( pUrlData->isSsh ){
155 rc = transport_ssh_open(pUrlData);
156 if( rc==0 ) transport.isOpen = 1;
157 }else if( pUrlData->isHttps ){
158 #ifdef FOSSIL_ENABLE_SSL
159 rc = ssl_open(pUrlData);
160 if( rc==0 ) transport.isOpen = 1;
161 #else
162 socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support");
163 rc = 1;
164 #endif
165 }else if( pUrlData->isFile ){
166 sqlite3_uint64 iRandId;
167 sqlite3_randomness(sizeof(iRandId), &iRandId);
168 transport.zOutFile = mprintf("%s-%llu-out.http",
169 g.zRepositoryName, iRandId);
170 transport.zInFile = mprintf("%s-%llu-in.http",
@@ -173,21 +173,21 @@
173 if( transport.pFile==0 ){
174 fossil_fatal("cannot output temporary file: %s", transport.zOutFile);
175 }
176 transport.isOpen = 1;
177 }else{
178 rc = socket_open(pUrlData);
179 if( rc==0 ) transport.isOpen = 1;
180 }
181 }
182 return rc;
183 }
184
185 /*
186 ** Close the current connection
187 */
188 void transport_close(UrlData *pUrlData){
189 if( transport.isOpen ){
190 free(transport.pBuf);
191 transport.pBuf = 0;
192 transport.nAlloc = 0;
193 transport.nUsed = 0;
@@ -194,17 +194,17 @@
194 transport.iCursor = 0;
195 if( transport.pLog ){
196 fclose(transport.pLog);
197 transport.pLog = 0;
198 }
199 if( pUrlData->isSsh ){
200 transport_ssh_close();
201 }else if( pUrlData->isHttps ){
202 #ifdef FOSSIL_ENABLE_SSL
203 ssl_close();
204 #endif
205 }else if( pUrlData->isFile ){
206 if( transport.pFile ){
207 fclose(transport.pFile);
208 transport.pFile = 0;
209 }
210 file_delete(transport.zInFile);
@@ -219,28 +219,28 @@
219 }
220
221 /*
222 ** Send content over the wire.
223 */
224 void transport_send(UrlData *pUrlData, Blob *toSend){
225 char *z = blob_buffer(toSend);
226 int n = blob_size(toSend);
227 transport.nSent += n;
228 if( pUrlData->isSsh ){
229 fwrite(z, 1, n, sshOut);
230 fflush(sshOut);
231 }else if( pUrlData->isHttps ){
232 #ifdef FOSSIL_ENABLE_SSL
233 int sent;
234 while( n>0 ){
235 sent = ssl_send(0, z, n);
236 /* printf("Sent %d of %d bytes\n", sent, n); fflush(stdout); */
237 if( sent<=0 ) break;
238 n -= sent;
239 }
240 #endif
241 }else if( pUrlData->isFile ){
242 fwrite(z, 1, n, transport.pFile);
243 }else{
244 int sent;
245 while( n>0 ){
246 sent = socket_send(0, z, n);
@@ -253,16 +253,16 @@
253
254 /*
255 ** This routine is called when the outbound message is complete and
256 ** it is time to being receiving a reply.
257 */
258 void transport_flip(UrlData *pUrlData){
259 if( pUrlData->isFile ){
260 char *zCmd;
261 fclose(transport.pFile);
262 zCmd = mprintf("\"%s\" http \"%s\" \"%s\" \"%s\" 127.0.0.1 --localauth",
263 g.nameOfExe, pUrlData->name, transport.zOutFile, transport.zInFile
264 );
265 fossil_system(zCmd);
266 free(zCmd);
267 transport.pFile = fossil_fopen(transport.zInFile, "rb");
268 }
@@ -282,21 +282,21 @@
282
283 /*
284 ** This routine is called when the inbound message has been received
285 ** and it is time to start sending again.
286 */
287 void transport_rewind(UrlData *pUrlData){
288 if( pUrlData->isFile ){
289 transport_close(pUrlData);
290 }
291 }
292
293 /*
294 ** Read N bytes of content directly from the wire and write into
295 ** the buffer.
296 */
297 static int transport_fetch(UrlData *pUrlData, char *zBuf, int N){
298 int got;
299 if( sshIn ){
300 int x;
301 int wanted = N;
302 got = 0;
@@ -304,17 +304,17 @@
304 x = read(sshIn, &zBuf[got], wanted);
305 if( x<=0 ) break;
306 got += x;
307 wanted -= x;
308 }
309 }else if( pUrlData->isHttps ){
310 #ifdef FOSSIL_ENABLE_SSL
311 got = ssl_receive(0, zBuf, N);
312 #else
313 got = 0;
314 #endif
315 }else if( pUrlData->isFile ){
316 got = fread(zBuf, 1, N, transport.pFile);
317 }else{
318 got = socket_receive(0, zBuf, N);
319 }
320 /* printf("received %d of %d bytes\n", got, N); fflush(stdout); */
@@ -327,11 +327,11 @@
327
328 /*
329 ** Read N bytes of content from the wire and store in the supplied buffer.
330 ** Return the number of bytes actually received.
331 */
332 int transport_receive(UrlData *pUrlData, char *zBuf, int N){
333 int onHand; /* Bytes current held in the transport buffer */
334 int nByte = 0; /* Bytes of content received */
335
336 onHand = transport.nUsed - transport.iCursor;
337 if( g.fSshTrace){
@@ -351,11 +351,11 @@
351 N -= toMove;
352 zBuf += toMove;
353 nByte += toMove;
354 }
355 if( N>0 ){
356 int got = transport_fetch(pUrlData, zBuf, N);
357 if( got>0 ){
358 nByte += got;
359 transport.nRcvd += got;
360 }
361 }
@@ -366,11 +366,11 @@
366 /*
367 ** Load up to N new bytes of content into the transport.pBuf buffer.
368 ** The buffer itself might be moved. And the transport.iCursor value
369 ** might be reset to 0.
370 */
371 static void transport_load_buffer(UrlData *pUrlData, int N){
372 int i, j;
373 if( transport.nAlloc==0 ){
374 transport.nAlloc = N;
375 transport.pBuf = fossil_malloc( N );
376 transport.iCursor = 0;
@@ -388,11 +388,11 @@
388 transport.nAlloc = transport.nUsed + N;
389 pNew = fossil_realloc(transport.pBuf, transport.nAlloc);
390 transport.pBuf = pNew;
391 }
392 if( N>0 ){
393 i = transport_fetch(pUrlData, &transport.pBuf[transport.nUsed], N);
394 if( i>0 ){
395 transport.nRcvd += i;
396 transport.nUsed += i;
397 }
398 }
@@ -404,18 +404,18 @@
404 ** from the received line and zero-terminate the result. Return a pointer
405 ** to the line.
406 **
407 ** Each call to this routine potentially overwrites the returned buffer.
408 */
409 char *transport_receive_line(UrlData *pUrlData){
410 int i;
411 int iStart;
412
413 i = iStart = transport.iCursor;
414 while(1){
415 if( i >= transport.nUsed ){
416 transport_load_buffer(pUrlData, pUrlData->isSsh ? 2 : 1000);
417 i -= iStart;
418 iStart = 0;
419 if( i >= transport.nUsed ){
420 transport.pBuf[i] = 0;
421 transport.iCursor = i;
@@ -437,15 +437,15 @@
437 }
438
439 /*
440 ** Global transport shutdown
441 */
442 void transport_global_shutdown(UrlData *pUrlData){
443 if( pUrlData->isSsh ){
444 transport_ssh_close();
445 }
446 if( pUrlData->isHttps ){
447 #ifdef FOSSIL_ENABLE_SSL
448 ssl_global_shutdown();
449 #endif
450 }else{
451 socket_global_shutdown();
452
+1 -1
--- src/info.c
+++ src/info.c
@@ -2195,11 +2195,11 @@
21952195
md5sum_blob(&ctrl, &cksum);
21962196
blob_appendf(&ctrl, "Z %b\n", &cksum);
21972197
db_begin_transaction();
21982198
g.markPrivate = content_is_private(rid);
21992199
nrid = content_put(&ctrl);
2200
- manifest_crosslink(nrid, &ctrl);
2200
+ manifest_crosslink(nrid, &ctrl, MC_PERMIT_HOOKS);
22012201
assert( blob_is_reset(&ctrl) );
22022202
db_end_transaction(0);
22032203
}
22042204
cgi_redirectf("ci?name=%s", zUuid);
22052205
}
22062206
--- src/info.c
+++ src/info.c
@@ -2195,11 +2195,11 @@
2195 md5sum_blob(&ctrl, &cksum);
2196 blob_appendf(&ctrl, "Z %b\n", &cksum);
2197 db_begin_transaction();
2198 g.markPrivate = content_is_private(rid);
2199 nrid = content_put(&ctrl);
2200 manifest_crosslink(nrid, &ctrl);
2201 assert( blob_is_reset(&ctrl) );
2202 db_end_transaction(0);
2203 }
2204 cgi_redirectf("ci?name=%s", zUuid);
2205 }
2206
--- src/info.c
+++ src/info.c
@@ -2195,11 +2195,11 @@
2195 md5sum_blob(&ctrl, &cksum);
2196 blob_appendf(&ctrl, "Z %b\n", &cksum);
2197 db_begin_transaction();
2198 g.markPrivate = content_is_private(rid);
2199 nrid = content_put(&ctrl);
2200 manifest_crosslink(nrid, &ctrl, MC_PERMIT_HOOKS);
2201 assert( blob_is_reset(&ctrl) );
2202 db_end_transaction(0);
2203 }
2204 cgi_redirectf("ci?name=%s", zUuid);
2205 }
2206
+1 -1
--- src/info.c
+++ src/info.c
@@ -2195,11 +2195,11 @@
21952195
md5sum_blob(&ctrl, &cksum);
21962196
blob_appendf(&ctrl, "Z %b\n", &cksum);
21972197
db_begin_transaction();
21982198
g.markPrivate = content_is_private(rid);
21992199
nrid = content_put(&ctrl);
2200
- manifest_crosslink(nrid, &ctrl);
2200
+ manifest_crosslink(nrid, &ctrl, MC_PERMIT_HOOKS);
22012201
assert( blob_is_reset(&ctrl) );
22022202
db_end_transaction(0);
22032203
}
22042204
cgi_redirectf("ci?name=%s", zUuid);
22052205
}
22062206
--- src/info.c
+++ src/info.c
@@ -2195,11 +2195,11 @@
2195 md5sum_blob(&ctrl, &cksum);
2196 blob_appendf(&ctrl, "Z %b\n", &cksum);
2197 db_begin_transaction();
2198 g.markPrivate = content_is_private(rid);
2199 nrid = content_put(&ctrl);
2200 manifest_crosslink(nrid, &ctrl);
2201 assert( blob_is_reset(&ctrl) );
2202 db_end_transaction(0);
2203 }
2204 cgi_redirectf("ci?name=%s", zUuid);
2205 }
2206
--- src/info.c
+++ src/info.c
@@ -2195,11 +2195,11 @@
2195 md5sum_blob(&ctrl, &cksum);
2196 blob_appendf(&ctrl, "Z %b\n", &cksum);
2197 db_begin_transaction();
2198 g.markPrivate = content_is_private(rid);
2199 nrid = content_put(&ctrl);
2200 manifest_crosslink(nrid, &ctrl, MC_PERMIT_HOOKS);
2201 assert( blob_is_reset(&ctrl) );
2202 db_end_transaction(0);
2203 }
2204 cgi_redirectf("ci?name=%s", zUuid);
2205 }
2206
--- src/json_branch.c
+++ src/json_branch.c
@@ -291,12 +291,12 @@
291291
brid = content_put(&branch);
292292
if( brid==0 ){
293293
fossil_fatal("Problem committing manifest: %s", g.zErrMsg);
294294
}
295295
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid);
296
- if( manifest_crosslink(brid, &branch)==0 ){
297
- fossil_fatal("unable to install new manifest");
296
+ if( manifest_crosslink(brid, &branch, MC_PERMIT_HOOKS)==0 ){
297
+ fossil_fatal("%s\n", g.zErrMsg);
298298
}
299299
assert( blob_is_reset(&branch) );
300300
content_deltify(rootid, brid, 0);
301301
if( zNewRid ){
302302
*zNewRid = brid;
303303
--- src/json_branch.c
+++ src/json_branch.c
@@ -291,12 +291,12 @@
291 brid = content_put(&branch);
292 if( brid==0 ){
293 fossil_fatal("Problem committing manifest: %s", g.zErrMsg);
294 }
295 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid);
296 if( manifest_crosslink(brid, &branch)==0 ){
297 fossil_fatal("unable to install new manifest");
298 }
299 assert( blob_is_reset(&branch) );
300 content_deltify(rootid, brid, 0);
301 if( zNewRid ){
302 *zNewRid = brid;
303
--- src/json_branch.c
+++ src/json_branch.c
@@ -291,12 +291,12 @@
291 brid = content_put(&branch);
292 if( brid==0 ){
293 fossil_fatal("Problem committing manifest: %s", g.zErrMsg);
294 }
295 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid);
296 if( manifest_crosslink(brid, &branch, MC_PERMIT_HOOKS)==0 ){
297 fossil_fatal("%s\n", g.zErrMsg);
298 }
299 assert( blob_is_reset(&branch) );
300 content_deltify(rootid, brid, 0);
301 if( zNewRid ){
302 *zNewRid = brid;
303
+16
--- src/main.c
+++ src/main.c
@@ -114,10 +114,12 @@
114114
#endif
115115
116116
/*
117117
** All global variables are in this structure.
118118
*/
119
+#define GLOBAL_URL() ((UrlData *)(&g.urlIsFile))
120
+
119121
struct Global {
120122
int argc; char **argv; /* Command-line arguments to the program */
121123
char *nameOfExe; /* Full path of executable. */
122124
const char *zErrlog; /* Log errors to this file, if not NULL */
123125
int isConst; /* True if the output is unchanging */
@@ -168,10 +170,14 @@
168170
int wikiFlags; /* Wiki conversion flags applied to %w and %W */
169171
char isHTTP; /* True if server/CGI modes, else assume CLI. */
170172
char javascriptHyperlink; /* If true, set href= using script, not HTML */
171173
Blob httpHeader; /* Complete text of the HTTP request header */
172174
175
+ /*
176
+ ** NOTE: These members MUST be kept in sync with those in the "UrlData"
177
+ ** structure defined in "url.c".
178
+ */
173179
int urlIsFile; /* True if a "file:" url */
174180
int urlIsHttps; /* True if a "https:" url */
175181
int urlIsSsh; /* True if an "ssh:" url */
176182
char *urlName; /* Hostname for http: or filename for file: */
177183
char *urlHostname; /* The HOST: parameter on http headers */
@@ -831,10 +837,20 @@
831837
const char *get_version(){
832838
static const char version[] = RELEASE_VERSION " " MANIFEST_VERSION " "
833839
MANIFEST_DATE " UTC";
834840
return version;
835841
}
842
+
843
+/*
844
+** This function returns the user-agent string for Fossil, for
845
+** use in HTTP(S) requests.
846
+*/
847
+const char *get_user_agent(){
848
+ static const char version[] = "Fossil/" RELEASE_VERSION " (" MANIFEST_DATE
849
+ " " MANIFEST_VERSION ")";
850
+ return version;
851
+}
836852
837853
/*
838854
** COMMAND: version
839855
**
840856
** Usage: %fossil version ?-verbose|-v?
841857
--- src/main.c
+++ src/main.c
@@ -114,10 +114,12 @@
114 #endif
115
116 /*
117 ** All global variables are in this structure.
118 */
 
 
119 struct Global {
120 int argc; char **argv; /* Command-line arguments to the program */
121 char *nameOfExe; /* Full path of executable. */
122 const char *zErrlog; /* Log errors to this file, if not NULL */
123 int isConst; /* True if the output is unchanging */
@@ -168,10 +170,14 @@
168 int wikiFlags; /* Wiki conversion flags applied to %w and %W */
169 char isHTTP; /* True if server/CGI modes, else assume CLI. */
170 char javascriptHyperlink; /* If true, set href= using script, not HTML */
171 Blob httpHeader; /* Complete text of the HTTP request header */
172
 
 
 
 
173 int urlIsFile; /* True if a "file:" url */
174 int urlIsHttps; /* True if a "https:" url */
175 int urlIsSsh; /* True if an "ssh:" url */
176 char *urlName; /* Hostname for http: or filename for file: */
177 char *urlHostname; /* The HOST: parameter on http headers */
@@ -831,10 +837,20 @@
831 const char *get_version(){
832 static const char version[] = RELEASE_VERSION " " MANIFEST_VERSION " "
833 MANIFEST_DATE " UTC";
834 return version;
835 }
 
 
 
 
 
 
 
 
 
 
836
837 /*
838 ** COMMAND: version
839 **
840 ** Usage: %fossil version ?-verbose|-v?
841
--- src/main.c
+++ src/main.c
@@ -114,10 +114,12 @@
114 #endif
115
116 /*
117 ** All global variables are in this structure.
118 */
119 #define GLOBAL_URL() ((UrlData *)(&g.urlIsFile))
120
121 struct Global {
122 int argc; char **argv; /* Command-line arguments to the program */
123 char *nameOfExe; /* Full path of executable. */
124 const char *zErrlog; /* Log errors to this file, if not NULL */
125 int isConst; /* True if the output is unchanging */
@@ -168,10 +170,14 @@
170 int wikiFlags; /* Wiki conversion flags applied to %w and %W */
171 char isHTTP; /* True if server/CGI modes, else assume CLI. */
172 char javascriptHyperlink; /* If true, set href= using script, not HTML */
173 Blob httpHeader; /* Complete text of the HTTP request header */
174
175 /*
176 ** NOTE: These members MUST be kept in sync with those in the "UrlData"
177 ** structure defined in "url.c".
178 */
179 int urlIsFile; /* True if a "file:" url */
180 int urlIsHttps; /* True if a "https:" url */
181 int urlIsSsh; /* True if an "ssh:" url */
182 char *urlName; /* Hostname for http: or filename for file: */
183 char *urlHostname; /* The HOST: parameter on http headers */
@@ -831,10 +837,20 @@
837 const char *get_version(){
838 static const char version[] = RELEASE_VERSION " " MANIFEST_VERSION " "
839 MANIFEST_DATE " UTC";
840 return version;
841 }
842
843 /*
844 ** This function returns the user-agent string for Fossil, for
845 ** use in HTTP(S) requests.
846 */
847 const char *get_user_agent(){
848 static const char version[] = "Fossil/" RELEASE_VERSION " (" MANIFEST_DATE
849 " " MANIFEST_VERSION ")";
850 return version;
851 }
852
853 /*
854 ** COMMAND: version
855 **
856 ** Usage: %fossil version ?-verbose|-v?
857
+38 -11
--- src/manifest.c
+++ src/manifest.c
@@ -42,10 +42,16 @@
4242
*/
4343
#define PERM_REG 0 /* regular file */
4444
#define PERM_EXE 1 /* executable */
4545
#define PERM_LNK 2 /* symlink */
4646
47
+/*
48
+** Flags for use with manifest_crosslink().
49
+*/
50
+#define MC_NONE 0 /* default handling */
51
+#define MC_PERMIT_HOOKS 1 /* permit hooks to execute */
52
+
4753
/*
4854
** A single F-card within a manifest
4955
*/
5056
struct ManifestFile {
5157
char *zName; /* Name of a file */
@@ -1650,34 +1656,41 @@
16501656
** Historical note: This routine original processed manifests only.
16511657
** Processing for other control artifacts was added later. The name
16521658
** of the routine, "manifest_crosslink", and the name of this source
16531659
** file, is a legacy of its original use.
16541660
*/
1655
-int manifest_crosslink(int rid, Blob *pContent){
1656
- int i;
1661
+int manifest_crosslink(int rid, Blob *pContent, int flags){
1662
+ int i, result = TH_OK;
16571663
Manifest *p;
16581664
Stmt q;
16591665
int parentid = 0;
1666
+ const char *zScript = 0;
1667
+ const char *zUuid = 0;
16601668
16611669
if( (p = manifest_cache_find(rid))!=0 ){
16621670
blob_reset(pContent);
16631671
}else if( (p = manifest_parse(pContent, rid, 0))==0 ){
16641672
assert( blob_is_reset(pContent) || pContent==0 );
1673
+ fossil_error(1, "syntax error in manifest");
16651674
return 0;
16661675
}
16671676
if( g.xlinkClusterOnly && p->type!=CFTYPE_CLUSTER ){
16681677
manifest_destroy(p);
16691678
assert( blob_is_reset(pContent) );
1679
+ fossil_error(1, "no manifest");
16701680
return 0;
16711681
}
16721682
if( p->type==CFTYPE_MANIFEST && fetch_baseline(p, 0) ){
16731683
manifest_destroy(p);
16741684
assert( blob_is_reset(pContent) );
1685
+ fossil_error(1, "cannot fetch baseline manifest");
16751686
return 0;
16761687
}
16771688
db_begin_transaction();
16781689
if( p->type==CFTYPE_MANIFEST ){
1690
+ zScript = xfer_commit_code();
1691
+ zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
16791692
if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){
16801693
char *zCom;
16811694
for(i=0; i<p->nParent; i++){
16821695
int pid = uuid_to_rid(p->azParent[i], 1);
16831696
db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)"
@@ -1768,11 +1781,11 @@
17681781
switch( p->aTag[i].zName[0] ){
17691782
case '-': type = 0; break; /* Cancel prior occurrences */
17701783
case '+': type = 1; break; /* Apply to target only */
17711784
case '*': type = 2; break; /* Propagate to descendants */
17721785
default:
1773
- fossil_fatal("unknown tag type in manifest: %s", p->aTag);
1786
+ fossil_error(1, "unknown tag type in manifest: %s", p->aTag);
17741787
return 0;
17751788
}
17761789
tag_insert(&p->aTag[i].zName[1], type, p->aTag[i].zValue,
17771790
rid, p->rDate, tid);
17781791
}
@@ -1870,10 +1883,12 @@
18701883
}
18711884
}
18721885
if( p->type==CFTYPE_TICKET ){
18731886
char *zTag;
18741887
1888
+ zScript = xfer_ticket_code();
1889
+ zUuid = p->zTicketUuid;
18751890
assert( manifest_crosslink_busy==1 );
18761891
zTag = mprintf("tkt-%s", p->zTicketUuid);
18771892
tag_insert(zTag, 1, 0, rid, p->rDate, rid);
18781893
free(zTag);
18791894
db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)",
@@ -1934,33 +1949,39 @@
19341949
if( p->type==CFTYPE_CONTROL ){
19351950
Blob comment;
19361951
int i;
19371952
const char *zName;
19381953
const char *zValue;
1939
- const char *zUuid;
1954
+ const char *zTagUuid;
19401955
int branchMove = 0;
19411956
blob_zero(&comment);
19421957
if( p->zComment ){
19431958
blob_appendf(&comment, " %s.", p->zComment);
19441959
}
19451960
/* Next loop expects tags to be sorted on UUID, so sort it. */
19461961
qsort(p->aTag, p->nTag, sizeof(p->aTag[0]), tag_compare);
19471962
for(i=0; i<p->nTag; i++){
1948
- zUuid = p->aTag[i].zUuid;
1949
- if( !zUuid ) continue;
1950
- if( i==0 || fossil_strcmp(zUuid, p->aTag[i-1].zUuid)!=0 ){
1963
+ zTagUuid = p->aTag[i].zUuid;
1964
+ if( !zTagUuid ) continue;
1965
+ if( i==0 || fossil_strcmp(zTagUuid, p->aTag[i-1].zUuid)!=0 ){
19511966
blob_appendf(&comment,
19521967
" Edit [%S]:",
1953
- zUuid);
1968
+ zTagUuid);
19541969
branchMove = 0;
1970
+ if( db_exists("SELECT 1 FROM event, blob"
1971
+ " WHERE event.type='ci' AND event.objid=blob.rid"
1972
+ " AND blob.uuid='%s'", zTagUuid) ){
1973
+ zScript = xfer_commit_code();
1974
+ zUuid = zTagUuid;
1975
+ }
19551976
}
19561977
zName = p->aTag[i].zName;
19571978
zValue = p->aTag[i].zValue;
19581979
if( strcmp(zName, "*branch")==0 ){
19591980
blob_appendf(&comment,
19601981
" Move to branch [/timeline?r=%h&nd&dp=%S | %h].",
1961
- zValue, zUuid, zValue);
1982
+ zValue, zTagUuid, zValue);
19621983
branchMove = 1;
19631984
continue;
19641985
}else if( strcmp(zName, "*bgcolor")==0 ){
19651986
blob_appendf(&comment,
19661987
" Change branch background color to \"%h\".", zValue);
@@ -2021,17 +2042,23 @@
20212042
p->rDate, rid, p->zUser, blob_str(&comment)+1
20222043
);
20232044
blob_reset(&comment);
20242045
}
20252046
db_end_transaction(0);
2047
+ if( zScript && (flags & MC_PERMIT_HOOKS) ){
2048
+ result = xfer_run_common_script();
2049
+ if( result==TH_OK ){
2050
+ result = xfer_run_script(zScript, zUuid);
2051
+ }
2052
+ }
20262053
if( p->type==CFTYPE_MANIFEST ){
20272054
manifest_cache_insert(p);
20282055
}else{
20292056
manifest_destroy(p);
20302057
}
20312058
assert( blob_is_reset(pContent) );
2032
- return 1;
2059
+ return ( result!=TH_ERROR );
20332060
}
20342061
20352062
/*
20362063
** COMMAND: test-crosslink
20372064
**
@@ -2045,7 +2072,7 @@
20452072
Blob content;
20462073
db_find_and_open_repository(0, 0);
20472074
if( g.argc!=3 ) usage("RECORDID");
20482075
rid = name_to_rid(g.argv[2]);
20492076
content_get(rid, &content);
2050
- manifest_crosslink(rid, &content);
2077
+ manifest_crosslink(rid, &content, MC_NONE);
20512078
}
20522079
--- src/manifest.c
+++ src/manifest.c
@@ -42,10 +42,16 @@
42 */
43 #define PERM_REG 0 /* regular file */
44 #define PERM_EXE 1 /* executable */
45 #define PERM_LNK 2 /* symlink */
46
 
 
 
 
 
 
47 /*
48 ** A single F-card within a manifest
49 */
50 struct ManifestFile {
51 char *zName; /* Name of a file */
@@ -1650,34 +1656,41 @@
1650 ** Historical note: This routine original processed manifests only.
1651 ** Processing for other control artifacts was added later. The name
1652 ** of the routine, "manifest_crosslink", and the name of this source
1653 ** file, is a legacy of its original use.
1654 */
1655 int manifest_crosslink(int rid, Blob *pContent){
1656 int i;
1657 Manifest *p;
1658 Stmt q;
1659 int parentid = 0;
 
 
1660
1661 if( (p = manifest_cache_find(rid))!=0 ){
1662 blob_reset(pContent);
1663 }else if( (p = manifest_parse(pContent, rid, 0))==0 ){
1664 assert( blob_is_reset(pContent) || pContent==0 );
 
1665 return 0;
1666 }
1667 if( g.xlinkClusterOnly && p->type!=CFTYPE_CLUSTER ){
1668 manifest_destroy(p);
1669 assert( blob_is_reset(pContent) );
 
1670 return 0;
1671 }
1672 if( p->type==CFTYPE_MANIFEST && fetch_baseline(p, 0) ){
1673 manifest_destroy(p);
1674 assert( blob_is_reset(pContent) );
 
1675 return 0;
1676 }
1677 db_begin_transaction();
1678 if( p->type==CFTYPE_MANIFEST ){
 
 
1679 if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){
1680 char *zCom;
1681 for(i=0; i<p->nParent; i++){
1682 int pid = uuid_to_rid(p->azParent[i], 1);
1683 db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)"
@@ -1768,11 +1781,11 @@
1768 switch( p->aTag[i].zName[0] ){
1769 case '-': type = 0; break; /* Cancel prior occurrences */
1770 case '+': type = 1; break; /* Apply to target only */
1771 case '*': type = 2; break; /* Propagate to descendants */
1772 default:
1773 fossil_fatal("unknown tag type in manifest: %s", p->aTag);
1774 return 0;
1775 }
1776 tag_insert(&p->aTag[i].zName[1], type, p->aTag[i].zValue,
1777 rid, p->rDate, tid);
1778 }
@@ -1870,10 +1883,12 @@
1870 }
1871 }
1872 if( p->type==CFTYPE_TICKET ){
1873 char *zTag;
1874
 
 
1875 assert( manifest_crosslink_busy==1 );
1876 zTag = mprintf("tkt-%s", p->zTicketUuid);
1877 tag_insert(zTag, 1, 0, rid, p->rDate, rid);
1878 free(zTag);
1879 db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)",
@@ -1934,33 +1949,39 @@
1934 if( p->type==CFTYPE_CONTROL ){
1935 Blob comment;
1936 int i;
1937 const char *zName;
1938 const char *zValue;
1939 const char *zUuid;
1940 int branchMove = 0;
1941 blob_zero(&comment);
1942 if( p->zComment ){
1943 blob_appendf(&comment, " %s.", p->zComment);
1944 }
1945 /* Next loop expects tags to be sorted on UUID, so sort it. */
1946 qsort(p->aTag, p->nTag, sizeof(p->aTag[0]), tag_compare);
1947 for(i=0; i<p->nTag; i++){
1948 zUuid = p->aTag[i].zUuid;
1949 if( !zUuid ) continue;
1950 if( i==0 || fossil_strcmp(zUuid, p->aTag[i-1].zUuid)!=0 ){
1951 blob_appendf(&comment,
1952 " Edit [%S]:",
1953 zUuid);
1954 branchMove = 0;
 
 
 
 
 
 
1955 }
1956 zName = p->aTag[i].zName;
1957 zValue = p->aTag[i].zValue;
1958 if( strcmp(zName, "*branch")==0 ){
1959 blob_appendf(&comment,
1960 " Move to branch [/timeline?r=%h&nd&dp=%S | %h].",
1961 zValue, zUuid, zValue);
1962 branchMove = 1;
1963 continue;
1964 }else if( strcmp(zName, "*bgcolor")==0 ){
1965 blob_appendf(&comment,
1966 " Change branch background color to \"%h\".", zValue);
@@ -2021,17 +2042,23 @@
2021 p->rDate, rid, p->zUser, blob_str(&comment)+1
2022 );
2023 blob_reset(&comment);
2024 }
2025 db_end_transaction(0);
 
 
 
 
 
 
2026 if( p->type==CFTYPE_MANIFEST ){
2027 manifest_cache_insert(p);
2028 }else{
2029 manifest_destroy(p);
2030 }
2031 assert( blob_is_reset(pContent) );
2032 return 1;
2033 }
2034
2035 /*
2036 ** COMMAND: test-crosslink
2037 **
@@ -2045,7 +2072,7 @@
2045 Blob content;
2046 db_find_and_open_repository(0, 0);
2047 if( g.argc!=3 ) usage("RECORDID");
2048 rid = name_to_rid(g.argv[2]);
2049 content_get(rid, &content);
2050 manifest_crosslink(rid, &content);
2051 }
2052
--- src/manifest.c
+++ src/manifest.c
@@ -42,10 +42,16 @@
42 */
43 #define PERM_REG 0 /* regular file */
44 #define PERM_EXE 1 /* executable */
45 #define PERM_LNK 2 /* symlink */
46
47 /*
48 ** Flags for use with manifest_crosslink().
49 */
50 #define MC_NONE 0 /* default handling */
51 #define MC_PERMIT_HOOKS 1 /* permit hooks to execute */
52
53 /*
54 ** A single F-card within a manifest
55 */
56 struct ManifestFile {
57 char *zName; /* Name of a file */
@@ -1650,34 +1656,41 @@
1656 ** Historical note: This routine original processed manifests only.
1657 ** Processing for other control artifacts was added later. The name
1658 ** of the routine, "manifest_crosslink", and the name of this source
1659 ** file, is a legacy of its original use.
1660 */
1661 int manifest_crosslink(int rid, Blob *pContent, int flags){
1662 int i, result = TH_OK;
1663 Manifest *p;
1664 Stmt q;
1665 int parentid = 0;
1666 const char *zScript = 0;
1667 const char *zUuid = 0;
1668
1669 if( (p = manifest_cache_find(rid))!=0 ){
1670 blob_reset(pContent);
1671 }else if( (p = manifest_parse(pContent, rid, 0))==0 ){
1672 assert( blob_is_reset(pContent) || pContent==0 );
1673 fossil_error(1, "syntax error in manifest");
1674 return 0;
1675 }
1676 if( g.xlinkClusterOnly && p->type!=CFTYPE_CLUSTER ){
1677 manifest_destroy(p);
1678 assert( blob_is_reset(pContent) );
1679 fossil_error(1, "no manifest");
1680 return 0;
1681 }
1682 if( p->type==CFTYPE_MANIFEST && fetch_baseline(p, 0) ){
1683 manifest_destroy(p);
1684 assert( blob_is_reset(pContent) );
1685 fossil_error(1, "cannot fetch baseline manifest");
1686 return 0;
1687 }
1688 db_begin_transaction();
1689 if( p->type==CFTYPE_MANIFEST ){
1690 zScript = xfer_commit_code();
1691 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
1692 if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){
1693 char *zCom;
1694 for(i=0; i<p->nParent; i++){
1695 int pid = uuid_to_rid(p->azParent[i], 1);
1696 db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)"
@@ -1768,11 +1781,11 @@
1781 switch( p->aTag[i].zName[0] ){
1782 case '-': type = 0; break; /* Cancel prior occurrences */
1783 case '+': type = 1; break; /* Apply to target only */
1784 case '*': type = 2; break; /* Propagate to descendants */
1785 default:
1786 fossil_error(1, "unknown tag type in manifest: %s", p->aTag);
1787 return 0;
1788 }
1789 tag_insert(&p->aTag[i].zName[1], type, p->aTag[i].zValue,
1790 rid, p->rDate, tid);
1791 }
@@ -1870,10 +1883,12 @@
1883 }
1884 }
1885 if( p->type==CFTYPE_TICKET ){
1886 char *zTag;
1887
1888 zScript = xfer_ticket_code();
1889 zUuid = p->zTicketUuid;
1890 assert( manifest_crosslink_busy==1 );
1891 zTag = mprintf("tkt-%s", p->zTicketUuid);
1892 tag_insert(zTag, 1, 0, rid, p->rDate, rid);
1893 free(zTag);
1894 db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)",
@@ -1934,33 +1949,39 @@
1949 if( p->type==CFTYPE_CONTROL ){
1950 Blob comment;
1951 int i;
1952 const char *zName;
1953 const char *zValue;
1954 const char *zTagUuid;
1955 int branchMove = 0;
1956 blob_zero(&comment);
1957 if( p->zComment ){
1958 blob_appendf(&comment, " %s.", p->zComment);
1959 }
1960 /* Next loop expects tags to be sorted on UUID, so sort it. */
1961 qsort(p->aTag, p->nTag, sizeof(p->aTag[0]), tag_compare);
1962 for(i=0; i<p->nTag; i++){
1963 zTagUuid = p->aTag[i].zUuid;
1964 if( !zTagUuid ) continue;
1965 if( i==0 || fossil_strcmp(zTagUuid, p->aTag[i-1].zUuid)!=0 ){
1966 blob_appendf(&comment,
1967 " Edit [%S]:",
1968 zTagUuid);
1969 branchMove = 0;
1970 if( db_exists("SELECT 1 FROM event, blob"
1971 " WHERE event.type='ci' AND event.objid=blob.rid"
1972 " AND blob.uuid='%s'", zTagUuid) ){
1973 zScript = xfer_commit_code();
1974 zUuid = zTagUuid;
1975 }
1976 }
1977 zName = p->aTag[i].zName;
1978 zValue = p->aTag[i].zValue;
1979 if( strcmp(zName, "*branch")==0 ){
1980 blob_appendf(&comment,
1981 " Move to branch [/timeline?r=%h&nd&dp=%S | %h].",
1982 zValue, zTagUuid, zValue);
1983 branchMove = 1;
1984 continue;
1985 }else if( strcmp(zName, "*bgcolor")==0 ){
1986 blob_appendf(&comment,
1987 " Change branch background color to \"%h\".", zValue);
@@ -2021,17 +2042,23 @@
2042 p->rDate, rid, p->zUser, blob_str(&comment)+1
2043 );
2044 blob_reset(&comment);
2045 }
2046 db_end_transaction(0);
2047 if( zScript && (flags & MC_PERMIT_HOOKS) ){
2048 result = xfer_run_common_script();
2049 if( result==TH_OK ){
2050 result = xfer_run_script(zScript, zUuid);
2051 }
2052 }
2053 if( p->type==CFTYPE_MANIFEST ){
2054 manifest_cache_insert(p);
2055 }else{
2056 manifest_destroy(p);
2057 }
2058 assert( blob_is_reset(pContent) );
2059 return ( result!=TH_ERROR );
2060 }
2061
2062 /*
2063 ** COMMAND: test-crosslink
2064 **
@@ -2045,7 +2072,7 @@
2072 Blob content;
2073 db_find_and_open_repository(0, 0);
2074 if( g.argc!=3 ) usage("RECORDID");
2075 rid = name_to_rid(g.argv[2]);
2076 content_get(rid, &content);
2077 manifest_crosslink(rid, &content, MC_NONE);
2078 }
2079
+1 -1
--- src/rebuild.c
+++ src/rebuild.c
@@ -250,11 +250,11 @@
250250
blob_copy(&copy, pBase);
251251
pUse = &copy;
252252
}
253253
if( zFNameFormat==0 ){
254254
/* We are doing "fossil rebuild" */
255
- manifest_crosslink(rid, pUse);
255
+ manifest_crosslink(rid, pUse, MC_NONE);
256256
}else{
257257
/* We are doing "fossil deconstruct" */
258258
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
259259
char *zFile = mprintf(zFNameFormat, zUuid, zUuid+prefixLength);
260260
blob_write_to_file(pUse,zFile);
261261
--- src/rebuild.c
+++ src/rebuild.c
@@ -250,11 +250,11 @@
250 blob_copy(&copy, pBase);
251 pUse = &copy;
252 }
253 if( zFNameFormat==0 ){
254 /* We are doing "fossil rebuild" */
255 manifest_crosslink(rid, pUse);
256 }else{
257 /* We are doing "fossil deconstruct" */
258 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
259 char *zFile = mprintf(zFNameFormat, zUuid, zUuid+prefixLength);
260 blob_write_to_file(pUse,zFile);
261
--- src/rebuild.c
+++ src/rebuild.c
@@ -250,11 +250,11 @@
250 blob_copy(&copy, pBase);
251 pUse = &copy;
252 }
253 if( zFNameFormat==0 ){
254 /* We are doing "fossil rebuild" */
255 manifest_crosslink(rid, pUse, MC_NONE);
256 }else{
257 /* We are doing "fossil deconstruct" */
258 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
259 char *zFile = mprintf(zFNameFormat, zUuid, zUuid+prefixLength);
260 blob_write_to_file(pUse,zFile);
261
+1 -1
--- src/setup.c
+++ src/setup.c
@@ -812,11 +812,11 @@
812812
if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
813813
login_verify_csrf_secret();
814814
db_set(zVar, zQ, 0);
815815
zVal = zQ;
816816
}
817
- @ <input type="text" name="%s(zQParm)" value="%h(zVal)" size="%d(width)"
817
+ @ <input type="text" id="%s(zQParm)" name="%s(zQParm)" value="%h(zVal)" size="%d(width)"
818818
if( disabled ){
819819
@ disabled="disabled"
820820
}
821821
@ /> <b>%s(zLabel)</b>
822822
}
823823
--- src/setup.c
+++ src/setup.c
@@ -812,11 +812,11 @@
812 if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
813 login_verify_csrf_secret();
814 db_set(zVar, zQ, 0);
815 zVal = zQ;
816 }
817 @ <input type="text" name="%s(zQParm)" value="%h(zVal)" size="%d(width)"
818 if( disabled ){
819 @ disabled="disabled"
820 }
821 @ /> <b>%s(zLabel)</b>
822 }
823
--- src/setup.c
+++ src/setup.c
@@ -812,11 +812,11 @@
812 if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
813 login_verify_csrf_secret();
814 db_set(zVar, zQ, 0);
815 zVal = zQ;
816 }
817 @ <input type="text" id="%s(zQParm)" name="%s(zQParm)" value="%h(zVal)" size="%d(width)"
818 if( disabled ){
819 @ disabled="disabled"
820 }
821 @ /> <b>%s(zLabel)</b>
822 }
823
+1 -1
--- src/tag.c
+++ src/tag.c
@@ -326,11 +326,11 @@
326326
}
327327
blob_appendf(&ctrl, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin);
328328
md5sum_blob(&ctrl, &cksum);
329329
blob_appendf(&ctrl, "Z %b\n", &cksum);
330330
nrid = content_put(&ctrl);
331
- manifest_crosslink(nrid, &ctrl);
331
+ manifest_crosslink(nrid, &ctrl, MC_PERMIT_HOOKS);
332332
assert( blob_is_reset(&ctrl) );
333333
}
334334
335335
/*
336336
** COMMAND: tag
337337
--- src/tag.c
+++ src/tag.c
@@ -326,11 +326,11 @@
326 }
327 blob_appendf(&ctrl, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin);
328 md5sum_blob(&ctrl, &cksum);
329 blob_appendf(&ctrl, "Z %b\n", &cksum);
330 nrid = content_put(&ctrl);
331 manifest_crosslink(nrid, &ctrl);
332 assert( blob_is_reset(&ctrl) );
333 }
334
335 /*
336 ** COMMAND: tag
337
--- src/tag.c
+++ src/tag.c
@@ -326,11 +326,11 @@
326 }
327 blob_appendf(&ctrl, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin);
328 md5sum_blob(&ctrl, &cksum);
329 blob_appendf(&ctrl, "Z %b\n", &cksum);
330 nrid = content_put(&ctrl);
331 manifest_crosslink(nrid, &ctrl, MC_PERMIT_HOOKS);
332 assert( blob_is_reset(&ctrl) );
333 }
334
335 /*
336 ** COMMAND: tag
337
+115
--- src/th_main.c
+++ src/th_main.c
@@ -827,10 +827,124 @@
827827
rc = TH_ERROR;
828828
}
829829
re_free(pRe);
830830
return rc;
831831
}
832
+
833
+/*
834
+** TH command: http ?-asynchronous? ?--? url ?payload?
835
+**
836
+** Perform an HTTP or HTTPS request for the specified URL. If a
837
+** payload is present, it will be interpreted as text/plain and
838
+** the POST method will be used; otherwise, the GET method will
839
+** be used. Upon success, if the -asynchronous option is used, an
840
+** empty string is returned as the result; otherwise, the response
841
+** from the server is returned as the result. Synchronous requests
842
+** are not currently implemented.
843
+*/
844
+#define HTTP_WRONGNUMARGS "http ?-asynchronous? ?--? url ?payload?"
845
+static int httpCmd(
846
+ Th_Interp *interp,
847
+ void *p,
848
+ int argc,
849
+ const char **argv,
850
+ int *argl
851
+){
852
+ int nArg = 1;
853
+ int fAsynchronous = 0;
854
+ const char *zType, *zRegexp;
855
+ Blob payload;
856
+ ReCompiled *pRe = 0;
857
+ UrlData urlData;
858
+
859
+ if( argc<2 || argc>5 ){
860
+ return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
861
+ }
862
+ if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){
863
+ fAsynchronous = 1; nArg++;
864
+ }
865
+ if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
866
+ if( nArg+1!=argc && nArg+2!=argc ){
867
+ return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
868
+ }
869
+ memset(&urlData, '\0', sizeof(urlData));
870
+ url_parse_local(argv[nArg], 0, &urlData);
871
+ if( urlData.isSsh || urlData.isFile ){
872
+ Th_ErrorMessage(interp, "url must be http:// or https://", 0, 0);
873
+ return TH_ERROR;
874
+ }
875
+ zRegexp = db_get("th1-uri-regexp", 0);
876
+ if( zRegexp && zRegexp[0] ){
877
+ const char *zErr = re_compile(&pRe, zRegexp, 0);
878
+ if( zErr ){
879
+ Th_SetResult(interp, zErr, -1);
880
+ return TH_ERROR;
881
+ }
882
+ }
883
+ if( !pRe || !re_match(pRe, (const unsigned char *)urlData.canonical, -1) ){
884
+ Th_SetResult(interp, "url not allowed", -1);
885
+ re_free(pRe);
886
+ return TH_ERROR;
887
+ }
888
+ re_free(pRe);
889
+ blob_zero(&payload);
890
+ if( nArg+2==argc ){
891
+ blob_append(&payload, argv[nArg+1], argl[nArg+1]);
892
+ zType = "POST";
893
+ }else{
894
+ zType = "GET";
895
+ }
896
+ if( fAsynchronous ){
897
+ const char *zSep, *zParams;
898
+ Blob hdr;
899
+ zParams = strrchr(argv[nArg], '?');
900
+ if( strlen(urlData.path)>0 && zParams!=argv[nArg] ){
901
+ zSep = "";
902
+ }else{
903
+ zSep = "/";
904
+ }
905
+ blob_zero(&hdr);
906
+ blob_appendf(&hdr, "%s %s%s%s HTTP/1.0\r\n",
907
+ zType, zSep, urlData.path, zParams ? zParams : "");
908
+ if( urlData.proxyAuth ){
909
+ blob_appendf(&hdr, "Proxy-Authorization: %s\r\n", urlData.proxyAuth);
910
+ }
911
+ if( urlData.passwd && urlData.user && urlData.passwd[0]=='#' ){
912
+ char *zCredentials = mprintf("%s:%s", urlData.user, &urlData.passwd[1]);
913
+ char *zEncoded = encode64(zCredentials, -1);
914
+ blob_appendf(&hdr, "Authorization: Basic %s\r\n", zEncoded);
915
+ fossil_free(zEncoded);
916
+ fossil_free(zCredentials);
917
+ }
918
+ blob_appendf(&hdr, "Host: %s\r\n"
919
+ "User-Agent: %s\r\n", urlData.hostname, get_user_agent());
920
+ if( zType[0]=='P' ){
921
+ blob_appendf(&hdr, "Content-Type: application/x-www-form-urlencoded\r\n"
922
+ "Content-Length: %d\r\n\r\n", blob_size(&payload));
923
+ }else{
924
+ blob_appendf(&hdr, "\r\n");
925
+ }
926
+ if( transport_open(&urlData) ){
927
+ Th_ErrorMessage(interp, transport_errmsg(&urlData), 0, 0);
928
+ blob_reset(&hdr);
929
+ blob_reset(&payload);
930
+ return TH_ERROR;
931
+ }
932
+ transport_send(&urlData, &hdr);
933
+ transport_send(&urlData, &payload);
934
+ blob_reset(&hdr);
935
+ blob_reset(&payload);
936
+ transport_close(&urlData);
937
+ Th_SetResult(interp, 0, 0); /* NOTE: Asynchronous, no results. */
938
+ return TH_OK;
939
+ }else{
940
+ Th_ErrorMessage(interp,
941
+ "synchronous requests are not yet implemented", 0, 0);
942
+ blob_reset(&payload);
943
+ return TH_ERROR;
944
+ }
945
+}
832946
833947
/*
834948
** Make sure the interpreter has been initialized. Initialize it if
835949
** it has not been already.
836950
**
@@ -855,10 +969,11 @@
855969
{"enable_output", enableOutputCmd, 0},
856970
{"hascap", hascapCmd, 0},
857971
{"hasfeature", hasfeatureCmd, 0},
858972
{"html", putsCmd, (void*)&aFlags[0]},
859973
{"htmlize", htmlizeCmd, 0},
974
+ {"http", httpCmd, 0},
860975
{"linecount", linecntCmd, 0},
861976
{"puts", putsCmd, (void*)&aFlags[1]},
862977
{"query", queryCmd, 0},
863978
{"randhex", randhexCmd, 0},
864979
{"regexp", regexpCmd, 0},
865980
--- src/th_main.c
+++ src/th_main.c
@@ -827,10 +827,124 @@
827 rc = TH_ERROR;
828 }
829 re_free(pRe);
830 return rc;
831 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
832
833 /*
834 ** Make sure the interpreter has been initialized. Initialize it if
835 ** it has not been already.
836 **
@@ -855,10 +969,11 @@
855 {"enable_output", enableOutputCmd, 0},
856 {"hascap", hascapCmd, 0},
857 {"hasfeature", hasfeatureCmd, 0},
858 {"html", putsCmd, (void*)&aFlags[0]},
859 {"htmlize", htmlizeCmd, 0},
 
860 {"linecount", linecntCmd, 0},
861 {"puts", putsCmd, (void*)&aFlags[1]},
862 {"query", queryCmd, 0},
863 {"randhex", randhexCmd, 0},
864 {"regexp", regexpCmd, 0},
865
--- src/th_main.c
+++ src/th_main.c
@@ -827,10 +827,124 @@
827 rc = TH_ERROR;
828 }
829 re_free(pRe);
830 return rc;
831 }
832
833 /*
834 ** TH command: http ?-asynchronous? ?--? url ?payload?
835 **
836 ** Perform an HTTP or HTTPS request for the specified URL. If a
837 ** payload is present, it will be interpreted as text/plain and
838 ** the POST method will be used; otherwise, the GET method will
839 ** be used. Upon success, if the -asynchronous option is used, an
840 ** empty string is returned as the result; otherwise, the response
841 ** from the server is returned as the result. Synchronous requests
842 ** are not currently implemented.
843 */
844 #define HTTP_WRONGNUMARGS "http ?-asynchronous? ?--? url ?payload?"
845 static int httpCmd(
846 Th_Interp *interp,
847 void *p,
848 int argc,
849 const char **argv,
850 int *argl
851 ){
852 int nArg = 1;
853 int fAsynchronous = 0;
854 const char *zType, *zRegexp;
855 Blob payload;
856 ReCompiled *pRe = 0;
857 UrlData urlData;
858
859 if( argc<2 || argc>5 ){
860 return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
861 }
862 if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){
863 fAsynchronous = 1; nArg++;
864 }
865 if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
866 if( nArg+1!=argc && nArg+2!=argc ){
867 return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
868 }
869 memset(&urlData, '\0', sizeof(urlData));
870 url_parse_local(argv[nArg], 0, &urlData);
871 if( urlData.isSsh || urlData.isFile ){
872 Th_ErrorMessage(interp, "url must be http:// or https://", 0, 0);
873 return TH_ERROR;
874 }
875 zRegexp = db_get("th1-uri-regexp", 0);
876 if( zRegexp && zRegexp[0] ){
877 const char *zErr = re_compile(&pRe, zRegexp, 0);
878 if( zErr ){
879 Th_SetResult(interp, zErr, -1);
880 return TH_ERROR;
881 }
882 }
883 if( !pRe || !re_match(pRe, (const unsigned char *)urlData.canonical, -1) ){
884 Th_SetResult(interp, "url not allowed", -1);
885 re_free(pRe);
886 return TH_ERROR;
887 }
888 re_free(pRe);
889 blob_zero(&payload);
890 if( nArg+2==argc ){
891 blob_append(&payload, argv[nArg+1], argl[nArg+1]);
892 zType = "POST";
893 }else{
894 zType = "GET";
895 }
896 if( fAsynchronous ){
897 const char *zSep, *zParams;
898 Blob hdr;
899 zParams = strrchr(argv[nArg], '?');
900 if( strlen(urlData.path)>0 && zParams!=argv[nArg] ){
901 zSep = "";
902 }else{
903 zSep = "/";
904 }
905 blob_zero(&hdr);
906 blob_appendf(&hdr, "%s %s%s%s HTTP/1.0\r\n",
907 zType, zSep, urlData.path, zParams ? zParams : "");
908 if( urlData.proxyAuth ){
909 blob_appendf(&hdr, "Proxy-Authorization: %s\r\n", urlData.proxyAuth);
910 }
911 if( urlData.passwd && urlData.user && urlData.passwd[0]=='#' ){
912 char *zCredentials = mprintf("%s:%s", urlData.user, &urlData.passwd[1]);
913 char *zEncoded = encode64(zCredentials, -1);
914 blob_appendf(&hdr, "Authorization: Basic %s\r\n", zEncoded);
915 fossil_free(zEncoded);
916 fossil_free(zCredentials);
917 }
918 blob_appendf(&hdr, "Host: %s\r\n"
919 "User-Agent: %s\r\n", urlData.hostname, get_user_agent());
920 if( zType[0]=='P' ){
921 blob_appendf(&hdr, "Content-Type: application/x-www-form-urlencoded\r\n"
922 "Content-Length: %d\r\n\r\n", blob_size(&payload));
923 }else{
924 blob_appendf(&hdr, "\r\n");
925 }
926 if( transport_open(&urlData) ){
927 Th_ErrorMessage(interp, transport_errmsg(&urlData), 0, 0);
928 blob_reset(&hdr);
929 blob_reset(&payload);
930 return TH_ERROR;
931 }
932 transport_send(&urlData, &hdr);
933 transport_send(&urlData, &payload);
934 blob_reset(&hdr);
935 blob_reset(&payload);
936 transport_close(&urlData);
937 Th_SetResult(interp, 0, 0); /* NOTE: Asynchronous, no results. */
938 return TH_OK;
939 }else{
940 Th_ErrorMessage(interp,
941 "synchronous requests are not yet implemented", 0, 0);
942 blob_reset(&payload);
943 return TH_ERROR;
944 }
945 }
946
947 /*
948 ** Make sure the interpreter has been initialized. Initialize it if
949 ** it has not been already.
950 **
@@ -855,10 +969,11 @@
969 {"enable_output", enableOutputCmd, 0},
970 {"hascap", hascapCmd, 0},
971 {"hasfeature", hasfeatureCmd, 0},
972 {"html", putsCmd, (void*)&aFlags[0]},
973 {"htmlize", htmlizeCmd, 0},
974 {"http", httpCmd, 0},
975 {"linecount", linecntCmd, 0},
976 {"puts", putsCmd, (void*)&aFlags[1]},
977 {"query", queryCmd, 0},
978 {"randhex", randhexCmd, 0},
979 {"regexp", regexpCmd, 0},
980
+9 -4
--- src/tkt.c
+++ src/tkt.c
@@ -513,15 +513,16 @@
513513
}
514514
515515
/*
516516
** Write a ticket into the repository.
517517
*/
518
-static void ticket_put(
518
+static int ticket_put(
519519
Blob *pTicket, /* The text of the ticket change record */
520520
const char *zTktId, /* The ticket to which this change is applied */
521521
int needMod /* True if moderation is needed */
522522
){
523
+ int result;
523524
int rid = content_put_ex(pTicket, 0, 0, 0, needMod);
524525
if( rid==0 ){
525526
fossil_fatal("trouble committing ticket: %s", g.zErrMsg);
526527
}
527528
if( needMod ){
@@ -533,13 +534,14 @@
533534
}else{
534535
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d);", rid);
535536
db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", rid);
536537
}
537538
manifest_crosslink_begin();
538
- manifest_crosslink(rid, pTicket);
539
+ result = (manifest_crosslink(rid, pTicket, MC_PERMIT_HOOKS)==0);
539540
assert( blob_is_reset(pTicket) );
540541
manifest_crosslink_end();
542
+ return result;
541543
}
542544
543545
/*
544546
** Subscript command: submit_ticket
545547
**
@@ -1344,11 +1346,14 @@
13441346
}
13451347
blob_appendf(&tktchng, "K %s\n", zTktUuid);
13461348
blob_appendf(&tktchng, "U %F\n", zUser);
13471349
md5sum_blob(&tktchng, &cksum);
13481350
blob_appendf(&tktchng, "Z %b\n", &cksum);
1349
- ticket_put(&tktchng, zTktUuid, 0);
1350
- printf("ticket %s succeeded for %s\n",
1351
+ if( ticket_put(&tktchng, zTktUuid, 0) ){
1352
+ fossil_fatal("%s\n", g.zErrMsg);
1353
+ }else{
1354
+ fossil_print("ticket %s succeeded for %s\n",
13511355
(eCmd==set?"set":"add"),zTktUuid);
1356
+ }
13521357
}
13531358
}
13541359
}
13551360
--- src/tkt.c
+++ src/tkt.c
@@ -513,15 +513,16 @@
513 }
514
515 /*
516 ** Write a ticket into the repository.
517 */
518 static void ticket_put(
519 Blob *pTicket, /* The text of the ticket change record */
520 const char *zTktId, /* The ticket to which this change is applied */
521 int needMod /* True if moderation is needed */
522 ){
 
523 int rid = content_put_ex(pTicket, 0, 0, 0, needMod);
524 if( rid==0 ){
525 fossil_fatal("trouble committing ticket: %s", g.zErrMsg);
526 }
527 if( needMod ){
@@ -533,13 +534,14 @@
533 }else{
534 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d);", rid);
535 db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", rid);
536 }
537 manifest_crosslink_begin();
538 manifest_crosslink(rid, pTicket);
539 assert( blob_is_reset(pTicket) );
540 manifest_crosslink_end();
 
541 }
542
543 /*
544 ** Subscript command: submit_ticket
545 **
@@ -1344,11 +1346,14 @@
1344 }
1345 blob_appendf(&tktchng, "K %s\n", zTktUuid);
1346 blob_appendf(&tktchng, "U %F\n", zUser);
1347 md5sum_blob(&tktchng, &cksum);
1348 blob_appendf(&tktchng, "Z %b\n", &cksum);
1349 ticket_put(&tktchng, zTktUuid, 0);
1350 printf("ticket %s succeeded for %s\n",
 
 
1351 (eCmd==set?"set":"add"),zTktUuid);
 
1352 }
1353 }
1354 }
1355
--- src/tkt.c
+++ src/tkt.c
@@ -513,15 +513,16 @@
513 }
514
515 /*
516 ** Write a ticket into the repository.
517 */
518 static int ticket_put(
519 Blob *pTicket, /* The text of the ticket change record */
520 const char *zTktId, /* The ticket to which this change is applied */
521 int needMod /* True if moderation is needed */
522 ){
523 int result;
524 int rid = content_put_ex(pTicket, 0, 0, 0, needMod);
525 if( rid==0 ){
526 fossil_fatal("trouble committing ticket: %s", g.zErrMsg);
527 }
528 if( needMod ){
@@ -533,13 +534,14 @@
534 }else{
535 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d);", rid);
536 db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", rid);
537 }
538 manifest_crosslink_begin();
539 result = (manifest_crosslink(rid, pTicket, MC_PERMIT_HOOKS)==0);
540 assert( blob_is_reset(pTicket) );
541 manifest_crosslink_end();
542 return result;
543 }
544
545 /*
546 ** Subscript command: submit_ticket
547 **
@@ -1344,11 +1346,14 @@
1346 }
1347 blob_appendf(&tktchng, "K %s\n", zTktUuid);
1348 blob_appendf(&tktchng, "U %F\n", zUser);
1349 md5sum_blob(&tktchng, &cksum);
1350 blob_appendf(&tktchng, "Z %b\n", &cksum);
1351 if( ticket_put(&tktchng, zTktUuid, 0) ){
1352 fossil_fatal("%s\n", g.zErrMsg);
1353 }else{
1354 fossil_print("ticket %s succeeded for %s\n",
1355 (eCmd==set?"set":"add"),zTktUuid);
1356 }
1357 }
1358 }
1359 }
1360
+244 -184
--- src/url.c
+++ src/url.c
@@ -39,10 +39,34 @@
3939
#define URL_REMEMBER 0x002 /* Remember the url for later reuse */
4040
#define URL_ASK_REMEMBER_PW 0x004 /* Ask whether to remember prompted pw */
4141
#define URL_REMEMBER_PW 0x008 /* Should remember pw */
4242
#define URL_PROMPTED 0x010 /* Prompted for PW already */
4343
44
+/*
45
+** The URL related data used with this subsystem.
46
+*/
47
+struct UrlData {
48
+ /*
49
+ ** NOTE: These members MUST be kept in sync with the related ones in the
50
+ ** "Global" structure defined in "main.c".
51
+ */
52
+ int isFile; /* True if a "file:" url */
53
+ int isHttps; /* True if a "https:" url */
54
+ int isSsh; /* True if an "ssh:" url */
55
+ char *name; /* Hostname for http: or filename for file: */
56
+ char *hostname; /* The HOST: parameter on http headers */
57
+ char *protocol; /* "http" or "https" */
58
+ int port; /* TCP port number for http: or https: */
59
+ int dfltPort; /* The default port for the given protocol */
60
+ char *path; /* Pathname for http: */
61
+ char *user; /* User id for http: */
62
+ char *passwd; /* Password for http: */
63
+ char *canonical; /* Canonical representation of the URL */
64
+ char *proxyAuth; /* Proxy-Authorizer: string */
65
+ char *fossil; /* The fossil query parameter on ssh: */
66
+ unsigned flags; /* Boolean flags controlling URL processing */
67
+};
4468
#endif /* INTERFACE */
4569
4670
4771
/*
4872
** Convert a string to lower-case.
@@ -51,10 +75,201 @@
5175
while( *z ){
5276
*z = fossil_tolower(*z);
5377
z++;
5478
}
5579
}
80
+
81
+/*
82
+** Parse the given URL. Populate members of the provided UrlData structure
83
+** as follows:
84
+**
85
+** isFile True if FILE:
86
+** isHttps True if HTTPS:
87
+** isSsh True if SSH:
88
+** protocol "http" or "https" or "file"
89
+** name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE:
90
+** port TCP port number for HTTP or HTTPS.
91
+** dfltPort Default TCP port number (80 or 443).
92
+** path Path name for HTTP or HTTPS.
93
+** user Userid.
94
+** passwd Password.
95
+** hostname HOST:PORT or just HOST if port is the default.
96
+** canonical The URL in canonical form, omitting the password
97
+**
98
+*/
99
+void url_parse_local(
100
+ const char *zUrl,
101
+ unsigned int urlFlags,
102
+ UrlData *pUrlData
103
+){
104
+ int i, j, c;
105
+ char *zFile = 0;
106
+
107
+ if( zUrl==0 ){
108
+ zUrl = db_get("last-sync-url", 0);
109
+ if( zUrl==0 ) return;
110
+ if( pUrlData->passwd==0 ){
111
+ pUrlData->passwd = unobscure(db_get("last-sync-pw", 0));
112
+ }
113
+ }
114
+
115
+ if( strncmp(zUrl, "http://", 7)==0
116
+ || strncmp(zUrl, "https://", 8)==0
117
+ || strncmp(zUrl, "ssh://", 6)==0
118
+ ){
119
+ int iStart;
120
+ char *zLogin;
121
+ char *zExe;
122
+ char cQuerySep = '?';
123
+
124
+ pUrlData->isFile = 0;
125
+ if( zUrl[4]=='s' ){
126
+ pUrlData->isHttps = 1;
127
+ pUrlData->protocol = "https";
128
+ pUrlData->dfltPort = 443;
129
+ iStart = 8;
130
+ }else if( zUrl[0]=='s' ){
131
+ pUrlData->isSsh = 1;
132
+ pUrlData->protocol = "ssh";
133
+ pUrlData->dfltPort = 22;
134
+ pUrlData->fossil = "fossil";
135
+ iStart = 6;
136
+ }else{
137
+ pUrlData->isHttps = 0;
138
+ pUrlData->protocol = "http";
139
+ pUrlData->dfltPort = 80;
140
+ iStart = 7;
141
+ }
142
+ for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!='@'; i++){}
143
+ if( c=='@' ){
144
+ /* Parse up the user-id and password */
145
+ for(j=iStart; j<i && zUrl[j]!=':'; j++){}
146
+ pUrlData->user = mprintf("%.*s", j-iStart, &zUrl[iStart]);
147
+ dehttpize(pUrlData->user);
148
+ if( j<i ){
149
+ if( ( urlFlags & URL_REMEMBER ) && pUrlData->isSsh==0 ){
150
+ urlFlags |= URL_ASK_REMEMBER_PW;
151
+ }
152
+ pUrlData->passwd = mprintf("%.*s", i-j-1, &zUrl[j+1]);
153
+ dehttpize(pUrlData->passwd);
154
+ }
155
+ if( pUrlData->isSsh ){
156
+ urlFlags &= ~URL_ASK_REMEMBER_PW;
157
+ }
158
+ zLogin = mprintf("%t@", pUrlData->user);
159
+ for(j=i+1; (c=zUrl[j])!=0 && c!='/' && c!=':'; j++){}
160
+ pUrlData->name = mprintf("%.*s", j-i-1, &zUrl[i+1]);
161
+ i = j;
162
+ }else{
163
+ for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!=':'; i++){}
164
+ pUrlData->name = mprintf("%.*s", i-iStart, &zUrl[iStart]);
165
+ zLogin = mprintf("");
166
+ }
167
+ url_tolower(pUrlData->name);
168
+ if( c==':' ){
169
+ pUrlData->port = 0;
170
+ i++;
171
+ while( (c = zUrl[i])!=0 && fossil_isdigit(c) ){
172
+ pUrlData->port = pUrlData->port*10 + c - '0';
173
+ i++;
174
+ }
175
+ pUrlData->hostname = mprintf("%s:%d", pUrlData->name, pUrlData->port);
176
+ }else{
177
+ pUrlData->port = pUrlData->dfltPort;
178
+ pUrlData->hostname = pUrlData->name;
179
+ }
180
+ dehttpize(pUrlData->name);
181
+ pUrlData->path = mprintf("%s", &zUrl[i]);
182
+ for(i=0; pUrlData->path[i] && pUrlData->path[i]!='?'; i++){}
183
+ if( pUrlData->path[i] ){
184
+ pUrlData->path[i] = 0;
185
+ i++;
186
+ }
187
+ zExe = mprintf("");
188
+ while( pUrlData->path[i]!=0 ){
189
+ char *zName, *zValue;
190
+ zName = &pUrlData->path[i];
191
+ zValue = zName;
192
+ while( pUrlData->path[i] && pUrlData->path[i]!='=' ){ i++; }
193
+ if( pUrlData->path[i]=='=' ){
194
+ pUrlData->path[i] = 0;
195
+ i++;
196
+ zValue = &pUrlData->path[i];
197
+ while( pUrlData->path[i] && pUrlData->path[i]!='&' ){ i++; }
198
+ }
199
+ if( pUrlData->path[i] ){
200
+ pUrlData->path[i] = 0;
201
+ i++;
202
+ }
203
+ if( fossil_strcmp(zName,"fossil")==0 ){
204
+ pUrlData->fossil = zValue;
205
+ dehttpize(pUrlData->fossil);
206
+ zExe = mprintf("%cfossil=%T", cQuerySep, pUrlData->fossil);
207
+ cQuerySep = '&';
208
+ }
209
+ }
210
+
211
+ dehttpize(pUrlData->path);
212
+ if( pUrlData->dfltPort==pUrlData->port ){
213
+ pUrlData->canonical = mprintf(
214
+ "%s://%s%T%T%s",
215
+ pUrlData->protocol, zLogin, pUrlData->name, pUrlData->path, zExe
216
+ );
217
+ }else{
218
+ pUrlData->canonical = mprintf(
219
+ "%s://%s%T:%d%T%s",
220
+ pUrlData->protocol, zLogin, pUrlData->name, pUrlData->port,
221
+ pUrlData->path, zExe
222
+ );
223
+ }
224
+ if( pUrlData->isSsh && pUrlData->path[1] ) pUrlData->path++;
225
+ free(zLogin);
226
+ }else if( strncmp(zUrl, "file:", 5)==0 ){
227
+ pUrlData->isFile = 1;
228
+ if( zUrl[5]=='/' && zUrl[6]=='/' ){
229
+ i = 7;
230
+ }else{
231
+ i = 5;
232
+ }
233
+ zFile = mprintf("%s", &zUrl[i]);
234
+ }else if( file_isfile(zUrl) ){
235
+ pUrlData->isFile = 1;
236
+ zFile = mprintf("%s", zUrl);
237
+ }else if( file_isdir(zUrl)==1 ){
238
+ zFile = mprintf("%s/FOSSIL", zUrl);
239
+ if( file_isfile(zFile) ){
240
+ pUrlData->isFile = 1;
241
+ }else{
242
+ free(zFile);
243
+ fossil_fatal("unknown repository: %s", zUrl);
244
+ }
245
+ }else{
246
+ fossil_fatal("unknown repository: %s", zUrl);
247
+ }
248
+ if( urlFlags ) pUrlData->flags = urlFlags;
249
+ if( pUrlData->isFile ){
250
+ Blob cfile;
251
+ dehttpize(zFile);
252
+ file_canonical_name(zFile, &cfile, 0);
253
+ free(zFile);
254
+ pUrlData->protocol = "file";
255
+ pUrlData->path = "";
256
+ pUrlData->name = mprintf("%b", &cfile);
257
+ pUrlData->canonical = mprintf("file://%T", pUrlData->name);
258
+ blob_reset(&cfile);
259
+ }else if( pUrlData->user!=0 && pUrlData->passwd==0 && (urlFlags & URL_PROMPT_PW) ){
260
+ url_prompt_for_password_local(pUrlData);
261
+ }else if( pUrlData->user!=0 && ( urlFlags & URL_ASK_REMEMBER_PW ) ){
262
+ if( isatty(fileno(stdin)) ){
263
+ if( save_password_prompt(pUrlData->passwd) ){
264
+ pUrlData->flags = urlFlags |= URL_REMEMBER_PW;
265
+ }else{
266
+ pUrlData->flags = urlFlags &= ~URL_REMEMBER_PW;
267
+ }
268
+ }
269
+ }
270
+}
56271
57272
/*
58273
** Parse the given URL, which describes a sync server. Populate variables
59274
** in the global "g" structure as follows:
60275
**
@@ -79,175 +294,11 @@
79294
**
80295
** ssh://userid@host:port/path?fossil=path/to/fossil.exe
81296
**
82297
*/
83298
void url_parse(const char *zUrl, unsigned int urlFlags){
84
- int i, j, c;
85
- char *zFile = 0;
86
-
87
- if( zUrl==0 ){
88
- zUrl = db_get("last-sync-url", 0);
89
- if( zUrl==0 ) return;
90
- if( g.urlPasswd==0 ){
91
- g.urlPasswd = unobscure(db_get("last-sync-pw", 0));
92
- }
93
- }
94
-
95
- if( strncmp(zUrl, "http://", 7)==0
96
- || strncmp(zUrl, "https://", 8)==0
97
- || strncmp(zUrl, "ssh://", 6)==0
98
- ){
99
- int iStart;
100
- char *zLogin;
101
- char *zExe;
102
- char cQuerySep = '?';
103
-
104
- g.urlIsFile = 0;
105
- if( zUrl[4]=='s' ){
106
- g.urlIsHttps = 1;
107
- g.urlProtocol = "https";
108
- g.urlDfltPort = 443;
109
- iStart = 8;
110
- }else if( zUrl[0]=='s' ){
111
- g.urlIsSsh = 1;
112
- g.urlProtocol = "ssh";
113
- g.urlDfltPort = 22;
114
- g.urlFossil = "fossil";
115
- iStart = 6;
116
- }else{
117
- g.urlIsHttps = 0;
118
- g.urlProtocol = "http";
119
- g.urlDfltPort = 80;
120
- iStart = 7;
121
- }
122
- for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!='@'; i++){}
123
- if( c=='@' ){
124
- /* Parse up the user-id and password */
125
- for(j=iStart; j<i && zUrl[j]!=':'; j++){}
126
- g.urlUser = mprintf("%.*s", j-iStart, &zUrl[iStart]);
127
- dehttpize(g.urlUser);
128
- if( j<i ){
129
- if( ( urlFlags & URL_REMEMBER ) && g.urlIsSsh==0 ){
130
- urlFlags |= URL_ASK_REMEMBER_PW;
131
- }
132
- g.urlPasswd = mprintf("%.*s", i-j-1, &zUrl[j+1]);
133
- dehttpize(g.urlPasswd);
134
- }
135
- if( g.urlIsSsh ){
136
- urlFlags &= ~URL_ASK_REMEMBER_PW;
137
- }
138
- zLogin = mprintf("%t@", g.urlUser);
139
- for(j=i+1; (c=zUrl[j])!=0 && c!='/' && c!=':'; j++){}
140
- g.urlName = mprintf("%.*s", j-i-1, &zUrl[i+1]);
141
- i = j;
142
- }else{
143
- for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!=':'; i++){}
144
- g.urlName = mprintf("%.*s", i-iStart, &zUrl[iStart]);
145
- zLogin = mprintf("");
146
- }
147
- url_tolower(g.urlName);
148
- if( c==':' ){
149
- g.urlPort = 0;
150
- i++;
151
- while( (c = zUrl[i])!=0 && fossil_isdigit(c) ){
152
- g.urlPort = g.urlPort*10 + c - '0';
153
- i++;
154
- }
155
- g.urlHostname = mprintf("%s:%d", g.urlName, g.urlPort);
156
- }else{
157
- g.urlPort = g.urlDfltPort;
158
- g.urlHostname = g.urlName;
159
- }
160
- dehttpize(g.urlName);
161
- g.urlPath = mprintf("%s", &zUrl[i]);
162
- for(i=0; g.urlPath[i] && g.urlPath[i]!='?'; i++){}
163
- if( g.urlPath[i] ){
164
- g.urlPath[i] = 0;
165
- i++;
166
- }
167
- zExe = mprintf("");
168
- while( g.urlPath[i]!=0 ){
169
- char *zName, *zValue;
170
- zName = &g.urlPath[i];
171
- zValue = zName;
172
- while( g.urlPath[i] && g.urlPath[i]!='=' ){ i++; }
173
- if( g.urlPath[i]=='=' ){
174
- g.urlPath[i] = 0;
175
- i++;
176
- zValue = &g.urlPath[i];
177
- while( g.urlPath[i] && g.urlPath[i]!='&' ){ i++; }
178
- }
179
- if( g.urlPath[i] ){
180
- g.urlPath[i] = 0;
181
- i++;
182
- }
183
- if( fossil_strcmp(zName,"fossil")==0 ){
184
- g.urlFossil = zValue;
185
- dehttpize(g.urlFossil);
186
- zExe = mprintf("%cfossil=%T", cQuerySep, g.urlFossil);
187
- cQuerySep = '&';
188
- }
189
- }
190
-
191
- dehttpize(g.urlPath);
192
- if( g.urlDfltPort==g.urlPort ){
193
- g.urlCanonical = mprintf(
194
- "%s://%s%T%T%s",
195
- g.urlProtocol, zLogin, g.urlName, g.urlPath, zExe
196
- );
197
- }else{
198
- g.urlCanonical = mprintf(
199
- "%s://%s%T:%d%T%s",
200
- g.urlProtocol, zLogin, g.urlName, g.urlPort, g.urlPath, zExe
201
- );
202
- }
203
- if( g.urlIsSsh && g.urlPath[1] ) g.urlPath++;
204
- free(zLogin);
205
- }else if( strncmp(zUrl, "file:", 5)==0 ){
206
- g.urlIsFile = 1;
207
- if( zUrl[5]=='/' && zUrl[6]=='/' ){
208
- i = 7;
209
- }else{
210
- i = 5;
211
- }
212
- zFile = mprintf("%s", &zUrl[i]);
213
- }else if( file_isfile(zUrl) ){
214
- g.urlIsFile = 1;
215
- zFile = mprintf("%s", zUrl);
216
- }else if( file_isdir(zUrl)==1 ){
217
- zFile = mprintf("%s/FOSSIL", zUrl);
218
- if( file_isfile(zFile) ){
219
- g.urlIsFile = 1;
220
- }else{
221
- free(zFile);
222
- fossil_fatal("unknown repository: %s", zUrl);
223
- }
224
- }else{
225
- fossil_fatal("unknown repository: %s", zUrl);
226
- }
227
- if( urlFlags ) g.urlFlags = urlFlags;
228
- if( g.urlIsFile ){
229
- Blob cfile;
230
- dehttpize(zFile);
231
- file_canonical_name(zFile, &cfile, 0);
232
- free(zFile);
233
- g.urlProtocol = "file";
234
- g.urlPath = "";
235
- g.urlName = mprintf("%b", &cfile);
236
- g.urlCanonical = mprintf("file://%T", g.urlName);
237
- blob_reset(&cfile);
238
- }else if( g.urlUser!=0 && g.urlPasswd==0 && (urlFlags & URL_PROMPT_PW) ){
239
- url_prompt_for_password();
240
- }else if( g.urlUser!=0 && ( urlFlags & URL_ASK_REMEMBER_PW ) ){
241
- if( isatty(fileno(stdin)) ){
242
- if( save_password_prompt() ){
243
- g.urlFlags = urlFlags |= URL_REMEMBER_PW;
244
- }else{
245
- g.urlFlags = urlFlags &= ~URL_REMEMBER_PW;
246
- }
247
- }
248
- }
299
+ url_parse_local(zUrl, urlFlags, GLOBAL_URL());
249300
}
250301
251302
/*
252303
** COMMAND: test-urlparser
253304
**
@@ -439,35 +490,44 @@
439490
}
440491
return blob_str(&p->url);
441492
}
442493
443494
/*
444
-** Prompt the user for the password for g.urlUser. Store the result
445
-** in g.urlPasswd.
446
-*/
447
-void url_prompt_for_password(void){
448
- if( g.urlIsSsh || g.urlIsFile ) return;
449
- if( isatty(fileno(stdin))
450
- && (g.urlFlags & URL_PROMPT_PW)!=0
451
- && (g.urlFlags & URL_PROMPTED)==0
452
- ){
453
- g.urlFlags |= URL_PROMPTED;
454
- g.urlPasswd = prompt_for_user_password(g.urlUser);
455
- if( g.urlPasswd[0]
456
- && (g.urlFlags & (URL_REMEMBER|URL_ASK_REMEMBER_PW))!=0
457
- ){
458
- if( save_password_prompt() ){
459
- g.urlFlags |= URL_REMEMBER_PW;
460
- }else{
461
- g.urlFlags &= ~URL_REMEMBER_PW;
495
+** Prompt the user for the password that corresponds to the "user" member of
496
+** the provided UrlData structure. Store the result into the "passwd" member
497
+** of the provided UrlData structure.
498
+*/
499
+void url_prompt_for_password_local(UrlData *pUrlData){
500
+ if( pUrlData->isSsh || pUrlData->isFile ) return;
501
+ if( isatty(fileno(stdin))
502
+ && (pUrlData->flags & URL_PROMPT_PW)!=0
503
+ && (pUrlData->flags & URL_PROMPTED)==0
504
+ ){
505
+ pUrlData->flags |= URL_PROMPTED;
506
+ pUrlData->passwd = prompt_for_user_password(pUrlData->user);
507
+ if( pUrlData->passwd[0]
508
+ && (pUrlData->flags & (URL_REMEMBER|URL_ASK_REMEMBER_PW))!=0
509
+ ){
510
+ if( save_password_prompt(pUrlData->passwd) ){
511
+ pUrlData->flags |= URL_REMEMBER_PW;
512
+ }else{
513
+ pUrlData->flags &= ~URL_REMEMBER_PW;
462514
}
463515
}
464516
}else{
465517
fossil_fatal("missing or incorrect password for user \"%s\"",
466
- g.urlUser);
518
+ pUrlData->user);
467519
}
468520
}
521
+
522
+/*
523
+** Prompt the user for the password for g.urlUser. Store the result
524
+** in g.urlPasswd.
525
+*/
526
+void url_prompt_for_password(void){
527
+ url_prompt_for_password_local(GLOBAL_URL());
528
+}
469529
470530
/*
471531
** Remember the URL and password if requested.
472532
*/
473533
void url_remember(void){
474534
--- src/url.c
+++ src/url.c
@@ -39,10 +39,34 @@
39 #define URL_REMEMBER 0x002 /* Remember the url for later reuse */
40 #define URL_ASK_REMEMBER_PW 0x004 /* Ask whether to remember prompted pw */
41 #define URL_REMEMBER_PW 0x008 /* Should remember pw */
42 #define URL_PROMPTED 0x010 /* Prompted for PW already */
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44 #endif /* INTERFACE */
45
46
47 /*
48 ** Convert a string to lower-case.
@@ -51,10 +75,201 @@
51 while( *z ){
52 *z = fossil_tolower(*z);
53 z++;
54 }
55 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
57 /*
58 ** Parse the given URL, which describes a sync server. Populate variables
59 ** in the global "g" structure as follows:
60 **
@@ -79,175 +294,11 @@
79 **
80 ** ssh://userid@host:port/path?fossil=path/to/fossil.exe
81 **
82 */
83 void url_parse(const char *zUrl, unsigned int urlFlags){
84 int i, j, c;
85 char *zFile = 0;
86
87 if( zUrl==0 ){
88 zUrl = db_get("last-sync-url", 0);
89 if( zUrl==0 ) return;
90 if( g.urlPasswd==0 ){
91 g.urlPasswd = unobscure(db_get("last-sync-pw", 0));
92 }
93 }
94
95 if( strncmp(zUrl, "http://", 7)==0
96 || strncmp(zUrl, "https://", 8)==0
97 || strncmp(zUrl, "ssh://", 6)==0
98 ){
99 int iStart;
100 char *zLogin;
101 char *zExe;
102 char cQuerySep = '?';
103
104 g.urlIsFile = 0;
105 if( zUrl[4]=='s' ){
106 g.urlIsHttps = 1;
107 g.urlProtocol = "https";
108 g.urlDfltPort = 443;
109 iStart = 8;
110 }else if( zUrl[0]=='s' ){
111 g.urlIsSsh = 1;
112 g.urlProtocol = "ssh";
113 g.urlDfltPort = 22;
114 g.urlFossil = "fossil";
115 iStart = 6;
116 }else{
117 g.urlIsHttps = 0;
118 g.urlProtocol = "http";
119 g.urlDfltPort = 80;
120 iStart = 7;
121 }
122 for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!='@'; i++){}
123 if( c=='@' ){
124 /* Parse up the user-id and password */
125 for(j=iStart; j<i && zUrl[j]!=':'; j++){}
126 g.urlUser = mprintf("%.*s", j-iStart, &zUrl[iStart]);
127 dehttpize(g.urlUser);
128 if( j<i ){
129 if( ( urlFlags & URL_REMEMBER ) && g.urlIsSsh==0 ){
130 urlFlags |= URL_ASK_REMEMBER_PW;
131 }
132 g.urlPasswd = mprintf("%.*s", i-j-1, &zUrl[j+1]);
133 dehttpize(g.urlPasswd);
134 }
135 if( g.urlIsSsh ){
136 urlFlags &= ~URL_ASK_REMEMBER_PW;
137 }
138 zLogin = mprintf("%t@", g.urlUser);
139 for(j=i+1; (c=zUrl[j])!=0 && c!='/' && c!=':'; j++){}
140 g.urlName = mprintf("%.*s", j-i-1, &zUrl[i+1]);
141 i = j;
142 }else{
143 for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!=':'; i++){}
144 g.urlName = mprintf("%.*s", i-iStart, &zUrl[iStart]);
145 zLogin = mprintf("");
146 }
147 url_tolower(g.urlName);
148 if( c==':' ){
149 g.urlPort = 0;
150 i++;
151 while( (c = zUrl[i])!=0 && fossil_isdigit(c) ){
152 g.urlPort = g.urlPort*10 + c - '0';
153 i++;
154 }
155 g.urlHostname = mprintf("%s:%d", g.urlName, g.urlPort);
156 }else{
157 g.urlPort = g.urlDfltPort;
158 g.urlHostname = g.urlName;
159 }
160 dehttpize(g.urlName);
161 g.urlPath = mprintf("%s", &zUrl[i]);
162 for(i=0; g.urlPath[i] && g.urlPath[i]!='?'; i++){}
163 if( g.urlPath[i] ){
164 g.urlPath[i] = 0;
165 i++;
166 }
167 zExe = mprintf("");
168 while( g.urlPath[i]!=0 ){
169 char *zName, *zValue;
170 zName = &g.urlPath[i];
171 zValue = zName;
172 while( g.urlPath[i] && g.urlPath[i]!='=' ){ i++; }
173 if( g.urlPath[i]=='=' ){
174 g.urlPath[i] = 0;
175 i++;
176 zValue = &g.urlPath[i];
177 while( g.urlPath[i] && g.urlPath[i]!='&' ){ i++; }
178 }
179 if( g.urlPath[i] ){
180 g.urlPath[i] = 0;
181 i++;
182 }
183 if( fossil_strcmp(zName,"fossil")==0 ){
184 g.urlFossil = zValue;
185 dehttpize(g.urlFossil);
186 zExe = mprintf("%cfossil=%T", cQuerySep, g.urlFossil);
187 cQuerySep = '&';
188 }
189 }
190
191 dehttpize(g.urlPath);
192 if( g.urlDfltPort==g.urlPort ){
193 g.urlCanonical = mprintf(
194 "%s://%s%T%T%s",
195 g.urlProtocol, zLogin, g.urlName, g.urlPath, zExe
196 );
197 }else{
198 g.urlCanonical = mprintf(
199 "%s://%s%T:%d%T%s",
200 g.urlProtocol, zLogin, g.urlName, g.urlPort, g.urlPath, zExe
201 );
202 }
203 if( g.urlIsSsh && g.urlPath[1] ) g.urlPath++;
204 free(zLogin);
205 }else if( strncmp(zUrl, "file:", 5)==0 ){
206 g.urlIsFile = 1;
207 if( zUrl[5]=='/' && zUrl[6]=='/' ){
208 i = 7;
209 }else{
210 i = 5;
211 }
212 zFile = mprintf("%s", &zUrl[i]);
213 }else if( file_isfile(zUrl) ){
214 g.urlIsFile = 1;
215 zFile = mprintf("%s", zUrl);
216 }else if( file_isdir(zUrl)==1 ){
217 zFile = mprintf("%s/FOSSIL", zUrl);
218 if( file_isfile(zFile) ){
219 g.urlIsFile = 1;
220 }else{
221 free(zFile);
222 fossil_fatal("unknown repository: %s", zUrl);
223 }
224 }else{
225 fossil_fatal("unknown repository: %s", zUrl);
226 }
227 if( urlFlags ) g.urlFlags = urlFlags;
228 if( g.urlIsFile ){
229 Blob cfile;
230 dehttpize(zFile);
231 file_canonical_name(zFile, &cfile, 0);
232 free(zFile);
233 g.urlProtocol = "file";
234 g.urlPath = "";
235 g.urlName = mprintf("%b", &cfile);
236 g.urlCanonical = mprintf("file://%T", g.urlName);
237 blob_reset(&cfile);
238 }else if( g.urlUser!=0 && g.urlPasswd==0 && (urlFlags & URL_PROMPT_PW) ){
239 url_prompt_for_password();
240 }else if( g.urlUser!=0 && ( urlFlags & URL_ASK_REMEMBER_PW ) ){
241 if( isatty(fileno(stdin)) ){
242 if( save_password_prompt() ){
243 g.urlFlags = urlFlags |= URL_REMEMBER_PW;
244 }else{
245 g.urlFlags = urlFlags &= ~URL_REMEMBER_PW;
246 }
247 }
248 }
249 }
250
251 /*
252 ** COMMAND: test-urlparser
253 **
@@ -439,35 +490,44 @@
439 }
440 return blob_str(&p->url);
441 }
442
443 /*
444 ** Prompt the user for the password for g.urlUser. Store the result
445 ** in g.urlPasswd.
446 */
447 void url_prompt_for_password(void){
448 if( g.urlIsSsh || g.urlIsFile ) return;
449 if( isatty(fileno(stdin))
450 && (g.urlFlags & URL_PROMPT_PW)!=0
451 && (g.urlFlags & URL_PROMPTED)==0
452 ){
453 g.urlFlags |= URL_PROMPTED;
454 g.urlPasswd = prompt_for_user_password(g.urlUser);
455 if( g.urlPasswd[0]
456 && (g.urlFlags & (URL_REMEMBER|URL_ASK_REMEMBER_PW))!=0
457 ){
458 if( save_password_prompt() ){
459 g.urlFlags |= URL_REMEMBER_PW;
460 }else{
461 g.urlFlags &= ~URL_REMEMBER_PW;
 
462 }
463 }
464 }else{
465 fossil_fatal("missing or incorrect password for user \"%s\"",
466 g.urlUser);
467 }
468 }
 
 
 
 
 
 
 
 
469
470 /*
471 ** Remember the URL and password if requested.
472 */
473 void url_remember(void){
474
--- src/url.c
+++ src/url.c
@@ -39,10 +39,34 @@
39 #define URL_REMEMBER 0x002 /* Remember the url for later reuse */
40 #define URL_ASK_REMEMBER_PW 0x004 /* Ask whether to remember prompted pw */
41 #define URL_REMEMBER_PW 0x008 /* Should remember pw */
42 #define URL_PROMPTED 0x010 /* Prompted for PW already */
43
44 /*
45 ** The URL related data used with this subsystem.
46 */
47 struct UrlData {
48 /*
49 ** NOTE: These members MUST be kept in sync with the related ones in the
50 ** "Global" structure defined in "main.c".
51 */
52 int isFile; /* True if a "file:" url */
53 int isHttps; /* True if a "https:" url */
54 int isSsh; /* True if an "ssh:" url */
55 char *name; /* Hostname for http: or filename for file: */
56 char *hostname; /* The HOST: parameter on http headers */
57 char *protocol; /* "http" or "https" */
58 int port; /* TCP port number for http: or https: */
59 int dfltPort; /* The default port for the given protocol */
60 char *path; /* Pathname for http: */
61 char *user; /* User id for http: */
62 char *passwd; /* Password for http: */
63 char *canonical; /* Canonical representation of the URL */
64 char *proxyAuth; /* Proxy-Authorizer: string */
65 char *fossil; /* The fossil query parameter on ssh: */
66 unsigned flags; /* Boolean flags controlling URL processing */
67 };
68 #endif /* INTERFACE */
69
70
71 /*
72 ** Convert a string to lower-case.
@@ -51,10 +75,201 @@
75 while( *z ){
76 *z = fossil_tolower(*z);
77 z++;
78 }
79 }
80
81 /*
82 ** Parse the given URL. Populate members of the provided UrlData structure
83 ** as follows:
84 **
85 ** isFile True if FILE:
86 ** isHttps True if HTTPS:
87 ** isSsh True if SSH:
88 ** protocol "http" or "https" or "file"
89 ** name Hostname for HTTP:, HTTPS:, SSH:. Filename for FILE:
90 ** port TCP port number for HTTP or HTTPS.
91 ** dfltPort Default TCP port number (80 or 443).
92 ** path Path name for HTTP or HTTPS.
93 ** user Userid.
94 ** passwd Password.
95 ** hostname HOST:PORT or just HOST if port is the default.
96 ** canonical The URL in canonical form, omitting the password
97 **
98 */
99 void url_parse_local(
100 const char *zUrl,
101 unsigned int urlFlags,
102 UrlData *pUrlData
103 ){
104 int i, j, c;
105 char *zFile = 0;
106
107 if( zUrl==0 ){
108 zUrl = db_get("last-sync-url", 0);
109 if( zUrl==0 ) return;
110 if( pUrlData->passwd==0 ){
111 pUrlData->passwd = unobscure(db_get("last-sync-pw", 0));
112 }
113 }
114
115 if( strncmp(zUrl, "http://", 7)==0
116 || strncmp(zUrl, "https://", 8)==0
117 || strncmp(zUrl, "ssh://", 6)==0
118 ){
119 int iStart;
120 char *zLogin;
121 char *zExe;
122 char cQuerySep = '?';
123
124 pUrlData->isFile = 0;
125 if( zUrl[4]=='s' ){
126 pUrlData->isHttps = 1;
127 pUrlData->protocol = "https";
128 pUrlData->dfltPort = 443;
129 iStart = 8;
130 }else if( zUrl[0]=='s' ){
131 pUrlData->isSsh = 1;
132 pUrlData->protocol = "ssh";
133 pUrlData->dfltPort = 22;
134 pUrlData->fossil = "fossil";
135 iStart = 6;
136 }else{
137 pUrlData->isHttps = 0;
138 pUrlData->protocol = "http";
139 pUrlData->dfltPort = 80;
140 iStart = 7;
141 }
142 for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!='@'; i++){}
143 if( c=='@' ){
144 /* Parse up the user-id and password */
145 for(j=iStart; j<i && zUrl[j]!=':'; j++){}
146 pUrlData->user = mprintf("%.*s", j-iStart, &zUrl[iStart]);
147 dehttpize(pUrlData->user);
148 if( j<i ){
149 if( ( urlFlags & URL_REMEMBER ) && pUrlData->isSsh==0 ){
150 urlFlags |= URL_ASK_REMEMBER_PW;
151 }
152 pUrlData->passwd = mprintf("%.*s", i-j-1, &zUrl[j+1]);
153 dehttpize(pUrlData->passwd);
154 }
155 if( pUrlData->isSsh ){
156 urlFlags &= ~URL_ASK_REMEMBER_PW;
157 }
158 zLogin = mprintf("%t@", pUrlData->user);
159 for(j=i+1; (c=zUrl[j])!=0 && c!='/' && c!=':'; j++){}
160 pUrlData->name = mprintf("%.*s", j-i-1, &zUrl[i+1]);
161 i = j;
162 }else{
163 for(i=iStart; (c=zUrl[i])!=0 && c!='/' && c!=':'; i++){}
164 pUrlData->name = mprintf("%.*s", i-iStart, &zUrl[iStart]);
165 zLogin = mprintf("");
166 }
167 url_tolower(pUrlData->name);
168 if( c==':' ){
169 pUrlData->port = 0;
170 i++;
171 while( (c = zUrl[i])!=0 && fossil_isdigit(c) ){
172 pUrlData->port = pUrlData->port*10 + c - '0';
173 i++;
174 }
175 pUrlData->hostname = mprintf("%s:%d", pUrlData->name, pUrlData->port);
176 }else{
177 pUrlData->port = pUrlData->dfltPort;
178 pUrlData->hostname = pUrlData->name;
179 }
180 dehttpize(pUrlData->name);
181 pUrlData->path = mprintf("%s", &zUrl[i]);
182 for(i=0; pUrlData->path[i] && pUrlData->path[i]!='?'; i++){}
183 if( pUrlData->path[i] ){
184 pUrlData->path[i] = 0;
185 i++;
186 }
187 zExe = mprintf("");
188 while( pUrlData->path[i]!=0 ){
189 char *zName, *zValue;
190 zName = &pUrlData->path[i];
191 zValue = zName;
192 while( pUrlData->path[i] && pUrlData->path[i]!='=' ){ i++; }
193 if( pUrlData->path[i]=='=' ){
194 pUrlData->path[i] = 0;
195 i++;
196 zValue = &pUrlData->path[i];
197 while( pUrlData->path[i] && pUrlData->path[i]!='&' ){ i++; }
198 }
199 if( pUrlData->path[i] ){
200 pUrlData->path[i] = 0;
201 i++;
202 }
203 if( fossil_strcmp(zName,"fossil")==0 ){
204 pUrlData->fossil = zValue;
205 dehttpize(pUrlData->fossil);
206 zExe = mprintf("%cfossil=%T", cQuerySep, pUrlData->fossil);
207 cQuerySep = '&';
208 }
209 }
210
211 dehttpize(pUrlData->path);
212 if( pUrlData->dfltPort==pUrlData->port ){
213 pUrlData->canonical = mprintf(
214 "%s://%s%T%T%s",
215 pUrlData->protocol, zLogin, pUrlData->name, pUrlData->path, zExe
216 );
217 }else{
218 pUrlData->canonical = mprintf(
219 "%s://%s%T:%d%T%s",
220 pUrlData->protocol, zLogin, pUrlData->name, pUrlData->port,
221 pUrlData->path, zExe
222 );
223 }
224 if( pUrlData->isSsh && pUrlData->path[1] ) pUrlData->path++;
225 free(zLogin);
226 }else if( strncmp(zUrl, "file:", 5)==0 ){
227 pUrlData->isFile = 1;
228 if( zUrl[5]=='/' && zUrl[6]=='/' ){
229 i = 7;
230 }else{
231 i = 5;
232 }
233 zFile = mprintf("%s", &zUrl[i]);
234 }else if( file_isfile(zUrl) ){
235 pUrlData->isFile = 1;
236 zFile = mprintf("%s", zUrl);
237 }else if( file_isdir(zUrl)==1 ){
238 zFile = mprintf("%s/FOSSIL", zUrl);
239 if( file_isfile(zFile) ){
240 pUrlData->isFile = 1;
241 }else{
242 free(zFile);
243 fossil_fatal("unknown repository: %s", zUrl);
244 }
245 }else{
246 fossil_fatal("unknown repository: %s", zUrl);
247 }
248 if( urlFlags ) pUrlData->flags = urlFlags;
249 if( pUrlData->isFile ){
250 Blob cfile;
251 dehttpize(zFile);
252 file_canonical_name(zFile, &cfile, 0);
253 free(zFile);
254 pUrlData->protocol = "file";
255 pUrlData->path = "";
256 pUrlData->name = mprintf("%b", &cfile);
257 pUrlData->canonical = mprintf("file://%T", pUrlData->name);
258 blob_reset(&cfile);
259 }else if( pUrlData->user!=0 && pUrlData->passwd==0 && (urlFlags & URL_PROMPT_PW) ){
260 url_prompt_for_password_local(pUrlData);
261 }else if( pUrlData->user!=0 && ( urlFlags & URL_ASK_REMEMBER_PW ) ){
262 if( isatty(fileno(stdin)) ){
263 if( save_password_prompt(pUrlData->passwd) ){
264 pUrlData->flags = urlFlags |= URL_REMEMBER_PW;
265 }else{
266 pUrlData->flags = urlFlags &= ~URL_REMEMBER_PW;
267 }
268 }
269 }
270 }
271
272 /*
273 ** Parse the given URL, which describes a sync server. Populate variables
274 ** in the global "g" structure as follows:
275 **
@@ -79,175 +294,11 @@
294 **
295 ** ssh://userid@host:port/path?fossil=path/to/fossil.exe
296 **
297 */
298 void url_parse(const char *zUrl, unsigned int urlFlags){
299 url_parse_local(zUrl, urlFlags, GLOBAL_URL());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300 }
301
302 /*
303 ** COMMAND: test-urlparser
304 **
@@ -439,35 +490,44 @@
490 }
491 return blob_str(&p->url);
492 }
493
494 /*
495 ** Prompt the user for the password that corresponds to the "user" member of
496 ** the provided UrlData structure. Store the result into the "passwd" member
497 ** of the provided UrlData structure.
498 */
499 void url_prompt_for_password_local(UrlData *pUrlData){
500 if( pUrlData->isSsh || pUrlData->isFile ) return;
501 if( isatty(fileno(stdin))
502 && (pUrlData->flags & URL_PROMPT_PW)!=0
503 && (pUrlData->flags & URL_PROMPTED)==0
504 ){
505 pUrlData->flags |= URL_PROMPTED;
506 pUrlData->passwd = prompt_for_user_password(pUrlData->user);
507 if( pUrlData->passwd[0]
508 && (pUrlData->flags & (URL_REMEMBER|URL_ASK_REMEMBER_PW))!=0
509 ){
510 if( save_password_prompt(pUrlData->passwd) ){
511 pUrlData->flags |= URL_REMEMBER_PW;
512 }else{
513 pUrlData->flags &= ~URL_REMEMBER_PW;
514 }
515 }
516 }else{
517 fossil_fatal("missing or incorrect password for user \"%s\"",
518 pUrlData->user);
519 }
520 }
521
522 /*
523 ** Prompt the user for the password for g.urlUser. Store the result
524 ** in g.urlPasswd.
525 */
526 void url_prompt_for_password(void){
527 url_prompt_for_password_local(GLOBAL_URL());
528 }
529
530 /*
531 ** Remember the URL and password if requested.
532 */
533 void url_remember(void){
534
+2 -2
--- src/user.c
+++ src/user.c
@@ -132,15 +132,15 @@
132132
}
133133
134134
/*
135135
** Prompt to save Fossil user password
136136
*/
137
-int save_password_prompt(){
137
+int save_password_prompt(const char *passwd){
138138
Blob x;
139139
char c;
140140
const char *old = db_get("last-sync-pw", 0);
141
- if( (old!=0) && fossil_strcmp(unobscure(old), g.urlPasswd)==0 ){
141
+ if( (old!=0) && fossil_strcmp(unobscure(old), passwd)==0 ){
142142
return 0;
143143
}
144144
prompt_user("remember password (Y/n)? ", &x);
145145
c = blob_str(&x)[0];
146146
blob_reset(&x);
147147
--- src/user.c
+++ src/user.c
@@ -132,15 +132,15 @@
132 }
133
134 /*
135 ** Prompt to save Fossil user password
136 */
137 int save_password_prompt(){
138 Blob x;
139 char c;
140 const char *old = db_get("last-sync-pw", 0);
141 if( (old!=0) && fossil_strcmp(unobscure(old), g.urlPasswd)==0 ){
142 return 0;
143 }
144 prompt_user("remember password (Y/n)? ", &x);
145 c = blob_str(&x)[0];
146 blob_reset(&x);
147
--- src/user.c
+++ src/user.c
@@ -132,15 +132,15 @@
132 }
133
134 /*
135 ** Prompt to save Fossil user password
136 */
137 int save_password_prompt(const char *passwd){
138 Blob x;
139 char c;
140 const char *old = db_get("last-sync-pw", 0);
141 if( (old!=0) && fossil_strcmp(unobscure(old), passwd)==0 ){
142 return 0;
143 }
144 prompt_user("remember password (Y/n)? ", &x);
145 c = blob_str(&x)[0];
146 blob_reset(&x);
147
+1 -1
--- src/wiki.c
+++ src/wiki.c
@@ -294,11 +294,11 @@
294294
moderation_table_create();
295295
db_multi_exec("INSERT INTO modreq(objid) VALUES(%d)", nrid);
296296
}
297297
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
298298
db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid);
299
- manifest_crosslink(nrid, pWiki);
299
+ manifest_crosslink(nrid, pWiki, MC_NONE);
300300
}
301301
302302
/*
303303
** Formal names and common names for the various wiki styles.
304304
*/
305305
--- src/wiki.c
+++ src/wiki.c
@@ -294,11 +294,11 @@
294 moderation_table_create();
295 db_multi_exec("INSERT INTO modreq(objid) VALUES(%d)", nrid);
296 }
297 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
298 db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid);
299 manifest_crosslink(nrid, pWiki);
300 }
301
302 /*
303 ** Formal names and common names for the various wiki styles.
304 */
305
--- src/wiki.c
+++ src/wiki.c
@@ -294,11 +294,11 @@
294 moderation_table_create();
295 db_multi_exec("INSERT INTO modreq(objid) VALUES(%d)", nrid);
296 }
297 db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
298 db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid);
299 manifest_crosslink(nrid, pWiki, MC_NONE);
300 }
301
302 /*
303 ** Formal names and common names for the various wiki styles.
304 */
305
+77 -32
--- src/xfer.c
+++ src/xfer.c
@@ -191,11 +191,11 @@
191191
if( rid==0 ){
192192
blob_appendf(&pXfer->err, "%s", g.zErrMsg);
193193
blob_reset(&content);
194194
}else{
195195
if( !isPriv ) content_make_public(rid);
196
- manifest_crosslink(rid, &content);
196
+ manifest_crosslink(rid, &content, MC_NONE);
197197
}
198198
assert( blob_is_reset(&content) );
199199
remote_has(rid);
200200
}
201201
@@ -820,33 +820,73 @@
820820
static void server_private_xfer_not_authorized(void){
821821
@ error not\sauthorized\sto\ssync\sprivate\scontent
822822
}
823823
824824
/*
825
-** Run the specified TH1 script, if any, and returns the return code or TH_OK
826
-** when there is no script.
827
-*/
828
-static int run_script(const char *zScript){
829
- if( !zScript ){
830
- return TH_OK; /* No script, return success. */
831
- }
832
- Th_FossilInit(TH_INIT_DEFAULT); /* Make sure TH1 is ready. */
833
- return Th_Eval(g.interp, 0, zScript, -1);
834
-}
835
-
836
-/*
837
-** Run the pre-transfer TH1 script, if any, and returns the return code.
838
-*/
839
-static int run_common_script(void){
840
- return run_script(db_get("xfer-common-script", 0));
841
-}
842
-
843
-/*
844
-** Run the post-push TH1 script, if any, and returns the return code.
845
-*/
846
-static int run_push_script(void){
847
- return run_script(db_get("xfer-push-script", 0));
825
+** Return the common TH1 code to evaluate prior to evaluating any other
826
+** TH1 transfer notification scripts.
827
+*/
828
+const char *xfer_common_code(void){
829
+ return db_get("xfer-common-script", 0);
830
+}
831
+
832
+/*
833
+** Return the TH1 code to evaluate when a push is processed.
834
+*/
835
+const char *xfer_push_code(void){
836
+ return db_get("xfer-push-script", 0);
837
+}
838
+
839
+/*
840
+** Return the TH1 code to evaluate when a commit is processed.
841
+*/
842
+const char *xfer_commit_code(void){
843
+ return db_get("xfer-commit-script", 0);
844
+}
845
+
846
+/*
847
+** Return the TH1 code to evaluate when a ticket change is processed.
848
+*/
849
+const char *xfer_ticket_code(void){
850
+ return db_get("xfer-ticket-script", 0);
851
+}
852
+
853
+/*
854
+** Run the specified TH1 script, if any, and returns 1 on error.
855
+*/
856
+int xfer_run_script(const char *zScript, const char *zUuid){
857
+ int result;
858
+ if( !zScript ) return TH_OK;
859
+ Th_FossilInit(TH_INIT_DEFAULT);
860
+ if( zUuid ){
861
+ result = Th_SetVar(g.interp, "uuid", -1, zUuid, -1);
862
+ if( result!=TH_OK ){
863
+ fossil_error(1, "%s", Th_GetResult(g.interp, 0));
864
+ return result;
865
+ }
866
+ }
867
+ result = Th_Eval(g.interp, 0, zScript, -1);
868
+ if( result!=TH_OK ){
869
+ fossil_error(1, "%s", Th_GetResult(g.interp, 0));
870
+ }
871
+ return result;
872
+}
873
+
874
+/*
875
+** Runs the pre-transfer TH1 script, if any, and returns its return code.
876
+** This script may be run multiple times. If the script performs actions
877
+** that cannot be redone, it should use an internal [if] guard similar to
878
+** the following:
879
+**
880
+** if {![info exists common_done]} {
881
+** # ... code here
882
+** set common_done 1
883
+** }
884
+*/
885
+int xfer_run_common_script(void){
886
+ Th_FossilInit(TH_INIT_DEFAULT);
887
+ return xfer_run_script(xfer_common_code(), 0);
848888
}
849889
850890
/*
851891
** If this variable is set, disable login checks. Used for debugging
852892
** only.
@@ -875,10 +915,11 @@
875915
int isClone = 0;
876916
int nGimme = 0;
877917
int size;
878918
int recvConfig = 0;
879919
char *zNow;
920
+ int result;
880921
881922
if( fossil_strcmp(PD("REQUEST_METHOD","POST"),"POST") ){
882923
fossil_redirect_home();
883924
}
884925
g.zLogin = "anonymous";
@@ -904,13 +945,14 @@
904945
db_begin_transaction();
905946
db_multi_exec(
906947
"CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
907948
);
908949
manifest_crosslink_begin();
909
- if( run_common_script()==TH_ERROR ){
950
+ result = xfer_run_common_script();
951
+ if( result==TH_ERROR ){
910952
cgi_reset_content();
911
- @ error common\sscript\sfailed:\s%F(Th_GetResult(g.interp, 0))
953
+ @ error common\sscript\sfailed:\s%F(g.zErrMsg)
912954
nErr++;
913955
}
914956
while( blob_line(xfer.pIn, &xfer.line) ){
915957
if( blob_buffer(&xfer.line)[0]=='#' ) continue;
916958
if( blob_size(&xfer.line)==0 ) continue;
@@ -1231,14 +1273,17 @@
12311273
}
12321274
blobarray_reset(xfer.aToken, xfer.nToken);
12331275
blob_reset(&xfer.line);
12341276
}
12351277
if( isPush ){
1236
- if( run_push_script()==TH_ERROR ){
1237
- cgi_reset_content();
1238
- @ error push\sscript\sfailed:\s%F(Th_GetResult(g.interp, 0))
1239
- nErr++;
1278
+ if( result==TH_OK ){
1279
+ result = xfer_run_script(xfer_push_code(), 0);
1280
+ if( result==TH_ERROR ){
1281
+ cgi_reset_content();
1282
+ @ error push\sscript\sfailed:\s%F(g.zErrMsg)
1283
+ nErr++;
1284
+ }
12401285
}
12411286
request_phantoms(&xfer, 500);
12421287
}
12431288
if( isClone && nGimme==0 ){
12441289
/* The initial "clone" message from client to server contains no
@@ -1881,13 +1926,13 @@
18811926
18821927
fossil_force_newline();
18831928
fossil_print(
18841929
"%s finished with %lld bytes sent, %lld bytes received\n",
18851930
zOpType, nSent, nRcvd);
1886
- transport_close();
1887
- transport_global_shutdown();
1931
+ transport_close(GLOBAL_URL());
1932
+ transport_global_shutdown(GLOBAL_URL());
18881933
db_multi_exec("DROP TABLE onremote");
18891934
manifest_crosslink_end();
18901935
content_enable_dephantomize(1);
18911936
db_end_transaction(0);
18921937
return nErr;
18931938
}
18941939
--- src/xfer.c
+++ src/xfer.c
@@ -191,11 +191,11 @@
191 if( rid==0 ){
192 blob_appendf(&pXfer->err, "%s", g.zErrMsg);
193 blob_reset(&content);
194 }else{
195 if( !isPriv ) content_make_public(rid);
196 manifest_crosslink(rid, &content);
197 }
198 assert( blob_is_reset(&content) );
199 remote_has(rid);
200 }
201
@@ -820,33 +820,73 @@
820 static void server_private_xfer_not_authorized(void){
821 @ error not\sauthorized\sto\ssync\sprivate\scontent
822 }
823
824 /*
825 ** Run the specified TH1 script, if any, and returns the return code or TH_OK
826 ** when there is no script.
827 */
828 static int run_script(const char *zScript){
829 if( !zScript ){
830 return TH_OK; /* No script, return success. */
831 }
832 Th_FossilInit(TH_INIT_DEFAULT); /* Make sure TH1 is ready. */
833 return Th_Eval(g.interp, 0, zScript, -1);
834 }
835
836 /*
837 ** Run the pre-transfer TH1 script, if any, and returns the return code.
838 */
839 static int run_common_script(void){
840 return run_script(db_get("xfer-common-script", 0));
841 }
842
843 /*
844 ** Run the post-push TH1 script, if any, and returns the return code.
845 */
846 static int run_push_script(void){
847 return run_script(db_get("xfer-push-script", 0));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
848 }
849
850 /*
851 ** If this variable is set, disable login checks. Used for debugging
852 ** only.
@@ -875,10 +915,11 @@
875 int isClone = 0;
876 int nGimme = 0;
877 int size;
878 int recvConfig = 0;
879 char *zNow;
 
880
881 if( fossil_strcmp(PD("REQUEST_METHOD","POST"),"POST") ){
882 fossil_redirect_home();
883 }
884 g.zLogin = "anonymous";
@@ -904,13 +945,14 @@
904 db_begin_transaction();
905 db_multi_exec(
906 "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
907 );
908 manifest_crosslink_begin();
909 if( run_common_script()==TH_ERROR ){
 
910 cgi_reset_content();
911 @ error common\sscript\sfailed:\s%F(Th_GetResult(g.interp, 0))
912 nErr++;
913 }
914 while( blob_line(xfer.pIn, &xfer.line) ){
915 if( blob_buffer(&xfer.line)[0]=='#' ) continue;
916 if( blob_size(&xfer.line)==0 ) continue;
@@ -1231,14 +1273,17 @@
1231 }
1232 blobarray_reset(xfer.aToken, xfer.nToken);
1233 blob_reset(&xfer.line);
1234 }
1235 if( isPush ){
1236 if( run_push_script()==TH_ERROR ){
1237 cgi_reset_content();
1238 @ error push\sscript\sfailed:\s%F(Th_GetResult(g.interp, 0))
1239 nErr++;
 
 
 
1240 }
1241 request_phantoms(&xfer, 500);
1242 }
1243 if( isClone && nGimme==0 ){
1244 /* The initial "clone" message from client to server contains no
@@ -1881,13 +1926,13 @@
1881
1882 fossil_force_newline();
1883 fossil_print(
1884 "%s finished with %lld bytes sent, %lld bytes received\n",
1885 zOpType, nSent, nRcvd);
1886 transport_close();
1887 transport_global_shutdown();
1888 db_multi_exec("DROP TABLE onremote");
1889 manifest_crosslink_end();
1890 content_enable_dephantomize(1);
1891 db_end_transaction(0);
1892 return nErr;
1893 }
1894
--- src/xfer.c
+++ src/xfer.c
@@ -191,11 +191,11 @@
191 if( rid==0 ){
192 blob_appendf(&pXfer->err, "%s", g.zErrMsg);
193 blob_reset(&content);
194 }else{
195 if( !isPriv ) content_make_public(rid);
196 manifest_crosslink(rid, &content, MC_NONE);
197 }
198 assert( blob_is_reset(&content) );
199 remote_has(rid);
200 }
201
@@ -820,33 +820,73 @@
820 static void server_private_xfer_not_authorized(void){
821 @ error not\sauthorized\sto\ssync\sprivate\scontent
822 }
823
824 /*
825 ** Return the common TH1 code to evaluate prior to evaluating any other
826 ** TH1 transfer notification scripts.
827 */
828 const char *xfer_common_code(void){
829 return db_get("xfer-common-script", 0);
830 }
831
832 /*
833 ** Return the TH1 code to evaluate when a push is processed.
834 */
835 const char *xfer_push_code(void){
836 return db_get("xfer-push-script", 0);
837 }
838
839 /*
840 ** Return the TH1 code to evaluate when a commit is processed.
841 */
842 const char *xfer_commit_code(void){
843 return db_get("xfer-commit-script", 0);
844 }
845
846 /*
847 ** Return the TH1 code to evaluate when a ticket change is processed.
848 */
849 const char *xfer_ticket_code(void){
850 return db_get("xfer-ticket-script", 0);
851 }
852
853 /*
854 ** Run the specified TH1 script, if any, and returns 1 on error.
855 */
856 int xfer_run_script(const char *zScript, const char *zUuid){
857 int result;
858 if( !zScript ) return TH_OK;
859 Th_FossilInit(TH_INIT_DEFAULT);
860 if( zUuid ){
861 result = Th_SetVar(g.interp, "uuid", -1, zUuid, -1);
862 if( result!=TH_OK ){
863 fossil_error(1, "%s", Th_GetResult(g.interp, 0));
864 return result;
865 }
866 }
867 result = Th_Eval(g.interp, 0, zScript, -1);
868 if( result!=TH_OK ){
869 fossil_error(1, "%s", Th_GetResult(g.interp, 0));
870 }
871 return result;
872 }
873
874 /*
875 ** Runs the pre-transfer TH1 script, if any, and returns its return code.
876 ** This script may be run multiple times. If the script performs actions
877 ** that cannot be redone, it should use an internal [if] guard similar to
878 ** the following:
879 **
880 ** if {![info exists common_done]} {
881 ** # ... code here
882 ** set common_done 1
883 ** }
884 */
885 int xfer_run_common_script(void){
886 Th_FossilInit(TH_INIT_DEFAULT);
887 return xfer_run_script(xfer_common_code(), 0);
888 }
889
890 /*
891 ** If this variable is set, disable login checks. Used for debugging
892 ** only.
@@ -875,10 +915,11 @@
915 int isClone = 0;
916 int nGimme = 0;
917 int size;
918 int recvConfig = 0;
919 char *zNow;
920 int result;
921
922 if( fossil_strcmp(PD("REQUEST_METHOD","POST"),"POST") ){
923 fossil_redirect_home();
924 }
925 g.zLogin = "anonymous";
@@ -904,13 +945,14 @@
945 db_begin_transaction();
946 db_multi_exec(
947 "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
948 );
949 manifest_crosslink_begin();
950 result = xfer_run_common_script();
951 if( result==TH_ERROR ){
952 cgi_reset_content();
953 @ error common\sscript\sfailed:\s%F(g.zErrMsg)
954 nErr++;
955 }
956 while( blob_line(xfer.pIn, &xfer.line) ){
957 if( blob_buffer(&xfer.line)[0]=='#' ) continue;
958 if( blob_size(&xfer.line)==0 ) continue;
@@ -1231,14 +1273,17 @@
1273 }
1274 blobarray_reset(xfer.aToken, xfer.nToken);
1275 blob_reset(&xfer.line);
1276 }
1277 if( isPush ){
1278 if( result==TH_OK ){
1279 result = xfer_run_script(xfer_push_code(), 0);
1280 if( result==TH_ERROR ){
1281 cgi_reset_content();
1282 @ error push\sscript\sfailed:\s%F(g.zErrMsg)
1283 nErr++;
1284 }
1285 }
1286 request_phantoms(&xfer, 500);
1287 }
1288 if( isClone && nGimme==0 ){
1289 /* The initial "clone" message from client to server contains no
@@ -1881,13 +1926,13 @@
1926
1927 fossil_force_newline();
1928 fossil_print(
1929 "%s finished with %lld bytes sent, %lld bytes received\n",
1930 zOpType, nSent, nRcvd);
1931 transport_close(GLOBAL_URL());
1932 transport_global_shutdown(GLOBAL_URL());
1933 db_multi_exec("DROP TABLE onremote");
1934 manifest_crosslink_end();
1935 content_enable_dephantomize(1);
1936 db_end_transaction(0);
1937 return nErr;
1938 }
1939
--- src/xfersetup.c
+++ src/xfersetup.c
@@ -31,16 +31,63 @@
3131
if( !g.perm.Setup ){
3232
login_needed();
3333
}
3434
3535
style_header("Transfer Setup");
36
+
3637
@ <table border="0" cellspacing="20">
3738
setup_menu_entry("Common", "xfersetup_com",
3839
"Common TH1 code run before all transfer request processing.");
3940
setup_menu_entry("Push", "xfersetup_push",
4041
"Specific TH1 code to run after \"push\" transfer requests.");
42
+ setup_menu_entry("Commit", "xfersetup_commit",
43
+ "Specific TH1 code to run after processing a commit.");
44
+ setup_menu_entry("Ticket", "xfersetup_ticket",
45
+ "Specific TH1 code to run after processing a ticket change.");
4146
@ </table>
47
+
48
+ url_parse(0, 0);
49
+ if( g.urlProtocol ){
50
+ unsigned syncFlags;
51
+ const char *zButton;
52
+ char *zWarning;
53
+
54
+ if( db_get_boolean("dont-push", 0) ){
55
+ syncFlags = SYNC_PULL;
56
+ zButton = "Pull";
57
+ zWarning = 0;
58
+ }else{
59
+ syncFlags = SYNC_PUSH | SYNC_PULL;
60
+ zButton = "Synchronize";
61
+ zWarning = mprintf("WARNING: Pushing to \"%s\" is enabled.",
62
+ g.urlCanonical);
63
+ }
64
+ if( P("sync") ){
65
+ user_select();
66
+ url_enable_proxy(0);
67
+ client_sync(syncFlags, 0, 0);
68
+ }
69
+ @ <p>Press the %h(zButton) button below to synchronize with the
70
+ @ "%h(g.urlCanonical)" repository now. This may be useful when
71
+ @ testing the various transfer scripts.</p>
72
+ @ <p>You can use the "http -async" command in your scripts, but
73
+ @ make sure the "th1-uri-regexp" setting is set first.</p>
74
+ if( zWarning ){
75
+ @
76
+ @ <big><b>%h(zWarning)</b></big>
77
+ free(zWarning);
78
+ }
79
+ @
80
+ @ <blockquote>
81
+ @ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><div>
82
+ login_insert_csrf_secret();
83
+ @ <input type="submit" name="sync" value="%h(zButton)" />
84
+ @ </div></form>
85
+ @ </blockquote>
86
+ @
87
+ }
88
+
4289
style_footer();
4390
}
4491
4592
/*
4693
** Common implementation for the transfer setup editor pages.
@@ -140,9 +187,49 @@
140187
"Transfer Push Script",
141188
"xfer-push-script",
142189
zDefaultXferPush,
143190
zDesc,
144191
0,
192
+ 0,
193
+ 30
194
+ );
195
+}
196
+
197
+static const char *zDefaultXferCommit = 0;
198
+
199
+/*
200
+** WEBPAGE: xfersetup_commit
201
+*/
202
+void xfersetup_commit_page(void){
203
+ static const char zDesc[] =
204
+ @ Enter TH1 script that runs when a commit is processed.
205
+ ;
206
+ xfersetup_generic(
207
+ "Transfer Commit Script",
208
+ "xfer-commit-script",
209
+ zDefaultXferCommit,
210
+ zDesc,
211
+ 0,
212
+ 0,
213
+ 30
214
+ );
215
+}
216
+
217
+static const char *zDefaultXferTicket = 0;
218
+
219
+/*
220
+** WEBPAGE: xfersetup_ticket
221
+*/
222
+void xfersetup_ticket_page(void){
223
+ static const char zDesc[] =
224
+ @ Enter TH1 script that runs when a ticket change is processed.
225
+ ;
226
+ xfersetup_generic(
227
+ "Transfer Ticket Script",
228
+ "xfer-ticket-script",
229
+ zDefaultXferTicket,
230
+ zDesc,
231
+ 0,
145232
0,
146233
30
147234
);
148235
}
149236
--- src/xfersetup.c
+++ src/xfersetup.c
@@ -31,16 +31,63 @@
31 if( !g.perm.Setup ){
32 login_needed();
33 }
34
35 style_header("Transfer Setup");
 
36 @ <table border="0" cellspacing="20">
37 setup_menu_entry("Common", "xfersetup_com",
38 "Common TH1 code run before all transfer request processing.");
39 setup_menu_entry("Push", "xfersetup_push",
40 "Specific TH1 code to run after \"push\" transfer requests.");
 
 
 
 
41 @ </table>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42 style_footer();
43 }
44
45 /*
46 ** Common implementation for the transfer setup editor pages.
@@ -140,9 +187,49 @@
140 "Transfer Push Script",
141 "xfer-push-script",
142 zDefaultXferPush,
143 zDesc,
144 0,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145 0,
146 30
147 );
148 }
149
--- src/xfersetup.c
+++ src/xfersetup.c
@@ -31,16 +31,63 @@
31 if( !g.perm.Setup ){
32 login_needed();
33 }
34
35 style_header("Transfer Setup");
36
37 @ <table border="0" cellspacing="20">
38 setup_menu_entry("Common", "xfersetup_com",
39 "Common TH1 code run before all transfer request processing.");
40 setup_menu_entry("Push", "xfersetup_push",
41 "Specific TH1 code to run after \"push\" transfer requests.");
42 setup_menu_entry("Commit", "xfersetup_commit",
43 "Specific TH1 code to run after processing a commit.");
44 setup_menu_entry("Ticket", "xfersetup_ticket",
45 "Specific TH1 code to run after processing a ticket change.");
46 @ </table>
47
48 url_parse(0, 0);
49 if( g.urlProtocol ){
50 unsigned syncFlags;
51 const char *zButton;
52 char *zWarning;
53
54 if( db_get_boolean("dont-push", 0) ){
55 syncFlags = SYNC_PULL;
56 zButton = "Pull";
57 zWarning = 0;
58 }else{
59 syncFlags = SYNC_PUSH | SYNC_PULL;
60 zButton = "Synchronize";
61 zWarning = mprintf("WARNING: Pushing to \"%s\" is enabled.",
62 g.urlCanonical);
63 }
64 if( P("sync") ){
65 user_select();
66 url_enable_proxy(0);
67 client_sync(syncFlags, 0, 0);
68 }
69 @ <p>Press the %h(zButton) button below to synchronize with the
70 @ "%h(g.urlCanonical)" repository now. This may be useful when
71 @ testing the various transfer scripts.</p>
72 @ <p>You can use the "http -async" command in your scripts, but
73 @ make sure the "th1-uri-regexp" setting is set first.</p>
74 if( zWarning ){
75 @
76 @ <big><b>%h(zWarning)</b></big>
77 free(zWarning);
78 }
79 @
80 @ <blockquote>
81 @ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><div>
82 login_insert_csrf_secret();
83 @ <input type="submit" name="sync" value="%h(zButton)" />
84 @ </div></form>
85 @ </blockquote>
86 @
87 }
88
89 style_footer();
90 }
91
92 /*
93 ** Common implementation for the transfer setup editor pages.
@@ -140,9 +187,49 @@
187 "Transfer Push Script",
188 "xfer-push-script",
189 zDefaultXferPush,
190 zDesc,
191 0,
192 0,
193 30
194 );
195 }
196
197 static const char *zDefaultXferCommit = 0;
198
199 /*
200 ** WEBPAGE: xfersetup_commit
201 */
202 void xfersetup_commit_page(void){
203 static const char zDesc[] =
204 @ Enter TH1 script that runs when a commit is processed.
205 ;
206 xfersetup_generic(
207 "Transfer Commit Script",
208 "xfer-commit-script",
209 zDefaultXferCommit,
210 zDesc,
211 0,
212 0,
213 30
214 );
215 }
216
217 static const char *zDefaultXferTicket = 0;
218
219 /*
220 ** WEBPAGE: xfersetup_ticket
221 */
222 void xfersetup_ticket_page(void){
223 static const char zDesc[] =
224 @ Enter TH1 script that runs when a ticket change is processed.
225 ;
226 xfersetup_generic(
227 "Transfer Ticket Script",
228 "xfer-ticket-script",
229 zDefaultXferTicket,
230 zDesc,
231 0,
232 0,
233 30
234 );
235 }
236

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button