rpm/neon/test/request.c

424 lines
9.2 KiB
C

/*
HTTP request handling tests
Copyright (C) 2001, Joe Orton <joe@manyfish.co.uk>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "config.h"
#include <sys/types.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "ne_request.h"
#include "ne_socket.h"
#include "tests.h"
#include "child.h"
static char buffer[BUFSIZ];
static int clength;
static int discard_request(nsocket *sock)
{
NE_DEBUG(NE_DBG_HTTP, "Discarding request...\n");
do {
ON(sock_readline(sock, buffer, 1024) < 0);
if (strncasecmp(buffer, "content-length:", 15) == 0) {
clength = atoi(buffer + 16);
}
NE_DEBUG(NE_DBG_HTTP, "[req] %s", buffer);
} while (strcmp(buffer, EOL) != 0);
return OK;
}
#define RESP200 "HTTP/1.1 200 OK\r\n" "Server: neon-test-server\r\n"
#define TE_CHUNKED "Transfer-Encoding: chunked\r\n"
static int single_serve_string(nsocket *s, void *userdata)
{
const char *str = userdata;
ON(discard_request(s));
ON(sock_send_string(s, str));
return OK;
}
/* takes response body chunks and appends them to a buffer. */
static void collector(void *ud, const char *data, size_t len)
{
ne_buffer *buf = ud;
ne_buffer_append(buf, data, len);
}
#define ONREQ(x) do { int _ret = (x); if (_ret) { snprintf(on_err_buf, 500, "%s line %d: HTTP error:\n%s", __FUNCTION__, __LINE__, ne_get_error(sess)); i_am(on_err_buf); return FAIL; } } while (0);
typedef ne_request *(*construct_request)(ne_session *sess, void *userdata);
static ne_request *construct_get(ne_session *sess, void *userdata)
{
ne_request *r = ne_request_create(sess, "GET", "/");
ne_buffer *buf = userdata;
ne_add_response_body_reader(r, ne_accept_2xx, collector, buf);
return r;
}
static int run_request(ne_session *sess, int status,
construct_request cb, void *userdata)
{
ne_request *req = cb(sess, userdata);
ON(req == NULL);
ONREQ(ne_request_dispatch(req));
ONN("response status", ne_get_status(req)->code != status);
ne_request_destroy(req);
return OK;
}
static int expect_response(const char *expect, server_fn fn, void *userdata)
{
ne_session *sess = ne_session_create();
ne_buffer *buf = ne_buffer_create();
int ret;
ON(sess == NULL || buf == NULL);
ON(ne_session_server(sess, "localhost", 7777));
ON(spawn_server(7777, fn, userdata));
ret = run_request(sess, 200, construct_get, buf);
if (ret) {
reap_server();
return ret;
}
ON(await_server());
ONN("response body match", strcmp(buf->data, expect));
ne_session_destroy(sess);
return OK;
}
static int single_get_eof(void)
{
return expect_response("a", single_serve_string,
RESP200
"Connection: close\r\n"
"\r\n"
"a");
}
static int single_get_clength(void)
{
return expect_response("a", single_serve_string,
RESP200
"Content-Length: 1\r\n"
"\r\n"
"a"
"bbbbbbbbasdasd");
}
static int single_get_chunked(void)
{
return expect_response("a", single_serve_string,
RESP200 TE_CHUNKED
"\r\n"
"1\r\n"
"a\r\n"
"0\r\n" "\r\n"
"g;lkjalskdjalksjd");
}
#define CHUNK(len, data) #len "\r\n" data "\r\n"
#define ABCDE_CHUNKS CHUNK(1, "a") CHUNK(1, "b") \
CHUNK(1, "c") CHUNK(1, "d") \
CHUNK(1, "e") CHUNK(0, "")
static int chunk_syntax_1(void)
{
/* lots of little chunks. */
ON(expect_response("abcde", single_serve_string,
RESP200 TE_CHUNKED
"\r\n"
ABCDE_CHUNKS));
return OK;
}
static int chunk_syntax_2(void)
{
/* leading zero's */
ON(expect_response("0123456789abcdef", single_serve_string,
RESP200 TE_CHUNKED
"\r\n"
"000000010\r\n" "0123456789abcdef\r\n"
"000000000\r\n" "\r\n"));
return OK;
}
static int chunk_syntax_3(void)
{
/* chunk-extensions. */
ON(expect_response("0123456789abcdef", single_serve_string,
RESP200 TE_CHUNKED
"\r\n"
"000000010; foo=bar; norm=fish\r\n"
"0123456789abcdef\r\n"
"000000000\r\n" "\r\n"));
return OK;
}
static int chunk_syntax_4(void)
{
/* trailers. */
ON(expect_response("abcde", single_serve_string,
RESP200 TE_CHUNKED
"\r\n"
"00000005; foo=bar; norm=fish\r\n"
"abcde\r\n"
"000000000\r\n"
"X-Hello: world\r\n"
"X-Another: header\r\n"
"\r\n"));
return OK;
}
static int chunk_syntax_5(void)
{
/* T-E dominates over C-L. */
ON(expect_response("abcde", single_serve_string,
RESP200 TE_CHUNKED
"Content-Length: 300\r\n"
"\r\n"
ABCDE_CHUNKS));
return OK;
}
static int fold_headers(void)
{
ON(expect_response("abcde", single_serve_string,
RESP200 "Content-Length: \r\n 5\r\n"
"\r\n"
"abcde"));
return OK;
}
static int fold_many_headers(void)
{
ON(expect_response("abcde", single_serve_string,
RESP200 "Content-Length: \r\n \r\n \r\n \r\n 5\r\n"
"\r\n"
"abcde"));
return 0;
}
static void mh_header(void *ctx, const char *value)
{
int *state = ctx;
static const char *hdrs[] = { "jim", "jab", "jar" };
if (*state < 0 || *state > 2) {
/* already failed. */
return;
}
if (strcmp(value, hdrs[*state]))
*state = -*state;
else
(*state)++;
}
/* check headers callbacks are working correctly. */
static int multi_header(void)
{
ne_session *sess = ne_session_create();
ne_request *req;
int ret, state = 0;
ON(sess == NULL);
ON(ne_session_server(sess, "localhost", 7777));
ON(spawn_server(7777, single_serve_string,
RESP200
"X-Header: jim\r\n"
"x-header: jab\r\n"
"x-Header: jar\r\n"
"Content-Length: 0\r\n\r\n"));
req = ne_request_create(sess, "GET", "/");
ON(req == NULL);
ne_add_response_header_handler(req, "x-header", mh_header, &state);
ret = ne_request_dispatch(req);
if (ret) {
reap_server();
ONREQ(ret);
ONN("shouldn't get here.", 1);
}
ON(await_server());
ON(state != 3);
ne_request_destroy(req);
ne_session_destroy(sess);
return OK;
}
struct body {
char *body;
size_t size;
};
static int want_body(nsocket *sock, void *userdata)
{
struct body *b = userdata;
char *buf = ne_malloc(b->size);
clength = 0;
CALL(discard_request(sock));
ONN("request has c-l header", clength == 0);
ONN("request length", clength != (int)b->size);
NE_DEBUG(NE_DBG_HTTP, "reading body of %d bytes...\n", b->size);
ON(sock_fullread(sock, buf, b->size));
ON(sock_send_string(sock, RESP200 "Content-Length: 0\r\n\r\n"));
ON(memcmp(buf, b->body, b->size));
return OK;
}
static ssize_t provide_body(void *userdata, char *buf, size_t buflen)
{
static const char *pnt;
static size_t left;
struct body *b = userdata;
if (buflen == 0) {
pnt = b->body;
left = b->size;
} else {
if (left < buflen) buflen = left;
memcpy(buf, pnt, buflen);
left -= buflen;
}
return buflen;
}
static int send_bodies(void)
{
int n, m;
#define B(x) { x, strlen(x) }
struct body bodies[] = {
B("abcde"),
{ "\0\0\0\0\0\0", 6 },
{ NULL, 50000 },
{ NULL }
};
#define BIG 2
/* make the body with some cruft. */
bodies[BIG].body = ne_malloc(bodies[BIG].size);
for (n = 0; n < bodies[BIG].size; n++) {
bodies[BIG].body[n] = (char)n%80;
}
for (m = 0; m < 2; m++) {
for (n = 0; bodies[n].body != NULL; n++) {
ne_session *sess = ne_session_create();
ne_request *req;
ON(sess == NULL);
ON(ne_session_server(sess, "localhost", 7777));
ON(spawn_server(7777, want_body, &(bodies[n])));
req = ne_request_create(sess, "PUT", "/");
ON(req == NULL);
if (m == 0) {
ne_set_request_body_buffer(req, bodies[n].body, bodies[n].size);
} else {
ne_set_request_body_provider(req, bodies[n].size,
provide_body, &bodies[n]);
}
ONREQ(ne_request_dispatch(req));
CALL(await_server());
ne_request_destroy(req);
ne_session_destroy(sess);
}
}
return OK;
}
/* TODO: */
/* test that header folding is bounded, e.g. have the server process do:
* send("Foo: norman");
* while(1) send(" Jim\r\n");
*/
/* test sending request body. */
static int send_body(void)
{
return FAIL;
}
test_func tests[] = {
lookup_localhost,
single_get_clength,
single_get_eof,
single_get_chunked,
chunk_syntax_1,
chunk_syntax_2,
chunk_syntax_3,
chunk_syntax_4,
chunk_syntax_5,
fold_headers,
fold_many_headers,
multi_header,
send_bodies,
NULL,
send_body,
NULL
};