/* 
   HTTP request handling tests
   Copyright (C) 2001-2002, 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"
#include "utils.h"

static char buffer[BUFSIZ];

static ne_session *def_sess;
static ne_request *def_req;

static int prepare_request(server_fn fn, void *ud)
{
    static char uri[100];

    def_sess = ne_session_create();

    sprintf(uri, "/test%d", test_num);
    ne_session_server(def_sess, "localhost", 7777);

    def_req = ne_request_create(def_sess, "GET", uri);

    CALL(spawn_server(7777, fn, ud));

    return OK;
}

static int finish_request(void)
{
    ne_request_destroy(def_req);
    ne_session_destroy(def_sess);
    return await_server();
}

#define RESP200 "HTTP/1.1 200 OK\r\n" "Server: neon-test-server\r\n"
#define TE_CHUNKED "Transfer-Encoding: chunked\r\n"

/* 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);
}

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 chunks(void)
{
    /* lots of little chunks. */
    ON(expect_response("abcde", single_serve_string,
		       RESP200 TE_CHUNKED
		       "\r\n"
		       ABCDE_CHUNKS));
    return OK;
}

static int te_header(void)
{
    ON(expect_response("abcde", single_serve_string,
		       RESP200 "Transfer-Encoding: CHUNKED\r\n"
		       "\r\n" ABCDE_CHUNKS));
    return OK;    
}

