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.
Commit
b708acd2b8db0e36e81468b1d40f7c9019189ff1e552e2c05407871b5f70ebce
Parent
9b686daeeda7b4c…
1 file changed
+64
-12
+64
-12
| --- src/http_transport.c | ||
| +++ src/http_transport.c | ||
| @@ -36,12 +36,13 @@ | ||
| 36 | 36 | i64 nRcvd; /* Number of bytes received */ |
| 37 | 37 | FILE *pFile; /* File I/O for FILE: */ |
| 38 | 38 | char *zOutFile; /* Name of outbound file for FILE: */ |
| 39 | 39 | char *zInFile; /* Name of inbound file for FILE: */ |
| 40 | 40 | FILE *pLog; /* Log output here */ |
| 41 | + int bTest; /* file:// testing */ | |
| 41 | 42 | } 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 | |
| 43 | 44 | }; |
| 44 | 45 | |
| 45 | 46 | /* |
| 46 | 47 | ** Information about the connection to the SSH subprocess when |
| 47 | 48 | ** using the ssh:// sync method. |
| @@ -162,19 +163,42 @@ | ||
| 162 | 163 | socket_set_errmsg("cannot start ssh tunnel using [%b]", &zCmd); |
| 163 | 164 | } |
| 164 | 165 | blob_reset(&zCmd); |
| 165 | 166 | return sshPid==0; |
| 166 | 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 | +} | |
| 167 | 176 | |
| 168 | 177 | /* |
| 169 | 178 | ** Open a connection to the server. The server is defined by the following |
| 170 | 179 | ** variables: |
| 171 | 180 | ** |
| 172 | 181 | ** pUrlData->name Name of the server. Ex: fossil-scm.org |
| 182 | +** | |
| 173 | 183 | ** pUrlData->port TCP/IP port. Ex: 80 |
| 184 | +** | |
| 174 | 185 | ** pUrlData->isHttps Use TLS for the connection |
| 175 | 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 | +** | |
| 176 | 200 | ** Return the number of errors. |
| 177 | 201 | */ |
| 178 | 202 | int transport_open(UrlData *pUrlData){ |
| 179 | 203 | int rc = 0; |
| 180 | 204 | if( transport.isOpen==0 ){ |
| @@ -188,15 +212,30 @@ | ||
| 188 | 212 | #else |
| 189 | 213 | socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support"); |
| 190 | 214 | rc = 1; |
| 191 | 215 | #endif |
| 192 | 216 | }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); | |
| 195 | 219 | } |
| 196 | 220 | transport.zOutFile = fossil_temp_filename(); |
| 197 | 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 | + } | |
| 198 | 237 | transport.pFile = fossil_fopen(transport.zOutFile, "wb"); |
| 199 | 238 | if( transport.pFile==0 ){ |
| 200 | 239 | fossil_fatal("cannot output temporary file: %s", transport.zOutFile); |
| 201 | 240 | } |
| 202 | 241 | transport.isOpen = 1; |
| @@ -231,12 +270,14 @@ | ||
| 231 | 270 | }else if( pUrlData->isFile ){ |
| 232 | 271 | if( transport.pFile ){ |
| 233 | 272 | fclose(transport.pFile); |
| 234 | 273 | transport.pFile = 0; |
| 235 | 274 | } |
| 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 | + } | |
| 238 | 279 | sqlite3_free(transport.zInFile); |
| 239 | 280 | sqlite3_free(transport.zOutFile); |
| 240 | 281 | }else{ |
| 241 | 282 | socket_close(); |
| 242 | 283 | } |
| @@ -278,23 +319,34 @@ | ||
| 278 | 319 | } |
| 279 | 320 | |
| 280 | 321 | /* |
| 281 | 322 | ** This routine is called when the outbound message is complete and |
| 282 | 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. | |
| 283 | 330 | */ |
| 284 | 331 | void transport_flip(UrlData *pUrlData){ |
| 285 | 332 | if( pUrlData->isFile ){ |
| 286 | 333 | char *zCmd; |
| 287 | 334 | 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 | + } | |
| 295 | 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 | + } | |
| 296 | 348 | } |
| 297 | 349 | } |
| 298 | 350 | |
| 299 | 351 | /* |
| 300 | 352 | ** Log all input to a file. The transport layer will take responsibility |
| 301 | 353 |
| --- 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 |