Fossil SCM
When an initial HTTP request fails with error code "400 Bad Request" when using NL-only line endings, retry using CRLF to see if the problem is a web server that does not follow RFC-2616 section 19.3 paragraph 3.
Commit
537cc60b5ff070e4a23edeb7de9ed15f7346815a1c7486b1c57c0d6d9da6ee63
Parent
038bcc9442e42c7…
1 file changed
+36
-12
+36
-12
| --- src/http.c | ||
| +++ src/http.c | ||
| @@ -46,10 +46,19 @@ | ||
| 46 | 46 | /* The N value for most recent http-request-N.txt and http-reply-N.txt |
| 47 | 47 | ** trace files. |
| 48 | 48 | */ |
| 49 | 49 | static int traceCnt = 0; |
| 50 | 50 | |
| 51 | +/* True to send full CRLF, not just a NL, in the HTTP headers. This is | |
| 52 | +** to work around issues in some web servers. | |
| 53 | +*/ | |
| 54 | +#if CRLF_SZ==1 | |
| 55 | +static int sendCRLF = 0; | |
| 56 | +#else | |
| 57 | +static int sendCRLF = 1; | |
| 58 | +#endif | |
| 59 | + | |
| 51 | 60 | /* |
| 52 | 61 | ** Construct the "login" card with the client credentials. |
| 53 | 62 | ** |
| 54 | 63 | ** login LOGIN NONCE SIGNATURE |
| 55 | 64 | ** |
| @@ -135,38 +144,40 @@ | ||
| 135 | 144 | Blob *pPayload, /* the payload that will be sent */ |
| 136 | 145 | Blob *pHdr, /* construct the header here */ |
| 137 | 146 | const char *zAltMimetype /* Alternative mimetype */ |
| 138 | 147 | ){ |
| 139 | 148 | int nPayload = pPayload ? blob_size(pPayload) : 0; |
| 149 | + const char *zCRLF = sendCRLF ? "\r\n" : "\n"; | |
| 140 | 150 | |
| 141 | 151 | blob_zero(pHdr); |
| 142 | - blob_appendf(pHdr, "%s %s%s HTTP/1.0" CRLF, | |
| 152 | + blob_appendf(pHdr, "%s %s%s HTTP/1.0%s", | |
| 143 | 153 | nPayload>0 ? "POST" : "GET", g.url.path, |
| 144 | - g.url.path[0]==0 ? "/" : ""); | |
| 154 | + g.url.path[0]==0 ? "/" : "", zCRLF); | |
| 145 | 155 | if( g.url.proxyAuth ){ |
| 146 | - blob_appendf(pHdr, "Proxy-Authorization: %s" CRLF, g.url.proxyAuth); | |
| 156 | + blob_appendf(pHdr, "Proxy-Authorization: %s%s", g.url.proxyAuth, zCRLF); | |
| 147 | 157 | } |
| 148 | 158 | if( g.zHttpAuth && g.zHttpAuth[0] ){ |
| 149 | 159 | const char *zCredentials = g.zHttpAuth; |
| 150 | 160 | char *zEncoded = encode64(zCredentials, -1); |
| 151 | - blob_appendf(pHdr, "Authorization: Basic %s" CRLF, zEncoded); | |
| 161 | + blob_appendf(pHdr, "Authorization: Basic %s%s", zEncoded, zCRLF); | |
| 152 | 162 | fossil_free(zEncoded); |
| 153 | 163 | } |
| 154 | - blob_appendf(pHdr, "Host: %s" CRLF, g.url.hostname); | |
| 155 | - blob_appendf(pHdr, "User-Agent: %s" CRLF, get_user_agent()); | |
| 156 | - if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH" CRLF); | |
| 164 | + blob_appendf(pHdr, "Host: %s%s", g.url.hostname, zCRLF); | |
| 165 | + blob_appendf(pHdr, "User-Agent: %s%s", get_user_agent(), zCRLF); | |
| 166 | + if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH%s", zCRLF); | |
| 157 | 167 | if( nPayload ){ |
| 158 | 168 | if( zAltMimetype ){ |
| 159 | - blob_appendf(pHdr, "Content-Type: %s" CRLF, zAltMimetype); | |
| 169 | + blob_appendf(pHdr, "Content-Type: %s", zAltMimetype); | |
| 160 | 170 | }else if( g.fHttpTrace ){ |
| 161 | - blob_appendf(pHdr, "Content-Type: application/x-fossil-debug" CRLF); | |
| 171 | + blob_appendf(pHdr, "Content-Type: application/x-fossil-debug"); | |
| 162 | 172 | }else{ |
| 163 | - blob_appendf(pHdr, "Content-Type: application/x-fossil" CRLF); | |
| 173 | + blob_appendf(pHdr, "Content-Type: application/x-fossil"); | |
| 164 | 174 | } |
| 165 | - blob_appendf(pHdr, "Content-Length: %d" CRLF, blob_size(pPayload)); | |
| 175 | + blob_appendf(pHdr, "%sContent-Length: %d%s", | |
| 176 | + zCRLF, blob_size(pPayload), zCRLF); | |
| 166 | 177 | } |
| 167 | - blob_append(pHdr, CRLF, CRLF_SZ); | |
| 178 | + blob_appendf(pHdr, "%s", zCRLF); | |
| 168 | 179 | } |
| 169 | 180 | |
| 170 | 181 | /* |
| 171 | 182 | ** Use Fossil credentials for HTTP Basic Authorization prompt |
| 172 | 183 | */ |
| @@ -541,10 +552,16 @@ | ||
| 541 | 552 | if( rc!=200 && rc!=301 && rc!=302 && rc!=307 && rc!=308 ){ |
| 542 | 553 | int ii; |
| 543 | 554 | for(ii=7; zLine[ii] && zLine[ii]!=' '; ii++){} |
| 544 | 555 | while( zLine[ii]==' ' ) ii++; |
| 545 | 556 | fossil_warning("server says: %s", &zLine[ii]); |
| 557 | + if( rc==400 && sendCRLF==0 && strstr(zLine,"Bad Request")!=0 ){ | |
| 558 | + sendCRLF = 1; | |
| 559 | + transport_close(&g.url); | |
| 560 | + return http_exchange(pSend, pReply, mHttpFlags, | |
| 561 | + maxRedirect, zAltMimetype); | |
| 562 | + } | |
| 546 | 563 | goto write_err; |
| 547 | 564 | } |
| 548 | 565 | if( iHttpVersion==0 ){ |
| 549 | 566 | closeConnection = 1; |
| 550 | 567 | }else{ |
| @@ -626,10 +643,17 @@ | ||
| 626 | 643 | if( mHttpFlags & HTTP_NOCOMPRESS ) isCompressed = 0; |
| 627 | 644 | }else if( fossil_strnicmp(&zLine[14], "application/x-fossil", -1)!=0 ){ |
| 628 | 645 | isError = 1; |
| 629 | 646 | } |
| 630 | 647 | } |
| 648 | + }else if( sendCRLF==1 | |
| 649 | + && rc==200 | |
| 650 | + && fossil_strnicmp(zLine, "server: ", 8)==0 ){ | |
| 651 | + fossil_warning("Server bug work-around: %s requires CRLF line endings " | |
| 652 | + "contra RFC-2616 section 19.3 paragraph 3", | |
| 653 | + zLine+8); | |
| 654 | + sendCRLF = 2; | |
| 631 | 655 | } |
| 632 | 656 | } |
| 633 | 657 | if( iHttpVersion<0 ){ |
| 634 | 658 | /* We got nothing back from the server. If using the ssh: protocol, |
| 635 | 659 | ** this might mean we need to add or remove the PATH=... argument |
| 636 | 660 |
| --- src/http.c | |
| +++ src/http.c | |
| @@ -46,10 +46,19 @@ | |
| 46 | /* The N value for most recent http-request-N.txt and http-reply-N.txt |
| 47 | ** trace files. |
| 48 | */ |
| 49 | static int traceCnt = 0; |
| 50 | |
| 51 | /* |
| 52 | ** Construct the "login" card with the client credentials. |
| 53 | ** |
| 54 | ** login LOGIN NONCE SIGNATURE |
| 55 | ** |
| @@ -135,38 +144,40 @@ | |
| 135 | Blob *pPayload, /* the payload that will be sent */ |
| 136 | Blob *pHdr, /* construct the header here */ |
| 137 | const char *zAltMimetype /* Alternative mimetype */ |
| 138 | ){ |
| 139 | int nPayload = pPayload ? blob_size(pPayload) : 0; |
| 140 | |
| 141 | blob_zero(pHdr); |
| 142 | blob_appendf(pHdr, "%s %s%s HTTP/1.0" CRLF, |
| 143 | nPayload>0 ? "POST" : "GET", g.url.path, |
| 144 | g.url.path[0]==0 ? "/" : ""); |
| 145 | if( g.url.proxyAuth ){ |
| 146 | blob_appendf(pHdr, "Proxy-Authorization: %s" CRLF, g.url.proxyAuth); |
| 147 | } |
| 148 | if( g.zHttpAuth && g.zHttpAuth[0] ){ |
| 149 | const char *zCredentials = g.zHttpAuth; |
| 150 | char *zEncoded = encode64(zCredentials, -1); |
| 151 | blob_appendf(pHdr, "Authorization: Basic %s" CRLF, zEncoded); |
| 152 | fossil_free(zEncoded); |
| 153 | } |
| 154 | blob_appendf(pHdr, "Host: %s" CRLF, g.url.hostname); |
| 155 | blob_appendf(pHdr, "User-Agent: %s" CRLF, get_user_agent()); |
| 156 | if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH" CRLF); |
| 157 | if( nPayload ){ |
| 158 | if( zAltMimetype ){ |
| 159 | blob_appendf(pHdr, "Content-Type: %s" CRLF, zAltMimetype); |
| 160 | }else if( g.fHttpTrace ){ |
| 161 | blob_appendf(pHdr, "Content-Type: application/x-fossil-debug" CRLF); |
| 162 | }else{ |
| 163 | blob_appendf(pHdr, "Content-Type: application/x-fossil" CRLF); |
| 164 | } |
| 165 | blob_appendf(pHdr, "Content-Length: %d" CRLF, blob_size(pPayload)); |
| 166 | } |
| 167 | blob_append(pHdr, CRLF, CRLF_SZ); |
| 168 | } |
| 169 | |
| 170 | /* |
| 171 | ** Use Fossil credentials for HTTP Basic Authorization prompt |
| 172 | */ |
| @@ -541,10 +552,16 @@ | |
| 541 | if( rc!=200 && rc!=301 && rc!=302 && rc!=307 && rc!=308 ){ |
| 542 | int ii; |
| 543 | for(ii=7; zLine[ii] && zLine[ii]!=' '; ii++){} |
| 544 | while( zLine[ii]==' ' ) ii++; |
| 545 | fossil_warning("server says: %s", &zLine[ii]); |
| 546 | goto write_err; |
| 547 | } |
| 548 | if( iHttpVersion==0 ){ |
| 549 | closeConnection = 1; |
| 550 | }else{ |
| @@ -626,10 +643,17 @@ | |
| 626 | if( mHttpFlags & HTTP_NOCOMPRESS ) isCompressed = 0; |
| 627 | }else if( fossil_strnicmp(&zLine[14], "application/x-fossil", -1)!=0 ){ |
| 628 | isError = 1; |
| 629 | } |
| 630 | } |
| 631 | } |
| 632 | } |
| 633 | if( iHttpVersion<0 ){ |
| 634 | /* We got nothing back from the server. If using the ssh: protocol, |
| 635 | ** this might mean we need to add or remove the PATH=... argument |
| 636 |
| --- src/http.c | |
| +++ src/http.c | |
| @@ -46,10 +46,19 @@ | |
| 46 | /* The N value for most recent http-request-N.txt and http-reply-N.txt |
| 47 | ** trace files. |
| 48 | */ |
| 49 | static int traceCnt = 0; |
| 50 | |
| 51 | /* True to send full CRLF, not just a NL, in the HTTP headers. This is |
| 52 | ** to work around issues in some web servers. |
| 53 | */ |
| 54 | #if CRLF_SZ==1 |
| 55 | static int sendCRLF = 0; |
| 56 | #else |
| 57 | static int sendCRLF = 1; |
| 58 | #endif |
| 59 | |
| 60 | /* |
| 61 | ** Construct the "login" card with the client credentials. |
| 62 | ** |
| 63 | ** login LOGIN NONCE SIGNATURE |
| 64 | ** |
| @@ -135,38 +144,40 @@ | |
| 144 | Blob *pPayload, /* the payload that will be sent */ |
| 145 | Blob *pHdr, /* construct the header here */ |
| 146 | const char *zAltMimetype /* Alternative mimetype */ |
| 147 | ){ |
| 148 | int nPayload = pPayload ? blob_size(pPayload) : 0; |
| 149 | const char *zCRLF = sendCRLF ? "\r\n" : "\n"; |
| 150 | |
| 151 | blob_zero(pHdr); |
| 152 | blob_appendf(pHdr, "%s %s%s HTTP/1.0%s", |
| 153 | nPayload>0 ? "POST" : "GET", g.url.path, |
| 154 | g.url.path[0]==0 ? "/" : "", zCRLF); |
| 155 | if( g.url.proxyAuth ){ |
| 156 | blob_appendf(pHdr, "Proxy-Authorization: %s%s", g.url.proxyAuth, zCRLF); |
| 157 | } |
| 158 | if( g.zHttpAuth && g.zHttpAuth[0] ){ |
| 159 | const char *zCredentials = g.zHttpAuth; |
| 160 | char *zEncoded = encode64(zCredentials, -1); |
| 161 | blob_appendf(pHdr, "Authorization: Basic %s%s", zEncoded, zCRLF); |
| 162 | fossil_free(zEncoded); |
| 163 | } |
| 164 | blob_appendf(pHdr, "Host: %s%s", g.url.hostname, zCRLF); |
| 165 | blob_appendf(pHdr, "User-Agent: %s%s", get_user_agent(), zCRLF); |
| 166 | if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH%s", zCRLF); |
| 167 | if( nPayload ){ |
| 168 | if( zAltMimetype ){ |
| 169 | blob_appendf(pHdr, "Content-Type: %s", zAltMimetype); |
| 170 | }else if( g.fHttpTrace ){ |
| 171 | blob_appendf(pHdr, "Content-Type: application/x-fossil-debug"); |
| 172 | }else{ |
| 173 | blob_appendf(pHdr, "Content-Type: application/x-fossil"); |
| 174 | } |
| 175 | blob_appendf(pHdr, "%sContent-Length: %d%s", |
| 176 | zCRLF, blob_size(pPayload), zCRLF); |
| 177 | } |
| 178 | blob_appendf(pHdr, "%s", zCRLF); |
| 179 | } |
| 180 | |
| 181 | /* |
| 182 | ** Use Fossil credentials for HTTP Basic Authorization prompt |
| 183 | */ |
| @@ -541,10 +552,16 @@ | |
| 552 | if( rc!=200 && rc!=301 && rc!=302 && rc!=307 && rc!=308 ){ |
| 553 | int ii; |
| 554 | for(ii=7; zLine[ii] && zLine[ii]!=' '; ii++){} |
| 555 | while( zLine[ii]==' ' ) ii++; |
| 556 | fossil_warning("server says: %s", &zLine[ii]); |
| 557 | if( rc==400 && sendCRLF==0 && strstr(zLine,"Bad Request")!=0 ){ |
| 558 | sendCRLF = 1; |
| 559 | transport_close(&g.url); |
| 560 | return http_exchange(pSend, pReply, mHttpFlags, |
| 561 | maxRedirect, zAltMimetype); |
| 562 | } |
| 563 | goto write_err; |
| 564 | } |
| 565 | if( iHttpVersion==0 ){ |
| 566 | closeConnection = 1; |
| 567 | }else{ |
| @@ -626,10 +643,17 @@ | |
| 643 | if( mHttpFlags & HTTP_NOCOMPRESS ) isCompressed = 0; |
| 644 | }else if( fossil_strnicmp(&zLine[14], "application/x-fossil", -1)!=0 ){ |
| 645 | isError = 1; |
| 646 | } |
| 647 | } |
| 648 | }else if( sendCRLF==1 |
| 649 | && rc==200 |
| 650 | && fossil_strnicmp(zLine, "server: ", 8)==0 ){ |
| 651 | fossil_warning("Server bug work-around: %s requires CRLF line endings " |
| 652 | "contra RFC-2616 section 19.3 paragraph 3", |
| 653 | zLine+8); |
| 654 | sendCRLF = 2; |
| 655 | } |
| 656 | } |
| 657 | if( iHttpVersion<0 ){ |
| 658 | /* We got nothing back from the server. If using the ssh: protocol, |
| 659 | ** this might mean we need to add or remove the PATH=... argument |
| 660 |