static int chunk_numeric(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_extensions(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_trailers(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 te_over_clength(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 serve_twice(nsocket *sock, void *userdata)
{
    const char *resp = userdata;
    
    CALL(discard_request(sock));
    sock_send_string(sock, resp);

    CALL(discard_request(sock));
    sock_send_string(sock, resp);

    return OK;
}

static int persist(void)
{
    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, serve_twice, RESP200
		    "Content-Length: 5\r\n\r\n"
		    "abcde"));
    
    ret = run_request(sess, 200, construct_get, buf);
    if (ret) {
	reap_server();
	return ret;
    }
    
    ONN("response body match", strcmp(buf->data, "abcde"));

    /* Run it again. */
    ne_buffer_clear(buf);
    ret = run_request(sess, 200, construct_get, buf);
    if (ret) {
	reap_server();
	return ret;
    }

    ON(await_server());

    ONN("response #2 body match", strcmp(buf->data, "abcde"));

    return OK;
}

/* Emulates a persistent connection timeout on the server. This tests
 * the timeout occuring after between 1 and 10 requests down the
 * connection. */
static int persist_timeout(void)
{
    ne_session *sess = ne_session_create();
    ne_buffer *buf = ne_buffer_create();
    int ret, n;
    struct many_serve_args args;

    ON(sess == NULL || buf == NULL);
    ON(ne_session_server(sess, "localhost", 7777));

    args.str = RESP200 "Content-Length: 5\r\n\r\n" "abcde";
    
    for (args.count = 1; args.count < 10; args.count++) {

	ON(spawn_server(7777, many_serve_string, &args));

	for (n = 0; n < args.count; n++) {
	    
	    ret = run_request(sess, 200, construct_get, buf);
	    if (ret) {
		reap_server();
		ONV(1, ("%d of %d, request failed: %s", n, args.count,
			ne_get_error(sess)));
	    }
	    
	    if (strcmp(buf->data, "abcde")) {
		reap_server();
		ONV(1, ("%d of %d, response body mismatch", n, args.count));
	    }

	    /* Ready for next time. */
	    ne_buffer_clear(buf);
	}

	ON(await_server());

    }


    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 s1xx_args {
    int count;
    int hdrs;
};

static int serve_1xx(nsocket *sock, void *ud)
{
    struct s1xx_args *args = ud;
    CALL(discard_request(sock));
    
    do {
	if (args->hdrs) {
	    sock_send_string(sock, "HTTP/1.1 100 Continue\r\n"
			     "Random: header\r\n"
			     "Another: header\r\n\r\n");
	} else {
	    sock_send_string(sock, "HTTP/1.1 100 Continue\r\n\r\n");
	}
    } while (--args->count > 0);
    
    sock_send_string(sock, RESP200 "Content-Length: 0\r\n\r\n");
    
    return OK;
}

#define sess def_sess

static int skip_interim_1xx(void)
{
    struct s1xx_args args = {0, 0};
    ON(prepare_request(serve_1xx, &args));
    ONREQ(ne_request_dispatch(def_req));
    return finish_request();
}

static int skip_many_1xx(void)
{
    struct s1xx_args args = {5, 0};
    ON(prepare_request(serve_1xx, &args));
    ONREQ(ne_request_dispatch(def_req));
    return finish_request();
}

static int skip_1xx_hdrs(void)
{
    struct s1xx_args args = {5, 5};
    ON(prepare_request(serve_1xx, &args));
    ONREQ(ne_request_dispatch(def_req));
    return finish_request();
}

#undef sess

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 %" NE_FMT_SIZE_T " 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)
{
    unsigned int n, m;

    struct body bodies[] = { 
	{ "abcde", 5 }, 
	{ "\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;
}

static int serve_infinite_headers(nsocket *sock, void *userdata)
{
    CALL(discard_request(sock));

    sock_send_string(sock, RESP200);
    
    for (;;) {
	sock_send_string(sock, "x-foo: bar\r\n");
    }

    return 0;	
}

/* Utility function: run a request using the given server fn, and the
 * request should fail. */
static int fail_request(int with_body, server_fn fn, void *ud, int forever)
{
    ne_session *sess = ne_session_create();
    ne_request *req;
    int ret;

    ON(sess == NULL);
    ne_session_server(sess, "localhost", 7777);
    
    if (forever) {
	ON(spawn_server_repeat(7777, fn, ud, 100));
    } else {
	ON(spawn_server(7777, fn, ud));
    }
    
    req = ne_request_create(sess, "GET", "/");
    ON(req == NULL);

    if (with_body) {
	static const char *body = "random stuff";
	
	ne_set_request_body_buffer(req, body, strlen(body));
    }

    /* request should fail. */
    ret = ne_request_dispatch(req);
    ONN("request succeeded", ret == NE_OK);

    if (!forever) {
	/* reap the server, don't care what it's doing. */
	reap_server();
    }
 
    ne_request_destroy(req);
    ne_session_destroy(sess);
   
    return OK;    
}

static int unbounded_headers(void)
{
    return fail_request(0, serve_infinite_headers, NULL, 0);
}

static int serve_non_http(nsocket *sock, void *ud)
{
    sock_send_string(sock, "Hello Mum.\n");
    sock_readline(sock, buffer, BUFSIZ);
    return OK;
}

/* Test behaviour when not speaking to an HTTP server. Regression test
 * for infinite loop. */
static int not_http(void)
{
    return fail_request(0, serve_non_http, NULL, 0);
}

static int serve_infinite_folds(nsocket *sock, void *ud)
{
    sock_send_string(sock, "HTTP/1.0 200 OK\r\nFoo: bar\r\n");
    for (;;) {
	sock_send_string(sock, "  hello there.\r\n");
    }
    return OK;
}

static int unbounded_folding(void)
{
    return fail_request(0, serve_infinite_folds, NULL, 0);
}

static int serve_close(nsocket *sock, void *ud)
{
    /* do nothing; the socket will be closed. */
    return 0;
}

/* Returns non-zero if port is alive. */
static int is_alive(int port)
{
    struct in_addr addr;
    nsocket *sock;

    sock_name_lookup("localhost", &addr);
    sock = sock_connect(addr, 7777);
    if (sock == NULL)
	return 0;
    else {
	sock_close(sock);
	return 1;
    }
}

/* This is a regression test for neon 0.17.0 and earlier, which goes
 * into an infinite loop if a request with a body is sent to a server
 * which simply closes the connection. */
static int closed_connection(void)
{
    int ret;

    /* This spawns a server process which will run the 'serve_close'
     * response function 200 times, then die. This guarantees that the
     * request eventually fails... */
    CALL(fail_request(1, serve_close, NULL, 1));
    /* if server died -> infinite loop was detected. */
    ret = !is_alive(7777);
    reap_server();
    ONN("server aborted, infinite loop?", ret);
    return OK;
}

static enum {
    prog_error, /* error */
    prog_transfer, /* doing a transfer */
    prog_done /* finished. */
} prog_state = prog_transfer;

static off_t prog_last = -1, prog_total;

/* callback for send_progress. */
static void s_progress(void *userdata, off_t prog, off_t total)
{
    NE_DEBUG(NE_DBG_HTTP, "progress callback: %ld/%ld.\n", prog, total);

    switch (prog_state) {
    case prog_error:
    case prog_done:
	return;
    case prog_transfer:
	if (total != prog_total) {
	    t_context("total unexpected: %ld not %ld", total, prog_total);
	    prog_state = prog_error;
	}
	else if (prog > total) {
	    t_context("first progress was invalid (%ld/%ld)", prog, total);
	    prog_state = prog_error;
	}
	else if (prog_last != -1 && prog_last > prog) {
	    t_context("progess went backwards: %ld to %ld", prog_last, prog);
	    prog_state = prog_error;
	}
	else if (prog_last == prog) {
	    t_context("no progress made! %ld to %ld", prog_last, prog);
	    prog_state = prog_error;
	}
	else if (prog == total) {
	    prog_state = prog_done;
	}
	break;
    }
	    
    prog_last = prog;
}

static ssize_t provide_progress(void *userdata, char *buf, size_t bufsiz)
{
    int *count = userdata;

    if (*count >= 0) {
	buffer[0] = 'a';
	*count -= 1;
	return 1;
    } else {
	return 0;
    }
}

static int send_progress(void)
{
    static int count = 200;

    ON(prepare_request(single_serve_string, 
		       RESP200 "Connection: close\r\n\r\n"));

    prog_total = 200;

    ne_set_progress(def_sess, s_progress, NULL);
    ne_set_request_body_provider(def_req, count,
				 provide_progress, &count);

#define sess def_sess
    ONREQ(ne_request_dispatch(def_req));
    
    ON(finish_request());

    CALL(prog_state == prog_error);

    return OK;    
}

ne_test tests[] = {
    T(lookup_localhost),
    T(single_get_clength),
    T(single_get_eof),
    T(single_get_chunked),
    T(chunks),
    T(te_header),
    T(chunk_numeric),
    T(chunk_extensions),
    T(chunk_trailers),
    T(te_over_clength),
    T(persist),
    T(persist_timeout),
    T(send_progress),
    T(fold_headers),
    T(fold_many_headers),
    T(multi_header),
    T(skip_interim_1xx),
    T(skip_many_1xx),
    T(skip_1xx_hdrs),
    T(send_bodies),
    T(unbounded_headers),
    T(unbounded_folding),
    T(not_http),
    T(closed_connection),
    T(NULL)
};
