1/*
2   Tests for high-level HTTP interface (ne_basic.h)
3   Copyright (C) 2002-2008, Joe Orton <joe@manyfish.co.uk>
4
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2 of the License, or
8   (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19*/
20
21#include "config.h"
22
23#include <sys/types.h>
24
25#ifdef HAVE_STDLIB_H
26#include <stdlib.h>
27#endif
28#ifdef HAVE_UNISTD_H
29#include <unistd.h>
30#endif
31
32#include <fcntl.h>
33
34#include "ne_basic.h"
35
36#include "tests.h"
37#include "child.h"
38#include "utils.h"
39
40static int content_type(void)
41{
42    int n;
43    static const struct {
44	const char *value, *type, *subtype, *charset;
45    } ctypes[] = {
46	{ "foo/bar", "foo", "bar", NULL },
47	{ "foo/bar  ", "foo", "bar", NULL },
48	{ "application/xml", "application", "xml", NULL },
49	/* text/ subtypes default to charset ISO-8859-1, per 2616. */
50	{ "text/lemon", "text", "lemon", "ISO-8859-1" },
51        /* text/xml defaults to charset us-ascii, per 3280 */
52        { "text/xml", "text", "xml", "us-ascii" },
53#undef TXU
54#define TXU "text", "xml", "utf-8"
55	/* 2616 doesn't *say* that charset can be quoted, but bets are
56	 * that some servers do it anyway. */
57	{ "text/xml; charset=utf-8", TXU },
58	{ "text/xml; charset=utf-8; foo=bar", TXU },
59	{ "text/xml;charset=utf-8", TXU },
60	{ "text/xml ;charset=utf-8", TXU },
61	{ "text/xml;charset=utf-8;foo=bar", TXU },
62	{ "text/xml; foo=bar; charset=utf-8", TXU },
63	{ "text/xml; foo=bar; charset=utf-8; bar=foo", TXU },
64	{ "text/xml; charset=\"utf-8\"", TXU },
65	{ "text/xml; charset='utf-8'", TXU },
66	{ "text/xml; foo=bar; charset=\"utf-8\"; bar=foo", TXU },
67#undef TXU
68	/* badly quoted charset should come out as NULL */
69	{ "foo/lemon; charset=\"utf-8", "foo", "lemon", NULL },
70	{ NULL }
71    };
72
73    for (n = 0; ctypes[n].value != NULL; n++) {
74        ne_content_type ct;
75        ne_session *sess;
76        ne_request *req;
77        char resp[200];
78        int rv;
79
80        ct.type = ct.subtype = ct.charset = ct.value = "unset";
81
82        ne_snprintf(resp, sizeof resp,
83                    "HTTP/1.0 200 OK\r\n" "Content-Length: 0\r\n"
84                    "Content-Type: %s\r\n" "\r\n", ctypes[n].value);
85
86        CALL(make_session(&sess, single_serve_string, resp));
87
88        req = ne_request_create(sess, "GET", "/anyfoo");
89        ONREQ(ne_request_dispatch(req));
90        rv = ne_get_content_type(req, &ct);
91
92        ONV(rv == 0 && !ctypes[n].type,
93            ("expected c-t parse failure for %s", ctypes[n].value));
94
95        ONV(rv != 0 && ctypes[n].type,
96            ("c-t parse failure %d for %s", rv, ctypes[n].value));
97
98        ne_request_destroy(req);
99        ne_session_destroy(sess);
100        CALL(await_server());
101
102        if (rv) continue;
103
104	ONV(strcmp(ct.type, ctypes[n].type),
105	    ("for `%s': type was `%s'", ctypes[n].value, ct.type));
106
107	ONV(strcmp(ct.subtype, ctypes[n].subtype),
108	    ("for `%s': subtype was `%s'", ctypes[n].value, ct.subtype));
109
110	ONV(ctypes[n].charset && ct.charset == NULL,
111	    ("for `%s': charset unset", ctypes[n].value));
112
113	ONV(ctypes[n].charset == NULL && ct.charset != NULL,
114	    ("for `%s': unexpected charset `%s'", ctypes[n].value,
115	     ct.charset));
116
117	ONV(ctypes[n].charset && ct.charset &&
118	    strcmp(ctypes[n].charset, ct.charset),
119	    ("for `%s': charset was `%s'", ctypes[n].value, ct.charset));
120
121	ne_free(ct.value);
122    }
123
124    return OK;
125}
126
127/* Do ranged GET for range 'start' to 'end'; with 'resp' as response.
128 * If 'fail' is non-NULL, expect ne_get_range to fail, and fail the
129 * test with given message if it doesn't. */
130static int do_range(off_t start, off_t end, const char *fail,
131		    char *resp)
132{
133    ne_session *sess;
134    ne_content_range range = {0};
135    int fd, ret;
136
137    CALL(make_session(&sess, single_serve_string, resp));
138
139    range.start = start;
140    range.end = end;
141
142    fd = open("/dev/null", O_WRONLY);
143
144    ret = ne_get_range(sess, "/foo", &range, fd);
145
146    close(fd);
147    CALL(await_server());
148
149    if (fail) {
150#if 0
151	t_warning("error was %s", ne_get_error(sess));
152#endif
153	ONV(ret == NE_OK, ("%s", fail));
154    } else {
155	ONREQ(ret);
156    }
157
158    ne_session_destroy(sess);
159    return OK;
160}
161
162static int get_range(void)
163{
164    return do_range(1, 10, NULL,
165		    "HTTP/1.1 206 Widgets\r\n" "Connection: close\r\n"
166		    "Content-Range: bytes 1-10/10\r\n"
167		    "Content-Length: 10\r\n\r\nabcdefghij");
168}
169
170static int get_eof_range(void)
171{
172    return do_range(1, -1, NULL,
173		    "HTTP/1.1 206 Widgets\r\n" "Connection: close\r\n"
174		    "Content-Range: bytes 1-10/10\r\n"
175		    "Content-Length: 10\r\n\r\nabcdefghij");
176}
177
178static int fail_range_length(void)
179{
180    return do_range(1, 10, "range response length mismatch should fail",
181		    "HTTP/1.1 206 Widgets\r\n" "Connection: close\r\n"
182		    "Content-Range: bytes 1-2/2\r\n"
183		    "Content-Length: 2\r\n\r\nab");
184}
185
186static int fail_range_units(void)
187{
188    return do_range(1, 2, "range response units check should fail",
189		    "HTTP/1.1 206 Widgets\r\n" "Connection: close\r\n"
190		    "Content-Range: fish 1-2/2\r\n"
191		    "Content-Length: 2\r\n\r\nab");
192}
193
194static int fail_range_notrange(void)
195{
196    return do_range(1, 2, "non-ranged response should fail",
197		    "HTTP/1.1 200 Widgets\r\n" "Connection: close\r\n"
198		    "Content-Range: bytes 1-2/2\r\n"
199		    "Content-Length: 2\r\n\r\nab");
200}
201
202static int fail_range_unsatify(void)
203{
204    return do_range(1, 2, "unsatisfiable range should fail",
205		    "HTTP/1.1 416 No Go\r\n" "Connection: close\r\n"
206		    "Content-Length: 2\r\n\r\nab");
207}
208
209static int dav_capabilities(void)
210{
211    static const struct {
212	const char *hdrs;
213	unsigned int class1, class2, exec;
214    } caps[] = {
215	{ "DAV: 1,2\r\n", 1, 1, 0 },
216	{ "DAV: 1 2\r\n", 0, 0, 0 },
217	/* these aren't strictly legal DAV: headers: */
218	{ "DAV: 2,1\r\n", 1, 1, 0 },
219	{ "DAV:  1, 2  \r\n", 1, 1, 0 },
220	{ "DAV: 1\r\nDAV:2\r\n", 1, 1, 0 },
221	{ NULL, 0, 0, 0 }
222    };
223    char resp[BUFSIZ];
224    int n;
225
226    for (n = 0; caps[n].hdrs != NULL; n++) {
227	ne_server_capabilities c = {0};
228	ne_session *sess;
229
230	ne_snprintf(resp, BUFSIZ, "HTTP/1.0 200 OK\r\n"
231		    "Connection: close\r\n"
232		    "%s" "\r\n", caps[n].hdrs);
233
234	CALL(make_session(&sess, single_serve_string, resp));
235
236	ONREQ(ne_options(sess, "/foo", &c));
237
238	ONV(c.dav_class1 != caps[n].class1,
239	    ("class1 was %d not %d", c.dav_class1, caps[n].class1));
240	ONV(c.dav_class2 != caps[n].class2,
241	    ("class2 was %d not %d", c.dav_class2, caps[n].class2));
242	ONV(c.dav_executable != caps[n].exec,
243	    ("class2 was %d not %d", c.dav_executable, caps[n].exec));
244
245	CALL(await_server());
246
247        ne_session_destroy(sess);
248    }
249
250    return OK;
251}
252
253static int get(void)
254{
255    ne_session *sess;
256    int fd;
257
258    CALL(make_session(&sess, single_serve_string,
259                      "HTTP/1.0 200 OK\r\n"
260                      "Content-Length: 5\r\n"
261                      "\r\n"
262                      "abcde"));
263
264    fd = open("/dev/null", O_WRONLY);
265    ONREQ(ne_get(sess, "/getit", fd));
266    close(fd);
267
268    ne_session_destroy(sess);
269    CALL(await_server());
270
271    return OK;
272}
273
274ne_test tests[] = {
275    T(lookup_localhost),
276    T(content_type),
277    T(get_range),
278    T(get_eof_range),
279    T(fail_range_length),
280    T(fail_range_units),
281    T(fail_range_notrange),
282    T(fail_range_unsatify),
283    T(dav_capabilities),
284    T(get),
285    T(NULL)
286};
287
288