etc/services - sync with NetBSD-8
[minix.git] / minix / tests / test82.c
blob2f157bfaeefccd1b51b32f6e424c7df969b0bf13
1 /*
2 * test82: test HTTP with a remote server (is $USENETWORK="yes")
3 */
5 #define DEBUG 0
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
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>
30 #include "common.h"
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);
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"
41 static void callback_verify_path1(const void *data, size_t size);
42 static void callback_verify_path2(const void *data, size_t size);
44 #define URL_COUNT 2
46 struct url {
47 const char *host;
48 int port;
49 const char *path;
50 void (* callback_verify)(const void *data, size_t size);
53 static const struct url urls[URL_COUNT] = {
54 { HOST, PORT, PATH1, callback_verify_path1 },
55 { HOST, PORT, PATH2, callback_verify_path2 },
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,
65 char serv[12];
67 assert(host);
69 snprintf(serv, sizeof(serv), "%d", port);
71 errno = 0;
72 if (getaddrinfo(host, serv, &hints, &addr) != 0 || !addr) {
73 efmt("host %s not found", host);
74 goto failure;
77 fd = socket(AF_INET, SOCK_STREAM, 0);
78 if (fd < 0) {
79 efmt("cannot create socket");
80 goto failure;
83 if (connect(fd, addr->ai_addr, addr->ai_addrlen) != 0) {
84 efmt("cannot connect to %s:%d", host, port);
85 goto failure;
88 freeaddrinfo(addr);
89 return fd;
91 failure:
92 if (fd >= 0) CLOSE(fd);
93 if (addr) freeaddrinfo(addr);
94 return -1;
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;
105 assert(fd >= 0);
106 assert(data);
107 assert(chunksize > 0);
109 while (size > 0) {
110 s = chunksize;
111 if (s > size) s = size;
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;
121 data += r;
122 size -= r;
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;
136 assert(fd >= 0);
137 assert(host);
138 assert(path);
139 assert(chunksize > 0);
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++;
155 len += snprintf(buf + len, sizeof(buf) - len, "\r\n");
157 dbgprintf("sending request:\n%.*s", (int) len, buf);
158 write_chunked(fd, buf, len, chunksize);
161 static int is_whitespace(char c) {
162 return c == ' ' || c == '\t';
165 static int is_whitespace_or_linebreak(char c) {
166 return is_whitespace(c) || c == '\r' || c == '\n';
169 static int is_numeric(char c) {
170 return c >= '0' && c <= '9';
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;
182 assert(data);
183 assert(index_p);
184 assert(*index_p <= len);
185 assert(linelen_p);
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]));
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);
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
203 if (index >= len && linelen > 0) goto notfound;
205 *linelen_p = linelen;
206 *index_p = index;
207 return 1;
209 notfound:
210 *linelen_p = 0;
211 *index_p = index;
212 return 0;
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;
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);
231 /* skip leading whitespace/blank lines */
232 index = *index_p;
233 while (index < len && is_whitespace_or_linebreak(data[index])) index++;
235 /* parse version */
236 while (index < len && !is_whitespace(data[index])) index++;
238 /* skip separator */
239 while (index < len && is_whitespace(data[index])) index++;
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;
252 code = code * 10 + (data[index++] - '0');
255 /* skip separator */
256 while (index < len && is_whitespace(data[index])) index++;
258 /* parse reason phrase */
259 while (index < len && data[index] != '\n') index++;
260 if (index >= len) goto notfound;
261 index++;
263 *code_p = code;
264 *index_p = index;
265 return 1;
267 notfound:
268 *code_p = 0;
269 *index_p = index;
270 return 0;
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;
281 assert(data);
282 assert(index <= len);
283 assert(name);
284 assert(index_value_p);
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++;
294 while (index < len && is_whitespace(data[index])) index++;
295 *index_value_p = index;
296 return 1;
298 notfound:
299 *index_value_p = 0;
300 return 0;
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;
311 assert(data);
312 assert(index <= index_end);
313 assert(value_p);
314 assert(error_p);
315 assert(!*error_p);
317 while (index < index_end && is_numeric(data[index])) {
318 value = value * 10 + (data[index++] - '0');
321 while (index < index_end && is_whitespace_or_linebreak(data[index])) {
322 index++;
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;
333 *value_p = value;
334 return 1;
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;
348 assert(data);
349 assert(error_p);
350 assert(!*error_p);
351 assert(code_p);
352 assert(index_body_p);
354 /* parse status line */
355 if (!http_get_status_line(data, len, &index, error_p, code_p)) {
356 return 0;
359 /* parse headers */
360 for (;;) {
361 index_line = index;
362 if (!http_get_header_line(data, len, &index, &linelen)) {
363 return 0;
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;
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;
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;
392 *index_body_p = index;
393 return len == index + content_length;
395 error:
396 *error_p = 1;
397 *code_p = 0;
398 *index_body_p = 0;
399 return 0;
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;
413 assert(fd >= 0);
414 assert(callback_verify);
415 assert(chunksize > 0);
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);
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;
433 datalen += r;
435 /* if we received all headers+data, we are done */
436 if (http_response_complete(data, datalen, &error, &code,
437 &index_body)) {
438 break;
440 if (error) goto cleanup;
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;
451 dbgprintf("received response:\n%.*s", (int) datalen, data);
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);
461 cleanup:
462 if (data) free(data);
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;
473 assert(url);
474 assert(chunksize > 0);
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);
481 fd = http_connect(url->host, url->port);
482 if (fd < 0) return;
484 http_send_request(fd, url->host, url->path, chunksize, bigrequest);
486 errno = 0;
487 if (withshutdown && shutdown(fd, SHUT_WR) != 0) {
488 efmt("shutdown failed");
491 if (delay) sleep(1);
492 http_recv_response(fd, url->callback_verify, chunksize);
494 CLOSE(fd);
496 dbgprintf("download attempt completed\n");
499 static int child_count;
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;
510 assert(url);
511 assert(chunksize > 0);
513 errno = 0;
514 pid = fork();
515 if (pid < 0) {
516 efmt("fork failed");
517 return;
520 if (pid > 0) {
521 child_count++;
522 return;
525 errctold = errct;
526 http_test(
527 url,
528 chunksize,
529 bigrequest,
530 delay,
531 withshutdown);
532 assert(errct >= errctold);
533 exit(errct - errctold);
536 static void wait_all(void) {
537 int exitcode, status;
538 pid_t pid;
540 while (child_count > 0) {
541 errno = 0;
542 pid = waitpid(-1, &status, 0);
543 if (pid <= 0) {
544 efmt("waitpid failed");
545 return;
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);
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");
566 child_count--;
569 errno = 0;
570 if (waitpid(-1, &status, 0) != -1 || errno != ECHILD) {
571 efmt("waitpid should have returned ECHILD");
575 #define OPTION_BIGREQUEST (1 << 0)
576 #define OPTION_DELAY (1 << 1)
577 #define OPTION_SHUTDOWN (1 << 2)
579 static void http_test_all(int multiproc) {
580 static const size_t chunksizes[] = { 1, 1024, 65536 };
581 static const int optionsets[] = {
583 OPTION_BIGREQUEST,
584 OPTION_DELAY,
585 OPTION_SHUTDOWN,
586 OPTION_BIGREQUEST | OPTION_DELAY | OPTION_SHUTDOWN,
588 int chunksizeindex;
589 int options;
590 int optionindex;
591 int urlindex;
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);
607 wait_all();
610 static void verify_data(
611 const void *httpdata, size_t httpsize,
612 const void *refdata, size_t refsize,
613 const char *path) {
615 assert(httpdata);
616 assert(refdata);
617 assert(path);
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);
631 static void callback_verify_path1(const void *data, size_t size) {
632 verify_data(data, size, PATH1_DATA, strlen(PATH1_DATA), PATH1);
635 static void callback_verify_path2(const void *data, size_t size) {
636 unsigned short buf[65536];
637 int i;
639 for (i = 0; i < 65536; i++) buf[i] = htons(i);
641 verify_data(data, size, buf, sizeof(buf), PATH2);
644 int main(int argc, char **argv)
646 int use_network;
648 start(82);
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");
658 quit();
659 return 0;