Fossil SCM
Account for CGI-hosted fossil instances by sending the xfer login card as a URL argument. This is somewhat inelegant but works around their inability to read HTTP headers. This version is still more verbose than it needs to be, and requires more testing for compatibility with trunk fossil versions.
Commit
439af9348b5c1d05b8f338a991ce0699a67104385d9830d395e0c6bd5485efb3
Parent
6c900645ea5765e…
6 files changed
+26
-8
+41
-9
+1
-1
+11
-4
+2
-2
+4
-2
+26
-8
| --- src/cgi.c | ||
| +++ src/cgi.c | ||
| @@ -964,11 +964,11 @@ | ||
| 964 | 964 | ** * it is impossible for a cookie or query parameter to |
| 965 | 965 | ** override the value of an environment variable since |
| 966 | 966 | ** environment variables always have uppercase names. |
| 967 | 967 | ** |
| 968 | 968 | ** 2018-03-29: Also ignore the entry if NAME that contains any characters |
| 969 | -** other than [a-zA-Z0-9_]. There are no known exploits involving unusual | |
| 969 | +** other than [-a-zA-Z0-9_]. There are no known exploits involving unusual | |
| 970 | 970 | ** names that contain characters outside that set, but it never hurts to |
| 971 | 971 | ** be extra cautious when sanitizing inputs. |
| 972 | 972 | ** |
| 973 | 973 | ** Parameters are separated by the "terminator" character. Whitespace |
| 974 | 974 | ** before the NAME is ignored. |
| @@ -1280,35 +1280,52 @@ | ||
| 1280 | 1280 | |
| 1281 | 1281 | /* Forward declaration */ |
| 1282 | 1282 | static NORETURN void malformed_request(const char *zMsg, ...); |
| 1283 | 1283 | |
| 1284 | 1284 | /* |
| 1285 | -** Checks the QUERY_STRING environment variable, sets it up | |
| 1286 | -** via add_param_list() and, if found, applies its "skin" | |
| 1287 | -** setting. Returns 0 if no QUERY_STRING is set, 1 if it is, | |
| 1288 | -** and 2 if it sets the skin (in which case the cookie may | |
| 1289 | -** still need flushing by the page, via cookie_render()). | |
| 1285 | +** Checks the QUERY_STRING environment variable, sets it up via | |
| 1286 | +** add_param_list() and, if found, applies its "skin" setting. Returns | |
| 1287 | +** 0 if no QUERY_STRING is set, else it returns a bitmask of: | |
| 1288 | +** | |
| 1289 | +** 0x01 = QUERY_STRING was set. | |
| 1290 | +** 0x02 = "skin" argument was set and processed | |
| 1291 | +** 0x04 = "x-f-x-l" arg was processed. | |
| 1292 | +** | |
| 1293 | +* In the case of the skin, the cookie may still need flushing | |
| 1294 | +** by the page, via cookie_render(). | |
| 1290 | 1295 | */ |
| 1291 | 1296 | int cgi_setup_query_string(void){ |
| 1292 | 1297 | int rc = 0; |
| 1293 | 1298 | char * z = (char*)P("QUERY_STRING"); |
| 1294 | 1299 | if( z ){ |
| 1295 | - ++rc; | |
| 1300 | + rc = 0x01; | |
| 1296 | 1301 | z = fossil_strdup(z); |
| 1297 | 1302 | add_param_list(z, '&'); |
| 1298 | 1303 | z = (char*)P("skin"); |
| 1299 | 1304 | if( z ){ |
| 1300 | 1305 | char *zErr = skin_use_alternative(z, 2, SKIN_FROM_QPARAM); |
| 1301 | - ++rc; | |
| 1306 | + rc |= 0x02; | |
| 1302 | 1307 | if( !zErr && P("once")==0 ){ |
| 1303 | 1308 | cookie_write_parameter("skin","skin",z); |
| 1304 | 1309 | /* Per /chat discussion, passing ?skin=... without "once" |
| 1305 | 1310 | ** implies the "udc" argument, so we force that into the |
| 1306 | 1311 | ** environment here. */ |
| 1307 | 1312 | cgi_set_parameter_nocopy("udc", "1", 1); |
| 1308 | 1313 | } |
| 1309 | 1314 | fossil_free(zErr); |
| 1315 | + } | |
| 1316 | + if( !g.syncInfo.zLoginCard && 0!=(z=(char*)P("x-f-x-l")) ){ | |
| 1317 | + /* CGI fossil instances do not read the HTTP headers, so | |
| 1318 | + ** they cannot see the X-Fossil-Xfer-Login card. As a consolation | |
| 1319 | + ** to them, we'll accept that via this query argument. */ | |
| 1320 | + rc |= 0x04; | |
| 1321 | + fossil_free( g.syncInfo.zLoginCard ); | |
| 1322 | + g.syncInfo.zLoginCard = fossil_strdup(z); | |
| 1323 | + g.syncInfo.bLoginCardHeader = 3; | |
| 1324 | + /*cgi_delete_parameter("x-f-x-l");*/ | |
| 1325 | + /*fprintf(stderr, "query string setup: x-f-x-l=%s\n", | |
| 1326 | + g.syncInfo.zLoginCard);*/ | |
| 1310 | 1327 | } |
| 1311 | 1328 | } |
| 1312 | 1329 | return rc; |
| 1313 | 1330 | } |
| 1314 | 1331 | |
| @@ -2224,10 +2241,11 @@ | ||
| 2224 | 2241 | rangeStart = x1; |
| 2225 | 2242 | rangeEnd = x2+1; |
| 2226 | 2243 | } |
| 2227 | 2244 | }else if( fossil_strcmp(zFieldName, "x-fossil-xfer-login:")==0 ){ |
| 2228 | 2245 | /*cgi_setenv("FOSSIL_LCH_cgi_handle_http_request", zVal);*/ |
| 2246 | + fossil_free( g.syncInfo.zLoginCard ); | |
| 2229 | 2247 | g.syncInfo.zLoginCard = fossil_strdup(zVal); |
| 2230 | 2248 | g.syncInfo.bLoginCardHeader = 1; |
| 2231 | 2249 | } |
| 2232 | 2250 | } |
| 2233 | 2251 | cgi_setenv("REQUEST_SCHEME",zScheme); |
| 2234 | 2252 |
| --- src/cgi.c | |
| +++ src/cgi.c | |
| @@ -964,11 +964,11 @@ | |
| 964 | ** * it is impossible for a cookie or query parameter to |
| 965 | ** override the value of an environment variable since |
| 966 | ** environment variables always have uppercase names. |
| 967 | ** |
| 968 | ** 2018-03-29: Also ignore the entry if NAME that contains any characters |
| 969 | ** other than [a-zA-Z0-9_]. There are no known exploits involving unusual |
| 970 | ** names that contain characters outside that set, but it never hurts to |
| 971 | ** be extra cautious when sanitizing inputs. |
| 972 | ** |
| 973 | ** Parameters are separated by the "terminator" character. Whitespace |
| 974 | ** before the NAME is ignored. |
| @@ -1280,35 +1280,52 @@ | |
| 1280 | |
| 1281 | /* Forward declaration */ |
| 1282 | static NORETURN void malformed_request(const char *zMsg, ...); |
| 1283 | |
| 1284 | /* |
| 1285 | ** Checks the QUERY_STRING environment variable, sets it up |
| 1286 | ** via add_param_list() and, if found, applies its "skin" |
| 1287 | ** setting. Returns 0 if no QUERY_STRING is set, 1 if it is, |
| 1288 | ** and 2 if it sets the skin (in which case the cookie may |
| 1289 | ** still need flushing by the page, via cookie_render()). |
| 1290 | */ |
| 1291 | int cgi_setup_query_string(void){ |
| 1292 | int rc = 0; |
| 1293 | char * z = (char*)P("QUERY_STRING"); |
| 1294 | if( z ){ |
| 1295 | ++rc; |
| 1296 | z = fossil_strdup(z); |
| 1297 | add_param_list(z, '&'); |
| 1298 | z = (char*)P("skin"); |
| 1299 | if( z ){ |
| 1300 | char *zErr = skin_use_alternative(z, 2, SKIN_FROM_QPARAM); |
| 1301 | ++rc; |
| 1302 | if( !zErr && P("once")==0 ){ |
| 1303 | cookie_write_parameter("skin","skin",z); |
| 1304 | /* Per /chat discussion, passing ?skin=... without "once" |
| 1305 | ** implies the "udc" argument, so we force that into the |
| 1306 | ** environment here. */ |
| 1307 | cgi_set_parameter_nocopy("udc", "1", 1); |
| 1308 | } |
| 1309 | fossil_free(zErr); |
| 1310 | } |
| 1311 | } |
| 1312 | return rc; |
| 1313 | } |
| 1314 | |
| @@ -2224,10 +2241,11 @@ | |
| 2224 | rangeStart = x1; |
| 2225 | rangeEnd = x2+1; |
| 2226 | } |
| 2227 | }else if( fossil_strcmp(zFieldName, "x-fossil-xfer-login:")==0 ){ |
| 2228 | /*cgi_setenv("FOSSIL_LCH_cgi_handle_http_request", zVal);*/ |
| 2229 | g.syncInfo.zLoginCard = fossil_strdup(zVal); |
| 2230 | g.syncInfo.bLoginCardHeader = 1; |
| 2231 | } |
| 2232 | } |
| 2233 | cgi_setenv("REQUEST_SCHEME",zScheme); |
| 2234 |
| --- src/cgi.c | |
| +++ src/cgi.c | |
| @@ -964,11 +964,11 @@ | |
| 964 | ** * it is impossible for a cookie or query parameter to |
| 965 | ** override the value of an environment variable since |
| 966 | ** environment variables always have uppercase names. |
| 967 | ** |
| 968 | ** 2018-03-29: Also ignore the entry if NAME that contains any characters |
| 969 | ** other than [-a-zA-Z0-9_]. There are no known exploits involving unusual |
| 970 | ** names that contain characters outside that set, but it never hurts to |
| 971 | ** be extra cautious when sanitizing inputs. |
| 972 | ** |
| 973 | ** Parameters are separated by the "terminator" character. Whitespace |
| 974 | ** before the NAME is ignored. |
| @@ -1280,35 +1280,52 @@ | |
| 1280 | |
| 1281 | /* Forward declaration */ |
| 1282 | static NORETURN void malformed_request(const char *zMsg, ...); |
| 1283 | |
| 1284 | /* |
| 1285 | ** Checks the QUERY_STRING environment variable, sets it up via |
| 1286 | ** add_param_list() and, if found, applies its "skin" setting. Returns |
| 1287 | ** 0 if no QUERY_STRING is set, else it returns a bitmask of: |
| 1288 | ** |
| 1289 | ** 0x01 = QUERY_STRING was set. |
| 1290 | ** 0x02 = "skin" argument was set and processed |
| 1291 | ** 0x04 = "x-f-x-l" arg was processed. |
| 1292 | ** |
| 1293 | * In the case of the skin, the cookie may still need flushing |
| 1294 | ** by the page, via cookie_render(). |
| 1295 | */ |
| 1296 | int cgi_setup_query_string(void){ |
| 1297 | int rc = 0; |
| 1298 | char * z = (char*)P("QUERY_STRING"); |
| 1299 | if( z ){ |
| 1300 | rc = 0x01; |
| 1301 | z = fossil_strdup(z); |
| 1302 | add_param_list(z, '&'); |
| 1303 | z = (char*)P("skin"); |
| 1304 | if( z ){ |
| 1305 | char *zErr = skin_use_alternative(z, 2, SKIN_FROM_QPARAM); |
| 1306 | rc |= 0x02; |
| 1307 | if( !zErr && P("once")==0 ){ |
| 1308 | cookie_write_parameter("skin","skin",z); |
| 1309 | /* Per /chat discussion, passing ?skin=... without "once" |
| 1310 | ** implies the "udc" argument, so we force that into the |
| 1311 | ** environment here. */ |
| 1312 | cgi_set_parameter_nocopy("udc", "1", 1); |
| 1313 | } |
| 1314 | fossil_free(zErr); |
| 1315 | } |
| 1316 | if( !g.syncInfo.zLoginCard && 0!=(z=(char*)P("x-f-x-l")) ){ |
| 1317 | /* CGI fossil instances do not read the HTTP headers, so |
| 1318 | ** they cannot see the X-Fossil-Xfer-Login card. As a consolation |
| 1319 | ** to them, we'll accept that via this query argument. */ |
| 1320 | rc |= 0x04; |
| 1321 | fossil_free( g.syncInfo.zLoginCard ); |
| 1322 | g.syncInfo.zLoginCard = fossil_strdup(z); |
| 1323 | g.syncInfo.bLoginCardHeader = 3; |
| 1324 | /*cgi_delete_parameter("x-f-x-l");*/ |
| 1325 | /*fprintf(stderr, "query string setup: x-f-x-l=%s\n", |
| 1326 | g.syncInfo.zLoginCard);*/ |
| 1327 | } |
| 1328 | } |
| 1329 | return rc; |
| 1330 | } |
| 1331 | |
| @@ -2224,10 +2241,11 @@ | |
| 2241 | rangeStart = x1; |
| 2242 | rangeEnd = x2+1; |
| 2243 | } |
| 2244 | }else if( fossil_strcmp(zFieldName, "x-fossil-xfer-login:")==0 ){ |
| 2245 | /*cgi_setenv("FOSSIL_LCH_cgi_handle_http_request", zVal);*/ |
| 2246 | fossil_free( g.syncInfo.zLoginCard ); |
| 2247 | g.syncInfo.zLoginCard = fossil_strdup(zVal); |
| 2248 | g.syncInfo.bLoginCardHeader = 1; |
| 2249 | } |
| 2250 | } |
| 2251 | cgi_setenv("REQUEST_SCHEME",zScheme); |
| 2252 |
+41
-9
| --- src/http.c | ||
| +++ src/http.c | ||
| @@ -127,10 +127,35 @@ | ||
| 127 | 127 | blob_appendf(pLogin, "login %F %b %b", zLogin, &nonce, &sig); |
| 128 | 128 | blob_reset(&pw); |
| 129 | 129 | blob_reset(&sig); |
| 130 | 130 | blob_reset(&nonce); |
| 131 | 131 | } |
| 132 | + | |
| 133 | +/* | |
| 134 | +** If we're in "login card header" mode, append ?x-f-x-l=ABC to | |
| 135 | +** g.url.path, replacing any "?..." part of g.url.path. ABC = the | |
| 136 | +** %T-encoded contents of pLogin. This is workaround for feeding the | |
| 137 | +** login card to CGI-hosted fossil instances, as those do not read the | |
| 138 | +** HTTP headers so cannot see the X-Fossil-Xfer-Login (x-f-x-l) | |
| 139 | +** header. | |
| 140 | +*/ | |
| 141 | +static void url_append_login_card(Blob * const pLogin){ | |
| 142 | + if( g.syncInfo.bLoginCardHeader || | |
| 143 | + g.syncInfo.remoteVersion >= RELEASE_VERSION_NUMBER ){ | |
| 144 | + char * x; | |
| 145 | + char * z = g.url.path; | |
| 146 | + while( z && *z && '?'!=*z ) ++z; | |
| 147 | + if( z && *z ) *z = 0; | |
| 148 | + x = mprintf("%s?x-f-x-l=%T", g.url.path ? g.url.path : "/", | |
| 149 | + blob_str(pLogin)); | |
| 150 | + fossil_free(g.url.path); | |
| 151 | + g.url.path = x; | |
| 152 | + if( !g.syncInfo.bLoginCardHeader ){ | |
| 153 | + g.syncInfo.bLoginCardHeader = 4; | |
| 154 | + } | |
| 155 | + } | |
| 156 | +} | |
| 132 | 157 | |
| 133 | 158 | /* |
| 134 | 159 | ** Construct an appropriate HTTP request header. Write the header |
| 135 | 160 | ** into pHdr. This routine initializes the pHdr blob. pPayload is |
| 136 | 161 | ** the complete payload (including the login card if pLogin is NULL or |
| @@ -143,13 +168,17 @@ | ||
| 143 | 168 | const char *zAltMimetype /* Alternative mimetype */ |
| 144 | 169 | ){ |
| 145 | 170 | int nPayload = pPayload ? blob_size(pPayload) : 0; |
| 146 | 171 | |
| 147 | 172 | blob_zero(pHdr); |
| 148 | - blob_appendf(pHdr, "%s %s%s HTTP/1.0\r\n", | |
| 149 | - nPayload>0 ? "POST" : "GET", g.url.path, | |
| 150 | - g.url.path[0]==0 ? "/" : ""); | |
| 173 | + if( nPayload>0 && pLogin && blob_size(pLogin)>0 ){ | |
| 174 | + /* Add login card URL arg for POST requests */ | |
| 175 | + url_append_login_card(pLogin); | |
| 176 | + } | |
| 177 | + blob_appendf(pHdr, "%s %s HTTP/1.0\r\n", | |
| 178 | + nPayload>0 ? "POST" : "GET", | |
| 179 | + (g.url.path && g.url.path[0]) ? g.url.path : "/"); | |
| 151 | 180 | if( g.url.proxyAuth ){ |
| 152 | 181 | blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth); |
| 153 | 182 | } |
| 154 | 183 | if( g.zHttpAuth && g.zHttpAuth[0] ){ |
| 155 | 184 | const char *zCredentials = g.zHttpAuth; |
| @@ -159,11 +188,12 @@ | ||
| 159 | 188 | } |
| 160 | 189 | blob_appendf(pHdr, "Host: %s\r\n", g.url.hostname); |
| 161 | 190 | blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent()); |
| 162 | 191 | if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n"); |
| 163 | 192 | if( pLogin && blob_size(pLogin) ){ |
| 164 | - blob_appendf(pHdr, "X-Fossil-Xfer-Login: %b\r\n", pLogin); | |
| 193 | + blob_appendf(pHdr, "X-Fossil-Xfer-Login: %b\r\n", pLogin) | |
| 194 | + /* Noting that CGIs can't read headers */; | |
| 165 | 195 | } |
| 166 | 196 | if( nPayload ){ |
| 167 | 197 | if( zAltMimetype ){ |
| 168 | 198 | blob_appendf(pHdr, "Content-Type: %s\r\n", zAltMimetype); |
| 169 | 199 | }else if( g.fHttpTrace ){ |
| @@ -394,11 +424,11 @@ | ||
| 394 | 424 | ** * The test-ssh-needs-path command that shows the settings |
| 395 | 425 | ** that cache whether or not a PATH= is needed for a particular |
| 396 | 426 | ** HOSTNAME. |
| 397 | 427 | */ |
| 398 | 428 | void ssh_add_path_argument(Blob *pCmd){ |
| 399 | - blob_append_escaped_arg(pCmd, | |
| 429 | + blob_append_escaped_arg(pCmd, | |
| 400 | 430 | "PATH=$HOME/bin:/usr/local/bin:/opt/homebrew/bin:$PATH", 1); |
| 401 | 431 | } |
| 402 | 432 | |
| 403 | 433 | /* |
| 404 | 434 | ** Return the complete text of the last HTTP reply as saved in the |
| @@ -471,17 +501,18 @@ | ||
| 471 | 501 | blob_zero(&login); |
| 472 | 502 | if( blob_size(pSend)==0 ){ |
| 473 | 503 | blob_zero(&payload); |
| 474 | 504 | }else{ |
| 475 | 505 | if( mHttpFlags & HTTP_USE_LOGIN ) http_build_login_card(pSend, &login); |
| 476 | - if( g.syncInfo.bLoginCardHeader ) { | |
| 477 | - /* Send the login card as an HTTP header. */ | |
| 506 | + if( g.syncInfo.bLoginCardHeader>0 ){ | |
| 507 | + /* The login card will be sent via an HTTP header and/or URL flag. */ | |
| 478 | 508 | if( g.fHttpTrace || (mHttpFlags & HTTP_NOCOMPRESS)!=0 ){ |
| 479 | 509 | /* Maintenance note: we cannot blob_swap(pSend,&payload) here |
| 480 | 510 | ** because the HTTP 401 and redirect response handling below |
| 481 | 511 | ** needs pSend unmodified. payload won't be modified after |
| 482 | - ** this point, so we can make it a proxy for pSend. */ | |
| 512 | + ** this point, so we can make it a proxy for pSend for | |
| 513 | + ** zero heap memory. */ | |
| 483 | 514 | blob_init(&payload, blob_buffer(pSend), blob_size(pSend)); |
| 484 | 515 | }else{ |
| 485 | 516 | blob_compress(pSend, &payload); |
| 486 | 517 | } |
| 487 | 518 | }else{ |
| @@ -530,10 +561,11 @@ | ||
| 530 | 561 | /* |
| 531 | 562 | ** Send the request to the server. |
| 532 | 563 | */ |
| 533 | 564 | if( mHttpFlags & HTTP_VERBOSE ){ |
| 534 | 565 | fossil_print("URL: %s\n", g.url.canonical); |
| 566 | + fossil_print("URL path: %s\n", g.url.path); | |
| 535 | 567 | fossil_print("Sending %d byte header and %d byte payload\n", |
| 536 | 568 | blob_size(&hdr), blob_size(&payload)); |
| 537 | 569 | } |
| 538 | 570 | transport_send(&g.url, &hdr); |
| 539 | 571 | transport_send(&g.url, &payload); |
| @@ -661,11 +693,11 @@ | ||
| 661 | 693 | }else if( fossil_strnicmp(&zLine[14], "application/x-fossil", -1)!=0 ){ |
| 662 | 694 | isError = 1; |
| 663 | 695 | } |
| 664 | 696 | } |
| 665 | 697 | }else if( fossil_strnicmp(zLine, "x-fossil-xfer-login: ", 21)==0 ){ |
| 666 | - /*cgi_setenv("FOSSIL_LCH_http_exchange", &zLine[21]);*/ | |
| 698 | + fossil_free( g.syncInfo.zLoginCard ); | |
| 667 | 699 | g.syncInfo.zLoginCard = fossil_strdup(&zLine[21]); |
| 668 | 700 | g.syncInfo.bLoginCardHeader = 1; |
| 669 | 701 | } |
| 670 | 702 | } |
| 671 | 703 | if( iHttpVersion<0 ){ |
| 672 | 704 |
| --- src/http.c | |
| +++ src/http.c | |
| @@ -127,10 +127,35 @@ | |
| 127 | blob_appendf(pLogin, "login %F %b %b", zLogin, &nonce, &sig); |
| 128 | blob_reset(&pw); |
| 129 | blob_reset(&sig); |
| 130 | blob_reset(&nonce); |
| 131 | } |
| 132 | |
| 133 | /* |
| 134 | ** Construct an appropriate HTTP request header. Write the header |
| 135 | ** into pHdr. This routine initializes the pHdr blob. pPayload is |
| 136 | ** the complete payload (including the login card if pLogin is NULL or |
| @@ -143,13 +168,17 @@ | |
| 143 | const char *zAltMimetype /* Alternative mimetype */ |
| 144 | ){ |
| 145 | int nPayload = pPayload ? blob_size(pPayload) : 0; |
| 146 | |
| 147 | blob_zero(pHdr); |
| 148 | blob_appendf(pHdr, "%s %s%s HTTP/1.0\r\n", |
| 149 | nPayload>0 ? "POST" : "GET", g.url.path, |
| 150 | g.url.path[0]==0 ? "/" : ""); |
| 151 | if( g.url.proxyAuth ){ |
| 152 | blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth); |
| 153 | } |
| 154 | if( g.zHttpAuth && g.zHttpAuth[0] ){ |
| 155 | const char *zCredentials = g.zHttpAuth; |
| @@ -159,11 +188,12 @@ | |
| 159 | } |
| 160 | blob_appendf(pHdr, "Host: %s\r\n", g.url.hostname); |
| 161 | blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent()); |
| 162 | if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n"); |
| 163 | if( pLogin && blob_size(pLogin) ){ |
| 164 | blob_appendf(pHdr, "X-Fossil-Xfer-Login: %b\r\n", pLogin); |
| 165 | } |
| 166 | if( nPayload ){ |
| 167 | if( zAltMimetype ){ |
| 168 | blob_appendf(pHdr, "Content-Type: %s\r\n", zAltMimetype); |
| 169 | }else if( g.fHttpTrace ){ |
| @@ -394,11 +424,11 @@ | |
| 394 | ** * The test-ssh-needs-path command that shows the settings |
| 395 | ** that cache whether or not a PATH= is needed for a particular |
| 396 | ** HOSTNAME. |
| 397 | */ |
| 398 | void ssh_add_path_argument(Blob *pCmd){ |
| 399 | blob_append_escaped_arg(pCmd, |
| 400 | "PATH=$HOME/bin:/usr/local/bin:/opt/homebrew/bin:$PATH", 1); |
| 401 | } |
| 402 | |
| 403 | /* |
| 404 | ** Return the complete text of the last HTTP reply as saved in the |
| @@ -471,17 +501,18 @@ | |
| 471 | blob_zero(&login); |
| 472 | if( blob_size(pSend)==0 ){ |
| 473 | blob_zero(&payload); |
| 474 | }else{ |
| 475 | if( mHttpFlags & HTTP_USE_LOGIN ) http_build_login_card(pSend, &login); |
| 476 | if( g.syncInfo.bLoginCardHeader ) { |
| 477 | /* Send the login card as an HTTP header. */ |
| 478 | if( g.fHttpTrace || (mHttpFlags & HTTP_NOCOMPRESS)!=0 ){ |
| 479 | /* Maintenance note: we cannot blob_swap(pSend,&payload) here |
| 480 | ** because the HTTP 401 and redirect response handling below |
| 481 | ** needs pSend unmodified. payload won't be modified after |
| 482 | ** this point, so we can make it a proxy for pSend. */ |
| 483 | blob_init(&payload, blob_buffer(pSend), blob_size(pSend)); |
| 484 | }else{ |
| 485 | blob_compress(pSend, &payload); |
| 486 | } |
| 487 | }else{ |
| @@ -530,10 +561,11 @@ | |
| 530 | /* |
| 531 | ** Send the request to the server. |
| 532 | */ |
| 533 | if( mHttpFlags & HTTP_VERBOSE ){ |
| 534 | fossil_print("URL: %s\n", g.url.canonical); |
| 535 | fossil_print("Sending %d byte header and %d byte payload\n", |
| 536 | blob_size(&hdr), blob_size(&payload)); |
| 537 | } |
| 538 | transport_send(&g.url, &hdr); |
| 539 | transport_send(&g.url, &payload); |
| @@ -661,11 +693,11 @@ | |
| 661 | }else if( fossil_strnicmp(&zLine[14], "application/x-fossil", -1)!=0 ){ |
| 662 | isError = 1; |
| 663 | } |
| 664 | } |
| 665 | }else if( fossil_strnicmp(zLine, "x-fossil-xfer-login: ", 21)==0 ){ |
| 666 | /*cgi_setenv("FOSSIL_LCH_http_exchange", &zLine[21]);*/ |
| 667 | g.syncInfo.zLoginCard = fossil_strdup(&zLine[21]); |
| 668 | g.syncInfo.bLoginCardHeader = 1; |
| 669 | } |
| 670 | } |
| 671 | if( iHttpVersion<0 ){ |
| 672 |
| --- src/http.c | |
| +++ src/http.c | |
| @@ -127,10 +127,35 @@ | |
| 127 | blob_appendf(pLogin, "login %F %b %b", zLogin, &nonce, &sig); |
| 128 | blob_reset(&pw); |
| 129 | blob_reset(&sig); |
| 130 | blob_reset(&nonce); |
| 131 | } |
| 132 | |
| 133 | /* |
| 134 | ** If we're in "login card header" mode, append ?x-f-x-l=ABC to |
| 135 | ** g.url.path, replacing any "?..." part of g.url.path. ABC = the |
| 136 | ** %T-encoded contents of pLogin. This is workaround for feeding the |
| 137 | ** login card to CGI-hosted fossil instances, as those do not read the |
| 138 | ** HTTP headers so cannot see the X-Fossil-Xfer-Login (x-f-x-l) |
| 139 | ** header. |
| 140 | */ |
| 141 | static void url_append_login_card(Blob * const pLogin){ |
| 142 | if( g.syncInfo.bLoginCardHeader || |
| 143 | g.syncInfo.remoteVersion >= RELEASE_VERSION_NUMBER ){ |
| 144 | char * x; |
| 145 | char * z = g.url.path; |
| 146 | while( z && *z && '?'!=*z ) ++z; |
| 147 | if( z && *z ) *z = 0; |
| 148 | x = mprintf("%s?x-f-x-l=%T", g.url.path ? g.url.path : "/", |
| 149 | blob_str(pLogin)); |
| 150 | fossil_free(g.url.path); |
| 151 | g.url.path = x; |
| 152 | if( !g.syncInfo.bLoginCardHeader ){ |
| 153 | g.syncInfo.bLoginCardHeader = 4; |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | /* |
| 159 | ** Construct an appropriate HTTP request header. Write the header |
| 160 | ** into pHdr. This routine initializes the pHdr blob. pPayload is |
| 161 | ** the complete payload (including the login card if pLogin is NULL or |
| @@ -143,13 +168,17 @@ | |
| 168 | const char *zAltMimetype /* Alternative mimetype */ |
| 169 | ){ |
| 170 | int nPayload = pPayload ? blob_size(pPayload) : 0; |
| 171 | |
| 172 | blob_zero(pHdr); |
| 173 | if( nPayload>0 && pLogin && blob_size(pLogin)>0 ){ |
| 174 | /* Add login card URL arg for POST requests */ |
| 175 | url_append_login_card(pLogin); |
| 176 | } |
| 177 | blob_appendf(pHdr, "%s %s HTTP/1.0\r\n", |
| 178 | nPayload>0 ? "POST" : "GET", |
| 179 | (g.url.path && g.url.path[0]) ? g.url.path : "/"); |
| 180 | if( g.url.proxyAuth ){ |
| 181 | blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth); |
| 182 | } |
| 183 | if( g.zHttpAuth && g.zHttpAuth[0] ){ |
| 184 | const char *zCredentials = g.zHttpAuth; |
| @@ -159,11 +188,12 @@ | |
| 188 | } |
| 189 | blob_appendf(pHdr, "Host: %s\r\n", g.url.hostname); |
| 190 | blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent()); |
| 191 | if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n"); |
| 192 | if( pLogin && blob_size(pLogin) ){ |
| 193 | blob_appendf(pHdr, "X-Fossil-Xfer-Login: %b\r\n", pLogin) |
| 194 | /* Noting that CGIs can't read headers */; |
| 195 | } |
| 196 | if( nPayload ){ |
| 197 | if( zAltMimetype ){ |
| 198 | blob_appendf(pHdr, "Content-Type: %s\r\n", zAltMimetype); |
| 199 | }else if( g.fHttpTrace ){ |
| @@ -394,11 +424,11 @@ | |
| 424 | ** * The test-ssh-needs-path command that shows the settings |
| 425 | ** that cache whether or not a PATH= is needed for a particular |
| 426 | ** HOSTNAME. |
| 427 | */ |
| 428 | void ssh_add_path_argument(Blob *pCmd){ |
| 429 | blob_append_escaped_arg(pCmd, |
| 430 | "PATH=$HOME/bin:/usr/local/bin:/opt/homebrew/bin:$PATH", 1); |
| 431 | } |
| 432 | |
| 433 | /* |
| 434 | ** Return the complete text of the last HTTP reply as saved in the |
| @@ -471,17 +501,18 @@ | |
| 501 | blob_zero(&login); |
| 502 | if( blob_size(pSend)==0 ){ |
| 503 | blob_zero(&payload); |
| 504 | }else{ |
| 505 | if( mHttpFlags & HTTP_USE_LOGIN ) http_build_login_card(pSend, &login); |
| 506 | if( g.syncInfo.bLoginCardHeader>0 ){ |
| 507 | /* The login card will be sent via an HTTP header and/or URL flag. */ |
| 508 | if( g.fHttpTrace || (mHttpFlags & HTTP_NOCOMPRESS)!=0 ){ |
| 509 | /* Maintenance note: we cannot blob_swap(pSend,&payload) here |
| 510 | ** because the HTTP 401 and redirect response handling below |
| 511 | ** needs pSend unmodified. payload won't be modified after |
| 512 | ** this point, so we can make it a proxy for pSend for |
| 513 | ** zero heap memory. */ |
| 514 | blob_init(&payload, blob_buffer(pSend), blob_size(pSend)); |
| 515 | }else{ |
| 516 | blob_compress(pSend, &payload); |
| 517 | } |
| 518 | }else{ |
| @@ -530,10 +561,11 @@ | |
| 561 | /* |
| 562 | ** Send the request to the server. |
| 563 | */ |
| 564 | if( mHttpFlags & HTTP_VERBOSE ){ |
| 565 | fossil_print("URL: %s\n", g.url.canonical); |
| 566 | fossil_print("URL path: %s\n", g.url.path); |
| 567 | fossil_print("Sending %d byte header and %d byte payload\n", |
| 568 | blob_size(&hdr), blob_size(&payload)); |
| 569 | } |
| 570 | transport_send(&g.url, &hdr); |
| 571 | transport_send(&g.url, &payload); |
| @@ -661,11 +693,11 @@ | |
| 693 | }else if( fossil_strnicmp(&zLine[14], "application/x-fossil", -1)!=0 ){ |
| 694 | isError = 1; |
| 695 | } |
| 696 | } |
| 697 | }else if( fossil_strnicmp(zLine, "x-fossil-xfer-login: ", 21)==0 ){ |
| 698 | fossil_free( g.syncInfo.zLoginCard ); |
| 699 | g.syncInfo.zLoginCard = fossil_strdup(&zLine[21]); |
| 700 | g.syncInfo.bLoginCardHeader = 1; |
| 701 | } |
| 702 | } |
| 703 | if( iHttpVersion<0 ){ |
| 704 |
+1
-1
| --- src/http_transport.c | ||
| +++ src/http_transport.c | ||
| @@ -141,11 +141,11 @@ | ||
| 141 | 141 | ){ |
| 142 | 142 | fossil_fatal("the ssh:// URL is asking to run an unsafe command [%s] on " |
| 143 | 143 | "the server.", pUrlData->fossil); |
| 144 | 144 | } |
| 145 | 145 | if( (pUrlData->flags & URL_SSH_EXE)==0 |
| 146 | - && (pUrlData->flags & URL_SSH_PATH)!=0 | |
| 146 | + && (pUrlData->flags & URL_SSH_PATH)!=0 | |
| 147 | 147 | ){ |
| 148 | 148 | ssh_add_path_argument(&zCmd); |
| 149 | 149 | } |
| 150 | 150 | blob_append_escaped_arg(&zCmd, pUrlData->fossil, 1); |
| 151 | 151 | blob_append(&zCmd, " test-http", 10); |
| 152 | 152 |
| --- src/http_transport.c | |
| +++ src/http_transport.c | |
| @@ -141,11 +141,11 @@ | |
| 141 | ){ |
| 142 | fossil_fatal("the ssh:// URL is asking to run an unsafe command [%s] on " |
| 143 | "the server.", pUrlData->fossil); |
| 144 | } |
| 145 | if( (pUrlData->flags & URL_SSH_EXE)==0 |
| 146 | && (pUrlData->flags & URL_SSH_PATH)!=0 |
| 147 | ){ |
| 148 | ssh_add_path_argument(&zCmd); |
| 149 | } |
| 150 | blob_append_escaped_arg(&zCmd, pUrlData->fossil, 1); |
| 151 | blob_append(&zCmd, " test-http", 10); |
| 152 |
| --- src/http_transport.c | |
| +++ src/http_transport.c | |
| @@ -141,11 +141,11 @@ | |
| 141 | ){ |
| 142 | fossil_fatal("the ssh:// URL is asking to run an unsafe command [%s] on " |
| 143 | "the server.", pUrlData->fossil); |
| 144 | } |
| 145 | if( (pUrlData->flags & URL_SSH_EXE)==0 |
| 146 | && (pUrlData->flags & URL_SSH_PATH)!=0 |
| 147 | ){ |
| 148 | ssh_add_path_argument(&zCmd); |
| 149 | } |
| 150 | blob_append_escaped_arg(&zCmd, pUrlData->fossil, 1); |
| 151 | blob_append(&zCmd, " test-http", 10); |
| 152 |
+11
-4
| --- src/main.c | ||
| +++ src/main.c | ||
| @@ -292,16 +292,23 @@ | ||
| 292 | 292 | int bAvoidDeltaManifests; /* Avoid using delta manifests if true */ |
| 293 | 293 | |
| 294 | 294 | /* State for communicating specific details between the inbound HTTP |
| 295 | 295 | ** header parser (cgi.c), xfer.c, and http.c. */ |
| 296 | 296 | struct { |
| 297 | - char *zLoginCard; /* Inbound X-Fossil-Xfer-Login request header */ | |
| 298 | - int bLoginCardHeader; /* If true, emit login cards in outbound | |
| 297 | + char *zLoginCard; /* Inbound X-Fossil-Xfer-Login request header | |
| 298 | + ** or x-f-x-l URL parameter. */ | |
| 299 | + int bLoginCardHeader; /* If non-0, emit login cards in outbound | |
| 299 | 300 | ** requests as HTTP headers instead of as |
| 300 | 301 | ** part of the payload. Gets activated |
| 301 | 302 | ** on-demand based on xfer traffic |
| 302 | - ** contents. */ | |
| 303 | + ** contents. Values, for | |
| 304 | + ** diagnostic/debuggin purposes: 1=set via | |
| 305 | + ** CLI --flag. 2=set via inbound HTTP | |
| 306 | + ** header. 3=set via query string | |
| 307 | + ** arg. 4=set via http_build_header(). */ | |
| 308 | + int remoteVersion; /* Remote fossil version. Used for negotiating | |
| 309 | + ** how to handle the login card. */ | |
| 303 | 310 | } syncInfo; |
| 304 | 311 | #ifdef FOSSIL_ENABLE_JSON |
| 305 | 312 | struct FossilJsonBits { |
| 306 | 313 | int isJsonMode; /* True if running in JSON mode, else |
| 307 | 314 | false. This changes how errors are |
| @@ -1506,11 +1513,11 @@ | ||
| 1506 | 1513 | /* In order for ?skin=... to work when visiting the site from |
| 1507 | 1514 | ** a typical external link, we have to process it here, as |
| 1508 | 1515 | ** that parameter gets lost during the redirect. We "could" |
| 1509 | 1516 | ** pass the whole query string along instead, but that seems |
| 1510 | 1517 | ** unnecessary. */ |
| 1511 | - if(cgi_setup_query_string()>1){ | |
| 1518 | + if(cgi_setup_query_string() & 0x02){ | |
| 1512 | 1519 | cookie_render(); |
| 1513 | 1520 | } |
| 1514 | 1521 | cgi_redirectf("%R%s", db_get("index-page", "/index")); |
| 1515 | 1522 | } |
| 1516 | 1523 | |
| 1517 | 1524 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -292,16 +292,23 @@ | |
| 292 | int bAvoidDeltaManifests; /* Avoid using delta manifests if true */ |
| 293 | |
| 294 | /* State for communicating specific details between the inbound HTTP |
| 295 | ** header parser (cgi.c), xfer.c, and http.c. */ |
| 296 | struct { |
| 297 | char *zLoginCard; /* Inbound X-Fossil-Xfer-Login request header */ |
| 298 | int bLoginCardHeader; /* If true, emit login cards in outbound |
| 299 | ** requests as HTTP headers instead of as |
| 300 | ** part of the payload. Gets activated |
| 301 | ** on-demand based on xfer traffic |
| 302 | ** contents. */ |
| 303 | } syncInfo; |
| 304 | #ifdef FOSSIL_ENABLE_JSON |
| 305 | struct FossilJsonBits { |
| 306 | int isJsonMode; /* True if running in JSON mode, else |
| 307 | false. This changes how errors are |
| @@ -1506,11 +1513,11 @@ | |
| 1506 | /* In order for ?skin=... to work when visiting the site from |
| 1507 | ** a typical external link, we have to process it here, as |
| 1508 | ** that parameter gets lost during the redirect. We "could" |
| 1509 | ** pass the whole query string along instead, but that seems |
| 1510 | ** unnecessary. */ |
| 1511 | if(cgi_setup_query_string()>1){ |
| 1512 | cookie_render(); |
| 1513 | } |
| 1514 | cgi_redirectf("%R%s", db_get("index-page", "/index")); |
| 1515 | } |
| 1516 | |
| 1517 |
| --- src/main.c | |
| +++ src/main.c | |
| @@ -292,16 +292,23 @@ | |
| 292 | int bAvoidDeltaManifests; /* Avoid using delta manifests if true */ |
| 293 | |
| 294 | /* State for communicating specific details between the inbound HTTP |
| 295 | ** header parser (cgi.c), xfer.c, and http.c. */ |
| 296 | struct { |
| 297 | char *zLoginCard; /* Inbound X-Fossil-Xfer-Login request header |
| 298 | ** or x-f-x-l URL parameter. */ |
| 299 | int bLoginCardHeader; /* If non-0, emit login cards in outbound |
| 300 | ** requests as HTTP headers instead of as |
| 301 | ** part of the payload. Gets activated |
| 302 | ** on-demand based on xfer traffic |
| 303 | ** contents. Values, for |
| 304 | ** diagnostic/debuggin purposes: 1=set via |
| 305 | ** CLI --flag. 2=set via inbound HTTP |
| 306 | ** header. 3=set via query string |
| 307 | ** arg. 4=set via http_build_header(). */ |
| 308 | int remoteVersion; /* Remote fossil version. Used for negotiating |
| 309 | ** how to handle the login card. */ |
| 310 | } syncInfo; |
| 311 | #ifdef FOSSIL_ENABLE_JSON |
| 312 | struct FossilJsonBits { |
| 313 | int isJsonMode; /* True if running in JSON mode, else |
| 314 | false. This changes how errors are |
| @@ -1506,11 +1513,11 @@ | |
| 1513 | /* In order for ?skin=... to work when visiting the site from |
| 1514 | ** a typical external link, we have to process it here, as |
| 1515 | ** that parameter gets lost during the redirect. We "could" |
| 1516 | ** pass the whole query string along instead, but that seems |
| 1517 | ** unnecessary. */ |
| 1518 | if(cgi_setup_query_string() & 0x02){ |
| 1519 | cookie_render(); |
| 1520 | } |
| 1521 | cgi_redirectf("%R%s", db_get("index-page", "/index")); |
| 1522 | } |
| 1523 | |
| 1524 |
+2
-2
| --- src/url.c | ||
| +++ src/url.c | ||
| @@ -227,17 +227,17 @@ | ||
| 227 | 227 | }else{ |
| 228 | 228 | pUrlData->port = pUrlData->dfltPort; |
| 229 | 229 | pUrlData->hostname = pUrlData->name; |
| 230 | 230 | } |
| 231 | 231 | dehttpize(pUrlData->name); |
| 232 | - pUrlData->path = mprintf("%s", &zUrl[i]); | |
| 232 | + pUrlData->path = fossil_strdup(&zUrl[i]); | |
| 233 | 233 | for(i=0; pUrlData->path[i] && pUrlData->path[i]!='?'; i++){} |
| 234 | 234 | if( pUrlData->path[i] ){ |
| 235 | 235 | pUrlData->path[i] = 0; |
| 236 | 236 | i++; |
| 237 | 237 | } |
| 238 | - zExe = mprintf(""); | |
| 238 | + zExe = fossil_strdup(""); | |
| 239 | 239 | while( pUrlData->path[i]!=0 ){ |
| 240 | 240 | char *zName, *zValue; |
| 241 | 241 | zName = &pUrlData->path[i]; |
| 242 | 242 | zValue = zName; |
| 243 | 243 | while( pUrlData->path[i] && pUrlData->path[i]!='=' ){ i++; } |
| 244 | 244 |
| --- src/url.c | |
| +++ src/url.c | |
| @@ -227,17 +227,17 @@ | |
| 227 | }else{ |
| 228 | pUrlData->port = pUrlData->dfltPort; |
| 229 | pUrlData->hostname = pUrlData->name; |
| 230 | } |
| 231 | dehttpize(pUrlData->name); |
| 232 | pUrlData->path = mprintf("%s", &zUrl[i]); |
| 233 | for(i=0; pUrlData->path[i] && pUrlData->path[i]!='?'; i++){} |
| 234 | if( pUrlData->path[i] ){ |
| 235 | pUrlData->path[i] = 0; |
| 236 | i++; |
| 237 | } |
| 238 | zExe = mprintf(""); |
| 239 | while( pUrlData->path[i]!=0 ){ |
| 240 | char *zName, *zValue; |
| 241 | zName = &pUrlData->path[i]; |
| 242 | zValue = zName; |
| 243 | while( pUrlData->path[i] && pUrlData->path[i]!='=' ){ i++; } |
| 244 |
| --- src/url.c | |
| +++ src/url.c | |
| @@ -227,17 +227,17 @@ | |
| 227 | }else{ |
| 228 | pUrlData->port = pUrlData->dfltPort; |
| 229 | pUrlData->hostname = pUrlData->name; |
| 230 | } |
| 231 | dehttpize(pUrlData->name); |
| 232 | pUrlData->path = fossil_strdup(&zUrl[i]); |
| 233 | for(i=0; pUrlData->path[i] && pUrlData->path[i]!='?'; i++){} |
| 234 | if( pUrlData->path[i] ){ |
| 235 | pUrlData->path[i] = 0; |
| 236 | i++; |
| 237 | } |
| 238 | zExe = fossil_strdup(""); |
| 239 | while( pUrlData->path[i]!=0 ){ |
| 240 | char *zName, *zValue; |
| 241 | zName = &pUrlData->path[i]; |
| 242 | zValue = zName; |
| 243 | while( pUrlData->path[i] && pUrlData->path[i]!='=' ){ i++; } |
| 244 |
+4
-2
| --- src/xfer.c | ||
| +++ src/xfer.c | ||
| @@ -1725,11 +1725,12 @@ | ||
| 1725 | 1725 | ** The client announces to the server what version of Fossil it |
| 1726 | 1726 | ** is running. The DATE and TIME are a pure numeric ISO8601 time |
| 1727 | 1727 | ** for the specific check-in of the client. |
| 1728 | 1728 | */ |
| 1729 | 1729 | if( xfer.nToken>=3 && blob_eq(&xfer.aToken[1], "client-version") ){ |
| 1730 | - xfer.remoteVersion = atoi(blob_str(&xfer.aToken[2])); | |
| 1730 | + xfer.remoteVersion = g.syncInfo.remoteVersion = | |
| 1731 | + atoi(blob_str(&xfer.aToken[2])); | |
| 1731 | 1732 | g.syncInfo.bLoginCardHeader = |
| 1732 | 1733 | xfer.remoteVersion>=RELEASE_VERSION_NUMBER; |
| 1733 | 1734 | if( xfer.nToken>=5 ){ |
| 1734 | 1735 | xfer.remoteDate = atoi(blob_str(&xfer.aToken[3])); |
| 1735 | 1736 | xfer.remoteTime = atoi(blob_str(&xfer.aToken[4])); |
| @@ -2786,11 +2787,12 @@ | ||
| 2786 | 2787 | ** The server announces to the server what version of Fossil it |
| 2787 | 2788 | ** is running. The DATE and TIME are a pure numeric ISO8601 time |
| 2788 | 2789 | ** for the specific check-in of the client. |
| 2789 | 2790 | */ |
| 2790 | 2791 | if( xfer.nToken>=3 && blob_eq(&xfer.aToken[1], "server-version") ){ |
| 2791 | - xfer.remoteVersion = atoi(blob_str(&xfer.aToken[2])); | |
| 2792 | + xfer.remoteVersion = g.syncInfo.remoteVersion = | |
| 2793 | + atoi(blob_str(&xfer.aToken[2])); | |
| 2792 | 2794 | g.syncInfo.bLoginCardHeader = |
| 2793 | 2795 | xfer.remoteVersion>=RELEASE_VERSION_NUMBER; |
| 2794 | 2796 | if( xfer.nToken>=5 ){ |
| 2795 | 2797 | xfer.remoteDate = atoi(blob_str(&xfer.aToken[3])); |
| 2796 | 2798 | xfer.remoteTime = atoi(blob_str(&xfer.aToken[4])); |
| 2797 | 2799 |
| --- src/xfer.c | |
| +++ src/xfer.c | |
| @@ -1725,11 +1725,12 @@ | |
| 1725 | ** The client announces to the server what version of Fossil it |
| 1726 | ** is running. The DATE and TIME are a pure numeric ISO8601 time |
| 1727 | ** for the specific check-in of the client. |
| 1728 | */ |
| 1729 | if( xfer.nToken>=3 && blob_eq(&xfer.aToken[1], "client-version") ){ |
| 1730 | xfer.remoteVersion = atoi(blob_str(&xfer.aToken[2])); |
| 1731 | g.syncInfo.bLoginCardHeader = |
| 1732 | xfer.remoteVersion>=RELEASE_VERSION_NUMBER; |
| 1733 | if( xfer.nToken>=5 ){ |
| 1734 | xfer.remoteDate = atoi(blob_str(&xfer.aToken[3])); |
| 1735 | xfer.remoteTime = atoi(blob_str(&xfer.aToken[4])); |
| @@ -2786,11 +2787,12 @@ | |
| 2786 | ** The server announces to the server what version of Fossil it |
| 2787 | ** is running. The DATE and TIME are a pure numeric ISO8601 time |
| 2788 | ** for the specific check-in of the client. |
| 2789 | */ |
| 2790 | if( xfer.nToken>=3 && blob_eq(&xfer.aToken[1], "server-version") ){ |
| 2791 | xfer.remoteVersion = atoi(blob_str(&xfer.aToken[2])); |
| 2792 | g.syncInfo.bLoginCardHeader = |
| 2793 | xfer.remoteVersion>=RELEASE_VERSION_NUMBER; |
| 2794 | if( xfer.nToken>=5 ){ |
| 2795 | xfer.remoteDate = atoi(blob_str(&xfer.aToken[3])); |
| 2796 | xfer.remoteTime = atoi(blob_str(&xfer.aToken[4])); |
| 2797 |
| --- src/xfer.c | |
| +++ src/xfer.c | |
| @@ -1725,11 +1725,12 @@ | |
| 1725 | ** The client announces to the server what version of Fossil it |
| 1726 | ** is running. The DATE and TIME are a pure numeric ISO8601 time |
| 1727 | ** for the specific check-in of the client. |
| 1728 | */ |
| 1729 | if( xfer.nToken>=3 && blob_eq(&xfer.aToken[1], "client-version") ){ |
| 1730 | xfer.remoteVersion = g.syncInfo.remoteVersion = |
| 1731 | atoi(blob_str(&xfer.aToken[2])); |
| 1732 | g.syncInfo.bLoginCardHeader = |
| 1733 | xfer.remoteVersion>=RELEASE_VERSION_NUMBER; |
| 1734 | if( xfer.nToken>=5 ){ |
| 1735 | xfer.remoteDate = atoi(blob_str(&xfer.aToken[3])); |
| 1736 | xfer.remoteTime = atoi(blob_str(&xfer.aToken[4])); |
| @@ -2786,11 +2787,12 @@ | |
| 2787 | ** The server announces to the server what version of Fossil it |
| 2788 | ** is running. The DATE and TIME are a pure numeric ISO8601 time |
| 2789 | ** for the specific check-in of the client. |
| 2790 | */ |
| 2791 | if( xfer.nToken>=3 && blob_eq(&xfer.aToken[1], "server-version") ){ |
| 2792 | xfer.remoteVersion = g.syncInfo.remoteVersion = |
| 2793 | atoi(blob_str(&xfer.aToken[2])); |
| 2794 | g.syncInfo.bLoginCardHeader = |
| 2795 | xfer.remoteVersion>=RELEASE_VERSION_NUMBER; |
| 2796 | if( xfer.nToken>=5 ){ |
| 2797 | xfer.remoteDate = atoi(blob_str(&xfer.aToken[3])); |
| 2798 | xfer.remoteTime = atoi(blob_str(&xfer.aToken[4])); |
| 2799 |