Fossil SCM

Enhance the transport layer such that on an HTTP request to file:// where the pathname ends with ".http-test" and a file with that pathname exists in the local filesystem, then the content of that file becomes the HTTP reply. Used for testing.

drh 2026-07-25 12:57 UTC http1-1-chunked
Commit b708acd2b8db0e36e81468b1d40f7c9019189ff1e552e2c05407871b5f70ebce
1 file changed +64 -12
--- 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