From a1dc687b5dac678d59ff9f85e34c00bf785f6cd5 Mon Sep 17 00:00:00 2001 From: Flavio Santes Date: Sun, 7 Aug 2016 01:47:40 -0500 Subject: [PATCH] iot/http: Add test-case for HTTP header fields This commit adds the following routines: - test_invalid_header_content - test_invalid_header_content - test_invalid_header_field - test_invalid_header_field - test_preserve_data - test_parse_url - test_method_str - test_header_nread_value - test_double_content_length_error - test_chunked_content_length_error - test_header_cr_no_lf_error - test_invalid_header_field_token_error - test_invalid_header_field_content_error - test_double_content_length_error - test_chunked_content_length_error - test_header_cr_no_lf_error - test_invalid_header_field_token_error - test_invalid_header_field_content_error Origin: https://github.com/nodejs/http-parser/releases/tag/v2.7.1 https://github.com/nodejs/http-parser/archive/v2.7.1.tar.gz This patch reformats some http_parser files to reduce checkpatch warnings. See: https://gitlab.com/santes/http_parser/tree/refactoring1 Commit: c58dd8350fdf6ead0807ba37107b19f58e4f434c Furthermore, method_str overflow test was updated to avoid compiler warnings on some platforms. Before: Line 627: http_method_str(1337) After: Line 627: http_method_str(127) Jira: ZEP-346 Jira: ZEP-776 Change-Id: If6163f59de21186f22f4f02d8c44f43ddbf9b59b Signed-off-by: Flavio Santes --- tests/iot/test_http_header/Makefile | 20 + tests/iot/test_http_header/README | 27 + tests/iot/test_http_header/prj.conf | 5 + tests/iot/test_http_header/src/Makefile | 19 + .../test_http_header/src/test_http_header.c | 941 ++++++++++++++++++ tests/iot/test_http_header/testcase.ini | 3 + 6 files changed, 1015 insertions(+) create mode 100644 tests/iot/test_http_header/Makefile create mode 100644 tests/iot/test_http_header/README create mode 100644 tests/iot/test_http_header/prj.conf create mode 100644 tests/iot/test_http_header/src/Makefile create mode 100644 tests/iot/test_http_header/src/test_http_header.c create mode 100644 tests/iot/test_http_header/testcase.ini diff --git a/tests/iot/test_http_header/Makefile b/tests/iot/test_http_header/Makefile new file mode 100644 index 00000000000..f6b8735d3a0 --- /dev/null +++ b/tests/iot/test_http_header/Makefile @@ -0,0 +1,20 @@ +# +# Copyright (c) 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +BOARD ?= qemu_x86 +CONF_FILE ?= prj.conf + +include $(ZEPHYR_BASE)/Makefile.inc diff --git a/tests/iot/test_http_header/README b/tests/iot/test_http_header/README new file mode 100644 index 00000000000..de3dff013c5 --- /dev/null +++ b/tests/iot/test_http_header/README @@ -0,0 +1,27 @@ +HTTP header fields test +----------------------- + +Sample output: + +tc_start() - HTTP header fields test +[PASS] test_preserve_data +[PASS] test_parse_url +[PASS] test_method_str +[PASS] test_header_nread_value +[PASS] test_double_content_length_error HTTP_REQUEST +[PASS] test_chunked_content_length_error HTTP_REQUEST +[PASS] test_header_cr_no_lf_error HTTP_REQUEST +[PASS] test_invalid_header_field_token_error HTTP_REQUEST +[PASS] test_invalid_header_field_content_error HTTP_REQUEST +[PASS] test_double_content_length_error HTTP_RESPONSE +[PASS] test_chunked_content_length_error HTTP_RESPONSE +[PASS] test_header_cr_no_lf_error HTTP_RESPONSE +[PASS] test_invalid_header_field_token_error HTTP_RESPONSE +[PASS] test_invalid_header_field_content_error HTTP_RESPONSE + + No errors detected +=================================================================== +PASS - main. +=================================================================== +PROJECT EXECUTION SUCCESSFUL + diff --git a/tests/iot/test_http_header/prj.conf b/tests/iot/test_http_header/prj.conf new file mode 100644 index 00000000000..b9f08eee506 --- /dev/null +++ b/tests/iot/test_http_header/prj.conf @@ -0,0 +1,5 @@ +# Comment the following line if you want to try another libc +CONFIG_MINIMAL_LIBC_EXTENDED=y +CONFIG_HTTP_PARSER=y +# Enable strict parser by uncommenting the following line +# CONFIG_HTTP_PARSER_STRICT=y diff --git a/tests/iot/test_http_header/src/Makefile b/tests/iot/test_http_header/src/Makefile new file mode 100644 index 00000000000..3457bca106e --- /dev/null +++ b/tests/iot/test_http_header/src/Makefile @@ -0,0 +1,19 @@ +# +# Copyright (c) 2016 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +ccflags-y += -I$(ZEPHYR_BASE)/tests/include + +obj-y += test_http_header.o diff --git a/tests/iot/test_http_header/src/test_http_header.c b/tests/iot/test_http_header/src/test_http_header.c new file mode 100644 index 00000000000..377d2ba6291 --- /dev/null +++ b/tests/iot/test_http_header/src/test_http_header.c @@ -0,0 +1,941 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include + +#include +#include + +static +struct http_parser_settings settings_null = { + .on_message_begin = 0, + .on_header_field = 0, + .on_header_value = 0, + .on_url = 0, + .on_status = 0, + .on_body = 0, + .on_headers_complete = 0, + .on_message_complete = 0, + .on_chunk_header = 0, + .on_chunk_complete = 0}; + +struct url_test { + const char *name; + const char *url; + int is_connect; + struct http_parser_url u; + int rv; +}; + +const struct url_test url_tests[] = { + { + .name = "proxy request", + .url = "http://hostname/", + .is_connect = 0, + .u = { .field_set = (1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH), + .port = 0, + .field_data = { + { 0, 4 }, /* UF_SCHEMA */ + { 7, 8 }, /* UF_HOST */ + { 0, 0 }, /* UF_PORT */ + { 15, 1 }, /* UF_PATH */ + { 0, 0 }, /* UF_QUERY */ + { 0, 0 }, /* UF_FRAGMENT */ + { 0, 0 } } }, /* UF_USERINFO */ + .rv = 0 + }, + + { + .name = "proxy request with port", + .url = "http://hostname:444/", + .is_connect = 0, + .u = { .field_set = (1 << UF_SCHEMA) | (1 << UF_HOST) | + (1 << UF_PORT) | (1 << UF_PATH), + .port = 444, + .field_data = { + { 0, 4 }, /* UF_SCHEMA */ + { 7, 8 }, /* UF_HOST */ + { 16, 3 }, /* UF_PORT */ + { 19, 1 }, /* UF_PATH */ + { 0, 0 }, /* UF_QUERY */ + { 0, 0 }, /* UF_FRAGMENT */ + { 0, 0 } } }, /* UF_USERINFO */ + .rv = 0 + }, + + { + .name = "CONNECT request", + .url = "hostname:443", + .is_connect = 1, + .u = { .field_set = (1 << UF_HOST) | (1 << UF_PORT), + .port = 443, + .field_data = { + { 0, 0 }, /* UF_SCHEMA */ + { 0, 8 }, /* UF_HOST */ + { 9, 3 }, /* UF_PORT */ + { 0, 0 }, /* UF_PATH */ + { 0, 0 }, /* UF_QUERY */ + { 0, 0 }, /* UF_FRAGMENT */ + { 0, 0 } } }, /* UF_USERINFO */ + .rv = 0 + }, + + { + .name = "CONNECT request but not connect", + .url = "hostname:443", + .is_connect = 0, + .rv = 1 + }, + + { + .name = "proxy ipv6 request", + .url = "http://[1:2::3:4]/", + .is_connect = 0, + .u = { .field_set = (1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH), + .port = 0, + .field_data = { + { 0, 4 }, /* UF_SCHEMA */ + { 8, 8 }, /* UF_HOST */ + { 0, 0 }, /* UF_PORT */ + { 17, 1 }, /* UF_PATH */ + { 0, 0 }, /* UF_QUERY */ + { 0, 0 }, /* UF_FRAGMENT */ + { 0, 0 } } }, /* UF_USERINFO */ + .rv = 0 + }, + + { + .name = "proxy ipv6 request with port", + .url = "http://[1:2::3:4]:67/", + .is_connect = 0, + .u = { .field_set = (1 << UF_SCHEMA) | (1 << UF_HOST) | + (1 << UF_PORT) | (1 << UF_PATH), + .port = 67, + .field_data = { + { 0, 4 }, /* UF_SCHEMA */ + { 8, 8 }, /* UF_HOST */ + { 18, 2 }, /* UF_PORT */ + { 20, 1 }, /* UF_PATH */ + { 0, 0 }, /* UF_QUERY */ + { 0, 0 }, /* UF_FRAGMENT */ + { 0, 0 } } }, /* UF_USERINFO */ + .rv = 0 + }, + + { + .name = "CONNECT ipv6 address", + .url = "[1:2::3:4]:443", + .is_connect = 1, + .u = { .field_set = (1 << UF_HOST) | (1 << UF_PORT), + .port = 443, + .field_data = { + { 0, 0 }, /* UF_SCHEMA */ + { 1, 8 }, /* UF_HOST */ + { 11, 3 }, /* UF_PORT */ + { 0, 0 }, /* UF_PATH */ + { 0, 0 }, /* UF_QUERY */ + { 0, 0 }, /* UF_FRAGMENT */ + { 0, 0 } } }, /* UF_USERINFO */ + .rv = 0 + }, + + { + .name = "ipv4 in ipv6 address", + .url = "http://[2001:0000:0000:0000:0000:0000:1.9.1.1]/", + .is_connect = 0, + .u = { .field_set = (1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH), + .port = 0, + .field_data = { + { 0, 4 }, /* UF_SCHEMA */ + { 8, 37 }, /* UF_HOST */ + { 0, 0 }, /* UF_PORT */ + { 46, 1 }, /* UF_PATH */ + { 0, 0 }, /* UF_QUERY */ + { 0, 0 }, /* UF_FRAGMENT */ + { 0, 0 } } }, /* UF_USERINFO */ + .rv = 0 + }, + + { + .name = "extra ? in query string", + .url = "http://a.tbcdn.cn/p/fp/2010c/??fp-header-min.css," + "fp-base-min.css,fp-channel-min.css,fp-product-min.css,fp-mall-" + "min.css,fp-category-min.css,fp-sub-min.css,fp-gdp4p-min.css," + "fp-css3-min.css,fp-misc-min.css?t=20101022.css", + .is_connect = 0, + .u = { .field_set = (1<url, + strlen(test->url), + test->is_connect, + &u); + + if (test->rv == 0) { + if (rv != 0) { + return TC_FAIL; + } + + if (memcmp(&u, &test->u, sizeof(u)) != 0) { + return TC_FAIL; + } + } else { + /* test->rv != 0 */ + if (rv == 0) { + return TC_FAIL; + } + } + } + return TC_PASS; +} + +int test_method_str(void) +{ + if (strcmp("GET", http_method_str(HTTP_GET)) != 0) { + return TC_FAIL; + } + if (strcmp("", http_method_str(127)) != 0) { + return TC_FAIL; + } + + return TC_PASS; +} + +int test_header_nread_value(void) +{ + struct http_parser parser; + const char *buf; + size_t parsed; + + http_parser_init(&parser, HTTP_REQUEST); + buf = "GET / HTTP/1.1\r\nheader: value\nhdr: value\r\n"; + parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); + + if (parsed != strlen(buf)) { + return TC_FAIL; + } + if (parser.nread != strlen(buf)) { + return TC_FAIL; + } + + return TC_PASS; +} + +int test_invalid_header_content(int req, const char *str) +{ + struct http_parser parser; + const char *buf; + size_t parsed; + size_t buflen; + + http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); + buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.1 200 OK\r\n"; + parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); + if (parsed != strlen(buf)) { + return TC_FAIL; + } + + buf = str; + buflen = strlen(buf); + parsed = http_parser_execute(&parser, &settings_null, buf, buflen); + if (parsed != buflen) { + if (HTTP_PARSER_ERRNO(&parser) != HPE_INVALID_HEADER_TOKEN) { + return TC_FAIL; + } + return TC_PASS; + } + + return TC_FAIL; +} + +int test_invalid_header_field_content_error(int req) +{ + int rc; + + rc = test_invalid_header_content(req, "Foo: F\01ailure"); + if (rc != 0) { + return TC_FAIL; + } + + rc = test_invalid_header_content(req, "Foo: B\02ar"); + if (rc != 0) { + return TC_FAIL; + } + + return TC_PASS; +} + +int test_invalid_header_field(int req, const char *str) +{ + struct http_parser parser; + const char *buf; + size_t parsed; + size_t buflen; + + http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); + buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.1 200 OK\r\n"; + parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); + if (parsed != strlen(buf)) { + return TC_FAIL; + } + + buf = str; + buflen = strlen(buf); + parsed = http_parser_execute(&parser, &settings_null, buf, buflen); + if (parsed != buflen) { + if (HTTP_PARSER_ERRNO(&parser) != HPE_INVALID_HEADER_TOKEN) { + return TC_FAIL; + } + return TC_PASS; + } + + return TC_FAIL; +} + +int test_invalid_header_field_token_error(int req) +{ + int rc; + + rc = test_invalid_header_field(req, "Fo@: Failure"); + if (rc != 0) { + return TC_FAIL; + } + + rc = test_invalid_header_field(req, "Foo\01\test: Bar"); + if (rc != 0) { + return TC_FAIL; + } + + return TC_PASS; +} + +int test_double_content_length_error(int req) +{ + struct http_parser parser; + const char *buf; + size_t parsed; + size_t buflen; + + http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); + buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.1 200 OK\r\n"; + parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); + if (parsed != strlen(buf)) { + return TC_FAIL; + } + + buf = "Content-Length: 0\r\nContent-Length: 1\r\n\r\n"; + buflen = strlen(buf); + parsed = http_parser_execute(&parser, &settings_null, buf, buflen); + if (parsed != buflen) { + int error = HTTP_PARSER_ERRNO(&parser); + + if (error != HPE_UNEXPECTED_CONTENT_LENGTH) { + return TC_FAIL; + } + return TC_PASS; + } + + return TC_FAIL; +} + +int test_chunked_content_length_error(int req) +{ + struct http_parser parser; + const char *buf; + size_t parsed; + size_t buflen; + + http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); + + buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.1 200 OK\r\n"; + parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); + if (parsed != strlen(buf)) { + return TC_FAIL; + } + + buf = "Transfer-Encoding: chunked\r\nContent-Length: 1\r\n\r\n"; + buflen = strlen(buf); + parsed = http_parser_execute(&parser, &settings_null, buf, buflen); + if (parsed != buflen) { + int error = HTTP_PARSER_ERRNO(&parser); + + if (error != HPE_UNEXPECTED_CONTENT_LENGTH) { + return TC_FAIL; + } + return TC_PASS; + } + + return TC_FAIL; +} + +int test_header_cr_no_lf_error(int req) +{ + struct http_parser parser; + const char *buf; + size_t parsed; + size_t buflen; + + http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); + buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.1 200 OK\r\n"; + parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); + if (parsed != strlen(buf)) { + return TC_FAIL; + } + + buf = "Foo: 1\rBar: 1\r\n\r\n"; + buflen = strlen(buf); + parsed = http_parser_execute(&parser, &settings_null, buf, buflen); + if (parsed != buflen) { + int error = HTTP_PARSER_ERRNO(&parser); + + if (error != HPE_LF_EXPECTED) { + return TC_FAIL; + } + return TC_PASS; + } + + return TC_FAIL; +} + +#define RC_STR(rc) (rc == TC_PASS ? PASS : FAIL) + +void main(void) +{ + int rc; + + TC_START("HTTP header fields test"); + + /* api */ + rc = test_preserve_data(); + TC_PRINT("[%s] test_preserve_data\n", RC_STR(rc)); + if (rc != TC_PASS) { + goto exit_test; + } + + rc = test_parse_url(); + TC_PRINT("[%s] test_parse_url\n", RC_STR(rc)); + if (rc != TC_PASS) { + goto exit_test; + } + + rc = test_method_str(); + TC_PRINT("[%s] test_method_str\n", RC_STR(rc)); + if (rc != TC_PASS) { + goto exit_test; + } + + /* nread */ + rc = test_header_nread_value(); + TC_PRINT("[%s] test_header_nread_value\n", RC_STR(rc)); + if (rc != TC_PASS) { + goto exit_test; + } + + /* header field tests */ + rc = test_double_content_length_error(HTTP_REQUEST); + TC_PRINT("[%s] test_double_content_length_error HTTP_REQUEST\n", + RC_STR(rc)); + if (rc != TC_PASS) { + goto exit_test; + } + + rc = test_chunked_content_length_error(HTTP_REQUEST); + TC_PRINT("[%s] test_chunked_content_length_error HTTP_REQUEST\n", + RC_STR(rc)); + if (rc != TC_PASS) { + goto exit_test; + } + + rc = test_header_cr_no_lf_error(HTTP_REQUEST); + TC_PRINT("[%s] test_header_cr_no_lf_error HTTP_REQUEST\n", RC_STR(rc)); + if (rc != TC_PASS) { + goto exit_test; + } + + rc = test_invalid_header_field_token_error(HTTP_REQUEST); + TC_PRINT("[%s] test_invalid_header_field_token_error HTTP_REQUEST\n", + RC_STR(rc)); + if (rc != TC_PASS) { + goto exit_test; + } + + rc = test_invalid_header_field_content_error(HTTP_REQUEST); + TC_PRINT("[%s] test_invalid_header_field_content_error HTTP_REQUEST\n", + RC_STR(rc)); + if (rc != TC_PASS) { + goto exit_test; + } + + rc = test_double_content_length_error(HTTP_RESPONSE); + TC_PRINT("[%s] test_double_content_length_error HTTP_RESPONSE\n", + RC_STR(rc)); + if (rc != TC_PASS) { + goto exit_test; + } + + rc = test_chunked_content_length_error(HTTP_RESPONSE); + TC_PRINT("[%s] test_chunked_content_length_error HTTP_RESPONSE\n", + RC_STR(rc)); + if (rc != TC_PASS) { + goto exit_test; + } + + rc = test_header_cr_no_lf_error(HTTP_RESPONSE); + TC_PRINT("[%s] test_header_cr_no_lf_error HTTP_RESPONSE\n", RC_STR(rc)); + if (rc != TC_PASS) { + goto exit_test; + } + + rc = test_invalid_header_field_token_error(HTTP_RESPONSE); + TC_PRINT("[%s] test_invalid_header_field_token_error HTTP_RESPONSE\n", + RC_STR(rc)); + if (rc != TC_PASS) { + goto exit_test; + } + + rc = test_invalid_header_field_content_error(HTTP_RESPONSE); + TC_PRINT("[%s] test_invalid_header_field_content_error HTTP_RESPONSE\n", + RC_STR(rc)); + if (rc != TC_PASS) { + goto exit_test; + } + + TC_PRINT("\n\tNo errors detected\n"); + rc = TC_PASS; + +exit_test: + TC_END_RESULT(rc); + TC_END_REPORT(rc); +} diff --git a/tests/iot/test_http_header/testcase.ini b/tests/iot/test_http_header/testcase.ini new file mode 100644 index 00000000000..9b6e93b4114 --- /dev/null +++ b/tests/iot/test_http_header/testcase.ini @@ -0,0 +1,3 @@ +[test] +tags = http iot +build_only = false