Commit af7efd98 authored by Sergey Lyubka's avatar Sergey Lyubka

Adjusting unit test for core

parent 2cb325c9
...@@ -70,6 +70,9 @@ all: ...@@ -70,6 +70,9 @@ all:
unix_unit_test: $(LUA_SOURCES) Makefile ../test/unit_test.c unix_unit_test: $(LUA_SOURCES) Makefile ../test/unit_test.c
$(CC) ../test/unit_test.c lua_5.2.1.c $(CFLAGS) -g -O0 -o t && ./t $(CC) ../test/unit_test.c lua_5.2.1.c $(CFLAGS) -g -O0 -o t && ./t
core_unit_test: Makefile ../test/unit_test.c
$(CC) ../test/unit_test.c $(CFLAGS) -g -O0 -o t && ./t
# Make sure that the compiler flags come last in the compilation string. # Make sure that the compiler flags come last in the compilation string.
# If not so, this can break some on some Linux distros which use # If not so, this can break some on some Linux distros which use
# "-Wl,--as-needed" turned on by default in cc command. # "-Wl,--as-needed" turned on by default in cc command.
......
...@@ -78,6 +78,7 @@ typedef struct _stati64 file_stat_t; ...@@ -78,6 +78,7 @@ typedef struct _stati64 file_stat_t;
#include <dirent.h> #include <dirent.h>
#include <unistd.h> #include <unistd.h>
#include <pthread.h> #include <pthread.h>
#include <arpa/inet.h> // For inet_pton() when USE_IPV6 is defined
#include <netinet/in.h> #include <netinet/in.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/select.h> #include <sys/select.h>
...@@ -766,7 +767,8 @@ static void close_conn(struct connection *conn) { ...@@ -766,7 +767,8 @@ static void close_conn(struct connection *conn) {
// -1 if request is malformed // -1 if request is malformed
// 0 if request is not yet fully buffered // 0 if request is not yet fully buffered
// >0 actual request length, including last \r\n\r\n // >0 actual request length, including last \r\n\r\n
static int get_request_len(const unsigned char *buf, int buf_len) { static int get_request_len(const char *s, int buf_len) {
const unsigned char *buf = (unsigned char *) s;
int i; int i;
for (i = 0; i < buf_len; i++) { for (i = 0; i < buf_len; i++) {
...@@ -874,6 +876,7 @@ static int is_valid_http_method(const char *method) { ...@@ -874,6 +876,7 @@ static int is_valid_http_method(const char *method) {
// Parse HTTP request, fill in mg_request structure. // Parse HTTP request, fill in mg_request structure.
// This function modifies the buffer by NUL-terminating // This function modifies the buffer by NUL-terminating
// HTTP request components, header names and header values. // HTTP request components, header names and header values.
// Note that len must point to the last \n of HTTP headers.
static int parse_http_message(char *buf, int len, struct mg_connection *ri) { static int parse_http_message(char *buf, int len, struct mg_connection *ri) {
int is_request, n; int is_request, n;
...@@ -1890,6 +1893,460 @@ static void send_options(struct connection *conn) { ...@@ -1890,6 +1893,460 @@ static void send_options(struct connection *conn) {
conn->flags |= CONN_SPOOL_DONE; conn->flags |= CONN_SPOOL_DONE;
} }
static int mg_printf(struct connection *conn, const char *fmt, ...) {
char buf[IOBUF_SIZE];
va_list ap;
int len;
va_start(ap, fmt);
len = vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
return spool(&conn->remote_iobuf, buf, len);
}
#ifndef NO_AUTH
static void send_authorization_request(struct connection *conn) {
conn->mg_conn.status_code = 401;
mg_printf(conn,
"HTTP/1.1 401 Unauthorized\r\n"
"Content-Length: 0\r\n"
"WWW-Authenticate: Digest qop=\"auth\", "
"realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
conn->server->config_options[AUTH_DOMAIN],
(unsigned long) time(NULL));
}
// Use the global passwords file, if specified by auth_gpass option,
// or search for .htpasswd in the requested directory.
static FILE *open_auth_file(struct connection *conn, const char *path) {
char name[MAX_PATH_SIZE];
const char *p, *gpass = conn->server->config_options[GLOBAL_AUTH_FILE];
file_stat_t st;
FILE *fp = NULL;
if (gpass != NULL) {
// Use global passwords file
fp = fopen(gpass, "r");
} else if (!stat(path, &st) && S_ISDIR(st.st_mode)) {
snprintf(name, sizeof(name), "%s%c%s", path, '/', PASSWORDS_FILE_NAME);
fp = fopen(name, "r");
} else {
// Try to find .htpasswd in requested directory.
if ((p = strrchr(path, '/')) == NULL) p = path;
snprintf(name, sizeof(name), "%.*s%c%s",
(int) (p - path), path, '/', PASSWORDS_FILE_NAME);
fp = fopen(name, "r");
}
return fp;
}
#ifndef HAVE_MD5
typedef struct MD5Context {
uint32_t buf[4];
uint32_t bits[2];
unsigned char in[64];
} MD5_CTX;
static void byteReverse(unsigned char *buf, unsigned longs) {
uint32_t t;
// Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
if (is_big_endian()) {
do {
t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
((unsigned) buf[1] << 8 | buf[0]);
* (uint32_t *) buf = t;
buf += 4;
} while (--longs);
}
}
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
#define MD5STEP(f, w, x, y, z, data, s) \
( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
// Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
// initialization constants.
static void MD5Init(MD5_CTX *ctx) {
ctx->buf[0] = 0x67452301;
ctx->buf[1] = 0xefcdab89;
ctx->buf[2] = 0x98badcfe;
ctx->buf[3] = 0x10325476;
ctx->bits[0] = 0;
ctx->bits[1] = 0;
}
static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
register uint32_t a, b, c, d;
a = buf[0];
b = buf[1];
c = buf[2];
d = buf[3];
MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
buf[0] += a;
buf[1] += b;
buf[2] += c;
buf[3] += d;
}
static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
uint32_t t;
t = ctx->bits[0];
if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
ctx->bits[1]++;
ctx->bits[1] += len >> 29;
t = (t >> 3) & 0x3f;
if (t) {
unsigned char *p = (unsigned char *) ctx->in + t;
t = 64 - t;
if (len < t) {
memcpy(p, buf, len);
return;
}
memcpy(p, buf, t);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
buf += t;
len -= t;
}
while (len >= 64) {
memcpy(ctx->in, buf, 64);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
buf += 64;
len -= 64;
}
memcpy(ctx->in, buf, len);
}
static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
unsigned count;
unsigned char *p;
uint32_t *a;
count = (ctx->bits[0] >> 3) & 0x3F;
p = ctx->in + count;
*p++ = 0x80;
count = 64 - 1 - count;
if (count < 8) {
memset(p, 0, count);
byteReverse(ctx->in, 16);
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
memset(ctx->in, 0, 56);
} else {
memset(p, 0, count - 8);
}
byteReverse(ctx->in, 14);
a = (uint32_t *)ctx->in;
a[14] = ctx->bits[0];
a[15] = ctx->bits[1];
MD5Transform(ctx->buf, (uint32_t *) ctx->in);
byteReverse((unsigned char *) ctx->buf, 4);
memcpy(digest, ctx->buf, 16);
memset((char *) ctx, 0, sizeof(*ctx));
}
#endif // !HAVE_MD5
// Stringify binary data. Output buffer must be twice as big as input,
// because each byte takes 2 bytes in string representation
static void bin2str(char *to, const unsigned char *p, size_t len) {
static const char *hex = "0123456789abcdef";
for (; len--; p++) {
*to++ = hex[p[0] >> 4];
*to++ = hex[p[0] & 0x0f];
}
*to = '\0';
}
// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
char *mg_md5(char buf[33], ...) {
unsigned char hash[16];
const char *p;
va_list ap;
MD5_CTX ctx;
MD5Init(&ctx);
va_start(ap, buf);
while ((p = va_arg(ap, const char *)) != NULL) {
MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
}
va_end(ap);
MD5Final(hash, &ctx);
bin2str(buf, hash, sizeof(hash));
return buf;
}
// Check the user's password, return 1 if OK
static int check_password(const char *method, const char *ha1, const char *uri,
const char *nonce, const char *nc, const char *cnonce,
const char *qop, const char *response) {
char ha2[32 + 1], expected_response[32 + 1];
// Some of the parameters may be NULL
if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL ||
qop == NULL || response == NULL) {
return 0;
}
// NOTE(lsm): due to a bug in MSIE, we do not compare the URI
// TODO(lsm): check for authentication timeout
if (// strcmp(dig->uri, c->ouri) != 0 ||
strlen(response) != 32
// || now - strtoul(dig->nonce, NULL, 10) > 3600
) {
return 0;
}
mg_md5(ha2, method, ":", uri, NULL);
mg_md5(expected_response, ha1, ":", nonce, ":", nc,
":", cnonce, ":", qop, ":", ha2, NULL);
return mg_strcasecmp(response, expected_response) == 0;
}
// Authorize against the opened passwords file. Return 1 if authorized.
static int authorize(struct connection *conn, FILE *fp) {
const char *hdr = mg_get_header(&conn->mg_conn, "Authorization");
char line[256], f_user[256], ha1[256], f_domain[256], user[100], uri[100],
cnonce[100], resp[100], qop[100], nc[100], nonce[100];
if (hdr == NULL || mg_strncasecmp(hdr, "Digest ", 7) != 0) return 0;
if (!mg_parse_header(hdr, "username", user, sizeof(user))) return 0;
if (!mg_parse_header(hdr, "cnonce", cnonce, sizeof(cnonce))) return 0;
if (!mg_parse_header(hdr, "response", resp, sizeof(resp))) return 0;
if (!mg_parse_header(hdr, "uri", uri, sizeof(uri))) return 0;
if (!mg_parse_header(hdr, "qop", qop, sizeof(qop))) return 0;
if (!mg_parse_header(hdr, "nc", nc, sizeof(nc))) return 0;
if (!mg_parse_header(hdr, "nonce", nonce, sizeof(nonce))) return 0;
while (fgets(line, sizeof(line), fp) != NULL) {
if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) == 3 &&
!strcmp(user, f_user) &&
!strcmp(conn->server->config_options[AUTH_DOMAIN], f_domain))
return check_password(conn->mg_conn.request_method, ha1, uri,
nonce, nc, cnonce, qop, resp);
}
return 0;
}
// Return 1 if request is authorised, 0 otherwise.
static int is_authorized(struct connection *conn, const char *path) {
FILE *fp;
int authorized = 1;
if ((fp = open_auth_file(conn, path)) != NULL) {
authorized = authorize(conn, fp);
fclose(fp);
}
return authorized;
}
static int is_authorized_for_put(struct connection *conn) {
const char *auth_file = conn->server->config_options[PUT_DELETE_AUTH_FILE];
FILE *fp;
int authorized = 0;
if (auth_file != NULL && (fp = fopen(auth_file, "r")) != NULL) {
authorized = authorize(conn, fp);
fclose(fp);
}
return authorized;
}
static int is_put_or_delete_request(const struct connection *conn) {
const char *s = conn->mg_conn.request_method;
return s && (!strcmp(s, "PUT") || !strcmp(s, "DELETE") ||
!strcmp(s, "MKCOL"));
}
#endif // NO_AUTH
#if 0
// Parsed Authorization header
struct ah { char *user, *uri, *cnonce, *response, *qop, *nc, *nonce; };
// Return 1 on success. Always initializes the ah structure.
static int parse_auth_header(struct mg_connection *conn, char *buf,
size_t buf_size, struct ah *ah) {
char *name, *value, *s;
const char *auth_header;
memset(ah, 0, sizeof(*ah));
if ((auth_header = mg_get_header(conn, "Authorization")) == NULL ||
mg_strncasecmp(auth_header, "Digest ", 7) != 0) {
return 0;
}
// Make modifiable copy of the auth header
strncpy(buf, auth_header + 7, buf_size);
s = buf;
// Parse authorization header
for (;;) {
// Gobble initial spaces
while (isspace(* (unsigned char *) s)) {
s++;
}
name = skip_quoted(&s, "=", " ", 0);
// Value is either quote-delimited, or ends at first comma or space.
if (s[0] == '\"') {
s++;
value = skip_quoted(&s, "\"", " ", '\\');
if (s[0] == ',') {
s++;
}
} else {
value = skip_quoted(&s, ", ", " ", 0); // IE uses commas, FF uses spaces
}
if (*name == '\0') {
break;
}
if (!strcmp(name, "username")) {
ah->user = value;
} else if (!strcmp(name, "cnonce")) {
ah->cnonce = value;
} else if (!strcmp(name, "response")) {
ah->response = value;
} else if (!strcmp(name, "uri")) {
ah->uri = value;
} else if (!strcmp(name, "qop")) {
ah->qop = value;
} else if (!strcmp(name, "nc")) {
ah->nc = value;
} else if (!strcmp(name, "nonce")) {
ah->nonce = value;
}
}
// CGI needs it as REMOTE_USER
if (ah->user != NULL) {
conn->request_info.remote_user = mg_strdup(ah->user);
} else {
return 0;
}
return 1;
}
#endif
int mg_parse_header(const char *str, const char *var_name, char *buf,
size_t buf_size) {
int ch = ' ', len = 0, n = strlen(var_name);
const char *p, *s = str == NULL ? NULL : strstr(str, var_name);
if (s != NULL && s[n] == '=' && s[n + 1] != '\0') {
s += n + 1;
if (*s == '"' || *s == '\'') {
ch = *s++;
}
if (*s != '\0' && (((p = strchr(s, ch)) != NULL &&
(size_t) (p - s) < buf_size) ||
(p == NULL && ch == ' ' && strlen(s) < buf_size &&
(p = s + strlen(s)) != NULL))) {
len = p - s;
memcpy(buf, s, len);
buf[len] = '\0';
}
}
return len;
}
static void open_local_endpoint(struct connection *conn) { static void open_local_endpoint(struct connection *conn) {
char path[MAX_PATH_SIZE] = {'\0'}; char path[MAX_PATH_SIZE] = {'\0'};
file_stat_t st; file_stat_t st;
...@@ -1916,6 +2373,11 @@ static void open_local_endpoint(struct connection *conn) { ...@@ -1916,6 +2373,11 @@ static void open_local_endpoint(struct connection *conn) {
send_options(conn); send_options(conn);
} else if (conn->server->config_options[DOCUMENT_ROOT] == NULL) { } else if (conn->server->config_options[DOCUMENT_ROOT] == NULL) {
send_http_error(conn, "%s", "HTTP/1.1 404 Not Found\r\n\r\n"); send_http_error(conn, "%s", "HTTP/1.1 404 Not Found\r\n\r\n");
#ifndef NO_AUTH
} else if ((!is_put_or_delete_request(conn) && !is_authorized(conn, path)) ||
(is_put_or_delete_request(conn) && !is_authorized_for_put(conn))) {
send_authorization_request(conn);
#endif
#ifndef NO_DAV #ifndef NO_DAV
} else if (!strcmp(conn->mg_conn.request_method, "PROPFIND")) { } else if (!strcmp(conn->mg_conn.request_method, "PROPFIND")) {
propfind(conn, path, &st); propfind(conn, path, &st);
...@@ -1970,8 +2432,7 @@ static void process_request(struct connection *conn) { ...@@ -1970,8 +2432,7 @@ static void process_request(struct connection *conn) {
struct iobuf *io = &conn->local_iobuf; struct iobuf *io = &conn->local_iobuf;
if (conn->request_len == 0 && if (conn->request_len == 0 &&
(conn->request_len = get_request_len((unsigned char *) io->buf, (conn->request_len = get_request_len(io->buf, io->len)) > 0) {
io->len)) > 0) {
// If request is buffered in, remove it from the iobuf. This is because // If request is buffered in, remove it from the iobuf. This is because
// iobuf could be reallocated, and pointers in parsed request could // iobuf could be reallocated, and pointers in parsed request could
// become invalid. // become invalid.
...@@ -2023,10 +2484,10 @@ static void read_from_cgi(struct connection *conn) { ...@@ -2023,10 +2484,10 @@ static void read_from_cgi(struct connection *conn) {
} }
} }
static int should_keep_alive(const struct connection *conn) { static int should_keep_alive(const struct mg_connection *conn) {
const char *method = conn->mg_conn.request_method; const char *method = conn->request_method;
const char *http_version = conn->mg_conn.http_version; const char *http_version = conn->http_version;
const char *header = mg_get_header(&conn->mg_conn, "Connection"); const char *header = mg_get_header(conn, "Connection");
return method != NULL && !strcmp(method, "GET") && return method != NULL && !strcmp(method, "GET") &&
((header != NULL && !strcmp(header, "keep-alive")) || ((header != NULL && !strcmp(header, "keep-alive")) ||
(header == NULL && http_version && !strcmp(http_version, "1.1"))); (header == NULL && http_version && !strcmp(http_version, "1.1")));
...@@ -2034,7 +2495,8 @@ static int should_keep_alive(const struct connection *conn) { ...@@ -2034,7 +2495,8 @@ static int should_keep_alive(const struct connection *conn) {
static void close_local_endpoint(struct connection *conn) { static void close_local_endpoint(struct connection *conn) {
// Must be done before free() // Must be done before free()
int keep_alive = should_keep_alive(conn) && conn->endpoint_type == EP_FILE; int keep_alive = should_keep_alive(&conn->mg_conn) &&
conn->endpoint_type == EP_FILE;
switch (conn->endpoint_type) { switch (conn->endpoint_type) {
case EP_FILE: close(conn->endpoint.fd); break; case EP_FILE: close(conn->endpoint.fd); break;
...@@ -2226,7 +2688,8 @@ static void set_default_option_values(char **opts) { ...@@ -2226,7 +2688,8 @@ static void set_default_option_values(char **opts) {
// Valid listening port spec is: [ip_address:]port, e.g. "80", "127.0.0.1:3128" // Valid listening port spec is: [ip_address:]port, e.g. "80", "127.0.0.1:3128"
static int parse_port_string(const char *str, union socket_address *sa) { static int parse_port_string(const char *str, union socket_address *sa) {
unsigned int a, b, c, d, port; unsigned int a, b, c, d, port;
#if defined(USE_IPV6) int len = 0;
#ifdef USE_IPV6
char buf[100]; char buf[100];
#endif #endif
...@@ -2236,25 +2699,25 @@ static int parse_port_string(const char *str, union socket_address *sa) { ...@@ -2236,25 +2699,25 @@ static int parse_port_string(const char *str, union socket_address *sa) {
memset(sa, 0, sizeof(*sa)); memset(sa, 0, sizeof(*sa));
sa->sin.sin_family = AF_INET; sa->sin.sin_family = AF_INET;
if (sscanf(str, "%u.%u.%u.%u:%u", &a, &b, &c, &d, &port) == 5) { if (sscanf(str, "%u.%u.%u.%u:%u%n", &a, &b, &c, &d, &port, &len) == 5) {
// Bind to a specific IPv4 address, e.g. 192.168.1.5:8080 // Bind to a specific IPv4 address, e.g. 192.168.1.5:8080
sa->sin.sin_addr.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | d); sa->sin.sin_addr.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | d);
sa->sin.sin_port = htons((uint16_t) port); sa->sin.sin_port = htons((uint16_t) port);
#if defined(USE_IPV6) #if defined(USE_IPV6)
} else if (sscanf(str, "[%49[^]]]:%d%n", buf, &port, &len) == 2 && } else if (sscanf(str, "[%49[^]]]:%d%n", buf, &port, &len) == 2 &&
inet_pton(AF_INET6, buf, &so->lsa.sin6.sin6_addr)) { inet_pton(AF_INET6, buf, &sa->sin6.sin6_addr)) {
// IPv6 address, e.g. [3ffe:2a00:100:7031::1]:8080 // IPv6 address, e.g. [3ffe:2a00:100:7031::1]:8080
so->lsa.sin6.sin6_family = AF_INET6; sa->sin6.sin6_family = AF_INET6;
so->lsa.sin6.sin6_port = htons((uint16_t) port); sa->sin6.sin6_port = htons((uint16_t) port);
#endif #endif
} else if (sscanf(str, "%u", &port) == 1) { } else if (sscanf(str, "%u%n", &port, &len) == 1) {
// If only port is specified, bind to IPv4, INADDR_ANY // If only port is specified, bind to IPv4, INADDR_ANY
sa->sin.sin_port = htons((uint16_t) port); sa->sin.sin_port = htons((uint16_t) port);
} else { } else {
port = 0; // Parsing failure. Make port invalid. port = 0; // Parsing failure. Make port invalid.
} }
return port > 0 && port < 0xffff; return port > 0 && port < 0xffff && str[len] == '\0';
} }
const char *mg_set_option(struct mg_server *server, const char *name, const char *mg_set_option(struct mg_server *server, const char *name,
...@@ -2288,7 +2751,7 @@ const char *mg_set_option(struct mg_server *server, const char *name, ...@@ -2288,7 +2751,7 @@ const char *mg_set_option(struct mg_server *server, const char *name,
} }
const char *mg_get_option(const struct mg_server *server, const char *name) { const char *mg_get_option(const struct mg_server *server, const char *name) {
const char *const *opts = server->config_options; const char **opts = (const char **) server->config_options;
int i = get_option_index(name); int i = get_option_index(name);
return i == -1 ? NULL : opts[i] == NULL ? "" : opts[i]; return i == -1 ? NULL : opts[i] == NULL ? "" : opts[i];
} }
......
...@@ -81,6 +81,7 @@ int mg_get_var(const struct mg_connection *conn, ...@@ -81,6 +81,7 @@ int mg_get_var(const struct mg_connection *conn,
const char *var_name, char *dst, size_t dst_len); const char *var_name, char *dst, size_t dst_len);
int mg_get_cookie(const char *cookie, const char *var_name, int mg_get_cookie(const char *cookie, const char *var_name,
char *buf, size_t buf_len); char *buf, size_t buf_len);
int mg_parse_header(const char *hdr, const char *var_name, char *buf, size_t);
// Utility functions // Utility functions
int mg_start_thread(void *(*func)(void *), void *param); int mg_start_thread(void *(*func)(void *), void *param);
......
...@@ -4,37 +4,34 @@ ...@@ -4,37 +4,34 @@
#define USE_LUA #define USE_LUA
#ifndef _WIN32 #ifndef _WIN32
#define __cdecl
#define USE_IPV6 #define USE_IPV6
#endif #endif
// USE_* definitions must be made before #include "mongoose.c" ! // USE_* definitions must be made before #include "mongoose.c" !
#include "src/core.c"
#include "mongoose.c" #define FAIL(str, line) do { \
printf("Fail on line %d: [%s]\n", line, str); \
static int s_total_tests = 0; return str; \
static int s_failed_tests = 0;
#define FAIL(str, line) do { \
printf("Fail on line %d: [%s]\n", line, str); \
s_failed_tests++; \
} while (0) } while (0)
#define ASSERT(expr) do { \ #define ASSERT(expr) do { \
s_total_tests++; \ static_num_tests++; \
if (!(expr)) FAIL(#expr, __LINE__); \ if (!(expr)) FAIL(#expr, __LINE__); \
} while (0) } while (0)
#define HTTP_PORT "56789" #define RUN_TEST(test) do { const char *msg = test(); \
#define HTTPS_PORT "56790" if (msg) return msg; } while (0)
#define HTTP_PORT2 "56791"
#define LISTENING_ADDR \ #define HTTP_PORT "45772"
"127.0.0.1:" HTTP_PORT "r" \ #define LISTENING_ADDR "127.0.0.1:" HTTP_PORT
",127.0.0.1:" HTTPS_PORT "s" \
",127.0.0.1:" HTTP_PORT2 static int static_num_tests = 0;
//static const char *fetch_data = "hello world!\n";
//static const char *upload_ok_message = "upload successful";
static void test_parse_http_message() { static const char *test_parse_http_message() {
struct mg_request_info ri; struct mg_connection ri;
char req1[] = "GET / HTTP/1.1\r\n\r\n"; char req1[] = "GET / HTTP/1.1\r\n\r\n";
char req2[] = "BLAH / HTTP/1.1\r\n\r\n"; char req2[] = "BLAH / HTTP/1.1\r\n\r\n";
char req3[] = "GET / HTTP/1.1\r\nBah\r\n"; char req3[] = "GET / HTTP/1.1\r\nBah\r\n";
...@@ -52,19 +49,19 @@ static void test_parse_http_message() { ...@@ -52,19 +49,19 @@ static void test_parse_http_message() {
ASSERT(get_request_len("\n\r\n", 3) == 3); ASSERT(get_request_len("\n\r\n", 3) == 3);
ASSERT(get_request_len("\xdd\xdd", 2) == 0); ASSERT(get_request_len("\xdd\xdd", 2) == 0);
ASSERT(get_request_len("\xdd\x03", 2) == -1); ASSERT(get_request_len("\xdd\x03", 2) == -1);
ASSERT(get_request_len(req3, sizeof(req3) - 1) == 0);
ASSERT(get_request_len(req6, sizeof(req6) - 1) == 0);
ASSERT(get_request_len(req7, sizeof(req7) - 1) == 0);
ASSERT(parse_http_message(req9, sizeof(req9), &ri) == sizeof(req9) - 1); ASSERT(parse_http_message(req9, sizeof(req9) - 1, &ri) == sizeof(req9) - 1);
ASSERT(ri.num_headers == 1); ASSERT(ri.num_headers == 1);
ASSERT(parse_http_message(req1, sizeof(req1), &ri) == sizeof(req1) - 1); ASSERT(parse_http_message(req1, sizeof(req1) - 1, &ri) == sizeof(req1) - 1);
ASSERT(strcmp(ri.http_version, "1.1") == 0); ASSERT(strcmp(ri.http_version, "1.1") == 0);
ASSERT(ri.num_headers == 0); ASSERT(ri.num_headers == 0);
ASSERT(parse_http_message(req2, sizeof(req2) - 1, &ri) == -1); ASSERT(parse_http_message(req2, sizeof(req2) - 1, &ri) == -1);
ASSERT(parse_http_message(req3, sizeof(req3) - 1, &ri) == 0); ASSERT(parse_http_message(req6, 0, &ri) == -1);
ASSERT(parse_http_message(req6, sizeof(req6) - 1, &ri) == 0);
ASSERT(parse_http_message(req7, sizeof(req7) - 1, &ri) == 0);
ASSERT(parse_http_message("", 0, &ri) == 0);
ASSERT(parse_http_message(req8, sizeof(req8) - 1, &ri) == sizeof(req8) - 1); ASSERT(parse_http_message(req8, sizeof(req8) - 1, &ri) == sizeof(req8) - 1);
// TODO(lsm): Fix this. Header value may span multiple lines. // TODO(lsm): Fix this. Header value may span multiple lines.
...@@ -81,49 +78,34 @@ static void test_parse_http_message() { ...@@ -81,49 +78,34 @@ static void test_parse_http_message() {
ASSERT(parse_http_message(req5, sizeof(req5) - 1, &ri) == sizeof(req5) - 1); ASSERT(parse_http_message(req5, sizeof(req5) - 1, &ri) == sizeof(req5) - 1);
ASSERT(strcmp(ri.request_method, "GET") == 0); ASSERT(strcmp(ri.request_method, "GET") == 0);
ASSERT(strcmp(ri.http_version, "1.1") == 0); ASSERT(strcmp(ri.http_version, "1.1") == 0);
return NULL;
} }
static void test_should_keep_alive(void) { static const char *test_should_keep_alive(void) {
struct mg_connection conn; struct mg_connection conn;
struct mg_context ctx;
char req1[] = "GET / HTTP/1.1\r\n\r\n"; char req1[] = "GET / HTTP/1.1\r\n\r\n";
char req2[] = "GET / HTTP/1.0\r\n\r\n"; char req2[] = "GET / HTTP/1.0\r\n\r\n";
char req3[] = "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"; char req3[] = "GET / HTTP/1.1\r\nConnection: close\r\n\r\n";
char req4[] = "GET / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"; char req4[] = "GET / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n";
memset(&conn, 0, sizeof(conn)); memset(&conn, 0, sizeof(conn));
conn.ctx = &ctx; ASSERT(parse_http_message(req1, sizeof(req1) - 1, &conn) == sizeof(req1) - 1);
ASSERT(parse_http_message(req1, sizeof(req1), &conn.request_info) == ASSERT(should_keep_alive(&conn) != 0);
sizeof(req1) - 1);
ctx.config[ENABLE_KEEP_ALIVE] = "no"; parse_http_message(req2, sizeof(req2) - 1, &conn);
ASSERT(should_keep_alive(&conn) == 0); ASSERT(should_keep_alive(&conn) == 0);
ctx.config[ENABLE_KEEP_ALIVE] = "yes"; parse_http_message(req3, sizeof(req3) - 1, &conn);
ASSERT(should_keep_alive(&conn) == 1);
conn.must_close = 1;
ASSERT(should_keep_alive(&conn) == 0); ASSERT(should_keep_alive(&conn) == 0);
conn.must_close = 0; parse_http_message(req4, sizeof(req4) - 1, &conn);
parse_http_message(req2, sizeof(req2), &conn.request_info); ASSERT(should_keep_alive(&conn) != 0);
ASSERT(should_keep_alive(&conn) == 0);
parse_http_message(req3, sizeof(req3), &conn.request_info);
ASSERT(should_keep_alive(&conn) == 0);
parse_http_message(req4, sizeof(req4), &conn.request_info);
ASSERT(should_keep_alive(&conn) == 1);
conn.status_code = 401;
ASSERT(should_keep_alive(&conn) == 0);
conn.status_code = 200; return NULL;
conn.must_close = 1;
ASSERT(should_keep_alive(&conn) == 0);
} }
static void test_match_prefix(void) { static const char *test_match_prefix(void) {
ASSERT(match_prefix("/api", 4, "/api") == 4); ASSERT(match_prefix("/api", 4, "/api") == 4);
ASSERT(match_prefix("/a/", 3, "/a/b/c") == 3); ASSERT(match_prefix("/a/", 3, "/a/b/c") == 3);
ASSERT(match_prefix("/a/", 3, "/ab/c") == -1); ASSERT(match_prefix("/a/", 3, "/ab/c") == -1);
...@@ -152,9 +134,10 @@ static void test_match_prefix(void) { ...@@ -152,9 +134,10 @@ static void test_match_prefix(void) {
ASSERT(match_prefix("**.a$|**.b$", 11, "/a/b.b") == 6); ASSERT(match_prefix("**.a$|**.b$", 11, "/a/b.b") == 6);
ASSERT(match_prefix("**.a$|**.b$", 11, "/a/B.A") == 6); ASSERT(match_prefix("**.a$|**.b$", 11, "/a/B.A") == 6);
ASSERT(match_prefix("**o$", 4, "HELLO") == 5); ASSERT(match_prefix("**o$", 4, "HELLO") == 5);
return NULL;
} }
static void test_remove_double_dots() { static const char *test_remove_double_dots() {
struct { char before[20], after[20]; } data[] = { struct { char before[20], after[20]; } data[] = {
{"////a", "/a"}, {"////a", "/a"},
{"/.....", "/."}, {"/.....", "/."},
...@@ -173,8 +156,11 @@ static void test_remove_double_dots() { ...@@ -173,8 +156,11 @@ static void test_remove_double_dots() {
remove_double_dots_and_double_slashes(data[i].before); remove_double_dots_and_double_slashes(data[i].before);
ASSERT(strcmp(data[i].before, data[i].after) == 0); ASSERT(strcmp(data[i].before, data[i].after) == 0);
} }
return NULL;
} }
#if 0
static char *read_file(const char *path, int *size) { static char *read_file(const char *path, int *size) {
FILE *fp; FILE *fp;
struct stat st; struct stat st;
...@@ -188,9 +174,6 @@ static char *read_file(const char *path, int *size) { ...@@ -188,9 +174,6 @@ static char *read_file(const char *path, int *size) {
return data; return data;
} }
static const char *fetch_data = "hello world!\n";
static const char *upload_ok_message = "upload successful";
static void test_upload(struct mg_connection *conn, const char *orig_path, static void test_upload(struct mg_connection *conn, const char *orig_path,
const char *uploaded_path) { const char *uploaded_path) {
int len1, len2; int len1, len2;
...@@ -378,18 +361,6 @@ static void test_mg_upload(void) { ...@@ -378,18 +361,6 @@ static void test_mg_upload(void) {
mg_stop(ctx); mg_stop(ctx);
} }
static void test_base64_encode(void) {
const char *in[] = {"a", "ab", "abc", "abcd", NULL};
const char *out[] = {"YQ==", "YWI=", "YWJj", "YWJjZA=="};
char buf[100];
int i;
for (i = 0; in[i] != NULL; i++) {
base64_encode((unsigned char *) in[i], strlen(in[i]), buf);
ASSERT(!strcmp(buf, out[i]));
}
}
static void test_mg_get_var(void) { static void test_mg_get_var(void) {
static const char *post[] = { static const char *post[] = {
"a=1&&b=2&d&=&c=3%20&e=", "a=1&&b=2&d&=&c=3%20&e=",
...@@ -560,6 +531,16 @@ static int api_cb(struct mg_event *event) { ...@@ -560,6 +531,16 @@ static int api_cb(struct mg_event *event) {
return 0; return 0;
} }
static const char *test_mg_strcasestr(void) {
static const char *big1 = "abcdef";
ASSERT(mg_strcasestr("Y", "X") == NULL);
ASSERT(mg_strcasestr("Y", "y") != NULL);
ASSERT(mg_strcasestr(big1, "X") == NULL);
ASSERT(mg_strcasestr(big1, "CD") == big1 + 2);
ASSERT(mg_strcasestr("aa", "AAB") == NULL);
return NULL;
}
static void test_api_calls(void) { static void test_api_calls(void) {
char ebuf[100]; char ebuf[100];
struct mg_connection *conn; struct mg_connection *conn;
...@@ -576,7 +557,24 @@ static void test_api_calls(void) { ...@@ -576,7 +557,24 @@ static void test_api_calls(void) {
mg_stop(ctx); mg_stop(ctx);
} }
static void test_url_decode(void) { static void test_mg_get_cookie(void) {
char buf[20];
ASSERT(mg_get_cookie("", "foo", NULL, sizeof(buf)) == -2);
ASSERT(mg_get_cookie("", "foo", buf, 0) == -2);
ASSERT(mg_get_cookie("", "foo", buf, sizeof(buf)) == -1);
ASSERT(mg_get_cookie("", NULL, buf, sizeof(buf)) == -1);
ASSERT(mg_get_cookie("a=1; b=2; c; d", "a", buf, sizeof(buf)) == 1);
ASSERT(strcmp(buf, "1") == 0);
ASSERT(mg_get_cookie("a=1; b=2; c; d", "b", buf, sizeof(buf)) == 1);
ASSERT(strcmp(buf, "2") == 0);
ASSERT(mg_get_cookie("a=1; b=123", "b", buf, sizeof(buf)) == 3);
ASSERT(strcmp(buf, "123") == 0);
ASSERT(mg_get_cookie("a=1; b=2; c; d", "c", buf, sizeof(buf)) == -1);
}
#endif
static const char *test_url_decode(void) {
char buf[100]; char buf[100];
ASSERT(mg_url_decode("foo", 3, buf, 3, 0) == -1); // No space for \0 ASSERT(mg_url_decode("foo", 3, buf, 3, 0) == -1); // No space for \0
...@@ -597,78 +595,85 @@ static void test_url_decode(void) { ...@@ -597,78 +595,85 @@ static void test_url_decode(void) {
ASSERT(mg_url_decode("%61", 3, buf, sizeof(buf), 1) == 1); ASSERT(mg_url_decode("%61", 3, buf, sizeof(buf), 1) == 1);
ASSERT(strcmp(buf, "a") == 0); ASSERT(strcmp(buf, "a") == 0);
return NULL;
} }
static void test_mg_strcasestr(void) { static const char *test_to64(void) {
static const char *big1 = "abcdef";
ASSERT(mg_strcasestr("Y", "X") == NULL);
ASSERT(mg_strcasestr("Y", "y") != NULL);
ASSERT(mg_strcasestr(big1, "X") == NULL);
ASSERT(mg_strcasestr(big1, "CD") == big1 + 2);
ASSERT(mg_strcasestr("aa", "AAB") == NULL);
}
static void test_mg_get_cookie(void) {
char buf[20];
ASSERT(mg_get_cookie("", "foo", NULL, sizeof(buf)) == -2);
ASSERT(mg_get_cookie("", "foo", buf, 0) == -2);
ASSERT(mg_get_cookie("", "foo", buf, sizeof(buf)) == -1);
ASSERT(mg_get_cookie("", NULL, buf, sizeof(buf)) == -1);
ASSERT(mg_get_cookie("a=1; b=2; c; d", "a", buf, sizeof(buf)) == 1);
ASSERT(strcmp(buf, "1") == 0);
ASSERT(mg_get_cookie("a=1; b=2; c; d", "b", buf, sizeof(buf)) == 1);
ASSERT(strcmp(buf, "2") == 0);
ASSERT(mg_get_cookie("a=1; b=123", "b", buf, sizeof(buf)) == 3);
ASSERT(strcmp(buf, "123") == 0);
ASSERT(mg_get_cookie("a=1; b=2; c; d", "c", buf, sizeof(buf)) == -1);
}
static void test_strtoll(void) {
ASSERT(strtoll("0", NULL, 10) == 0); ASSERT(strtoll("0", NULL, 10) == 0);
ASSERT(strtoll("123", NULL, 10) == 123); ASSERT(strtoll("123", NULL, 10) == 123);
ASSERT(strtoll("-34", NULL, 10) == -34); ASSERT(strtoll("-34", NULL, 10) == -34);
ASSERT(strtoll("3566626116", NULL, 10) == 3566626116); ASSERT(strtoll("3566626116", NULL, 10) == 3566626116);
return NULL;
} }
static void test_parse_port_string(void) { static const char *test_parse_port_string(void) {
static const char *valid[] = { static const char *valid[] = {
"1", "1s", "1r", "1.2.3.4:1", "1.2.3.4:1s", "1.2.3.4:1r", "1", "1.2.3.4:1",
#if defined(USE_IPV6) #if defined(USE_IPV6)
"[::1]:123", "[3ffe:2a00:100:7031::1]:900", "[::1]:123", "[3ffe:2a00:100:7031::1]:900",
#endif #endif
NULL NULL
}; };
static const char *invalid[] = { static const char *invalid[] = {
"0", "99999", "1k", "1.2.3", "1.2.3.4:", "1.2.3.4:2p", "0", "99999", "1k", "1.2.3", "1.2.3.4:", "1.2.3.4:2p", NULL
NULL
}; };
struct socket so; union socket_address sa;
struct vec vec;
int i; int i;
for (i = 0; valid[i] != NULL; i++) { for (i = 0; valid[i] != NULL; i++) {
vec.ptr = valid[i]; ASSERT(parse_port_string(valid[i], &sa) != 0);
vec.len = strlen(vec.ptr);
ASSERT(parse_port_string(&vec, &so) != 0);
} }
for (i = 0; invalid[i] != NULL; i++) { for (i = 0; invalid[i] != NULL; i++) {
vec.ptr = invalid[i]; ASSERT(parse_port_string(invalid[i], &sa) == 0);
vec.len = strlen(vec.ptr);
ASSERT(parse_port_string(&vec, &so) == 0);
} }
return NULL;
} }
int __cdecl main(void) { static const char *test_base64_encode(void) {
test_parse_port_string(); const char *in[] = {"a", "ab", "abc", "abcd", NULL};
const char *out[] = {"YQ==", "YWI=", "YWJj", "YWJjZA=="};
char buf[100];
int i;
for (i = 0; in[i] != NULL; i++) {
base64_encode((unsigned char *) in[i], strlen(in[i]), buf);
ASSERT(!strcmp(buf, out[i]));
}
return NULL;
}
static const char *test_mg_parse_header() {
const char *str = "xx yy, ert=234 ii zz='aa bb', gf=\"xx d=1234";
char buf[10];
ASSERT(mg_parse_header(str, "yy", buf, sizeof(buf)) == 0);
ASSERT(mg_parse_header(str, "ert", buf, sizeof(buf)) == 3);
ASSERT(strcmp(buf, "234") == 0);
ASSERT(mg_parse_header(str, "ert", buf, 2) == 0);
ASSERT(mg_parse_header(str, "ert", buf, 3) == 0);
ASSERT(mg_parse_header(str, "ert", buf, 4) == 3);
ASSERT(mg_parse_header(str, "gf", buf, sizeof(buf)) == 0);
ASSERT(mg_parse_header(str, "zz", buf, sizeof(buf)) == 5);
ASSERT(strcmp(buf, "aa bb") == 0);
ASSERT(mg_parse_header(str, "d", buf, sizeof(buf)) == 4);
ASSERT(strcmp(buf, "1234") == 0);
return NULL;
}
static const char *run_all_tests(void) {
RUN_TEST(test_should_keep_alive);
RUN_TEST(test_match_prefix);
RUN_TEST(test_remove_double_dots);
RUN_TEST(test_parse_http_message);
RUN_TEST(test_parse_port_string);
RUN_TEST(test_to64);
RUN_TEST(test_url_decode);
RUN_TEST(test_base64_encode);
RUN_TEST(test_mg_parse_header);
#if 0
test_mg_strcasestr(); test_mg_strcasestr();
test_alloc_vprintf();
test_base64_encode();
test_match_prefix();
test_remove_double_dots();
test_parse_http_message();
test_should_keep_alive();
test_mg_download(); test_mg_download();
test_mg_get_var(); test_mg_get_var();
test_next_option(); test_next_option();
...@@ -677,14 +682,16 @@ int __cdecl main(void) { ...@@ -677,14 +682,16 @@ int __cdecl main(void) {
test_mg_upload(); test_mg_upload();
test_request_replies(); test_request_replies();
test_api_calls(); test_api_calls();
test_url_decode();
test_mg_get_cookie(); test_mg_get_cookie();
test_strtoll();
#ifdef USE_LUA #ifdef USE_LUA
test_lua(); test_lua();
#endif #endif
#endif
return NULL;
}
printf("TOTAL TESTS: %d, FAILED: %d\n", s_total_tests, s_failed_tests); int __cdecl main(void) {
const char *fail_msg = run_all_tests();
return s_failed_tests == 0 ? EXIT_SUCCESS : EXIT_FAILURE; printf("%s, tests run: %d\n", fail_msg ? "FAIL" : "PASS", static_num_tests);
return fail_msg == NULL ? EXIT_SUCCESS : EXIT_FAILURE;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment