Fossil SCM

When fossil is behaving as an HTTP client, it always uses HTTP/1.1 and it is able to understand the "chunked" transfer-encoding. Also add support for URI values of the form "file://NAME.http-test" which read the HTTP reply text out of the file "NAME.http-test" in the local filesystem, for testing purposes.

drh 2026-07-25 13:00 UTC trunk merge
Commit c3bd7c3a0edff89a47350dad9b98c5a50035a82eedc9f0e3f22651a9d1ed831d
2 files changed +73 -2 +64 -12
+73 -2
--- src/http.c
+++ src/http.c
@@ -151,11 +151,11 @@
151151
}else if( g.url.path==0 || g.url.path[0]==0 ){
152152
zPath = "/";
153153
}else{
154154
zPath = g.url.path;
155155
}
156
- blob_appendf(pHdr, "%s %s HTTP/1.0\r\n",
156
+ blob_appendf(pHdr, "%s %s HTTP/1.1\r\n",
157157
nPayload>0 ? "POST" : "GET", zPath);
158158
if( g.url.proxyAuth ){
159159
blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth);
160160
}
161161
if( g.zHttpAuth && g.zHttpAuth[0] ){
@@ -163,10 +163,11 @@
163163
char *zEncoded = encode64(zCredentials, -1);
164164
blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded);
165165
fossil_free(zEncoded);
166166
}
167167
blob_appendf(pHdr, "Host: %s\r\n", g.url.hostname);
168
+ blob_appendf(pHdr, "Connection: close\r\n");
168169
blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent());
169170
if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n");
170171
if( g.syncInfo.fLoginCardMode>0
171172
&& nPayload>0 && pLogin && blob_size(pLogin) ){
172173
/* Add sync login card via a transient cookie. We can only do this
@@ -455,10 +456,11 @@
455456
int iHttpVersion; /* Which version of HTTP protocol server uses */
456457
char *zLine; /* A single line of the reply header */
457458
int i; /* Loop counter */
458459
int isError = 0; /* True if the reply is an error message */
459460
int isCompressed = 1; /* True if the reply is compressed */
461
+ int isChunked = 0; /* True if Transfer-Encoding: chunked */
460462
461463
if( g.zHttpCmd!=0 ){
462464
/* Handle the --transport-command option for "fossil sync" and similar */
463465
return http_exchange_external(pSend,pReply,mHttpFlags,zAltMimetype);
464466
}
@@ -605,10 +607,17 @@
605607
if( iHttpVersion<0 ) iHttpVersion = 1;
606608
closeConnection = 0;
607609
}else if( fossil_strnicmp(zLine, "content-length:", 15)==0 ){
608610
for(i=15; fossil_isspace(zLine[i]); i++){}
609611
iLength = atoi(&zLine[i]);
612
+ }else if( fossil_strnicmp(zLine, "transfer-encoding:", 18)==0 ){
613
+ /* RFC 7230: "chunked" must be the final transfer-coding so only
614
+ ** match when it appears at the end of the line. */
615
+ if( sqlite3_strlike("%chunked", &zLine[18], 0)==0 ){
616
+ size_t nx = strlen(&zLine[18]);
617
+ if( !fossil_isalnum(zLine[nx+10]) ) isChunked = 1;
618
+ }
610619
}else if( fossil_strnicmp(zLine, "connection:", 11)==0 ){
611620
if( sqlite3_strlike("%close%", &zLine[11], 0)==0 ){
612621
closeConnection = 1;
613622
}else if( sqlite3_strlike("%keep-alive%", &zLine[11], 0)==0 ){
614623
closeConnection = 0;
@@ -735,11 +744,73 @@
735744
736745
/*
737746
** Extract the reply payload that follows the header
738747
*/
739748
blob_zero(pReply);
740
- if( iLength==0 ){
749
+ if( isChunked ){
750
+ /* Decode an HTTP/1.1 "Transfer-Encoding: chunked" reply body. Each
751
+ ** chunk is a hex length on its own line (optionally followed by a
752
+ ** ";extension" that is ignored), then that many payload bytes, then a
753
+ ** bare CRLF. A zero-length chunk terminates the body, after which any
754
+ ** trailer header lines are read and discarded up to the blank line. */
755
+ char *zChunk;
756
+ int sawTerminator = 0; /* True once the 0-length chunk is seen */
757
+ while( (zChunk = transport_receive_line(&g.url))!=0 && zChunk[0]!=0 ){
758
+ i64 nChunk; /* Size of this chunk in bytes (wide, unclamped) */
759
+ i64 nPrior; /* Bytes already in pReply (matches blob nUsed) */
760
+ char *zEnd = 0; /* End of the hex digits actually parsed */
761
+ while( fossil_isspace(zChunk[0]) ) zChunk++;
762
+ nChunk = strtoll(zChunk, &zEnd, 16);
763
+ if( zEnd==zChunk ){
764
+ /* No hex digit consumed: a blank or malformed chunk-size line, which
765
+ ** is the symptom of a connection that closed mid-stream. Treat it as
766
+ ** a truncated (failed) */
767
+ fossil_warning("chunked reply: missing or malformed chunk size");
768
+ goto write_err;
769
+ }
770
+ if( nChunk<0 || nChunk>0x7fffffff ){
771
+ /* Negative, or larger than we will ever accept in one chunk. */
772
+ fossil_warning("chunked reply: invalid chunk size");
773
+ goto write_err;
774
+ }
775
+ if( nChunk==0 ){
776
+ /* Final chunk: consume trailer lines up to the terminating blank. */
777
+ sawTerminator = 1;
778
+ while( (zChunk = transport_receive_line(&g.url))!=0 && zChunk[0]!=0 ){}
779
+ break;
780
+ }
781
+ nPrior = blob_size(pReply);
782
+ /* Reserve space without advancing nUsed, so that on error
783
+ ** the blob's reported size equals the bytes actually
784
+ ** received rather the claimed chunk length */
785
+ blob_reserve(pReply, (u64)(nPrior+nChunk));
786
+ {
787
+ unsigned int nRemaining = (unsigned int)nChunk;
788
+ /* transport_receive() may return short; loop until the chunk is
789
+ ** full. */
790
+ while( nRemaining>0 ){
791
+ int nGot;
792
+ nGot = transport_receive(&g.url, &pReply->aData[nPrior], nRemaining);
793
+ if( nGot<=0 ){
794
+ fossil_warning("chunked reply truncated");
795
+ goto write_err;
796
+ }
797
+ nPrior += nGot;
798
+ nRemaining -= nGot;
799
+ pReply->nUsed = (unsigned int)nPrior;
800
+ }
801
+ }
802
+ transport_receive_line(&g.url); /* CRLF that follows the chunk data */
803
+ }
804
+ if( !sawTerminator ){
805
+ /* The loop exited without ever seeing the 0-length terminator chunk,
806
+ ** meaning the peer closed before the body was complete. A truncated
807
+ ** sync must be an error, never silent success. */
808
+ fossil_warning("chunked reply ended without terminator");
809
+ goto write_err;
810
+ }
811
+ }else if( iLength==0 ){
741812
/* No content to read */
742813
}else if( iLength>0 ){
743814
/* Read content of a known length */
744815
int iRecvLen; /* Received length of the reply payload */
745816
blob_resize(pReply, iLength);
746817
--- src/http.c
+++ src/http.c
@@ -151,11 +151,11 @@
151 }else if( g.url.path==0 || g.url.path[0]==0 ){
152 zPath = "/";
153 }else{
154 zPath = g.url.path;
155 }
156 blob_appendf(pHdr, "%s %s HTTP/1.0\r\n",
157 nPayload>0 ? "POST" : "GET", zPath);
158 if( g.url.proxyAuth ){
159 blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth);
160 }
161 if( g.zHttpAuth && g.zHttpAuth[0] ){
@@ -163,10 +163,11 @@
163 char *zEncoded = encode64(zCredentials, -1);
164 blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded);
165 fossil_free(zEncoded);
166 }
167 blob_appendf(pHdr, "Host: %s\r\n", g.url.hostname);
 
168 blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent());
169 if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n");
170 if( g.syncInfo.fLoginCardMode>0
171 && nPayload>0 && pLogin && blob_size(pLogin) ){
172 /* Add sync login card via a transient cookie. We can only do this
@@ -455,10 +456,11 @@
455 int iHttpVersion; /* Which version of HTTP protocol server uses */
456 char *zLine; /* A single line of the reply header */
457 int i; /* Loop counter */
458 int isError = 0; /* True if the reply is an error message */
459 int isCompressed = 1; /* True if the reply is compressed */
 
460
461 if( g.zHttpCmd!=0 ){
462 /* Handle the --transport-command option for "fossil sync" and similar */
463 return http_exchange_external(pSend,pReply,mHttpFlags,zAltMimetype);
464 }
@@ -605,10 +607,17 @@
605 if( iHttpVersion<0 ) iHttpVersion = 1;
606 closeConnection = 0;
607 }else if( fossil_strnicmp(zLine, "content-length:", 15)==0 ){
608 for(i=15; fossil_isspace(zLine[i]); i++){}
609 iLength = atoi(&zLine[i]);
 
 
 
 
 
 
 
610 }else if( fossil_strnicmp(zLine, "connection:", 11)==0 ){
611 if( sqlite3_strlike("%close%", &zLine[11], 0)==0 ){
612 closeConnection = 1;
613 }else if( sqlite3_strlike("%keep-alive%", &zLine[11], 0)==0 ){
614 closeConnection = 0;
@@ -735,11 +744,73 @@
735
736 /*
737 ** Extract the reply payload that follows the header
738 */
739 blob_zero(pReply);
740 if( iLength==0 ){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
741 /* No content to read */
742 }else if( iLength>0 ){
743 /* Read content of a known length */
744 int iRecvLen; /* Received length of the reply payload */
745 blob_resize(pReply, iLength);
746
--- src/http.c
+++ src/http.c
@@ -151,11 +151,11 @@
151 }else if( g.url.path==0 || g.url.path[0]==0 ){
152 zPath = "/";
153 }else{
154 zPath = g.url.path;
155 }
156 blob_appendf(pHdr, "%s %s HTTP/1.1\r\n",
157 nPayload>0 ? "POST" : "GET", zPath);
158 if( g.url.proxyAuth ){
159 blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth);
160 }
161 if( g.zHttpAuth && g.zHttpAuth[0] ){
@@ -163,10 +163,11 @@
163 char *zEncoded = encode64(zCredentials, -1);
164 blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded);
165 fossil_free(zEncoded);
166 }
167 blob_appendf(pHdr, "Host: %s\r\n", g.url.hostname);
168 blob_appendf(pHdr, "Connection: close\r\n");
169 blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent());
170 if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n");
171 if( g.syncInfo.fLoginCardMode>0
172 && nPayload>0 && pLogin && blob_size(pLogin) ){
173 /* Add sync login card via a transient cookie. We can only do this
@@ -455,10 +456,11 @@
456 int iHttpVersion; /* Which version of HTTP protocol server uses */
457 char *zLine; /* A single line of the reply header */
458 int i; /* Loop counter */
459 int isError = 0; /* True if the reply is an error message */
460 int isCompressed = 1; /* True if the reply is compressed */
461 int isChunked = 0; /* True if Transfer-Encoding: chunked */
462
463 if( g.zHttpCmd!=0 ){
464 /* Handle the --transport-command option for "fossil sync" and similar */
465 return http_exchange_external(pSend,pReply,mHttpFlags,zAltMimetype);
466 }
@@ -605,10 +607,17 @@
607 if( iHttpVersion<0 ) iHttpVersion = 1;
608 closeConnection = 0;
609 }else if( fossil_strnicmp(zLine, "content-length:", 15)==0 ){
610 for(i=15; fossil_isspace(zLine[i]); i++){}
611 iLength = atoi(&zLine[i]);
612 }else if( fossil_strnicmp(zLine, "transfer-encoding:", 18)==0 ){
613 /* RFC 7230: "chunked" must be the final transfer-coding so only
614 ** match when it appears at the end of the line. */
615 if( sqlite3_strlike("%chunked", &zLine[18], 0)==0 ){
616 size_t nx = strlen(&zLine[18]);
617 if( !fossil_isalnum(zLine[nx+10]) ) isChunked = 1;
618 }
619 }else if( fossil_strnicmp(zLine, "connection:", 11)==0 ){
620 if( sqlite3_strlike("%close%", &zLine[11], 0)==0 ){
621 closeConnection = 1;
622 }else if( sqlite3_strlike("%keep-alive%", &zLine[11], 0)==0 ){
623 closeConnection = 0;
@@ -735,11 +744,73 @@
744
745 /*
746 ** Extract the reply payload that follows the header
747 */
748 blob_zero(pReply);
749 if( isChunked ){
750 /* Decode an HTTP/1.1 "Transfer-Encoding: chunked" reply body. Each
751 ** chunk is a hex length on its own line (optionally followed by a
752 ** ";extension" that is ignored), then that many payload bytes, then a
753 ** bare CRLF. A zero-length chunk terminates the body, after which any
754 ** trailer header lines are read and discarded up to the blank line. */
755 char *zChunk;
756 int sawTerminator = 0; /* True once the 0-length chunk is seen */
757 while( (zChunk = transport_receive_line(&g.url))!=0 && zChunk[0]!=0 ){
758 i64 nChunk; /* Size of this chunk in bytes (wide, unclamped) */
759 i64 nPrior; /* Bytes already in pReply (matches blob nUsed) */
760 char *zEnd = 0; /* End of the hex digits actually parsed */
761 while( fossil_isspace(zChunk[0]) ) zChunk++;
762 nChunk = strtoll(zChunk, &zEnd, 16);
763 if( zEnd==zChunk ){
764 /* No hex digit consumed: a blank or malformed chunk-size line, which
765 ** is the symptom of a connection that closed mid-stream. Treat it as
766 ** a truncated (failed) */
767 fossil_warning("chunked reply: missing or malformed chunk size");
768 goto write_err;
769 }
770 if( nChunk<0 || nChunk>0x7fffffff ){
771 /* Negative, or larger than we will ever accept in one chunk. */
772 fossil_warning("chunked reply: invalid chunk size");
773 goto write_err;
774 }
775 if( nChunk==0 ){
776 /* Final chunk: consume trailer lines up to the terminating blank. */
777 sawTerminator = 1;
778 while( (zChunk = transport_receive_line(&g.url))!=0 && zChunk[0]!=0 ){}
779 break;
780 }
781 nPrior = blob_size(pReply);
782 /* Reserve space without advancing nUsed, so that on error
783 ** the blob's reported size equals the bytes actually
784 ** received rather the claimed chunk length */
785 blob_reserve(pReply, (u64)(nPrior+nChunk));
786 {
787 unsigned int nRemaining = (unsigned int)nChunk;
788 /* transport_receive() may return short; loop until the chunk is
789 ** full. */
790 while( nRemaining>0 ){
791 int nGot;
792 nGot = transport_receive(&g.url, &pReply->aData[nPrior], nRemaining);
793 if( nGot<=0 ){
794 fossil_warning("chunked reply truncated");
795 goto write_err;
796 }
797 nPrior += nGot;
798 nRemaining -= nGot;
799 pReply->nUsed = (unsigned int)nPrior;
800 }
801 }
802 transport_receive_line(&g.url); /* CRLF that follows the chunk data */
803 }
804 if( !sawTerminator ){
805 /* The loop exited without ever seeing the 0-length terminator chunk,
806 ** meaning the peer closed before the body was complete. A truncated
807 ** sync must be an error, never silent success. */
808 fossil_warning("chunked reply ended without terminator");
809 goto write_err;
810 }
811 }else if( iLength==0 ){
812 /* No content to read */
813 }else if( iLength>0 ){
814 /* Read content of a known length */
815 int iRecvLen; /* Received length of the reply payload */
816 blob_resize(pReply, iLength);
817
--- src/http_transport.c
+++ src/http_transport.c
@@ -36,12 +36,13 @@
3636
i64 nRcvd; /* Number of bytes received */
3737
FILE *pFile; /* File I/O for FILE: */
3838
char *zOutFile; /* Name of outbound file for FILE: */
3939
char *zInFile; /* Name of inbound file for FILE: */
4040
FILE *pLog; /* Log output here */
41
+ int bTest; /* file:// testing */
4142
} transport = {
42
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
43
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
4344
};
4445
4546
/*
4647
** Information about the connection to the SSH subprocess when
4748
** using the ssh:// sync method.
@@ -162,19 +163,42 @@
162163
socket_set_errmsg("cannot start ssh tunnel using [%b]", &zCmd);
163164
}
164165
blob_reset(&zCmd);
165166
return sshPid==0;
166167
}
168
+
169
+/*
170
+** Return true if the filename given matches the pattern that means
171
+** it is a test-reply.
172
+*/
173
+static int transport_is_test(const char *zFilename){
174
+ return sqlite3_strglob("*.http-test",zFilename)==0;
175
+}
167176
168177
/*
169178
** Open a connection to the server. The server is defined by the following
170179
** variables:
171180
**
172181
** pUrlData->name Name of the server. Ex: fossil-scm.org
182
+**
173183
** pUrlData->port TCP/IP port. Ex: 80
184
+**
174185
** pUrlData->isHttps Use TLS for the connection
175186
**
187
+** pUrlData->isSsh Use an SSH connection to the server. name is
188
+** the SSH host name. subpath is the server
189
+** method name (ex: timeline)
190
+**
191
+** pUrlData->isFile The .name is a filename in the local filesystem.
192
+** If that file is a Fossil repo, then launch
193
+** "fossil http" as a subprocess to handle the request.
194
+** Or if filename ends with ".test-http" then the
195
+** content of that file becomes the HTTP reply.
196
+** (The latter is used for testing only.)
197
+**
198
+** pUrlData->subpath The method name on the request for SSH and FILE
199
+**
176200
** Return the number of errors.
177201
*/
178202
int transport_open(UrlData *pUrlData){
179203
int rc = 0;
180204
if( transport.isOpen==0 ){
@@ -188,15 +212,30 @@
188212
#else
189213
socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support");
190214
rc = 1;
191215
#endif
192216
}else if( pUrlData->isFile ){
193
- if( !db_looks_like_a_repository(pUrlData->name) ){
194
- fossil_fatal("not a fossil repository: \"%s\"", pUrlData->name);
217
+ if( !file_isfile(pUrlData->name, ExtFILE) ){
218
+ fossil_fatal("no such file: \"%s\"", pUrlData->name);
195219
}
196220
transport.zOutFile = fossil_temp_filename();
197221
transport.zInFile = fossil_temp_filename();
222
+ if( !db_looks_like_a_repository(pUrlData->name) ){
223
+ if( transport_is_test(pUrlData->name) ){
224
+ /* If the URI filename ends with ".http-test" then the content
225
+ ** of that file becomes the HTTP reply and the request is stored
226
+ ** in a file with the same name but with ".request" added to the
227
+ ** end. Used for testing only */
228
+ transport.bTest = 1;
229
+ sqlite3_free(transport.zOutFile);
230
+ transport.zOutFile = sqlite3_mprintf("%s.request",pUrlData->name);
231
+ sqlite3_free(transport.zInFile);
232
+ transport.zInFile = sqlite3_mprintf("%s",pUrlData->name);
233
+ }else{
234
+ fossil_fatal("not a fossil repository: \"%s\"", pUrlData->name);
235
+ }
236
+ }
198237
transport.pFile = fossil_fopen(transport.zOutFile, "wb");
199238
if( transport.pFile==0 ){
200239
fossil_fatal("cannot output temporary file: %s", transport.zOutFile);
201240
}
202241
transport.isOpen = 1;
@@ -231,12 +270,14 @@
231270
}else if( pUrlData->isFile ){
232271
if( transport.pFile ){
233272
fclose(transport.pFile);
234273
transport.pFile = 0;
235274
}
236
- file_delete(transport.zInFile);
237
- file_delete(transport.zOutFile);
275
+ if( !transport.bTest ){
276
+ file_delete(transport.zInFile);
277
+ file_delete(transport.zOutFile);
278
+ }
238279
sqlite3_free(transport.zInFile);
239280
sqlite3_free(transport.zOutFile);
240281
}else{
241282
socket_close();
242283
}
@@ -278,23 +319,34 @@
278319
}
279320
280321
/*
281322
** This routine is called when the outbound message is complete and
282323
** it is time to begin receiving a reply.
324
+**
325
+** This is a no-op for http:, https:, and ssh:. It only does this
326
+** for file:. If the file is a Fossil database, then an instance of
327
+** "fossil http" is invoked in a subprocess to handle the request.
328
+** Or if the filename ends with ".http-test" then the text of that
329
+** file becomes the reply. The ".http-test" hack is for testing purposes.
283330
*/
284331
void transport_flip(UrlData *pUrlData){
285332
if( pUrlData->isFile ){
286333
char *zCmd;
287334
fclose(transport.pFile);
288
- zCmd = mprintf("%$ http --in %$ --out %$ --ipaddr 127.0.0.1"
289
- " %$ --localauth",
290
- g.nameOfExe, transport.zOutFile, transport.zInFile, pUrlData->name
291
- );
292
- if( g.fHttpTrace ) fossil_print("RUN %s\n", zCmd);
293
- fossil_system(zCmd);
294
- free(zCmd);
335
+ if( !transport.bTest ){
336
+ zCmd = mprintf("%$ http --in %$ --out %$ --ipaddr 127.0.0.1"
337
+ " %$ --localauth",
338
+ g.nameOfExe, transport.zOutFile, transport.zInFile, pUrlData->name
339
+ );
340
+ if( g.fHttpTrace ) fossil_print("RUN %s\n", zCmd);
341
+ fossil_system(zCmd);
342
+ free(zCmd);
343
+ }
295344
transport.pFile = fossil_fopen(transport.zInFile, "rb");
345
+ if( transport.pFile==0 ){
346
+ fossil_fatal("unable to open HTTP reply: \"%s\"",transport.zInFile);
347
+ }
296348
}
297349
}
298350
299351
/*
300352
** Log all input to a file. The transport layer will take responsibility
301353
--- src/http_transport.c
+++ src/http_transport.c
@@ -36,12 +36,13 @@
36 i64 nRcvd; /* Number of bytes received */
37 FILE *pFile; /* File I/O for FILE: */
38 char *zOutFile; /* Name of outbound file for FILE: */
39 char *zInFile; /* Name of inbound file for FILE: */
40 FILE *pLog; /* Log output here */
 
41 } transport = {
42 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
43 };
44
45 /*
46 ** Information about the connection to the SSH subprocess when
47 ** using the ssh:// sync method.
@@ -162,19 +163,42 @@
162 socket_set_errmsg("cannot start ssh tunnel using [%b]", &zCmd);
163 }
164 blob_reset(&zCmd);
165 return sshPid==0;
166 }
 
 
 
 
 
 
 
 
167
168 /*
169 ** Open a connection to the server. The server is defined by the following
170 ** variables:
171 **
172 ** pUrlData->name Name of the server. Ex: fossil-scm.org
 
173 ** pUrlData->port TCP/IP port. Ex: 80
 
174 ** pUrlData->isHttps Use TLS for the connection
175 **
 
 
 
 
 
 
 
 
 
 
 
 
 
176 ** Return the number of errors.
177 */
178 int transport_open(UrlData *pUrlData){
179 int rc = 0;
180 if( transport.isOpen==0 ){
@@ -188,15 +212,30 @@
188 #else
189 socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support");
190 rc = 1;
191 #endif
192 }else if( pUrlData->isFile ){
193 if( !db_looks_like_a_repository(pUrlData->name) ){
194 fossil_fatal("not a fossil repository: \"%s\"", pUrlData->name);
195 }
196 transport.zOutFile = fossil_temp_filename();
197 transport.zInFile = fossil_temp_filename();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198 transport.pFile = fossil_fopen(transport.zOutFile, "wb");
199 if( transport.pFile==0 ){
200 fossil_fatal("cannot output temporary file: %s", transport.zOutFile);
201 }
202 transport.isOpen = 1;
@@ -231,12 +270,14 @@
231 }else if( pUrlData->isFile ){
232 if( transport.pFile ){
233 fclose(transport.pFile);
234 transport.pFile = 0;
235 }
236 file_delete(transport.zInFile);
237 file_delete(transport.zOutFile);
 
 
238 sqlite3_free(transport.zInFile);
239 sqlite3_free(transport.zOutFile);
240 }else{
241 socket_close();
242 }
@@ -278,23 +319,34 @@
278 }
279
280 /*
281 ** This routine is called when the outbound message is complete and
282 ** it is time to begin receiving a reply.
 
 
 
 
 
 
283 */
284 void transport_flip(UrlData *pUrlData){
285 if( pUrlData->isFile ){
286 char *zCmd;
287 fclose(transport.pFile);
288 zCmd = mprintf("%$ http --in %$ --out %$ --ipaddr 127.0.0.1"
289 " %$ --localauth",
290 g.nameOfExe, transport.zOutFile, transport.zInFile, pUrlData->name
291 );
292 if( g.fHttpTrace ) fossil_print("RUN %s\n", zCmd);
293 fossil_system(zCmd);
294 free(zCmd);
 
 
295 transport.pFile = fossil_fopen(transport.zInFile, "rb");
 
 
 
296 }
297 }
298
299 /*
300 ** Log all input to a file. The transport layer will take responsibility
301
--- src/http_transport.c
+++ src/http_transport.c
@@ -36,12 +36,13 @@
36 i64 nRcvd; /* Number of bytes received */
37 FILE *pFile; /* File I/O for FILE: */
38 char *zOutFile; /* Name of outbound file for FILE: */
39 char *zInFile; /* Name of inbound file for FILE: */
40 FILE *pLog; /* Log output here */
41 int bTest; /* file:// testing */
42 } transport = {
43 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
44 };
45
46 /*
47 ** Information about the connection to the SSH subprocess when
48 ** using the ssh:// sync method.
@@ -162,19 +163,42 @@
163 socket_set_errmsg("cannot start ssh tunnel using [%b]", &zCmd);
164 }
165 blob_reset(&zCmd);
166 return sshPid==0;
167 }
168
169 /*
170 ** Return true if the filename given matches the pattern that means
171 ** it is a test-reply.
172 */
173 static int transport_is_test(const char *zFilename){
174 return sqlite3_strglob("*.http-test",zFilename)==0;
175 }
176
177 /*
178 ** Open a connection to the server. The server is defined by the following
179 ** variables:
180 **
181 ** pUrlData->name Name of the server. Ex: fossil-scm.org
182 **
183 ** pUrlData->port TCP/IP port. Ex: 80
184 **
185 ** pUrlData->isHttps Use TLS for the connection
186 **
187 ** pUrlData->isSsh Use an SSH connection to the server. name is
188 ** the SSH host name. subpath is the server
189 ** method name (ex: timeline)
190 **
191 ** pUrlData->isFile The .name is a filename in the local filesystem.
192 ** If that file is a Fossil repo, then launch
193 ** "fossil http" as a subprocess to handle the request.
194 ** Or if filename ends with ".test-http" then the
195 ** content of that file becomes the HTTP reply.
196 ** (The latter is used for testing only.)
197 **
198 ** pUrlData->subpath The method name on the request for SSH and FILE
199 **
200 ** Return the number of errors.
201 */
202 int transport_open(UrlData *pUrlData){
203 int rc = 0;
204 if( transport.isOpen==0 ){
@@ -188,15 +212,30 @@
212 #else
213 socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support");
214 rc = 1;
215 #endif
216 }else if( pUrlData->isFile ){
217 if( !file_isfile(pUrlData->name, ExtFILE) ){
218 fossil_fatal("no such file: \"%s\"", pUrlData->name);
219 }
220 transport.zOutFile = fossil_temp_filename();
221 transport.zInFile = fossil_temp_filename();
222 if( !db_looks_like_a_repository(pUrlData->name) ){
223 if( transport_is_test(pUrlData->name) ){
224 /* If the URI filename ends with ".http-test" then the content
225 ** of that file becomes the HTTP reply and the request is stored
226 ** in a file with the same name but with ".request" added to the
227 ** end. Used for testing only */
228 transport.bTest = 1;
229 sqlite3_free(transport.zOutFile);
230 transport.zOutFile = sqlite3_mprintf("%s.request",pUrlData->name);
231 sqlite3_free(transport.zInFile);
232 transport.zInFile = sqlite3_mprintf("%s",pUrlData->name);
233 }else{
234 fossil_fatal("not a fossil repository: \"%s\"", pUrlData->name);
235 }
236 }
237 transport.pFile = fossil_fopen(transport.zOutFile, "wb");
238 if( transport.pFile==0 ){
239 fossil_fatal("cannot output temporary file: %s", transport.zOutFile);
240 }
241 transport.isOpen = 1;
@@ -231,12 +270,14 @@
270 }else if( pUrlData->isFile ){
271 if( transport.pFile ){
272 fclose(transport.pFile);
273 transport.pFile = 0;
274 }
275 if( !transport.bTest ){
276 file_delete(transport.zInFile);
277 file_delete(transport.zOutFile);
278 }
279 sqlite3_free(transport.zInFile);
280 sqlite3_free(transport.zOutFile);
281 }else{
282 socket_close();
283 }
@@ -278,23 +319,34 @@
319 }
320
321 /*
322 ** This routine is called when the outbound message is complete and
323 ** it is time to begin receiving a reply.
324 **
325 ** This is a no-op for http:, https:, and ssh:. It only does this
326 ** for file:. If the file is a Fossil database, then an instance of
327 ** "fossil http" is invoked in a subprocess to handle the request.
328 ** Or if the filename ends with ".http-test" then the text of that
329 ** file becomes the reply. The ".http-test" hack is for testing purposes.
330 */
331 void transport_flip(UrlData *pUrlData){
332 if( pUrlData->isFile ){
333 char *zCmd;
334 fclose(transport.pFile);
335 if( !transport.bTest ){
336 zCmd = mprintf("%$ http --in %$ --out %$ --ipaddr 127.0.0.1"
337 " %$ --localauth",
338 g.nameOfExe, transport.zOutFile, transport.zInFile, pUrlData->name
339 );
340 if( g.fHttpTrace ) fossil_print("RUN %s\n", zCmd);
341 fossil_system(zCmd);
342 free(zCmd);
343 }
344 transport.pFile = fossil_fopen(transport.zInFile, "rb");
345 if( transport.pFile==0 ){
346 fossil_fatal("unable to open HTTP reply: \"%s\"",transport.zInFile);
347 }
348 }
349 }
350
351 /*
352 ** Log all input to a file. The transport layer will take responsibility
353

Keyboard Shortcuts

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