Hi Curlers,
for a few days i've been trying to tackle this issue and Bagder asked me on
IRC to put it up on the ML.
On my server, i'm running a cgi-bin (written in plain c, see minimal testcase
attachment ret_400.c ) which is supposed to handle incoming HTTP PUT uploads.
Under certain conditions (e.g. if argument validation fails) it may set an
HTTP status code 400 and exit. when i use the command line curl utility and
PUT a file to this cgi, i receive the appropriate error code (case 2 in
testcases.log)
In my peer application which is supposed to invoke the put operation, i could
never receive the server's error response, instead it kept calling the
READFUNC and upload forever, even though the cgi-bin had already terminated.
i hacked a simple test case (curlputtest.c), which shows that the error status
is very well received if i upload a file with a known size (test-case 4). if
the size is unknown (i generate dummy buffers to simulate our live source use
case), then the problem occurs (test-case 7). when the specified url is
faulty, then the server 404 response is received correctly no matter what
(test-cases 5+8), only when cgi-bin itself generates the status, then i don't
get it.
apparently though, i am doing something wrong in my code, because the cli curl
utility receives the 400 status just fine.
another issue that i'm having:
sp.curl->res = curl_easy_pause(sp.curl->easy, CURLPAUSE_SEND) seems to be
ineffctive too, since the READFUNC is started anyway, neither does it pause if
i return CURL_READFUNC_PAUSE there.
i'd be grateful for any suggestions :)
fraxinas
//////////////////////////////////////////////////////////////////////////////
// 1) uploading a file without unknown size and with correct arguments
$ curl -XPUT -T - "http://fox/cgi-bin/ret_400.cgi?1234567890" -vvv < ret_400.c
* About to connect() to fox port 80 (#0)
* Trying 127.0.0.1...
* Connected to fox (127.0.0.1) port 80 (#0)
> PUT /cgi-bin/ret_400.cgi?1234567890 HTTP/1.1
> User-Agent: curl/7.29.0
> Host: fox
> Accept: */*
> Transfer-Encoding: chunked
> Expect: 100-continue
>
< HTTP/1.1 100 Continue
< HTTP/1.1 201 Created
< Date: Wed, 13 Mar 2013 22:01:14 GMT
< Server: Apache/2.2.23 (Unix) mod_ssl/2.2.23 OpenSSL/1.0.1e DAV/2
< Connection: Close
< Content-Length: 22
< Content-Type: text/html
<
Created the resource
* Closing connection 0
//////////////////////////////////////////////////////////////////////////////
// 2) uploading a file without unknown size and with FALSE arguments
$ curl -XPUT -T - "http://fox/cgi-bin/ret_400.cgi?false_arguments" -vvv < ret_400.c
* About to connect() to fox port 80 (#0)
* Trying 127.0.0.1...
* Connected to fox (127.0.0.1) port 80 (#0)
> PUT /cgi-bin/ret_400.cgi?false_arguments HTTP/1.1
> User-Agent: curl/7.29.0
> Host: fox
> Accept: */*
> Transfer-Encoding: chunked
> Expect: 100-continue
>
< HTTP/1.1 100 Continue
< HTTP/1.1 400 Bad Request
< Date: Wed, 13 Mar 2013 22:01:51 GMT
< Server: Apache/2.2.23 (Unix) mod_ssl/2.2.23 OpenSSL/1.0.1e DAV/2
< Connection: Close
< Content-Length: 40
< Content-Type: text/html
<
Invalid argument data: false_arguments
* Closing connection 0
//////////////////////////////////////////////////////////////////////////////
// 3) uploading a file with determined size and with correct arguments
$ ./curlputtest
GET /home/fraxinas/corelpolyguiconf.txt HTTP/1.0
X-DESTINATION: http://fox/cgi-bin/ret_400.cgi?1234567890
curl put mode on!! source path '/home/fraxinas/corelpolyguiconf.txt' destination url 'http://fox/cgi-bin/ret_400.cgi?1234567890'
HTTP/1.0 200 OK
Connection: Close
Content-Length: 144
Content-Type: video/mpeg
PAUSED OK* About to connect() to fox port 80 (#0)
* Trying 127.0.0.1...
* Connected to fox (127.0.0.1) port 80 (#0)
> PUT /cgi-bin/ret_400.cgi?1234567890 HTTP/1.1
Host: fox
Accept: */*
Content-Length: 144
Expect: 100-continue
< HTTP/1.1 100 Continue
curl read_callback nread=144
* We are completely uploaded and fine
< HTTP/1.1 201 Created
< Date: Mon, 18 Mar 2013 10:49:35 GMT
< Server: Apache/2.2.23 (Unix) mod_ssl/2.2.23 OpenSSL/1.0.1e DAV/2
< Connection: Close
< Transfer-Encoding: chunked
< Content-Type: text/html
<
QOS: peer finished <body>Created the resource
</body>
* Closing connection 0
//////////////////////////////////////////////////////////////////////////////
// 4) uploading a file with determined size and with FALSE arguments
$ ./curlputtest
GET /home/fraxinas/corelpolyguiconf.txt HTTP/1.0
X-DESTINATION: http://fox/cgi-bin/ret_400.cgi
curl put mode on!! source path '/home/fraxinas/corelpolyguiconf.txt' destination url 'http://fox/cgi-bin/ret_400.cgi'
HTTP/1.0 200 OK
Connection: Close
Content-Length: 144
Content-Type: video/mpeg
PAUSED OK* About to connect() to fox port 80 (#0)
* Trying 127.0.0.1...
* Connected to fox (127.0.0.1) port 80 (#0)
> PUT /cgi-bin/ret_400.cgi HTTP/1.1
Host: fox
Accept: */*
Content-Length: 144
Expect: 100-continue
< HTTP/1.1 100 Continue
curl read_callback nread=144
* We are completely uploaded and fine
< HTTP/1.1 400 Bad Request
< Date: Mon, 18 Mar 2013 10:49:44 GMT
< Server: Apache/2.2.23 (Unix) mod_ssl/2.2.23 OpenSSL/1.0.1e DAV/2
< Connection: Close
< Content-Length: 25
< Content-Type: text/html
<
QOS: peer error 400, <body>Invalid argument data:
</body>* Closing connection 0
//////////////////////////////////////////////////////////////////////////////
// 5) uploading a file with determined size and with incorrect URL
$ ./curlputtest
GET /home/fraxinas/corelpolyguiconf.txt HTTP/1.0
X-DESTINATION: http://fox/cgi-bin/alkvjasdv
curl put mode on!! source path '/home/fraxinas/corelpolyguiconf.txt' destination url 'http://fox/cgi-bin/alkvjasdv'
HTTP/1.0 200 OK
Connection: Close
Content-Length: 144
Content-Type: video/mpeg
PAUSED OK* About to connect() to fox port 80 (#0)
* Trying 127.0.0.1...
* Connected to fox (127.0.0.1) port 80 (#0)
> PUT /cgi-bin/alkvjasdv HTTP/1.1
Host: fox
Accept: */*
Content-Length: 144
Expect: 100-continue
< HTTP/1.1 404 Not Found
< Date: Mon, 18 Mar 2013 10:49:53 GMT
< Server: Apache/2.2.23 (Unix) mod_ssl/2.2.23 OpenSSL/1.0.1e DAV/2
< Vary: accept-language,accept-charset
< Accept-Ranges: bytes
< Connection: close
< Content-Type: text/html; charset=iso-8859-1
< Content-Language: en
<
QOS: peer error 404, <body><?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>Object not found!</title>
<link rev="made" href="mailto:[email protected]" />
<style type="text/css"><!--/*--><![CDATA[/*><!--*/
body { color: #000000; background-color: #FFFFFF; }
a:link { color: #0000CC; }
p, address {margin-left: 3em;}
span {font-size: smaller;}
/*]]>*/--></style>
</head>
<body>
<h1>Object not found!</h1>
<p>
The requested URL was not found on this server.
If you entered the URL manually please check your
spelling and try again.
</p>
<p>
If you think this is a server error, please contact
the <a href="mailto:[email protected]">webmaster</a>.
</p>
<h2>Error 404</h2>
<address>
<a href="/">fox</a><br />
<span>Mon Mar 18 11:49:53 2013<br />
Apache/2.2.23 (Unix) mod_ssl/2.2.23 OpenSSL/1.0.1e DAV/2</span>
</address>
</body>
</html>
</body>* Closing connection 0
//////////////////////////////////////////////////////////////////////////////
// 6) uploading a stream without predetermined size and with correct arguments
$ ./curlputtest
GET /dummy HTTP/1.0
X-DESTINATION: http://fox/cgi-bin/ret_400.cgi?1234567890
curl put mode on!! source path '/dummy' destination url 'http://fox/cgi-bin/ret_400.cgi?1234567890'
PAUSED OK* About to connect() to fox port 80 (#0)
* Trying 127.0.0.1...
* Connected to fox (127.0.0.1) port 80 (#0)
> PUT /cgi-bin/ret_400.cgi?1234567890 HTTP/1.1
Host: fox
Accept: */*
Transfer-Encoding: chunked
Expect: 100-continue
< HTTP/1.1 100 Continue
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
^C
//////////////////////////////////////////////////////////////////////////////
// 7) uploading a stream without predetermined size and with FALSE arguments
// this is the error case! it's supposed to receive the status 400 from the
// cgi-bin, but it doesn't and just keeps uploading
$ ./curlputtest
GET /dummy HTTP/1.0
X-DESTINATION: http://fox/cgi-bin/ret_400.cgi
curl put mode on!! source path '/dummy' destination url 'http://fox/cgi-bin/ret_400.cgi'
PAUSED OK* About to connect() to fox port 80 (#0)
* Trying 127.0.0.1...
* Connected to fox (127.0.0.1) port 80 (#0)
> PUT /cgi-bin/ret_400.cgi HTTP/1.1
Host: fox
Accept: */*
Transfer-Encoding: chunked
Expect: 100-continue
< HTTP/1.1 100 Continue
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
curl read_callback nread=100
^C
//////////////////////////////////////////////////////////////////////////////
// 8) uploading a stream without predetermined size and with incorrect URL
$ ./curlputtest
GET /dummy HTTP/1.0
X-DESTINATION: http://fox/cgi-bin/alkvjasdv
curl put mode on!! source path '/dummy' destination url 'http://fox/cgi-bin/alkvjasdv'
PAUSED OK* About to connect() to fox port 80 (#0)
* Trying 127.0.0.1...
* Connected to fox (127.0.0.1) port 80 (#0)
> PUT /cgi-bin/alkvjasdv HTTP/1.1
Host: fox
Accept: */*
Transfer-Encoding: chunked
Expect: 100-continue
< HTTP/1.1 404 Not Found
< Date: Mon, 18 Mar 2013 10:51:06 GMT
< Server: Apache/2.2.23 (Unix) mod_ssl/2.2.23 OpenSSL/1.0.1e DAV/2
< Vary: accept-language,accept-charset
< Accept-Ranges: bytes
< Connection: close
< Content-Type: text/html; charset=iso-8859-1
< Content-Language: en
<
QOS: peer error 404, <body><?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>Object not found!</title>
<link rev="made" href="mailto:[email protected]" />
<style type="text/css"><!--/*--><![CDATA[/*><!--*/
body { color: #000000; background-color: #FFFFFF; }
a:link { color: #0000CC; }
p, address {margin-left: 3em;}
span {font-size: smaller;}
/*]]>*/--></style>
</head>
<body>
<h1>Object not found!</h1>
<p>
The requested URL was not found on this server.
If you entered the URL manually please check your
spelling and try again.
</p>
<p>
If you think this is a server error, please contact
the <a href="mailto:[email protected]">webmaster</a>.
</p>
<h2>Error 404</h2>
<address>
<a href="/">fox</a><br />
<span>Mon Mar 18 11:51:06 2013<br />
Apache/2.2.23 (Unix) mod_ssl/2.2.23 OpenSSL/1.0.1e DAV/2</span>
</address>
</body>
</html>
</body>* Closing connection 0
$
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
char* arguments = getenv("QUERY_STRING");
if (!arguments || strlen(arguments) != 10)
{
fprintf(stdout, "Status: %i\r\nConnection: Close\r\nContent-Type: text/html\r\n\r\nInvalid argument data: %s\r\n", 400, arguments);fflush(stdout);
exit(-1);
}
fprintf(stdout,"Status: 201\r\nConnection: Close\r\nContent-Type: text/html\r\n\r\nCreated the resource\r\n");
exit(0);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <curl/curl.h>
#define MAX_LINE_LENGTH 512
typedef struct _curl_private {
char put_url[MAX_LINE_LENGTH]; /* upload-url for streaming server */
CURL *easy;
CURLM *multi;
struct curl_slist *headers;
CURLcode res;
CURLMcode mres;
} curl_private;
typedef struct _curlputtest {
curl_private *curl;
int source_fd;
int response_code;
} curlputtest;
void cleanup(curlputtest *sp)
{
if (sp->curl)
{
if (sp->curl->headers)
curl_slist_free_all (sp->curl->headers);
if (sp->curl->multi)
curl_multi_cleanup(sp->curl->multi);
if (sp->curl->easy)
curl_easy_cleanup(sp->curl->easy);
curl_global_cleanup();
free(sp->curl);
}
sp->curl = NULL;
}
static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp)
{
curlputtest *sp = (curlputtest *)userp;
size_t nread;
if ( sp->source_fd > 0 )
nread = read(sp->source_fd, ptr, size*nmemb);
else
{
nread = 100;
memset (ptr, 'X', 100);
}
if (nread == -1)
return CURL_READFUNC_ABORT;
fprintf(stdout, "curl read_callback nread=%d\n", (int)nread);fflush(stdout);
return nread;
}
static size_t write_callback(char *d, size_t n, size_t l, void *userp)
{
curlputtest *sp = (curlputtest *)userp;
long respcode;
curl_easy_getinfo(sp->curl->easy,CURLINFO_RESPONSE_CODE, &respcode);
if (respcode == 201) {
fprintf(stdout, "QOS: peer finished <body>%s</body>\n", d);fflush(stdout);
}
else {
fprintf(stdout, "QOS: peer error %lu, <body>%s</body>", respcode, d);fflush(stdout);
}
return n*l;
}
int main(int argc, char **argv)
{
char request[MAX_LINE_LENGTH], upstream_request[256];
char *c;
curlputtest sp;
sp.curl = malloc(sizeof(curl_private));
sp.curl->easy = NULL;
sp.curl->multi = NULL;
sp.curl->headers = NULL;
sp.curl->put_url[0] = '\0';
sp.source_fd = -1;
if (!fgets(request, MAX_LINE_LENGTH - 1, stdin))
goto bad_request;
if (strncmp(request, "GET /", 5))
goto bad_request;
c = strchr(request + 5, ' ');
if (!c || strncmp(c, " HTTP/1.", 7))
goto bad_request;
*c++ = 0;
c = request + 4;
while (1)
{
char option[MAX_LINE_LENGTH];
if (!fgets(option, MAX_LINE_LENGTH - 1, stdin))
break;
if (!strncasecmp(option, "X-DESTINATION: ", 15))
{
strncpy(sp.curl->put_url, option+15, strlen(option)-16);
fprintf(stdout, "curl put mode on!! source path '%s' destination url '%s'\n", c, sp.curl->put_url);fflush(stdout);
}
if (option[1] && option[strlen(option)-2] == '\r')
option[strlen(option)-2] = 0;
else
option[strlen(option)-1] = 0;
if (!*option)
break;
}
if (sp.curl->put_url[0] == '\0')
goto bad_request;
curl_global_init(CURL_GLOBAL_ALL);
sp.curl->easy = curl_easy_init();
off_t filesize = 0L;
{
char *p = c;
while ( *p != '\0') {
if (*p == '+')
*p = ' ';
p++;
}
char *path = curl_easy_unescape(sp.curl->easy, c, strlen(c), 0);
{
struct stat stInfo;
sp.response_code = 404;
if ( stat(path, &stInfo) == 0 ) {
filesize = stInfo.st_size;
sp.source_fd = open(path, O_RDONLY);
if ( sp.source_fd > 0 ) {
snprintf(upstream_request, sizeof(upstream_request), "HTTP/1.0 200 OK\r\nConnection: Close\r\nContent-Length: %ld\r\nContent-Type: video/mpeg\r\n\r\n", filesize);
write(1, upstream_request, strlen(upstream_request));
}
}
}
}
{
sp.curl->multi = curl_multi_init();
if (sp.curl->easy && sp.curl->multi) {
curl_easy_setopt(sp.curl->easy, CURLOPT_READFUNCTION, read_callback);
curl_easy_setopt(sp.curl->easy, CURLOPT_READDATA, &sp);
curl_easy_setopt(sp.curl->easy, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(sp.curl->easy, CURLOPT_WRITEDATA, &sp);
curl_easy_setopt(sp.curl->easy, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(sp.curl->easy, CURLOPT_PUT, 1L);
curl_easy_setopt(sp.curl->easy, CURLOPT_URL, sp.curl->put_url);
// sp.curl->headers = curl_slist_append(sp.curl->headers, "Transfer-Encoding: chunked");
// curl_easy_setopt(sp.curl->easy, CURLOPT_HTTPHEADER, sp.curl->headers);
// sp.curl->headers = curl_slist_append(sp.curl->headers, "Expect:");
// curl_easy_setopt(sp.curl->easy, CURLOPT_HTTPHEADER, sp.curl->headers);
if (filesize)
curl_easy_setopt(sp.curl->easy, CURLOPT_INFILESIZE_LARGE, (curl_off_t)filesize);
curl_easy_setopt(sp.curl->easy, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(sp.curl->easy, CURLOPT_STDERR, stdout);
sp.curl->mres = curl_multi_add_handle(sp.curl->multi, sp.curl->easy);
if ( sp.curl->mres != CURLM_OK )
goto curl_error;
sp.curl->res = curl_easy_pause(sp.curl->easy, CURLPAUSE_SEND);
if ( sp.curl->res != CURLE_OK )
goto curl_error;
else
fprintf(stdout, "PAUSED OK");
}
else
goto curl_error;
}
while (1)
{
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 100000;
fd_set r;
FD_ZERO(&r);
FD_SET(0, &r);
fd_set w;
FD_ZERO(&w);
int maxfd = 5, curl_maxfd;
if (sp.source_fd != -1)
{
maxfd = sp.source_fd;
FD_SET(sp.source_fd, &r);
}
{
fd_set fdwrite, fdexcep;
curl_multi_fdset(sp.curl->multi, &r, &fdwrite, &fdexcep, &curl_maxfd);
// fprintf(stdout, "curl_multi_fdset curl_maxfd=%d\n", curl_maxfd);fflush(stdout);
if ( curl_maxfd > sp.source_fd )
maxfd = curl_maxfd;
}
if (select(maxfd+1, &r, &w, 0, &timeout) < 0) {
break;
}
if (FD_ISSET(0, &r)) /* check for client disconnect */
if (read(0, request, sizeof(request)) <= 0)
break;
// if (sp.source_fd > 0 && FD_ISSET(sp.source_fd, &r))
{
{
// curl_easy_pause(sp.curl->easy, CURLPAUSE_CONT );
int curl_still_running;
sp.curl->mres = curl_multi_perform(sp.curl->multi, &curl_still_running);
long respcode;
curl_easy_getinfo(sp.curl->easy,CURLINFO_RESPONSE_CODE, &respcode);
// fprintf(stdout, "#### curl_easy_getinfo CURLINFO_RESPONSE_CODE %ld\n", respcode);fflush(stdout);
if ( sp.curl->mres != CURLM_OK )
goto curl_error;
// fprintf(stdout, "curl_still_running=%d\n", curl_still_running);fflush(stdout);
if ( curl_still_running == 0 )
{
CURLMsg *msg;
int msgs_left;
while ((msg = curl_multi_info_read(sp.curl->multi, &msgs_left))) {
// fprintf(stdout, "curl_multi_info_read msg %d data.result %d\n", msg->msg, msg->data.result);fflush(stdout);
if ( msg->msg == CURLMSG_DONE )
{
if ( msg->data.result != CURLE_OK )
{
goto destination_error;
}
}
}
break;
}
}
}
}
cleanup(&sp);
return 0;
bad_request:
printf("HTTP/1.0 400 Bad Request\r\n\r\n");
cleanup(&sp);
return 1;
curl_error:
printf("HTTP/1.0 500 Internal Server Error\r\n\r\n");
printf("curl perform failed: %s %s\n",
curl_easy_strerror(sp.curl->res), curl_multi_strerror(sp.curl->mres));
cleanup(&sp);
return 1;
destination_error:
printf("HTTP/1.0 462 Destination Unreachable\r\ncurl error code %d\r\n", sp.response_code);
cleanup(&sp);
return 1;
}
-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette: http://curl.haxx.se/mail/etiquette.html