From: Lukáš Ježek Date: Fri, 27 Nov 2020 08:43:13 +0000 (+0100) Subject: doh2: send HTTP error status code X-Git-Tag: v5.3.1~13^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c2358a4ed838e65d80bf9e73a9c2d0e2f41dbc66;p=thirdparty%2Fknot-resolver.git doh2: send HTTP error status code --- diff --git a/NEWS b/NEWS index 7536f1f0e..0498a5aeb 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,11 @@ +Knot Resolver 5.y.z (2021-0m-dd) +================================ + +Improvements +------------ +- doh2: send HTTP error status codes (#618, !1102) + + Knot Resolver 5.3.0 (2021-02-25) ================================ diff --git a/daemon/http.c b/daemon/http.c index f72465f65..fd4ad262c 100644 --- a/daemon/http.c +++ b/daemon/http.c @@ -131,10 +131,189 @@ static int send_data_callback(nghttp2_session *h2, nghttp2_frame *frame, const u return 0; } +/* + * Provide data from buffer to HTTP/2 library. + * + * To avoid copying the packet wire buffer, we use NGHTTP2_DATA_FLAG_NO_COPY + * and take care of sending entire DATA frames ourselves with nghttp2_send_data_callback. + * + * See https://www.nghttp2.org/documentation/types.html#c.nghttp2_data_source_read_callback + */ +static ssize_t read_callback(nghttp2_session *h2, int32_t stream_id, uint8_t *buf, + size_t length, uint32_t *data_flags, + nghttp2_data_source *source, void *user_data) +{ + struct http_data *data; + size_t avail; + size_t send; + + data = (struct http_data*)source->ptr; + avail = data->len - data->pos; + send = MIN(avail, length); + + if (avail == send) + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + + *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY; + return send; +} + +/* + * Get pointer to stream status. + */ +static struct http_stream_status * http_status_get(struct http_ctx *ctx, int32_t stream_id) +{ + assert(ctx); + struct http_stream_status *stat = NULL; + + if (stream_id == ctx->incomplete_stream) + //return ctx->current_stream_index; + return ctx->current_stream; + + + for (size_t idx = 0; idx < ctx->stream_status.len; ++idx) { + stat = ctx->stream_status.at[idx]; + if (stat->stream_id == stream_id) + return stat; + } + return NULL; +} + +/* + * Remove error stream status from list + */ +static int http_status_remove(struct http_ctx *ctx, struct http_stream_status * stat) +{ + if (!stat) + return 0; + + //assert(array_del(ctx->stream_status, idx) == 0); + // TODO + ctx->stream_status.len -= 1; + if (stat->err_msg) + free(stat->err_msg); + stat = ctx->stream_status.at[ctx->stream_status.len]; + //free(stat); + return 0; +} + +/* + * Send http error status code. + */ +static int send_err_status(struct http_ctx *ctx, int32_t stream_id) +{ + int ret; + int status_len; + nghttp2_data_provider prov; + struct http_stream_status *stat = NULL; + + stat = http_status_get(ctx, stream_id); + assert(stat); + if (stat->err_status == 200) { + http_status_remove(ctx, stat); + return 0; + } + + prov.source.ptr = NULL; + prov.read_callback = NULL; + + char status_str[MAX_DECIMAL_LENGTH(stat->err_status)] = { 0 }; + status_len = snprintf(status_str, MAX_DECIMAL_LENGTH(stat->err_status), "%u", stat->err_status); + nghttp2_nv hdrs_err[] = { + MAKE_NV(":status", 7, status_str, status_len), + }; + + if (stat->err_msg) { + struct http_data *data = malloc(sizeof(struct http_data)); + if (!data) + return kr_error(ENOMEM); + + data->buf = (uint8_t *)stat->err_msg; + data->len = strlen(stat->err_msg); + data->pos = 0; + data->on_write = NULL; + data->req = NULL; + data->ttl = 0; + + prov.source.ptr = data; + prov.read_callback = read_callback; + } + + ret = nghttp2_submit_response(ctx->h2, stream_id, hdrs_err, sizeof(hdrs_err)/sizeof(*hdrs_err), &prov); + if (ret != 0) + return kr_error(EIO); + + http_status_remove(ctx, stat); + + return 0; +} + +/* + * Set error status for particural stream_id and return array index or error + * + * status_msg is optional and define error message. + */ +static struct http_stream_status * set_error_status(struct http_ctx *ctx, int32_t stream_id, int status, const char *const status_msg) +{ + + struct http_stream_status *stat = http_status_get(ctx, stream_id); + if (stat && stat->err_status != 200) + return stat; + + // add new item to array + if (!stat) { + stat = malloc(sizeof(*stat)); + if (!stat) + return NULL; + + if (array_push(ctx->stream_status, stat) < 0) { + free(stat); + return NULL; + } + + stat->err_msg = NULL; + } + stat->stream_id = stream_id; + stat->err_status = status; + + if (!status_msg) { + if (stat->err_msg) { // remove previous message + free(stat->err_msg); + stat->err_msg = NULL; + } + + return stat; + } + + stat->err_msg = realloc(stat->err_msg, sizeof(*stat->err_msg) * (strlen(status_msg) + 1)); + if (!stat->err_msg) { + return stat; + } + + memcpy(stat->err_msg, status_msg, strlen(status_msg)); + stat->err_msg[strlen(status_msg)] = '\0'; + + return stat; +} + +/* + * Reinit temporaly data of current stream + */ +static void http_status_reinit(struct http_ctx *ctx) +{ + ctx->incomplete_stream = -1; + ctx->current_method = HTTP_METHOD_NONE; + ctx->current_stream = NULL; + if (ctx->content_type) { + free(ctx->content_type); + ctx->content_type = NULL; + } +} + /* * Check endpoint and uri path */ -static int check_uri(const char* uri_path) +static int check_uri(struct http_ctx *ctx, int32_t stream_id, const char* uri_path) { static const char key[] = "dns="; static const char *delim = "&"; @@ -143,6 +322,7 @@ static int check_uri(const char* uri_path) char *end_prev; ssize_t endpoint_len; ssize_t ret; + struct http_stream_status *stat; if (!uri_path) return kr_error(EINVAL); @@ -170,8 +350,10 @@ static int check_uri(const char* uri_path) break; } - if (ret) /* no endpoint found */ - return -1; + if (ret) { /* no endpoint found */ + stat = set_error_status(ctx, stream_id, 400, "missing endpoint"); + return stat ? kr_error(EINVAL) : kr_error(ENOMEM); + } if (endpoint_len == strlen(path) - 1) /* done for POST method */ return 0; @@ -182,37 +364,43 @@ static int check_uri(const char* uri_path) if (!strncmp(beg, key, 4)) { /* dns variable in path found */ break; } - end_prev = beg + strlen(beg); + end_prev = beg + strlen(beg) - 1; beg = strtok(NULL, delim); - if (beg-1 != end_prev) { /* detect && */ - return -1; + if (beg && beg-1 != end_prev+1) { /* detect && */ + stat = set_error_status(ctx, stream_id, 400, "invalid uri path"); + return stat ? kr_error(EINVAL) : kr_error(ENOMEM); } } if (!beg) { /* no dns variable in path */ - return -1; + stat = set_error_status(ctx, stream_id, 400, "'dns' key in path not found"); + return stat ? kr_error(EINVAL) : kr_error(ENOMEM); } } return 0; } + /* * Process a query from URI path if there's base64url encoded dns variable. */ -static int process_uri_path(struct http_ctx *ctx, const char* path, int32_t stream_id) +static int process_uri_path(struct http_ctx *ctx, int32_t stream_id) { - if (!ctx || !path) - return kr_error(EINVAL); - static const char key[] = "dns="; - char *beg = strstr(path, key); - char *end; + char *beg, *end; size_t remaining; ssize_t ret; uint8_t *dest; + struct http_stream_status *stat; - if (!beg) /* No dns variable in path. */ + if (!ctx || !ctx->uri_path) { + stat = set_error_status(ctx, stream_id, 400, "invalid uri path"); + return stat ? 0 : kr_error(ENOMEM); + } + + beg = strstr(ctx->uri_path, key); + if (!beg) /* No dns variable in ctx->uri_path. */ return 0; beg += sizeof(key) - 1; @@ -227,9 +415,13 @@ static int process_uri_path(struct http_ctx *ctx, const char* path, int32_t stre ret = kr_base64url_decode((uint8_t*)beg, end - beg, dest, remaining); if (ret < 0) { ctx->buf_pos = 0; - kr_log_verbose("[http] base64url decode failed %s\n", - strerror(ret)); - return ret; + kr_log_verbose("[http] base64url decode failed %s\n", strerror(ret)); + if (ret == KNOT_ERANGE) { + stat = set_error_status(ctx, stream_id, 414, NULL);// ? ; + } else { + stat = set_error_status(ctx, stream_id, 400, NULL); + } + return stat ? 0 : kr_error(ENOMEM); } ctx->buf_pos += ret; @@ -237,12 +429,6 @@ static int process_uri_path(struct http_ctx *ctx, const char* path, int32_t stre return 0; } -static void refuse_stream(nghttp2_session *h2, int32_t stream_id) -{ - nghttp2_submit_rst_stream( - h2, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_REFUSED_STREAM); -} - /* * Save stream id from first header's frame. * @@ -263,10 +449,12 @@ static int begin_headers_callback(nghttp2_session *h2, const nghttp2_frame *fram if (ctx->incomplete_stream != -1) { kr_log_verbose( - "[http] stream %d incomplete, refusing\n", ctx->incomplete_stream); - refuse_stream(h2, stream_id); + "[http] stream %d incomplete\n", ctx->incomplete_stream); + if (!set_error_status(ctx, stream_id, 501, "incomplete stream")) + return NGHTTP2_ERR_CALLBACK_FAILURE; } else { ctx->incomplete_stream = stream_id; + ctx->current_stream = set_error_status(ctx, stream_id, 200, NULL); } return 0; } @@ -289,22 +477,24 @@ static int header_callback(nghttp2_session *h2, const nghttp2_frame *frame, if (ctx->incomplete_stream != stream_id) { kr_log_verbose( - "[http] stream %d incomplete, refusing\n", ctx->incomplete_stream); - refuse_stream(h2, stream_id); + "[http] stream %d incomplete\n", ctx->incomplete_stream); + if (!set_error_status(ctx, stream_id, 501, "incomplete stream")) + return NGHTTP2_ERR_CALLBACK_FAILURE; return 0; } if (!strcasecmp(":path", (const char *)name)) { - if (check_uri((const char *)value) < 0) { - refuse_stream(h2, stream_id); - return 0; + int rc = check_uri(ctx, stream_id, (const char *)value); + if (rc < 0) { + if (rc == kr_error(ENOMEM)) + return NGHTTP2_ERR_CALLBACK_FAILURE; + } else { + ctx->uri_path = malloc(sizeof(*ctx->uri_path) * (valuelen + 1)); + if (!ctx->uri_path) + return NGHTTP2_ERR_CALLBACK_FAILURE; + memcpy(ctx->uri_path, value, valuelen); + ctx->uri_path[valuelen] = '\0'; } - - ctx->uri_path = malloc(sizeof(*ctx->uri_path) * (valuelen + 1)); - if (!ctx->uri_path) - return kr_error(ENOMEM); - memcpy(ctx->uri_path, value, valuelen); - ctx->uri_path[valuelen] = '\0'; } if (!strcasecmp(":method", (const char *)name)) { @@ -317,6 +507,14 @@ static int header_callback(nghttp2_session *h2, const nghttp2_frame *frame, } } + if (!strcasecmp("content-type", (const char *)name)) { + ctx->content_type = malloc(sizeof(*ctx->content_type) * valuelen+1); + if (!ctx->content_type) + return NGHTTP2_ERR_CALLBACK_FAILURE; + memcpy(ctx->content_type, value, valuelen); + ctx->content_type[valuelen] = '\0'; + } + return 0; } @@ -338,10 +536,10 @@ static int data_chunk_recv_callback(nghttp2_session *h2, uint8_t flags, int32_t if (ctx->incomplete_stream != stream_id) { kr_log_verbose( - "[http] stream %d incomplete, refusing\n", + "[http] stream %d incomplete\n", ctx->incomplete_stream); - refuse_stream(h2, stream_id); - ctx->incomplete_stream = -1; + if (!set_error_status(ctx, stream_id, 501, "incomplete stream")) + return NGHTTP2_ERR_CALLBACK_FAILURE; return 0; } @@ -353,8 +551,11 @@ static int data_chunk_recv_callback(nghttp2_session *h2, uint8_t flags, int32_t if (required > remaining) { kr_log_error("[http] insufficient space in buffer\n"); - ctx->incomplete_stream = -1; - return NGHTTP2_ERR_CALLBACK_FAILURE; + if (!set_error_status(ctx, stream_id, 413, NULL)) { + http_status_reinit(ctx); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + return 0; } if (is_first) { @@ -382,27 +583,72 @@ static int on_frame_recv_callback(nghttp2_session *h2, const nghttp2_frame *fram int32_t stream_id = frame->hd.stream_id; assert(stream_id != -1); - if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) && ctx->incomplete_stream == stream_id) { - if (ctx->current_method == HTTP_METHOD_GET) { - if (process_uri_path(ctx, ctx->uri_path, stream_id) < 0) { - refuse_stream(h2, stream_id); - } + if (stream_id == 0) + return 0; + + if (ctx->current_method == HTTP_METHOD_NONE) { + kr_log_verbose("[http] unsupported HTTP method\n"); + if (!set_error_status(ctx, stream_id, 405, "only HTTP POST and GET are supported\n")) { + http_status_reinit(ctx); + return NGHTTP2_ERR_CALLBACK_FAILURE; } - ctx->incomplete_stream = -1; - ctx->current_method = HTTP_METHOD_NONE; - free(ctx->uri_path); - ctx->uri_path = NULL; - - len = ctx->buf_pos - sizeof(uint16_t); - if (len <= 0 || len > KNOT_WIRE_MAX_PKTSIZE) { - kr_log_verbose("[http] invalid dnsmsg size: %zd B\n", len); + } + + if (ctx->content_type && strcasecmp("application/dns-message", (const char *)ctx->content_type)) { + kr_log_verbose("[http] unsupported content-type %s\n", ctx->content_type); + if (!set_error_status(ctx, stream_id, 415, "only Content-Type: application/dns-message is supported\n")) { + http_status_reinit(ctx); return NGHTTP2_ERR_CALLBACK_FAILURE; } + } - knot_wire_write_u16(ctx->buf, len); - ctx->submitted += ctx->buf_pos; - ctx->buf += ctx->buf_pos; - ctx->buf_pos = 0; + if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { + struct http_stream_status *stat = ctx->current_stream; + if (ctx->incomplete_stream == stream_id) { + if (ctx->current_method == HTTP_METHOD_GET) { + if (process_uri_path(ctx, stream_id) < 0) { + http_status_reinit(ctx); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + free(ctx->uri_path); + ctx->uri_path = NULL; + + if (ctx->buf_pos) { + len = ctx->buf_pos - sizeof(uint16_t); + if (len <= 0 || len > KNOT_WIRE_MAX_PKTSIZE) { + kr_log_verbose("[http] invalid dnsmsg size: %zd B\n", len); + http_status_reinit(ctx); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + if (len < 12) { + if (!set_error_status(ctx, stream_id, 400, "input too short\n")) { + http_status_reinit(ctx); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + + if (stat->err_status == 200) { + knot_wire_write_u16(ctx->buf, len); + ctx->submitted += ctx->buf_pos; + ctx->buf += ctx->buf_pos; + } + } + + if (stat->err_status != 200) { + if (send_err_status(ctx, stream_id) < 0) + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + http_status_reinit(ctx); + ctx->buf_pos = 0; + } else { + /* send error for non-processed stream */ + if (send_err_status(ctx, stream_id) < 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } } return 0; @@ -473,9 +719,13 @@ struct http_ctx* http_new(struct session *session, http_send_callback send_cb) ctx->session = session; queue_init(ctx->streams); ctx->incomplete_stream = -1; + ctx->current_stream = NULL; ctx->submitted = 0; ctx->current_method = HTTP_METHOD_NONE; ctx->uri_path = NULL; + ctx->content_type = NULL; + array_init(ctx->stream_status); + nghttp2_session_server_new(&ctx->h2, callbacks, ctx); nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE, @@ -522,33 +772,6 @@ ssize_t http_process_input_data(struct session *session, const uint8_t *buf, return ctx->submitted; } -/* - * Provide data from buffer to HTTP/2 library. - * - * To avoid copying the packet wire buffer, we use NGHTTP2_DATA_FLAG_NO_COPY - * and take care of sending entire DATA frames ourselves with nghttp2_send_data_callback. - * - * See https://www.nghttp2.org/documentation/types.html#c.nghttp2_data_source_read_callback - */ -static ssize_t read_callback(nghttp2_session *h2, int32_t stream_id, uint8_t *buf, - size_t length, uint32_t *data_flags, - nghttp2_data_source *source, void *user_data) -{ - struct http_data *data; - size_t avail; - size_t send; - - data = (struct http_data*)source->ptr; - avail = data->len - data->pos; - send = MIN(avail, length); - - if (avail == send) - *data_flags |= NGHTTP2_DATA_FLAG_EOF; - - *data_flags |= NGHTTP2_DATA_FLAG_NO_COPY; - return send; -} - /* * Send dns response provided by the HTTTP/2 data provider. * @@ -663,7 +886,12 @@ void http_free(struct http_ctx *ctx) if (!ctx) return; + // TODO +// while(ctx->stream_status.len) +// http_status_remove(ctx, 0); + queue_deinit(ctx->streams); nghttp2_session_del(ctx->h2); + free(ctx->content_type); free(ctx); } diff --git a/daemon/http.h b/daemon/http.h index f0662779b..114f261e3 100644 --- a/daemon/http.h +++ b/daemon/http.h @@ -32,6 +32,12 @@ typedef enum { HTTP_METHOD_POST = 2, } http_method_t; +struct http_stream_status { + int32_t stream_id; + int err_status; + char *err_msg; +}; + struct http_ctx { struct nghttp2_session *h2; http_send_callback send_cb; @@ -41,9 +47,12 @@ struct http_ctx { ssize_t submitted; http_method_t current_method; char *uri_path; + char *content_type; uint8_t *buf; /* Part of the wire_buf that belongs to current HTTP/2 stream. */ ssize_t buf_pos; ssize_t buf_size; + array_t(struct http_stream_status*) stream_status; + struct http_stream_status *current_stream; }; #if ENABLE_DOH2 diff --git a/tests/config/doh2.test.lua b/tests/config/doh2.test.lua index 2c4779e90..6111f1148 100644 --- a/tests/config/doh2.test.lua +++ b/tests/config/doh2.test.lua @@ -63,15 +63,15 @@ local function check_ok(req, desc) return headers, pkt end ---local function check_err(req, exp_status, desc) --- local headers, errmsg, errno = req:go(8) -- randomly chosen timeout by tkrizek --- if errno then --- nok(errmsg, desc .. ': ' .. errmsg) --- return --- end --- local got_status = headers:get(':status') --- same(got_status, exp_status, desc) ---end +local function check_err(req, exp_status, desc) + local headers, errmsg, errno = req:go(8) -- randomly chosen timeout by tkrizek + if errno then + nok(errmsg, desc .. ': ' .. errmsg) + return + end + local got_status = headers:get(':status') + same(got_status, exp_status, desc) +end -- check prerequisites local bound, port @@ -168,34 +168,36 @@ else end -- test an invalid DNS query using POST --- local function test_post_short_input() --- local req = assert(req_templ:clone()) --- req.headers:upsert(':method', 'POST') --- req:set_body(string.rep('0', 11)) -- 11 bytes < DNS msg header --- check_err(req, '400', 'too short POST finishes with 400') --- end --- --- local function test_post_long_input() --- local req = assert(req_templ:clone()) --- req.headers:upsert(':method', 'POST') --- req:set_body(string.rep('s', 1025)) -- > DNS msg over UDP --- check_err(req, '413', 'too long POST finishes with 413') --- end --- --- local function test_post_unparseable_input() --- local req = assert(req_templ:clone()) --- req.headers:upsert(':method', 'POST') --- req:set_body(string.rep('\0', 1024)) -- garbage --- check_err(req, '400', 'unparseable DNS message finishes with 400') --- end --- --- local function test_post_unsupp_type() --- local req = assert(req_templ:clone()) --- req.headers:upsert(':method', 'POST') --- req.headers:upsert('content-type', 'application/dns+json') --- req:set_body(string.rep('\0', 12)) -- valid message --- check_err(req, '415', 'unsupported request content type finishes with 415') --- end + local function test_post_short_input() + local req = assert(req_templ:clone()) + req.headers:upsert(':method', 'POST') + req:set_body(string.rep('0', 11)) -- 11 bytes < DNS msg header + check_err(req, '400', 'too short POST finishes with 400') + test_post_noerror() + end + + local function test_post_unsupp_type() + local req = assert(req_templ:clone()) + req.headers:upsert(':method', 'POST') + req.headers:upsert('content-type', 'application/dns+json') + req:set_body(string.rep('\0', 12)) -- valid message + check_err(req, '415', 'unsupported request content type finishes with 415') + test_post_noerror() + end + + local function test_get_right_endpoints() + local desc = 'GET query with "doh" endpoint' + local req = req_templ:clone() + req.headers:upsert(':method', 'GET') + req.headers:upsert(':path', '/doh?dns=vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB') + check_ok(req, desc) + + desc = 'GET query with "dns-query" endpoint' + req = req_templ:clone() + req.headers:upsert(':method', 'GET') + req.headers:upsert(':path', '/dns-query?dns=vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB') + check_ok(req, desc) + end -- test a valid DNS query using GET local function test_get_servfail() @@ -273,47 +275,75 @@ else check_ok(req, desc) end --- -- test an invalid DNS query using GET --- local function test_get_long_input() --- local req = assert(req_templ:clone()) --- req.headers:upsert(':method', 'GET') --- req.headers:upsert(':path', '/doh?dns=' .. basexx.to_url64(string.rep('\0', 1030))) --- check_err(req, '414', 'too long GET finishes with 414') --- end --- --- local function test_get_no_dns_param() --- local req = assert(req_templ:clone()) --- req.headers:upsert(':method', 'GET') --- req.headers:upsert(':path', '/doh?notdns=' .. basexx.to_url64(string.rep('\0', 1024))) --- check_err(req, '400', 'GET without dns paramter finishes with 400') --- end --- --- local function test_get_unparseable() --- local req = assert(req_templ:clone()) --- req.headers:upsert(':method', 'GET') --- req.headers:upsert(':path', '/doh??dns=' .. basexx.to_url64(string.rep('\0', 1024))) --- check_err(req, '400', 'unparseable GET finishes with 400') --- end --- --- local function test_get_invalid_b64() --- local req = assert(req_templ:clone()) --- req.headers:upsert(':method', 'GET') --- req.headers:upsert(':path', '/doh?dns=thisisnotb64') --- check_err(req, '400', 'GET with invalid base64 finishes with 400') --- end --- --- local function test_get_invalid_chars() --- local req = assert(req_templ:clone()) --- req.headers:upsert(':method', 'GET') --- req.headers:upsert(':path', '/doh?dns=' .. basexx.to_url64(string.rep('\0', 200)) .. '@#$%?!') --- check_err(req, '400', 'GET with invalid characters in b64 finishes with 400') --- end --- --- local function test_unsupp_method() --- local req = assert(req_templ:clone()) --- req.headers:upsert(':method', 'PUT') --- check_err(req, '405', 'unsupported method finishes with 405') --- end + -- test an invalid DNS query using GET + local function test_get_wrong_endpoints() + local req = req_templ:clone() + req.headers:upsert(':method', 'GET') + req.headers:upsert(':path', '/bad?dns=vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB') + check_err(req, '400', 'wrong "bad" endpoint finishes with 400') + test_get_other_params() + + req = req_templ:clone() + req.headers:upsert(':method', 'GET') + req.headers:upsert(':path', '/dns?dns=vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB') + check_err(req, '400', 'wrong "dns" endpoint finishes with 400') + test_get_other_params() + end + + local function test_get_no_dns_param() + local req = assert(req_templ:clone()) + req.headers:upsert(':method', 'GET') + req.headers:upsert(':path', '/doh?notdns=' .. basexx.to_url64(string.rep('\0', 1024))) + check_err(req, '400', 'GET without dns parameter finishes with 400') + test_get_other_params() + end + + local function test_get_unparseable() + local req = assert(req_templ:clone()) + req.headers:upsert(':method', 'GET') + req.headers:upsert(':path', '/doh??dns=' .. basexx.to_url64(string.rep('\0', 1024))) + check_err(req, '400', 'unparseable GET finishes with 400') + test_get_other_params() + end + + local function test_get_invalid_b64() + local req = assert(req_templ:clone()) + req.headers:upsert(':method', 'GET') + req.headers:upsert(':path', '/doh?dns=thisisnotb64') + check_err(req, '400', 'GET with invalid base64 finishes with 400') + test_get_other_params() + end + + local function test_get_invalid_chars() + local req = assert(req_templ:clone()) + req.headers:upsert(':method', 'GET') + req.headers:upsert(':path', '/doh?dns=' .. basexx.to_url64(string.rep('\0', 200)) .. '@#$%?!') + check_err(req, '400', 'GET with invalid characters in b64 finishes with 400') + test_get_other_params() + end + + local function test_get_two_ampersands() + local req = req_templ:clone() + req.headers:upsert(':method', 'GET') + req.headers:upsert(':path', + '/doh?other=something&&dns=vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB') + check_err(req, '400', 'GET with two ampersands finishes with 400') + test_get_other_params() + + req = req_templ:clone() + req.headers:upsert(':method', 'GET') + req.headers:upsert(':path', + '/doh?other=something&&nodns=vMEBAAABAAAAAAAAB25vZXJyb3IEdGVzdAAAAQAB') + check_err(req, '400', 'GET with two ampersands finishes with 400') + test_get_other_params() + end + + local function test_unsupp_method() + local req = assert(req_templ:clone()) + req.headers:upsert(':method', 'PUT') + check_err(req, '405', 'unsupported method finishes with 405') + test_get_other_params() + end local function test_dstaddr() local triggered = false @@ -353,39 +383,29 @@ else modules.unload('view') end --- not implemented --- local function test_post_unsupp_accept() --- local req = assert(req_templ:clone()) --- req.headers:upsert(':method', 'POST') --- req.headers:upsert('accept', 'application/dns+json') --- req:set_body(string.rep('\0', 12)) -- valid message --- check_err(req, '406', 'unsupported Accept type finishes with 406') --- end - -- plan tests - -- TODO: implement (some) of the error status codes local tests = { start_server, test_post_servfail, test_post_noerror, test_post_nxdomain, test_huge_answer, - --test_post_short_input, - --test_post_long_input, - --test_post_unparseable_input, - --test_post_unsupp_type, + test_post_short_input, + test_post_unsupp_type, + test_get_right_endpoints, test_get_servfail, test_get_noerror, test_get_nxdomain, test_get_other_params_before_dns, test_get_other_params_after_dns, test_get_other_params, - --test_get_long_input, - --test_get_no_dns_param, - --test_get_unparseable, - --test_get_invalid_b64, - --test_get_invalid_chars, - --test_unsupp_method, + test_get_wrong_endpoints, + test_get_no_dns_param, + test_get_unparseable, + test_get_invalid_b64, + test_get_invalid_chars, + test_get_two_ampersands, + test_unsupp_method, test_dstaddr, test_srcaddr }