-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
libcurl fails when buffering trailing headers when there's a paused response body write #1354
Comments
Have you tried to make any sort of reproducible example for this? It is a rather complicated scenario what you describe, so getting code to debug would help a lot! And ideally to add to the test suite. |
Sure, here you go: // file: curlrepro.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <curl/curl.h>
#include <pthread.h>
static size_t write_callback(void *ptr, size_t size, size_t nmemb, void *userp)
{
return size * nmemb;
//return CURL_WRITEFUNC_PAUSE;
}
void* acceptConnection(void* listenfdPtr)
{
struct sockaddr_in clientaddr;
int clientaddrlen = sizeof(clientaddr);
int fd = accept(*(int*)listenfdPtr, (struct sockaddr*)&clientaddr, &clientaddrlen);
if (fd < 0) { perror("accept failed"); exit(-1); }
char* response =
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: chunked\r\n"
"Trailer: MyCoolTrailerHeader\r\n"
"\r\n"
"4\r\n"
"data\r\n"
"0\r\n"
"MyCoolTrailerHeader: amazingtrailer\r\n"
"\r\n";
int r = write(fd, response, strlen(response));
if (r < 0) { perror("write failed"); exit(-1); }
shutdown(fd, SHUT_WR);
return NULL;
}
int main(void)
{
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
if (listenfd < 0) { perror("socket failed"); return -1; }
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = 0;
int addrlen = sizeof(addr);
if (bind(listenfd, (struct sockaddr*)&addr, addrlen) < 0) { perror("bind failed"); return -1; }
if (getsockname(listenfd, (struct sockaddr*)&addr, (socklen_t*)&addrlen) < 0) { perror("getsockname failed"); return -1; }
if (listen(listenfd, 100) < 0) { perror("listen failed"); return -1; }
pthread_t acceptThread;
int err = pthread_create(&acceptThread, NULL, acceptConnection, &listenfd);
if (err != 0) { printf("pthread_create failed: %d\n", err); return -1; }
char url[100];
sprintf(url, "http://localhost:%d/", ntohs(addr.sin_port));
curl_global_init(CURL_GLOBAL_ALL);
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
CURLcode res = curl_easy_perform(curl);
printf("curl_easy_perform returned: %s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
curl_global_cleanup();
return 0;
} I compile it with:
The above runs successfully:
If I then change the write_callback implementation to switch the line that's commented out (commenting the one that returns the correct size and uncommenting the one that returns CURL_WRITEFUNC_PAUSE, I get:
noting in particular the pause followed by the failure:
If I then go back to the repro and just comment out the line:
things go back to working successfully, though obviously hanging as expected since my write callback paused everything:
|
Perfect, thanks a lot for the code. I can reproduce this issue and I see the problem clearly. I'll work on a fix. I've also managed to convert your code into a test case for the test suite for inclusion once I get somewhere on this. |
When receiving chunked encoded data with trailers, and the write callback returns PAUSE, there might be both body and header to store to resend on unpause. Previously libcurl returned error for that case. Added test case 1540 to verify. Reported-by: Stephen Toub Ref: #1354
@stephentoub, it would be great if you can try that PR #1357 on your case and see if it works as good as it does for my test case! |
@bagder, thanks for the fast turnaround! I pulled down the PR, tried it out, and it does address the problem I was hitting. |
Lovely, thanks for confirming @stephentoub. I'll get this landed once everything looks fine. |
When receiving chunked encoded data with trailers, and the write callback returns PAUSE, there might be both body and header to store to resend on unpause. Previously libcurl returned error for that case. Added test case 1540 to verify. Reported-by: Stephen Toub Fixes #1354
Using a build from recent libcurl sources (7.53.0-DEV) on Ubuntu 16.10, it appears libcurl has trouble with the following situation:
For example, if the HTTP response is:
and the write callback returns CURL_WRITEFUNC_PAUSE when handed the body data, libcurl ends up failing here:
curl/lib/transfer.c
Lines 638 to 640 in 66de563
It looks like this is because it's processing a buffer that contains both the chunk data and the trailing header, so after issuing the pausewrite and getting a CURLE_OK from that, it continues to process the remainder of the buffer, and ends up here while processing the trailer:
curl/lib/http_chunks.c
Line 275 in 66de563
That then ends up in Curl_client_chop_write here:
curl/lib/sendf.c
Lines 517 to 522 in 66de563
and since the type of the data buffered is for the body but it's now trying to buffer header data, this fails as "major internal confusion", returns CURLE_RECV_ERROR, which then gets translated up the call stack to a CHUNKE_WRITE_ERROR and then to the CURLE_WRITE_ERROR, and the request fails.
The text was updated successfully, but these errors were encountered: