xref: /minix/minix/tests/test82.c (revision 9f988b79)
1 /*
2  * test82: test HTTP with a remote server (is $USENETWORK="yes")
3  */
4 
5 #define DEBUG 0
6 
7 #if DEBUG
8 #define dbgprintf(...)	do { 						\
9 				fprintf(stderr, "[%s:%s:%d %d] ",	\
10 					__FILE__, __FUNCTION__,		\
11 					__LINE__, getpid());		\
12 				fprintf(stderr, __VA_ARGS__);		\
13 				fflush(stderr);				\
14 			} while (0)
15 #else
16 #define	dbgprintf(...)
17 #endif
18 
19 #include <arpa/inet.h>
20 #include <assert.h>
21 #include <netdb.h>
22 #include <netinet/in.h>
23 #include <stdarg.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/socket.h>
28 #include <sys/wait.h>
29 
30 #include "common.h"
31 
32 #define CLOSE(fd) do { assert(fd >= 0); if (close((fd)) != 0) efmt("close failed"); } while (0);
33 #define REALLOC(p, size) do { p = realloc(p, size); if (!p) efmt("realloc of %zu bytes failed", size); } while (0);
34 
35 #define HOST "test82.minix3.org"
36 #define PORT 80
37 #define PATH1 "/test1.txt"
38 #define PATH1_DATA "Hello world\n"
39 #define PATH2 "/test2.bin"
40 
41 static void callback_verify_path1(const void *data, size_t size);
42 static void callback_verify_path2(const void *data, size_t size);
43 
44 #define URL_COUNT 2
45 
46 struct url {
47 	const char *host;
48 	int port;
49 	const char *path;
50 	void (* callback_verify)(const void *data, size_t size);
51 };
52 
53 static const struct url urls[URL_COUNT] = {
54 	{ HOST, PORT, PATH1, callback_verify_path1 },
55 	{ HOST, PORT, PATH2, callback_verify_path2 },
56 };
57 
58 static int http_connect(const char *host, int port) {
59 	struct addrinfo *addr = NULL;
60 	int fd = -1;
61 	struct addrinfo hints = {
62 		.ai_family = PF_INET,
63 		.ai_socktype = SOCK_STREAM,
64 	};
65 	char serv[12];
66 
67 	assert(host);
68 
69 	snprintf(serv, sizeof(serv), "%d", port);
70 
71 	errno = 0;
72 	if (getaddrinfo(host, serv, &hints, &addr) != 0 || !addr) {
73 		efmt("host %s not found", host);
74 		goto failure;
75 	}
76 
77 	fd = socket(AF_INET, SOCK_STREAM, 0);
78 	if (fd < 0) {
79 		efmt("cannot create socket");
80 		goto failure;
81 	}
82 
83 	if (connect(fd, addr->ai_addr, addr->ai_addrlen) != 0) {
84 		efmt("cannot connect to %s:%d", host, port);
85 		goto failure;
86 	}
87 
88 	freeaddrinfo(addr);
89 	return fd;
90 
91 failure:
92 	if (fd >= 0) CLOSE(fd);
93 	if (addr) freeaddrinfo(addr);
94 	return -1;
95 }
96 
97 static void write_chunked(
98 	int fd,
99 	const char *data,
100 	size_t size,
101 	size_t chunksize) {
102 	ssize_t r;
103 	size_t s;
104 
105 	assert(fd >= 0);
106 	assert(data);
107 	assert(chunksize > 0);
108 
109 	while (size > 0) {
110 		s = chunksize;
111 		if (s > size) s = size;
112 
113 		errno = 0;
114 		r = write(fd, data, s);
115 		if (r <= 0 || (size_t) r > s) {
116 			errno = 0;
117 			efmt("write of %zu bytes failed with result %zd", s, r);
118 			break;
119 		}
120 
121 		data += r;
122 		size -= r;
123 	}
124 }
125 
126 static void http_send_request(
127 	int fd,
128 	const char *host,
129 	const char *path,
130 	size_t chunksize,
131 	int bigrequest) {
132 	char buf[8192];
133 	size_t len;
134 	int lineno;
135 
136 	assert(fd >= 0);
137 	assert(host);
138 	assert(path);
139 	assert(chunksize > 0);
140 
141 	/* http://tools.ietf.org/html/rfc2616#section-5 */
142 	len = snprintf(buf, sizeof(buf),
143 		"GET %s HTTP/1.1\r\n"
144 		"Host: %s\r\n",
145 		path, host);
146 	if (bigrequest) {
147 		lineno = 0;
148 		while (len + 24 < sizeof(buf)) {
149 			len += snprintf(buf + len, sizeof(buf) - len,
150 				"X-Padding%d: %d\r\n",
151 				lineno, lineno);
152 			lineno++;
153 		}
154 	}
155 	len += snprintf(buf + len, sizeof(buf) - len, "\r\n");
156 
157 	dbgprintf("sending request:\n%.*s", (int) len, buf);
158 	write_chunked(fd, buf, len, chunksize);
159 }
160 
161 static int is_whitespace(char c) {
162 	return c == ' ' || c == '\t';
163 }
164 
165 static int is_whitespace_or_linebreak(char c) {
166 	return is_whitespace(c) || c == '\r' || c == '\n';
167 }
168 
169 static int is_numeric(char c) {
170 	return c >= '0' && c <= '9';
171 }
172 
173 static int http_get_header_line(
174 	const char *data,
175 	size_t len,
176 	size_t *index_p,
177 	size_t *linelen_p) {
178 	int has_cr;
179 	size_t index;
180 	size_t linelen;
181 
182 	assert(data);
183 	assert(index_p);
184 	assert(*index_p <= len);
185 	assert(linelen_p);
186 
187 	/* starting the next line with whitespace means the line is continued */
188 	index = *index_p;
189 	do {
190 		while (index < len && data[index] != '\n') index++;
191 		if (index >= len) goto notfound;
192 		index++;
193 	} while (index < len && is_whitespace(data[index]));
194 
195 	/* exclude LF or CR+LF from line length */
196 	assert(index - 1 >= *index_p && data[index - 1] == '\n');
197 	has_cr = (index - 2 >= *index_p) && data[index - 2] == '\r';
198 	linelen = index - *index_p - (has_cr ? 2 : 1);
199 
200 	/* if LF is the last character in the buffer, the line may be continued
201 	 * when more data is retrieved unless we reached the end of the headers
202 	 */
203 	if (index >= len && linelen > 0) goto notfound;
204 
205 	*linelen_p = linelen;
206 	*index_p = index;
207 	return 1;
208 
209 notfound:
210 	*linelen_p = 0;
211 	*index_p = index;
212 	return 0;
213 }
214 
215 static int http_get_status_line(
216 	const char *data,
217 	size_t len,
218 	size_t *index_p,
219 	int *error_p,
220 	int *code_p) {
221 	int code, i;
222 	size_t index;
223 
224 	assert(data);
225 	assert(index_p);
226 	assert(*index_p <= len);
227 	assert(error_p);
228 	assert(*error_p == 0);
229 	assert(code_p);
230 
231 	/* skip leading whitespace/blank lines */
232 	index = *index_p;
233 	while (index < len && is_whitespace_or_linebreak(data[index])) index++;
234 
235 	/* parse version */
236 	while (index < len && !is_whitespace(data[index])) index++;
237 
238 	/* skip separator */
239 	while (index < len && is_whitespace(data[index])) index++;
240 
241 	/* parse status code */
242 	code = 0;
243 	for (i = 0; i < 3; i++) {
244 		if (index >= len) goto notfound;
245 		if (!is_numeric(data[index])) {
246 			errno = 0;
247 			efmt("HTTP error: bad status line: \"%.*s\"",
248 				(int) (index - *index_p), data + *index_p);
249 			*error_p = 1;
250 			goto notfound;
251 		}
252 		code = code * 10 + (data[index++] - '0');
253 	}
254 
255 	/* skip separator */
256 	while (index < len && is_whitespace(data[index])) index++;
257 
258 	/* parse reason phrase */
259 	while (index < len && data[index] != '\n') index++;
260 	if (index >= len) goto notfound;
261 	index++;
262 
263 	*code_p = code;
264 	*index_p = index;
265 	return 1;
266 
267 notfound:
268 	*code_p = 0;
269 	*index_p = index;
270 	return 0;
271 }
272 
273 static int http_header_is(
274 	const char *data,
275 	size_t len,
276 	size_t index,
277 	const char *name,
278 	size_t *index_value_p) {
279 	size_t namelen;
280 
281 	assert(data);
282 	assert(index <= len);
283 	assert(name);
284 	assert(index_value_p);
285 
286 	namelen = strlen(name);
287 	if (index + namelen > len) goto notfound;
288 	if (strncasecmp(data + index, name, namelen) != 0) goto notfound;
289 	index += namelen;
290 	while (index < len && is_whitespace(data[index])) index++;
291 	if (index >= len || data[index] != ':') goto notfound;
292 	index++;
293 
294 	while (index < len && is_whitespace(data[index])) index++;
295 	*index_value_p = index;
296 	return 1;
297 
298 notfound:
299 	*index_value_p = 0;
300 	return 0;
301 }
302 
303 static int http_parse_int_header(
304 	const char *data,
305 	size_t index,
306 	size_t index_end,
307 	int *value_p,
308 	int *error_p) {
309 	int value = 0;
310 
311 	assert(data);
312 	assert(index <= index_end);
313 	assert(value_p);
314 	assert(error_p);
315 	assert(!*error_p);
316 
317 	while (index < index_end && is_numeric(data[index])) {
318 		value = value * 10 + (data[index++] - '0');
319 	}
320 
321 	while (index < index_end && is_whitespace_or_linebreak(data[index])) {
322 		index++;
323 	}
324 
325 	if (index < index_end) {
326 		errno = 0;
327 		efmt("HTTP error: bad numeric header value: \"%.*s\"",
328 			(int) (index_end - index), data + index);
329 		*error_p = 1;
330 		return 0;
331 	}
332 
333 	*value_p = value;
334 	return 1;
335 }
336 
337 static int http_response_complete(
338 	const char *data,
339 	size_t len,
340 	int *error_p,
341 	int *code_p,
342 	size_t *index_body_p) {
343 	int content_length = -1;
344 	size_t index = 0, index_line;
345 	size_t index_value;
346 	size_t linelen;
347 
348 	assert(data);
349 	assert(error_p);
350 	assert(!*error_p);
351 	assert(code_p);
352 	assert(index_body_p);
353 
354 	/* parse status line */
355 	if (!http_get_status_line(data, len, &index, error_p, code_p)) {
356 		return 0;
357 	}
358 
359 	/* parse headers */
360 	for (;;) {
361 		index_line = index;
362 		if (!http_get_header_line(data, len, &index, &linelen)) {
363 			return 0;
364 		}
365 		if (linelen == 0) break;
366 		if (http_header_is(data, len, index_line,
367 			"Content-Length", &index_value)) {
368 			if (!http_parse_int_header(data, index_value,
369 				index_line + linelen, &content_length,
370 				error_p)) {
371 				return 0;
372 			}
373 		}
374 	}
375 
376 	/* do we know how long the response will be? */
377 	if (content_length < 0) {
378 		errno = 0;
379 		efmt("HTTP error: missing Content-Length header "
380 			"(maybe Transfer-Encoding is specified instead "
381 			"but this is currently unsupported)");
382 		goto error;
383 	}
384 
385 	/* check whether the amount of data is correct */
386 	if (len > index + content_length) {
387 		errno = 0;
388 		efmt("HTTP error: more data received than expected");
389 		goto error;
390 	}
391 
392 	*index_body_p = index;
393 	return len == index + content_length;
394 
395 error:
396 	*error_p = 1;
397 	*code_p = 0;
398 	*index_body_p = 0;
399 	return 0;
400 }
401 
402 static void http_recv_response(
403 	int fd,
404 	void (* callback_verify)(const void *data, size_t size),
405 	size_t chunksize) {
406 	int code;
407 	char *data;
408 	size_t datalen = 0, datasize = 0;
409 	int error = 0;
410 	size_t index_body;
411 	ssize_t r;
412 
413 	assert(fd >= 0);
414 	assert(callback_verify);
415 	assert(chunksize > 0);
416 
417 	data = NULL;
418 	for (;;) {
419 		/* make room for another chunk in the buffer if needed */
420 		if (datasize < datalen + chunksize) {
421 			datasize = (datalen + chunksize) * 2;
422 			REALLOC(data, datasize);
423 		}
424 
425 		/* read a chunk of data */
426 		errno = 0;
427 		r = read(fd, data + datalen, chunksize);
428 		if (r < 0 || (size_t) r > chunksize) {
429 			efmt("read of %zu bytes failed with result %zd",
430 				chunksize, r);
431 			goto cleanup;
432 		}
433 		datalen += r;
434 
435 		/* if we received all headers+data, we are done */
436 		if (http_response_complete(data, datalen, &error, &code,
437 			&index_body)) {
438 			break;
439 		}
440 		if (error) goto cleanup;
441 
442 		/* check for premature disconnection */
443 		if (r == 0) {
444 			errno = 0;
445 			efmt("server disconnected even though the response "
446 				"seems to be incomplete");
447 			goto cleanup;
448 		}
449 	}
450 
451 	dbgprintf("received response:\n%.*s", (int) datalen, data);
452 
453 	assert(index_body <= datalen);
454 	if (code == 200) {
455 		callback_verify(data + index_body, datalen - index_body);
456 	} else {
457 		errno = 0;
458 		efmt("unexpected HTTP status code %d", code);
459 	}
460 
461 cleanup:
462 	if (data) free(data);
463 }
464 
465 static void http_test(
466 	const struct url *url,
467 	size_t chunksize,
468 	int bigrequest,
469 	int delay,
470 	int withshutdown) {
471 	int fd;
472 
473 	assert(url);
474 	assert(chunksize > 0);
475 
476 	dbgprintf("attempting download from http://%s:%d%s, "
477 		"chunksize=%zu, bigrequest=%d, delay=%d, withshutdown=%d\n",
478 		url->host, url->port, url->path, chunksize, bigrequest,
479 		delay, withshutdown);
480 
481 	fd = http_connect(url->host, url->port);
482 	if (fd < 0) return;
483 
484 	http_send_request(fd, url->host, url->path, chunksize, bigrequest);
485 
486 	errno = 0;
487 	if (withshutdown && shutdown(fd, SHUT_WR) != 0) {
488 		efmt("shutdown failed");
489 	}
490 
491 	if (delay) sleep(1);
492 	http_recv_response(fd, url->callback_verify, chunksize);
493 
494 	CLOSE(fd);
495 
496 	dbgprintf("download attempt completed\n");
497 }
498 
499 static int child_count;
500 
501 static void http_test_fork(
502 	const struct url *url,
503 	size_t chunksize,
504 	int bigrequest,
505 	int delay,
506 	int withshutdown) {
507 	int errctold;
508 	pid_t pid;
509 
510 	assert(url);
511 	assert(chunksize > 0);
512 
513 	errno = 0;
514 	pid = fork();
515 	if (pid < 0) {
516 		efmt("fork failed");
517 		return;
518 	}
519 
520 	if (pid > 0) {
521 		child_count++;
522 		return;
523 	}
524 
525 	errctold = errct;
526 	http_test(
527 		url,
528 		chunksize,
529 		bigrequest,
530 		delay,
531 		withshutdown);
532 	assert(errct >= errctold);
533 	exit(errct - errctold);
534 }
535 
536 static void wait_all(void) {
537 	int exitcode, status;
538 	pid_t pid;
539 
540 	while (child_count > 0) {
541 		errno = 0;
542 		pid = waitpid(-1, &status, 0);
543 		if (pid <= 0) {
544 			efmt("waitpid failed");
545 			return;
546 		}
547 		if (WIFEXITED(status)) {
548 			exitcode = WEXITSTATUS(status);
549 			dbgprintf("child %d completed with exit code %d\n",
550 				(int) pid, exitcode);
551 			if (exitcode >= 0) {
552 				errct += exitcode;
553 			} else {
554 				efmt("child has negative exit code %d",
555 					exitcode);
556 			}
557 		} else if (WIFSIGNALED(status)) {
558 			dbgprintf("child %d killed by signal %d\n",
559 				(int) pid, WTERMSIG(status));
560 			efmt("child killed by signal %d", WTERMSIG(status));
561 		} else {
562 			dbgprintf("child %d gone with status 0x%x\n",
563 				(int) pid, status);
564 			efmt("child gone, but neither exit nor signal");
565 		}
566 		child_count--;
567 	}
568 
569 	errno = 0;
570 	if (waitpid(-1, &status, 0) != -1 || errno != ECHILD) {
571 		efmt("waitpid should have returned ECHILD");
572 	}
573 }
574 
575 #define OPTION_BIGREQUEST	(1 << 0)
576 #define OPTION_DELAY		(1 << 1)
577 #define OPTION_SHUTDOWN		(1 << 2)
578 
579 static void http_test_all(int multiproc) {
580 	static const size_t chunksizes[] = { 1, 1024, 65536 };
581 	static const int optionsets[] = {
582 		0,
583 		OPTION_BIGREQUEST,
584 		OPTION_DELAY,
585 		OPTION_SHUTDOWN,
586 		OPTION_BIGREQUEST | OPTION_DELAY | OPTION_SHUTDOWN,
587 	};
588 	int chunksizeindex;
589 	int options;
590 	int optionindex;
591 	int urlindex;
592 
593 	for (urlindex = 0; urlindex < URL_COUNT; urlindex++) {
594 	for (chunksizeindex = 0; chunksizeindex < 3; chunksizeindex++) {
595 	for (optionindex = 0; optionindex < 3; optionindex++) {
596 		options = optionsets[optionindex];
597 		(multiproc ? http_test_fork : http_test)(
598 			&urls[urlindex],
599 			chunksizes[chunksizeindex],
600 			options & OPTION_BIGREQUEST,
601 			options & OPTION_DELAY,
602 			options & OPTION_SHUTDOWN);
603 	}
604 	}
605 	}
606 
607 	wait_all();
608 }
609 
610 static void verify_data(
611 	const void *httpdata, size_t httpsize,
612 	const void *refdata, size_t refsize,
613 	const char *path) {
614 
615 	assert(httpdata);
616 	assert(refdata);
617 	assert(path);
618 
619 	if (httpsize != refsize) {
620 		errno = 0;
621 		efmt("download from http://%s:%d%s returned wrong number "
622 			"of bytes: %zd (expected %zd)",
623 			HOST, PORT, path, httpsize, refsize);
624 	} else if (memcmp(httpdata, refdata, refsize) != 0) {
625 		errno = 0;
626 		efmt("download from http://%s:%d%s returned wrong data",
627 			HOST, PORT, path);
628 	}
629 }
630 
631 static void callback_verify_path1(const void *data, size_t size) {
632 	verify_data(data, size, PATH1_DATA, strlen(PATH1_DATA), PATH1);
633 }
634 
635 static void callback_verify_path2(const void *data, size_t size) {
636 	unsigned short buf[65536];
637 	int i;
638 
639 	for (i = 0; i < 65536; i++) buf[i] = htons(i);
640 
641 	verify_data(data, size, buf, sizeof(buf), PATH2);
642 }
643 
644 int main(int argc, char **argv)
645 {
646 	int use_network;
647 
648 	start(82);
649 
650 	use_network = get_setting_use_network();
651 	if (use_network) {
652 		http_test_all(0 /* multiproc */);
653 		http_test_all(1 /* multiproc */);
654 	} else {
655 		dbgprintf("test disabled, set USENETWORK=yes to enable\n");
656 	}
657 
658 	quit();
659 	return 0;
660 }
661