minix/minix/tests/test82.c
Erik van der Kouwe 17fbdaf514 test82: share support functions in common.[ch] for test83
Change-Id: I3dfeacc3c2c143d3b696efa39a6f257d38281742
2015-07-22 22:24:22 +02:00

661 lines
14 KiB
C

/*
* test82: test HTTP with a remote server (is $USENETWORK="yes")
*/
#define DEBUG 0
#if DEBUG
#define dbgprintf(...) do { \
fprintf(stderr, "[%s:%s:%d %d] ", \
__FILE__, __FUNCTION__, \
__LINE__, getpid()); \
fprintf(stderr, __VA_ARGS__); \
fflush(stderr); \
} while (0)
#else
#define dbgprintf(...)
#endif
#include <arpa/inet.h>
#include <assert.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include "common.h"
#define CLOSE(fd) do { assert(fd >= 0); if (close((fd)) != 0) efmt("close failed"); } while (0);
#define REALLOC(p, size) do { p = realloc(p, size); if (!p) efmt("realloc of %zu bytes failed", size); } while (0);
#define HOST "test82.minix3.org"
#define PORT 80
#define PATH1 "/test1.txt"
#define PATH1_DATA "Hello world\n"
#define PATH2 "/test2.bin"
static void callback_verify_path1(const void *data, size_t size);
static void callback_verify_path2(const void *data, size_t size);
#define URL_COUNT 2
struct url {
const char *host;
int port;
const char *path;
void (* callback_verify)(const void *data, size_t size);
};
static const struct url urls[URL_COUNT] = {
{ HOST, PORT, PATH1, callback_verify_path1 },
{ HOST, PORT, PATH2, callback_verify_path2 },
};
static int http_connect(const char *host, int port) {
struct addrinfo *addr = NULL;
int fd = -1;
struct addrinfo hints = {
.ai_family = PF_INET,
.ai_socktype = SOCK_STREAM,
};
char serv[12];
assert(host);
snprintf(serv, sizeof(serv), "%d", port);
errno = 0;
if (getaddrinfo(host, serv, &hints, &addr) != 0 || !addr) {
efmt("host %s not found", host);
goto failure;
}
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
efmt("cannot create socket");
goto failure;
}
if (connect(fd, addr->ai_addr, addr->ai_addrlen) != 0) {
efmt("cannot connect to %s:%d", host, port);
goto failure;
}
freeaddrinfo(addr);
return fd;
failure:
if (fd >= 0) CLOSE(fd);
if (addr) freeaddrinfo(addr);
return -1;
}
static void write_chunked(
int fd,
const char *data,
size_t size,
size_t chunksize) {
ssize_t r;
size_t s;
assert(fd >= 0);
assert(data);
assert(chunksize > 0);
while (size > 0) {
s = chunksize;
if (s > size) s = size;
errno = 0;
r = write(fd, data, s);
if (r <= 0 || (size_t) r > s) {
errno = 0;
efmt("write of %zu bytes failed with result %zd", s, r);
break;
}
data += r;
size -= r;
}
}
static void http_send_request(
int fd,
const char *host,
const char *path,
size_t chunksize,
int bigrequest) {
char buf[8192];
size_t len;
int lineno;
assert(fd >= 0);
assert(host);
assert(path);
assert(chunksize > 0);
/* http://tools.ietf.org/html/rfc2616#section-5 */
len = snprintf(buf, sizeof(buf),
"GET %s HTTP/1.1\r\n"
"Host: %s\r\n",
path, host);
if (bigrequest) {
lineno = 0;
while (len + 24 < sizeof(buf)) {
len += snprintf(buf + len, sizeof(buf) - len,
"X-Padding%d: %d\r\n",
lineno, lineno);
lineno++;
}
}
len += snprintf(buf + len, sizeof(buf) - len, "\r\n");
dbgprintf("sending request:\n%.*s", (int) len, buf);
write_chunked(fd, buf, len, chunksize);
}
static int is_whitespace(char c) {
return c == ' ' || c == '\t';
}
static int is_whitespace_or_linebreak(char c) {
return is_whitespace(c) || c == '\r' || c == '\n';
}
static int is_numeric(char c) {
return c >= '0' && c <= '9';
}
static int http_get_header_line(
const char *data,
size_t len,
size_t *index_p,
size_t *linelen_p) {
int has_cr;
size_t index;
size_t linelen;
assert(data);
assert(index_p);
assert(*index_p <= len);
assert(linelen_p);
/* starting the next line with whitespace means the line is continued */
index = *index_p;
do {
while (index < len && data[index] != '\n') index++;
if (index >= len) goto notfound;
index++;
} while (index < len && is_whitespace(data[index]));
/* exclude LF or CR+LF from line length */
assert(index - 1 >= *index_p && data[index - 1] == '\n');
has_cr = (index - 2 >= *index_p) && data[index - 2] == '\r';
linelen = index - *index_p - (has_cr ? 2 : 1);
/* if LF is the last character in the buffer, the line may be continued
* when more data is retrieved unless we reached the end of the headers
*/
if (index >= len && linelen > 0) goto notfound;
*linelen_p = linelen;
*index_p = index;
return 1;
notfound:
*linelen_p = 0;
*index_p = index;
return 0;
}
static int http_get_status_line(
const char *data,
size_t len,
size_t *index_p,
int *error_p,
int *code_p) {
int code, i;
size_t index;
assert(data);
assert(index_p);
assert(*index_p <= len);
assert(error_p);
assert(*error_p == 0);
assert(code_p);
/* skip leading whitespace/blank lines */
index = *index_p;
while (index < len && is_whitespace_or_linebreak(data[index])) index++;
/* parse version */
while (index < len && !is_whitespace(data[index])) index++;
/* skip separator */
while (index < len && is_whitespace(data[index])) index++;
/* parse status code */
code = 0;
for (i = 0; i < 3; i++) {
if (index >= len) goto notfound;
if (!is_numeric(data[index])) {
errno = 0;
efmt("HTTP error: bad status line: \"%.*s\"",
(int) (index - *index_p), data + *index_p);
*error_p = 1;
goto notfound;
}
code = code * 10 + (data[index++] - '0');
}
/* skip separator */
while (index < len && is_whitespace(data[index])) index++;
/* parse reason phrase */
while (index < len && data[index] != '\n') index++;
if (index >= len) goto notfound;
index++;
*code_p = code;
*index_p = index;
return 1;
notfound:
*code_p = 0;
*index_p = index;
return 0;
}
static int http_header_is(
const char *data,
size_t len,
size_t index,
const char *name,
size_t *index_value_p) {
size_t namelen;
assert(data);
assert(index <= len);
assert(name);
assert(index_value_p);
namelen = strlen(name);
if (index + namelen > len) goto notfound;
if (strncasecmp(data + index, name, namelen) != 0) goto notfound;
index += namelen;
while (index < len && is_whitespace(data[index])) index++;
if (index >= len || data[index] != ':') goto notfound;
index++;
while (index < len && is_whitespace(data[index])) index++;
*index_value_p = index;
return 1;
notfound:
*index_value_p = 0;
return 0;
}
static int http_parse_int_header(
const char *data,
size_t index,
size_t index_end,
int *value_p,
int *error_p) {
int value = 0;
assert(data);
assert(index <= index_end);
assert(value_p);
assert(error_p);
assert(!*error_p);
while (index < index_end && is_numeric(data[index])) {
value = value * 10 + (data[index++] - '0');
}
while (index < index_end && is_whitespace_or_linebreak(data[index])) {
index++;
}
if (index < index_end) {
errno = 0;
efmt("HTTP error: bad numeric header value: \"%.*s\"",
(int) (index_end - index), data + index);
*error_p = 1;
return 0;
}
*value_p = value;
return 1;
}
static int http_response_complete(
const char *data,
size_t len,
int *error_p,
int *code_p,
size_t *index_body_p) {
int content_length = -1;
size_t index = 0, index_line;
size_t index_value;
size_t linelen;
assert(data);
assert(error_p);
assert(!*error_p);
assert(code_p);
assert(index_body_p);
/* parse status line */
if (!http_get_status_line(data, len, &index, error_p, code_p)) {
return 0;
}
/* parse headers */
for (;;) {
index_line = index;
if (!http_get_header_line(data, len, &index, &linelen)) {
return 0;
}
if (linelen == 0) break;
if (http_header_is(data, len, index_line,
"Content-Length", &index_value)) {
if (!http_parse_int_header(data, index_value,
index_line + linelen, &content_length,
error_p)) {
return 0;
}
}
}
/* do we know how long the response will be? */
if (content_length < 0) {
errno = 0;
efmt("HTTP error: missing Content-Length header "
"(maybe Transfer-Encoding is specified instead "
"but this is currently unsupported)");
goto error;
}
/* check whether the amount of data is correct */
if (len > index + content_length) {
errno = 0;
efmt("HTTP error: more data received than expected");
goto error;
}
*index_body_p = index;
return len == index + content_length;
error:
*error_p = 1;
*code_p = 0;
*index_body_p = 0;
return 0;
}
static void http_recv_response(
int fd,
void (* callback_verify)(const void *data, size_t size),
size_t chunksize) {
int code;
char *data;
size_t datalen = 0, datasize = 0;
int error = 0;
size_t index_body;
ssize_t r;
assert(fd >= 0);
assert(callback_verify);
assert(chunksize > 0);
data = NULL;
for (;;) {
/* make room for another chunk in the buffer if needed */
if (datasize < datalen + chunksize) {
datasize = (datalen + chunksize) * 2;
REALLOC(data, datasize);
}
/* read a chunk of data */
errno = 0;
r = read(fd, data + datalen, chunksize);
if (r < 0 || (size_t) r > chunksize) {
efmt("read of %zu bytes failed with result %zd",
chunksize, r);
goto cleanup;
}
datalen += r;
/* if we received all headers+data, we are done */
if (http_response_complete(data, datalen, &error, &code,
&index_body)) {
break;
}
if (error) goto cleanup;
/* check for premature disconnection */
if (r == 0) {
errno = 0;
efmt("server disconnected even though the response "
"seems to be incomplete");
goto cleanup;
}
}
dbgprintf("received response:\n%.*s", (int) datalen, data);
assert(index_body <= datalen);
if (code == 200) {
callback_verify(data + index_body, datalen - index_body);
} else {
errno = 0;
efmt("unexpected HTTP status code %d", code);
}
cleanup:
if (data) free(data);
}
static void http_test(
const struct url *url,
size_t chunksize,
int bigrequest,
int delay,
int withshutdown) {
int fd;
assert(url);
assert(chunksize > 0);
dbgprintf("attempting download from http://%s:%d%s, "
"chunksize=%zu, bigrequest=%d, delay=%d, withshutdown=%d\n",
url->host, url->port, url->path, chunksize, bigrequest,
delay, withshutdown);
fd = http_connect(url->host, url->port);
if (fd < 0) return;
http_send_request(fd, url->host, url->path, chunksize, bigrequest);
errno = 0;
if (withshutdown && shutdown(fd, SHUT_WR) != 0) {
efmt("shutdown failed");
}
if (delay) sleep(1);
http_recv_response(fd, url->callback_verify, chunksize);
CLOSE(fd);
dbgprintf("download attempt completed\n");
}
static int child_count;
static void http_test_fork(
const struct url *url,
size_t chunksize,
int bigrequest,
int delay,
int withshutdown) {
int errctold;
pid_t pid;
assert(url);
assert(chunksize > 0);
errno = 0;
pid = fork();
if (pid < 0) {
efmt("fork failed");
return;
}
if (pid > 0) {
child_count++;
return;
}
errctold = errct;
http_test(
url,
chunksize,
bigrequest,
delay,
withshutdown);
assert(errct >= errctold);
exit(errct - errctold);
}
static void wait_all(void) {
int exitcode, status;
pid_t pid;
while (child_count > 0) {
errno = 0;
pid = waitpid(-1, &status, 0);
if (pid <= 0) {
efmt("waitpid failed");
return;
}
if (WIFEXITED(status)) {
exitcode = WEXITSTATUS(status);
dbgprintf("child %d completed with exit code %d\n",
(int) pid, exitcode);
if (exitcode >= 0) {
errct += exitcode;
} else {
efmt("child has negative exit code %d",
exitcode);
}
} else if (WIFSIGNALED(status)) {
dbgprintf("child %d killed by signal %d\n",
(int) pid, WTERMSIG(status));
efmt("child killed by signal %d", WTERMSIG(status));
} else {
dbgprintf("child %d gone with status 0x%x\n",
(int) pid, status);
efmt("child gone, but neither exit nor signal");
}
child_count--;
}
errno = 0;
if (waitpid(-1, &status, 0) != -1 || errno != ECHILD) {
efmt("waitpid should have returned ECHILD");
}
}
#define OPTION_BIGREQUEST (1 << 0)
#define OPTION_DELAY (1 << 1)
#define OPTION_SHUTDOWN (1 << 2)
static void http_test_all(int multiproc) {
static const size_t chunksizes[] = { 1, 1024, 65536 };
static const int optionsets[] = {
0,
OPTION_BIGREQUEST,
OPTION_DELAY,
OPTION_SHUTDOWN,
OPTION_BIGREQUEST | OPTION_DELAY | OPTION_SHUTDOWN,
};
int chunksizeindex;
int options;
int optionindex;
int urlindex;
for (urlindex = 0; urlindex < URL_COUNT; urlindex++) {
for (chunksizeindex = 0; chunksizeindex < 3; chunksizeindex++) {
for (optionindex = 0; optionindex < 3; optionindex++) {
options = optionsets[optionindex];
(multiproc ? http_test_fork : http_test)(
&urls[urlindex],
chunksizes[chunksizeindex],
options & OPTION_BIGREQUEST,
options & OPTION_DELAY,
options & OPTION_SHUTDOWN);
}
}
}
wait_all();
}
static void verify_data(
const void *httpdata, size_t httpsize,
const void *refdata, size_t refsize,
const char *path) {
assert(httpdata);
assert(refdata);
assert(path);
if (httpsize != refsize) {
errno = 0;
efmt("download from http://%s:%d%s returned wrong number "
"of bytes: %zd (expected %zd)",
HOST, PORT, path, httpsize, refsize);
} else if (memcmp(httpdata, refdata, refsize) != 0) {
errno = 0;
efmt("download from http://%s:%d%s returned wrong data",
HOST, PORT, path);
}
}
static void callback_verify_path1(const void *data, size_t size) {
verify_data(data, size, PATH1_DATA, strlen(PATH1_DATA), PATH1);
}
static void callback_verify_path2(const void *data, size_t size) {
unsigned short buf[65536];
int i;
for (i = 0; i < 65536; i++) buf[i] = htons(i);
verify_data(data, size, buf, sizeof(buf), PATH2);
}
int main(int argc, char **argv)
{
int use_network;
start(82);
use_network = get_setting_use_network();
if (use_network) {
http_test_all(0 /* multiproc */);
http_test_all(1 /* multiproc */);
} else {
dbgprintf("test disabled, set USENETWORK=yes to enable\n");
}
quit();
return 0;
}