diff --git a/sys/include/net/sock/util.h b/sys/include/net/sock/util.h index cacee59912b7eefb19ab9ca5d82920467e248dbd..1f69a9d1d376dbc83025c656cb8ce50fa33a2ccc 100644 --- a/sys/include/net/sock/util.h +++ b/sys/include/net/sock/util.h @@ -26,6 +26,7 @@ #define NET_SOCK_UTIL_H #include <stdbool.h> +#include <stdint.h> #include "net/sock/udp.h" @@ -52,8 +53,9 @@ int sock_udp_ep_fmt(const sock_udp_ep_t *endpoint, char *addr_str, uint16_t *por * "host.name:1234" and "/url/path". * * @note Caller has to make sure hostport and urlpath can hold the results! - * Make sure to provide space for SOCK_HOSTPORT_MAXLEN respectively - * SOCK_URLPATH_MAXLEN bytes. + * Make sure to provide space for @ref SOCK_HOSTPORT_MAXLEN respectively + * @ref SOCK_URLPATH_MAXLEN bytes. + * Scheme part of the URL is limited to @ref SOCK_SCHEME_MAXLEN length. * * @param[in] url URL to split * @param[out] hostport where to write host:port @@ -97,6 +99,9 @@ bool sock_udp_ep_equal(const sock_udp_ep_t *a, const sock_udp_ep_t *b); * @name helper definitions * @{ */ +#define SOCK_SCHEME_MAXLEN (16U) /**< maximum length of the scheme part + for sock_urlsplit. Ensures a hard + limit on the string iterator */ #define SOCK_HOSTPORT_MAXLEN (64U) /**< maximum length of host:port part for sock_urlsplit() */ #define SOCK_URLPATH_MAXLEN (64U) /**< maximum length path for diff --git a/sys/net/sock/sock_util.c b/sys/net/sock/sock_util.c index 6f52a25ccb7937742de0e8774e9ede2f5152774b..6f265716f9eba885cd96f72abc4875a6cc1ba976 100644 --- a/sys/net/sock/sock_util.c +++ b/sys/net/sock/sock_util.c @@ -22,6 +22,7 @@ #include <arpa/inet.h> #include <errno.h> #include <stdio.h> +#include <stdlib.h> #include <string.h> #include "net/sock/udp.h" @@ -31,9 +32,6 @@ #include "fmt.h" #endif -#define SOCK_HOST_MAXLEN (64U) /**< maximum length of host part for - sock_udp_str2ep() */ - int sock_udp_ep_fmt(const sock_udp_ep_t *endpoint, char *addr_str, uint16_t *port) { void *addr_ptr; @@ -68,7 +66,7 @@ int sock_udp_ep_fmt(const sock_udp_ep_t *endpoint, char *addr_str, uint16_t *por char *tmp = addr_str + strlen(addr_str); *tmp++ = '%'; tmp += fmt_u16_dec(tmp, endpoint->netif); - *tmp = '0'; + *tmp = '\0'; #else sprintf(addr_str + strlen(addr_str), "%%%4u", endpoint->netif); #endif @@ -84,8 +82,13 @@ int sock_udp_ep_fmt(const sock_udp_ep_t *endpoint, char *addr_str, uint16_t *por static char* _find_hoststart(const char *url) { + /* Increment SOCK_SCHEME_MAXLEN due to comparison with the colon after the + * scheme part + */ + size_t remaining = SOCK_SCHEME_MAXLEN + 1; char *urlpos = (char*)url; - while(*urlpos) { + while(*urlpos && remaining) { + remaining--; if (*urlpos++ == ':') { if (strncmp(urlpos, "//", 2) == 0) { return urlpos + 2; @@ -99,14 +102,16 @@ static char* _find_hoststart(const char *url) static char* _find_pathstart(const char *url) { + size_t remaining = SOCK_HOSTPORT_MAXLEN; char *urlpos = (char*)url; - while(*urlpos) { + while(*urlpos && remaining) { + remaining--; if (*urlpos == '/') { return urlpos; } urlpos++; } - return NULL; + return urlpos; } int sock_urlsplit(const char *url, char *hostport, char *urlpath) @@ -117,19 +122,24 @@ int sock_urlsplit(const char *url, char *hostport, char *urlpath) } char *pathstart = _find_pathstart(hoststart); - if(!pathstart) { - return -EINVAL; - } - memcpy(hostport, hoststart, pathstart - hoststart); + size_t hostlen = pathstart - hoststart; + /* hostlen must be smaller SOCK_HOSTPORT_MAXLEN to have space for the null + * terminator */ + if (hostlen > SOCK_HOSTPORT_MAXLEN - 1) { + return -EOVERFLOW; + } + memcpy(hostport, hoststart, hostlen); + *(hostport + hostlen) = '\0'; size_t pathlen = strlen(pathstart); if (pathlen) { + if (pathlen > SOCK_URLPATH_MAXLEN - 1) { + return -EOVERFLOW; + } memcpy(urlpath, pathstart, pathlen); } - else { - *urlpath = '\0'; - } + *(urlpath + pathlen) = '\0'; return 0; } @@ -139,7 +149,7 @@ int sock_udp_str2ep(sock_udp_ep_t *ep_out, const char *str) char *hoststart = (char*)str; char *hostend = hoststart; - char hostbuf[SOCK_HOST_MAXLEN]; + char hostbuf[SOCK_HOSTPORT_MAXLEN]; memset(ep_out, 0, sizeof(sock_udp_ep_t)); @@ -147,22 +157,32 @@ int sock_udp_str2ep(sock_udp_ep_t *ep_out, const char *str) brackets_flag = 1; for (hostend = ++hoststart; *hostend && *hostend != ']'; hostend++); - if (! *hostend) { + if (! *hostend || ((size_t)(hostend - hoststart) >= sizeof(hostbuf))) { /* none found, bail out */ return -EINVAL; } } else { brackets_flag = 0; - for (hostend = hoststart; *hostend && *hostend != ':'; - hostend++); + for (hostend = hoststart; *hostend && (*hostend != ':') && \ + ((size_t)(hostend - hoststart) < sizeof(hostbuf)); hostend++) {} } + size_t hostlen = hostend - hoststart; if (*(hostend + brackets_flag) == ':') { - ep_out->port = atoi(hostend + brackets_flag + 1); + char *portstart = hostend + brackets_flag + 1; + /* Checks here verify that the supplied port number is up to 5 (random) + * chars in size and result is smaller or equal to UINT16_MAX. */ + if (strlen(portstart) > 5) { + return -EINVAL; + } + uint32_t port = atol(portstart); + if (port > UINT16_MAX) { + return -EINVAL; + } + ep_out->port = (uint16_t)port; } - size_t hostlen = hostend - hoststart; if (hostlen >= sizeof(hostbuf)) { return -EINVAL; } diff --git a/tests/unittests/tests-sock_util/Makefile b/tests/unittests/tests-sock_util/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..48422e909a47d7cd428d10fa73825060ccc8d8c2 --- /dev/null +++ b/tests/unittests/tests-sock_util/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/tests/unittests/tests-sock_util/Makefile.include b/tests/unittests/tests-sock_util/Makefile.include new file mode 100644 index 0000000000000000000000000000000000000000..c94b82ec69872ec700e105d259366e39c40e7cef --- /dev/null +++ b/tests/unittests/tests-sock_util/Makefile.include @@ -0,0 +1,6 @@ +USEMODULE += sock_util +USEMODULE += gnrc_sock +USEMODULE += gnrc_ipv6 +USEMODULE += ipv4_addr +USEMODULE += ipv6_addr +USEMODULE += posix diff --git a/tests/unittests/tests-sock_util/tests-sock_util.c b/tests/unittests/tests-sock_util/tests-sock_util.c new file mode 100644 index 0000000000000000000000000000000000000000..ea46e538cdd46fa216392ff4c56a74eae1d0b8df --- /dev/null +++ b/tests/unittests/tests-sock_util/tests-sock_util.c @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2018 Freie Universität Berlin + * Copyright (C) 2018 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Unit tests for sock_util module + * + * @author Koen Zandberg <koen@bergzand.net> + */ + +#include "embUnit.h" +#include "net/sock/util.h" +#include "stdio.h" + +#define TEST_IPV6_ADDR { 0x20, 0x01, 0x0d, 0xb8, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x01 } +#define TEST_IPV6_NETIF 60000 + +#define TEST_IPV6_ADDR_STR "2001:db8::1" +#define TEST_IPV6_ADDR_NETIF_STR "2001:db8::1%60000" + +#define TEST_IPV6_FMT_UDP_EP { \ + .family = AF_INET6, \ + .addr = { \ + .ipv6 = TEST_IPV6_ADDR, \ + }, \ + .port = 53, \ + } + + +#define TEST_URL "http://[2001:db8::1]:80/local" +#define TEST_URL_HOSTPART "[2001:db8::1]:80" +#define TEST_URL_LOCALPART "/local" +#define TEST_URL_NOLOCAL "coap://[2001:db8::1]" +#define TEST_URL_NOLOCAL_HOSTPART "[2001:db8::1]" +#define TEST_URL_DNS "http://test.local/local" +#define TEST_URL_DNS_HOSTPART "test.local" +#define TEST_URL_INVALID "[2001:db8::1]://local" +#define TEST_URL_INVALID2 "[2001:db8::1]/local" +#define TEST_URL_LONG_HOSTPORT "http://veryveryvery.long.hostname.that." \ + "doesnt.fit.inside.sixtyfour.characters." \ + "of.buffer.space/localpart" +#define TEST_URL_LONG_URLPATH "http://shorthostname/very/very/long/ " \ + "path/that/doesnt/fit/inside/sixtyfour/" \ + "chars/of/buffer/space" + +#define TEST_STR2EP "[2001:db8::1]" +#define TEST_STR2EP_V4 "10.0.0.1" +#define TEST_STR2EP_V4_2 "10.0.0.1:53" +#define TEST_STR2EP_V4_INVALID "[10.0.0.1]:53" +#define TEST_STR2EP_INVALID "[2001:db8:a:b:c:d:e:f:1]" +#define TEST_STR2EP_INVALID2 "[2001:db8:a:b:c:d:e:f]:66000" + +static char addr[SOCK_URLPATH_MAXLEN]; +static char urlpath[SOCK_URLPATH_MAXLEN]; + + +static void setup(void) +{ + /* Force both arrays to contain nonzero content to detect missing null + * terminator */ + memset(addr, 1, sizeof(addr)); + memset(urlpath, 1, sizeof(urlpath)); +} + +static void test_sock_util_fmt__netif_unset(void) +{ + sock_udp_ep_t ep = TEST_IPV6_FMT_UDP_EP; + uint16_t port; + TEST_ASSERT_EQUAL_INT(strlen(TEST_IPV6_ADDR_STR), + sock_udp_ep_fmt(&ep, addr, &port)); + TEST_ASSERT_EQUAL_INT(ep.port, port); + TEST_ASSERT_EQUAL_STRING(TEST_IPV6_ADDR_STR, (char *)addr); +} + +static void test_sock_util_fmt__netif_set(void) +{ + sock_udp_ep_t ep = TEST_IPV6_FMT_UDP_EP; + uint16_t port; + ep.netif = TEST_IPV6_NETIF; + TEST_ASSERT_EQUAL_INT(strlen(TEST_IPV6_ADDR_NETIF_STR), + sock_udp_ep_fmt(&ep, addr, &port)); + TEST_ASSERT_EQUAL_STRING(TEST_IPV6_ADDR_NETIF_STR, (char *)addr); +} + +static void test_sock_util_fmt__unsupported(void) +{ + sock_udp_ep_t ep = TEST_IPV6_FMT_UDP_EP; + uint16_t port; + ep.family = AF_UNIX; /* Intentionally chosen for testing an unsupported + protocol */ + TEST_ASSERT_EQUAL_INT(sock_udp_ep_fmt(&ep, addr, &port), -ENOTSUP); + TEST_ASSERT_EQUAL_STRING("", (char *)addr); +} + +static void test_sock_util_urlsplit__host_path(void) +{ + TEST_ASSERT_EQUAL_INT(0, + sock_urlsplit(TEST_URL, addr, urlpath)); + TEST_ASSERT_EQUAL_STRING(TEST_URL_HOSTPART, (char*)addr); + TEST_ASSERT_EQUAL_STRING(TEST_URL_LOCALPART, (char*)urlpath); +} + +static void test_sock_util_urlsplit__no_path(void) +{ + TEST_ASSERT_EQUAL_INT(0, + sock_urlsplit(TEST_URL_NOLOCAL, addr, urlpath)); + TEST_ASSERT_EQUAL_STRING(TEST_URL_NOLOCAL_HOSTPART, (char*)addr); + TEST_ASSERT_EQUAL_INT(0, strlen(urlpath)); +} + +static void test_sock_util_urlsplit__dnsname(void) +{ + TEST_ASSERT_EQUAL_INT(0, + sock_urlsplit(TEST_URL_DNS, addr, urlpath)); + TEST_ASSERT_EQUAL_STRING(TEST_URL_DNS_HOSTPART, (char*)addr); + TEST_ASSERT_EQUAL_STRING(TEST_URL_LOCALPART, (char*)urlpath); +} + +static void test_sock_util_urlsplit__invalid_sep(void) +{ + TEST_ASSERT_EQUAL_INT(-EINVAL, + sock_urlsplit(TEST_URL_INVALID, addr, urlpath)); +} + +static void test_sock_util_urlsplit__no_schema(void) +{ + TEST_ASSERT_EQUAL_INT(-EINVAL, + sock_urlsplit(TEST_URL_INVALID2, addr, urlpath)); +} + +static void test_sock_util_urlsplit__hostport_too_long(void) +{ + TEST_ASSERT_EQUAL_INT(-EOVERFLOW, + sock_urlsplit(TEST_URL_LONG_HOSTPORT, addr, urlpath)); +} + +static void test_sock_util_urlsplit__urlpath_too_long(void) +{ + TEST_ASSERT_EQUAL_INT(-EOVERFLOW, + sock_urlsplit(TEST_URL_LONG_URLPATH, addr, urlpath)); +} + +static void test_sock_util_str2ep__ipv6_noport(void) +{ + sock_udp_ep_t ep; + ep.port = 0; + TEST_ASSERT_EQUAL_INT(0, sock_udp_str2ep(&ep, TEST_STR2EP)); + TEST_ASSERT_EQUAL_INT(0, ep.port); + TEST_ASSERT_EQUAL_INT(AF_INET6, ep.family); +} + +static void test_sock_util_str2ep__ipv4_noport(void) +{ + sock_udp_ep_t ep; + ep.port = 0; + TEST_ASSERT_EQUAL_INT(0, sock_udp_str2ep(&ep, TEST_STR2EP_V4)); + TEST_ASSERT_EQUAL_INT(0, ep.port); + TEST_ASSERT_EQUAL_INT(AF_INET, ep.family); +} + +static void test_sock_util_str2ep__ipv4_port(void) +{ + sock_udp_ep_t ep; + TEST_ASSERT_EQUAL_INT(0, sock_udp_str2ep(&ep, TEST_STR2EP_V4_2)); + TEST_ASSERT_EQUAL_INT(53, ep.port); + TEST_ASSERT_EQUAL_INT(AF_INET, ep.family); +} + +static void test_sock_util_str2ep__ipv4_bracketed(void) +{ + sock_udp_ep_t ep; + TEST_ASSERT_EQUAL_INT(-EINVAL, sock_udp_str2ep(&ep, + TEST_STR2EP_V4_INVALID)); +} + +static void test_sock_util_str2ep__invalid_ipv6(void) +{ + sock_udp_ep_t ep; + TEST_ASSERT_EQUAL_INT(-EINVAL, sock_udp_str2ep(&ep, TEST_STR2EP_INVALID)); +} + +static void test_sock_util_str2ep__invalid_port(void) +{ + sock_udp_ep_t ep; + TEST_ASSERT_EQUAL_INT(-EINVAL, sock_udp_str2ep(&ep, TEST_STR2EP_INVALID2)); +} + +Test *tests_sock_util_all(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_sock_util_fmt__netif_unset), + new_TestFixture(test_sock_util_fmt__netif_set), + new_TestFixture(test_sock_util_fmt__unsupported), + new_TestFixture(test_sock_util_urlsplit__host_path), + new_TestFixture(test_sock_util_urlsplit__no_path), + new_TestFixture(test_sock_util_urlsplit__dnsname), + new_TestFixture(test_sock_util_urlsplit__invalid_sep), + new_TestFixture(test_sock_util_urlsplit__no_schema), + new_TestFixture(test_sock_util_urlsplit__hostport_too_long), + new_TestFixture(test_sock_util_urlsplit__urlpath_too_long), + new_TestFixture(test_sock_util_str2ep__ipv6_noport), + new_TestFixture(test_sock_util_str2ep__ipv4_noport), + new_TestFixture(test_sock_util_str2ep__ipv4_port), + new_TestFixture(test_sock_util_str2ep__ipv4_bracketed), + new_TestFixture(test_sock_util_str2ep__invalid_ipv6), + new_TestFixture(test_sock_util_str2ep__invalid_port), + }; + + EMB_UNIT_TESTCALLER(sockutil_tests, setup, NULL, fixtures); + return (Test *)&sockutil_tests; +} + +void tests_sock_util(void) +{ + TESTS_RUN(tests_sock_util_all()); +} diff --git a/tests/unittests/tests-sock_util/tests-sock_util.h b/tests/unittests/tests-sock_util/tests-sock_util.h new file mode 100644 index 0000000000000000000000000000000000000000..2bdf12ed28139c94d417a589a597160afc763e4a --- /dev/null +++ b/tests/unittests/tests-sock_util/tests-sock_util.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018 Freie Universität Berlin + * Copyright (C) 2018 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @addtogroup unittests + * @{ + * + * @file + * @brief Unittests for the sock_util module + * + */ +#ifndef TESTS_SOCK_UTIL_H +#define TESTS_SOCK_UTIL_H + +#include "embUnit/embUnit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief The entry point of this test suite. + */ +void tests_sockutil(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TESTS_SOCK_UTIL_H */ +/** @} */