Merge pull request #16702 from miri64/ut_process/feat/initial
ut_process: initial import of a URI template processor
This commit is contained in:
commit
913573a200
@ -647,6 +647,10 @@ ifneq (,$(filter usbus_dfu,$(USEMODULE)))
|
||||
USEMODULE += riotboot_slot
|
||||
endif
|
||||
|
||||
ifneq (,$(filter ut_process,$(USEMODULE)))
|
||||
USEMODULE += fmt
|
||||
endif
|
||||
|
||||
ifneq (,$(filter uuid,$(USEMODULE)))
|
||||
USEMODULE += hashes
|
||||
USEMODULE += random
|
||||
|
||||
101
sys/include/ut_process.h
Normal file
101
sys/include/ut_process.h
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Freie Universität Berlin
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup sys_ut_process URI template processor
|
||||
* @ingroup sys
|
||||
* @brief URI template processor
|
||||
*
|
||||
* A parser and processor for URI templates up to level 3 expression types
|
||||
* according to [RFC 6570](https://tools.ietf.org/html/rfc6570).
|
||||
*
|
||||
* @see [RFC 6570](https://tools.ietf.org/html/rfc6570)
|
||||
*
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief URI template processor definitions
|
||||
*
|
||||
* @author Martine Lenders <m.lenders@fu-berlin.de>
|
||||
*/
|
||||
#ifndef UT_PROCESS_H
|
||||
#define UT_PROCESS_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Name-value-pair of a variable for URI template expansion
|
||||
*/
|
||||
typedef struct {
|
||||
const char *name; /**< name of the variable */
|
||||
const char *value; /**< value for the variable */
|
||||
} ut_process_var_t;
|
||||
|
||||
/**
|
||||
* @brief Expands a URI template by a given value set
|
||||
*
|
||||
* @pre `(vars_len == 0) || (vars != NULL)`
|
||||
* @pre `(uri != NULL) && (uri_len > 0)`
|
||||
*
|
||||
* @param[in] ut A URI template.
|
||||
* @param[in] ut_len Length of @p ut.
|
||||
* @param[in] vars A set of variable-value pairs.
|
||||
* @param[in] vars_len The length of @p vars.
|
||||
* @param[out] uri The resulting URI.
|
||||
* @param[in, out] uri_len The maximum length for @p uri on in, the actual
|
||||
* length of @p uri on out.
|
||||
*
|
||||
* @return The length of @p uri on success
|
||||
* @return -EINVAL, when @p ut is not parseable.
|
||||
* @return -ENOBUFS, when @p uri_len is too small to fit the resulting URI.
|
||||
* Potentially broken data will be written to @p uri.
|
||||
*/
|
||||
int ut_process_expand(const char *ut, size_t ut_len,
|
||||
const ut_process_var_t *vars, size_t vars_len,
|
||||
char *uri, size_t *uri_len);
|
||||
|
||||
/**
|
||||
* @brief Expands a URI template by a given value set
|
||||
*
|
||||
* @pre `(vars_len == 0) || (vars != NULL)`
|
||||
* @pre `(uri != NULL) && (uri_len > 0)`
|
||||
*
|
||||
* @param[in] ut A `\0`-terminated URI template.
|
||||
* @param[in] vars A set of variable-value pairs.
|
||||
* @param[in] vars_len The length of @p vars.
|
||||
* @param[out] uri The resulting URI.
|
||||
* @param[in, out] uri_len The maximum length for @p uri on in, the actual
|
||||
* length of @p uri on out.
|
||||
*
|
||||
* @return The length of @p uri on success
|
||||
* @return -EINVAL, when @p ut is not parseable.
|
||||
* @return -ENOENT, when any ut_process_var_t::name in @p vars contains
|
||||
* an invalid character.
|
||||
* @return -ENOBUFS, when @p uri_len is too small to fit the resulting URI.
|
||||
* A truncated version of the resulting URI will then be stored in
|
||||
* @p uri.
|
||||
*/
|
||||
static inline int ut_process_str_expand(const char *ut,
|
||||
const ut_process_var_t *vars,
|
||||
size_t vars_len,
|
||||
char *uri, size_t *uri_len)
|
||||
{
|
||||
return ut_process_expand(ut, strlen(ut), vars, vars_len, uri, uri_len);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* UT_PROCESS_H */
|
||||
/** @} */
|
||||
1
sys/ut_process/Makefile
Normal file
1
sys/ut_process/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
||||
446
sys/ut_process/ut_process.c
Normal file
446
sys/ut_process/ut_process.c
Normal file
@ -0,0 +1,446 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Freie Universität Berlin
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @author Martine Lenders <m.lenders@fu-berlin.de>
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "fmt.h"
|
||||
|
||||
#include "ut_process.h"
|
||||
|
||||
#define ENABLE_DEBUG 0
|
||||
#include "debug.h"
|
||||
|
||||
typedef enum {
|
||||
INVAL = -1, /* reserved operators '=', ',', '!', '@', '|', '$', '(', ')' */
|
||||
SIMPLE = 0, /* no operator */
|
||||
RESERVED, /* '+' */
|
||||
FRAGMENT, /* '#' */
|
||||
LABEL, /* '.' */
|
||||
PATH, /* '/' */
|
||||
PATH_PARAM, /* ';' */
|
||||
QUERY, /* '?' */
|
||||
QUERY_CONT, /* '&' */
|
||||
} _op_t;
|
||||
|
||||
static int _set_var_list(const char *var_list, size_t var_list_len,
|
||||
const ut_process_var_t *vars, size_t vars_len,
|
||||
char *uri, size_t uri_len, unsigned uri_idx);
|
||||
static int _set_var(const char *var, size_t var_len,
|
||||
const ut_process_var_t *vars, size_t vars_len,
|
||||
char *uri, size_t uri_len, unsigned uri_idx, _op_t op,
|
||||
bool first);
|
||||
static int _fill_var(const ut_process_var_t *var, bool has_reserved,
|
||||
bool has_name, bool empty_equal,
|
||||
char *uri, size_t uri_len, unsigned uri_idx);
|
||||
static int _copy_char(char c, char *out, size_t out_len);
|
||||
static int _copy_str(const char *in, size_t in_len, char *out, size_t out_len);
|
||||
|
||||
int ut_process_expand(const char *ut, size_t ut_len,
|
||||
const ut_process_var_t *vars, size_t vars_len,
|
||||
char *uri, size_t *uri_len_ptr)
|
||||
{
|
||||
const char *exp_start = NULL;
|
||||
int res;
|
||||
unsigned i, uri_idx = 0;
|
||||
size_t uri_len = *uri_len_ptr;
|
||||
|
||||
assert(ut != NULL);
|
||||
assert((vars_len == 0) || (vars != NULL));
|
||||
assert((uri != NULL) && (uri_len > 0));
|
||||
for (i = 0; i < ut_len; i++) {
|
||||
switch (ut[i]) {
|
||||
case '{':
|
||||
if (exp_start) {
|
||||
/* nested variable expressions are not allowed so we are
|
||||
* not in a variable expression. Write-back all collected
|
||||
* chars so far */
|
||||
res = _copy_str(exp_start, (&ut[i] - exp_start),
|
||||
&uri[uri_idx], uri_len - uri_idx);
|
||||
if (res < 0) {
|
||||
DEBUG("ut_process: %p(%u) does not fit write-back %.*s "
|
||||
"of template %s\n",
|
||||
(void *)uri, (unsigned)uri_len,
|
||||
(int)(&ut[i] - exp_start), exp_start, ut);
|
||||
return res;
|
||||
}
|
||||
uri_idx += res;
|
||||
}
|
||||
exp_start = &ut[i];
|
||||
break;
|
||||
case '}':
|
||||
if (exp_start) {
|
||||
res = _set_var_list(
|
||||
exp_start + 1,
|
||||
i - (exp_start - ut) - 1,
|
||||
vars, vars_len,
|
||||
uri, uri_len, uri_idx
|
||||
);
|
||||
|
||||
if (res < 0) {
|
||||
return res;
|
||||
}
|
||||
uri_idx = res;
|
||||
exp_start = NULL;
|
||||
break;
|
||||
}
|
||||
/* else treat as literal */
|
||||
/* Intentionally falls through */
|
||||
default:
|
||||
if (!exp_start) {
|
||||
res = _copy_char(ut[i], &uri[uri_idx], uri_len - uri_idx);
|
||||
if (res < 0) {
|
||||
DEBUG("ut_process: %p(%u) does not fit literal %c of "
|
||||
"template %s\n", (void *)uri, (unsigned)uri_len,
|
||||
ut[i], ut);
|
||||
return res;
|
||||
}
|
||||
uri_idx += res;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (exp_start) {
|
||||
/* a { was opened but not closed until the end of template, copy it
|
||||
* into the URI */
|
||||
res = _copy_str(exp_start, (&ut[i] - exp_start),
|
||||
&uri[uri_idx], uri_len - uri_idx);
|
||||
if (res < 0) {
|
||||
DEBUG("ut_process: %p(%u) does not fit terminal write-back %.*s of "
|
||||
"template %s\n",
|
||||
(void *)uri, (unsigned)uri_len,
|
||||
(int)(&ut[i] - exp_start), exp_start, ut);
|
||||
return res;
|
||||
}
|
||||
uri_idx += res;
|
||||
}
|
||||
res = _copy_char('\0', &uri[uri_idx], uri_len - uri_idx);
|
||||
if (res < 0) {
|
||||
DEBUG("ut_process: %p(%u) does not fit terminating '\\0' char\n",
|
||||
(void *)uri, (unsigned)uri_len);
|
||||
return res;
|
||||
}
|
||||
/* do not increment uri_idx. We want the string length so \0 does not count
|
||||
* ;-) */
|
||||
*uri_len_ptr = uri_idx;
|
||||
return uri_idx;
|
||||
}
|
||||
|
||||
static inline bool _is_lower(char c)
|
||||
{
|
||||
return (c >= 'a') && (c <= 'z');
|
||||
}
|
||||
|
||||
static inline bool _is_alpha(char c)
|
||||
{
|
||||
return (fmt_is_upper(c)) || (_is_lower(c));
|
||||
}
|
||||
|
||||
static inline bool _is_valid_name_char(char c)
|
||||
{
|
||||
return fmt_is_digit(c) || _is_alpha(c) || (c == '_') ||
|
||||
(c == '%'); /* pct-encoded, hex is within fmt_is_digit and _is_alpha */
|
||||
}
|
||||
|
||||
static bool _is_unreserved(char c)
|
||||
{
|
||||
return fmt_is_digit(c) || _is_alpha(c) || (c == '-') || (c == '.') ||
|
||||
(c == '_') || (c == '~');
|
||||
}
|
||||
|
||||
static bool _is_reserved(char c)
|
||||
{
|
||||
return _is_lower(c) || (c == '!') || (c == '#') || (c == '$') ||
|
||||
/* ascii range & to ; includes digits and a few unreserved but
|
||||
* safes us a few checks */
|
||||
((c >= '&') && (c <= ';')) ||
|
||||
/* ascii range ? to [ includes upper alphas and a few unreserved but
|
||||
* safes us a few checks */
|
||||
(c == '=') || ((c >= '?') && (c <= '[')) ||
|
||||
(c == ']') || _is_unreserved(c);
|
||||
}
|
||||
|
||||
static _op_t _get_op(char ch)
|
||||
{
|
||||
switch (ch) {
|
||||
case '#':
|
||||
return FRAGMENT;
|
||||
case '&':
|
||||
return QUERY_CONT;
|
||||
case '+':
|
||||
return RESERVED;
|
||||
case '.':
|
||||
return LABEL;
|
||||
case '/':
|
||||
return PATH;
|
||||
case ';':
|
||||
return PATH_PARAM;
|
||||
case '?':
|
||||
return QUERY;
|
||||
break;
|
||||
case '!': case '$': case '(': case ')':
|
||||
case ',': case '=': case '|': case '@':
|
||||
/* reserved operators;
|
||||
* see https://datatracker.ietf.org/doc/html/rfc6570#section-2.2 */
|
||||
return INVAL;
|
||||
default:
|
||||
return SIMPLE;
|
||||
}
|
||||
}
|
||||
|
||||
static const ut_process_var_t *_find_var(const char *var, size_t var_len,
|
||||
const ut_process_var_t *vars,
|
||||
size_t vars_len)
|
||||
|
||||
{
|
||||
for (unsigned i = 0; i < vars_len; i++) {
|
||||
const char *name = vars[i].name;
|
||||
|
||||
if ((strlen(name) == var_len) && strncmp(name, var, var_len) == 0) {
|
||||
return &vars[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static size_t _enc_reserved(char c, char *enc)
|
||||
{
|
||||
if (_is_reserved(c)) {
|
||||
*enc = c;
|
||||
return sizeof(c);
|
||||
}
|
||||
else {
|
||||
enc[0] = '%';
|
||||
return 1 + fmt_byte_hex(&enc[1], (uint8_t)c);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t _enc_unreserved(char c, char *enc)
|
||||
{
|
||||
if (_is_unreserved(c)) {
|
||||
*enc = c;
|
||||
return sizeof(c);
|
||||
}
|
||||
else {
|
||||
enc[0] = '%';
|
||||
return 1 + fmt_byte_hex(&enc[1], (uint8_t)c);
|
||||
}
|
||||
}
|
||||
|
||||
static int _set_var_list(const char *var_list, size_t var_list_len,
|
||||
const ut_process_var_t *vars, size_t vars_len,
|
||||
char *uri, size_t uri_len, unsigned uri_idx)
|
||||
{
|
||||
int res;
|
||||
bool first = true;
|
||||
const char *cur_var;
|
||||
_op_t op = _get_op(*var_list);
|
||||
|
||||
if (op == INVAL) {
|
||||
DEBUG("ut_process: reserved operator %c used\n", *var_list);
|
||||
return -EINVAL;
|
||||
}
|
||||
else if (op != SIMPLE) {
|
||||
var_list++;
|
||||
var_list_len--;
|
||||
}
|
||||
|
||||
cur_var = var_list;
|
||||
for (unsigned i = 0; i < var_list_len; i++) {
|
||||
switch (var_list[i]) {
|
||||
case ',':
|
||||
res = _set_var(
|
||||
cur_var, &var_list[i] - cur_var,
|
||||
vars, vars_len, uri, uri_len, uri_idx, op, first
|
||||
);
|
||||
|
||||
if (res < 0) {
|
||||
return res;
|
||||
}
|
||||
if (res > 0) {
|
||||
uri_idx = res;
|
||||
first = false;
|
||||
}
|
||||
cur_var = &var_list[i + 1];
|
||||
break;
|
||||
default:
|
||||
if (!_is_valid_name_char(var_list[i])) {
|
||||
DEBUG("ut_process: invalid variable name character %c\n",
|
||||
var_list[i]);
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
res = _set_var(cur_var, var_list_len - (cur_var - var_list),
|
||||
vars, vars_len, uri, uri_len, uri_idx, op, first);
|
||||
if (res == 0) { /* the variable was not expanded */
|
||||
return uri_idx;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int _set_var(const char *var, size_t var_len,
|
||||
const ut_process_var_t *vars, size_t vars_len,
|
||||
char *uri, size_t uri_len, unsigned uri_idx, _op_t op,
|
||||
bool first)
|
||||
{
|
||||
int res;
|
||||
const ut_process_var_t *value;
|
||||
bool has_name = false;
|
||||
bool has_reserved = false;
|
||||
bool empty_equal = true;
|
||||
char prefix = '\0', sep = '\0';
|
||||
|
||||
if ((var == NULL) || (var_len == 0)) {
|
||||
DEBUG("ut_process: zero-length variable found\n")
|
||||
return -EINVAL;
|
||||
}
|
||||
value = _find_var(var, var_len, vars, vars_len);
|
||||
if ((value == NULL) || (value->value == NULL)) {
|
||||
return 0;
|
||||
}
|
||||
switch (op) {
|
||||
case SIMPLE:
|
||||
sep = ',';
|
||||
break;
|
||||
case RESERVED:
|
||||
sep = ',';
|
||||
has_reserved = true; /* reserved chars are allowed in expansion */
|
||||
break;
|
||||
case FRAGMENT:
|
||||
prefix = '#';
|
||||
sep = ',';
|
||||
has_reserved = true; /* reserved chars are allowed in expansion */
|
||||
break;
|
||||
case LABEL:
|
||||
prefix = sep = '.';
|
||||
break;
|
||||
case PATH:
|
||||
prefix = sep = '/';
|
||||
break;
|
||||
case PATH_PARAM:
|
||||
prefix = sep = ';';
|
||||
has_name = true; /* name-value pair is used completely */
|
||||
empty_equal = false; /* append equal only if value is non-empty */
|
||||
break;
|
||||
case QUERY:
|
||||
prefix = '?';
|
||||
sep = '&';
|
||||
has_name = true; /* name-value pair is used completely */
|
||||
break;
|
||||
case QUERY_CONT:
|
||||
prefix = sep = '&';
|
||||
has_name = true; /* name-value pair is used completely */
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (first) {
|
||||
if (prefix) {
|
||||
res = _copy_char(prefix, &uri[uri_idx], uri_len - uri_idx);
|
||||
if (res < 0) {
|
||||
DEBUG("ut_process: %p(%u) does not fit prefix %c\n",
|
||||
(void *)uri, (unsigned)uri_len, prefix);
|
||||
return res;
|
||||
}
|
||||
uri_idx += res;
|
||||
}
|
||||
}
|
||||
else {
|
||||
assert(sep); /* all operators have a separator defined */
|
||||
res = _copy_char(sep, &uri[uri_idx], uri_len - uri_idx);
|
||||
if (res < 0) {
|
||||
DEBUG("ut_process: %p(%u) does not fit separator '%c'\n",
|
||||
(void *)uri, (unsigned)uri_len, sep);
|
||||
return -ENOBUFS;
|
||||
}
|
||||
uri_idx += res;
|
||||
}
|
||||
return _fill_var(value, has_reserved, has_name, empty_equal, uri, uri_len,
|
||||
uri_idx);
|
||||
}
|
||||
|
||||
static int _fill_var(const ut_process_var_t *var, bool has_reserved,
|
||||
bool has_name, bool empty_equal,
|
||||
char *uri, size_t uri_len, unsigned uri_idx)
|
||||
{
|
||||
int res;
|
||||
|
||||
if (has_name) {
|
||||
/* copy one by one so we do not iterate twice for strlen(var->name)
|
||||
* and strcpy() (also code size becomes smaller due to the omitted
|
||||
* strlen()) */
|
||||
for (const char *c = var->name; *c != '\0'; c++) {
|
||||
res = _copy_char(*c, &uri[uri_idx], uri_len - uri_idx);
|
||||
if (res < 0) {
|
||||
DEBUG("ut_process: %p(%u) does not var name %s\n", (void *)uri,
|
||||
(unsigned)uri_len, var->name);
|
||||
return res;
|
||||
}
|
||||
uri_idx += res;
|
||||
}
|
||||
/* value is not an empty string */
|
||||
if ((var->value[0] != '\0') || empty_equal) {
|
||||
res = _copy_char('=', &uri[uri_idx], uri_len - uri_idx);
|
||||
if (res < 0) {
|
||||
DEBUG("ut_process: %p(%u) does not fit =\n", (void *)uri,
|
||||
(unsigned)uri_len);
|
||||
return res;
|
||||
}
|
||||
uri_idx += res;
|
||||
}
|
||||
}
|
||||
for (const char *c = var->value; *c != '\0'; c++) {
|
||||
char enc[sizeof("%00")];
|
||||
size_t enc_len;
|
||||
|
||||
if (has_reserved) {
|
||||
enc_len = _enc_reserved(*c, enc);
|
||||
}
|
||||
else {
|
||||
enc_len = _enc_unreserved(*c, enc);
|
||||
}
|
||||
res = _copy_str(enc, enc_len, &uri[uri_idx], uri_len - uri_idx);
|
||||
if (res < 0) {
|
||||
DEBUG("ut_process: %p(%u) does not fit value encoding %.*s\n",
|
||||
(void *)uri, (unsigned)uri_len, (unsigned)enc_len, enc);
|
||||
return res;
|
||||
}
|
||||
uri_idx += res;
|
||||
}
|
||||
return uri_idx;
|
||||
}
|
||||
|
||||
static int _copy_char(char c, char *out, size_t out_len)
|
||||
{
|
||||
if (out_len == 0) {
|
||||
return -ENOBUFS;
|
||||
}
|
||||
out[0] = c;
|
||||
return sizeof(c);
|
||||
}
|
||||
|
||||
static int _copy_str(const char *in, size_t in_len, char *out, size_t out_len)
|
||||
{
|
||||
if (in_len >= out_len) {
|
||||
return -ENOBUFS;
|
||||
}
|
||||
strncpy(out, in, in_len);
|
||||
return in_len;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
1
tests/unittests/tests-ut_process/Makefile
Normal file
1
tests/unittests/tests-ut_process/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
||||
1
tests/unittests/tests-ut_process/Makefile.include
Normal file
1
tests/unittests/tests-ut_process/Makefile.include
Normal file
@ -0,0 +1 @@
|
||||
USEMODULE += ut_process
|
||||
329
tests/unittests/tests-ut_process/tests-ut_process.c
Normal file
329
tests/unittests/tests-ut_process/tests-ut_process.c
Normal file
@ -0,0 +1,329 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Freie Universität Berlin
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @author Martine Lenders <m.lenders@fu-berlin.de>
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include "embUnit.h"
|
||||
|
||||
#include "kernel_defines.h"
|
||||
#include "ut_process.h"
|
||||
|
||||
#include "tests-ut_process.h"
|
||||
|
||||
static char _res_buf[128U];
|
||||
static const ut_process_var_t _test_vec[] = {
|
||||
/* Taken from https://datatracker.ietf.org/doc/html/rfc6570#section-3.2
|
||||
* minus the level 4 variables */
|
||||
{ .name = "dub", .value = "me/too", },
|
||||
{ .name = "hello", .value = "Hello World!", },
|
||||
{ .name = "half", .value = "50%", },
|
||||
{ .name = "var", .value = "value", },
|
||||
{ .name = "who", .value = "fred", },
|
||||
{ .name = "base", .value = "http://example.com/home/", },
|
||||
{ .name = "path", .value = "/foo/bar", },
|
||||
{ .name = "v", .value = "6", },
|
||||
{ .name = "x", .value = "1024", },
|
||||
{ .name = "y", .value = "768", },
|
||||
{ .name = "empty", .value = "", },
|
||||
{ .name = "undef", .value = NULL },
|
||||
};
|
||||
|
||||
static void _setup(void)
|
||||
{
|
||||
memset(_res_buf, 0, sizeof(_res_buf));
|
||||
}
|
||||
|
||||
static void test_expand_str__overview_example(void)
|
||||
{
|
||||
/* Tests example from
|
||||
* https://datatracker.ietf.org/doc/html/rfc6570#section-1.1 */
|
||||
static const char *ut = "http://www.example.com/foo{?query,number}";
|
||||
static const ut_process_var_t vars[] = {
|
||||
{ .name = "number", .value = "100", },
|
||||
{ .name = "query", .value = "mycelium", },
|
||||
};
|
||||
size_t res_buf_len = sizeof(_res_buf);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(
|
||||
sizeof("http://www.example.com/foo?query=mycelium&number=100") - 1,
|
||||
ut_process_str_expand(ut, vars, ARRAY_SIZE(vars), _res_buf, &res_buf_len)
|
||||
);
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"http://www.example.com/foo?query=mycelium&number=100", _res_buf
|
||||
);
|
||||
TEST_ASSERT_EQUAL_INT(
|
||||
sizeof("http://www.example.com/foo?query=mycelium&number=100") - 1,
|
||||
res_buf_len
|
||||
);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(
|
||||
sizeof("http://www.example.com/foo?number=100") - 1,
|
||||
ut_process_str_expand(ut, vars, 1U, _res_buf, &res_buf_len)
|
||||
);
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"http://www.example.com/foo?number=100", _res_buf
|
||||
);
|
||||
TEST_ASSERT_EQUAL_INT(
|
||||
sizeof("http://www.example.com/foo?number=100") - 1,
|
||||
res_buf_len
|
||||
);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(
|
||||
sizeof("http://www.example.com/foo") - 1,
|
||||
ut_process_str_expand(ut, NULL, 0U, _res_buf, &res_buf_len)
|
||||
);
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"http://www.example.com/foo", _res_buf
|
||||
);
|
||||
TEST_ASSERT_EQUAL_INT(
|
||||
sizeof("http://www.example.com/foo") - 1,
|
||||
res_buf_len
|
||||
);
|
||||
}
|
||||
|
||||
#define ASSERT_EXPANSION(template, expansion) \
|
||||
res_buf_len = sizeof(_res_buf); \
|
||||
TEST_ASSERT_EQUAL_INT(sizeof(expansion) - 1, ut_process_str_expand( \
|
||||
template, _test_vec, ARRAY_SIZE(_test_vec), _res_buf, &res_buf_len \
|
||||
)); \
|
||||
TEST_ASSERT_EQUAL_STRING(expansion, _res_buf)
|
||||
|
||||
static void test_expand_str__simple(void)
|
||||
{
|
||||
size_t res_buf_len;
|
||||
|
||||
/* see https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.2
|
||||
* except level 4 expansions */
|
||||
ASSERT_EXPANSION("{var}", "value");
|
||||
ASSERT_EXPANSION("{hello}", "Hello%20World%21");
|
||||
ASSERT_EXPANSION("{half}", "50%25");
|
||||
ASSERT_EXPANSION("O{empty}X", "OX");
|
||||
ASSERT_EXPANSION("O{undef}X", "OX");
|
||||
ASSERT_EXPANSION("{x,y}", "1024,768");
|
||||
ASSERT_EXPANSION("{x,hello,y}", "1024,Hello%20World%21,768");
|
||||
ASSERT_EXPANSION("?{x,empty}", "?1024,");
|
||||
ASSERT_EXPANSION("?{x,undef}", "?1024");
|
||||
ASSERT_EXPANSION("?{undef,y}", "?768");
|
||||
}
|
||||
|
||||
static void test_expand_str__reserved(void)
|
||||
{
|
||||
size_t res_buf_len;
|
||||
|
||||
/* see https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.3
|
||||
* except level 4 expansions */
|
||||
ASSERT_EXPANSION("{+var}", "value");
|
||||
ASSERT_EXPANSION("{+hello}", "Hello%20World!");
|
||||
ASSERT_EXPANSION("{+half}", "50%25");
|
||||
ASSERT_EXPANSION("{base}index", "http%3A%2F%2Fexample.com%2Fhome%2Findex");
|
||||
ASSERT_EXPANSION("{+base}index", "http://example.com/home/index");
|
||||
ASSERT_EXPANSION("O{+empty}X", "OX");
|
||||
ASSERT_EXPANSION("O{+undef}X", "OX");
|
||||
ASSERT_EXPANSION("{+path}/here", "/foo/bar/here");
|
||||
ASSERT_EXPANSION("here?ref={+path}", "here?ref=/foo/bar");
|
||||
ASSERT_EXPANSION("up{+path}{var}/here", "up/foo/barvalue/here");
|
||||
ASSERT_EXPANSION("{+x,hello,y}", "1024,Hello%20World!,768");
|
||||
ASSERT_EXPANSION("{+path,x}/here", "/foo/bar,1024/here");
|
||||
}
|
||||
|
||||
static void test_expand_str__fragment(void)
|
||||
{
|
||||
size_t res_buf_len;
|
||||
|
||||
/* see https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.4
|
||||
* except level 4 expansions */
|
||||
ASSERT_EXPANSION("{#var}", "#value");
|
||||
ASSERT_EXPANSION("{#hello}", "#Hello%20World!");
|
||||
ASSERT_EXPANSION("{#half}", "#50%25");
|
||||
ASSERT_EXPANSION("foo{#empty}", "foo#");
|
||||
ASSERT_EXPANSION("foo{#undef}", "foo");
|
||||
ASSERT_EXPANSION("{#x,hello,y}", "#1024,Hello%20World!,768");
|
||||
ASSERT_EXPANSION("{#path,x}/here", "#/foo/bar,1024/here");
|
||||
}
|
||||
|
||||
static void test_expand_str__label(void)
|
||||
{
|
||||
size_t res_buf_len;
|
||||
|
||||
/* see https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.5
|
||||
* except level 4 expansions */
|
||||
ASSERT_EXPANSION("{.who}", ".fred");
|
||||
ASSERT_EXPANSION("{.who,who}", ".fred.fred");
|
||||
ASSERT_EXPANSION("{.half,who}", ".50%25.fred");
|
||||
ASSERT_EXPANSION("X{.var}", "X.value");
|
||||
ASSERT_EXPANSION("X{.empty}", "X.");
|
||||
ASSERT_EXPANSION("X{.undef}", "X");
|
||||
|
||||
/* unreserved set is not really tested with provided set, so add a test for
|
||||
* that */
|
||||
ASSERT_EXPANSION("{.hello}", ".Hello%20World%21");
|
||||
}
|
||||
|
||||
static void test_expand_str__path(void)
|
||||
{
|
||||
size_t res_buf_len;
|
||||
|
||||
/* see https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.6
|
||||
* except level 4 expansions */
|
||||
ASSERT_EXPANSION("{/who}", "/fred");
|
||||
ASSERT_EXPANSION("{/who,who}", "/fred/fred");
|
||||
ASSERT_EXPANSION("{/half,who}", "/50%25/fred");
|
||||
ASSERT_EXPANSION("{/who,dub}", "/fred/me%2Ftoo");
|
||||
ASSERT_EXPANSION("{/var}", "/value");
|
||||
ASSERT_EXPANSION("{/var,empty}", "/value/");
|
||||
ASSERT_EXPANSION("{/var,undef}", "/value");
|
||||
ASSERT_EXPANSION("{/var,x}/here", "/value/1024/here");
|
||||
|
||||
/* unreserved set is not really tested with provided set, so add a test for
|
||||
* that */
|
||||
ASSERT_EXPANSION("{/hello}", "/Hello%20World%21");
|
||||
}
|
||||
|
||||
static void test_expand_str__path_param(void)
|
||||
{
|
||||
size_t res_buf_len;
|
||||
|
||||
/* see https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.7
|
||||
* except level 4 expansions */
|
||||
ASSERT_EXPANSION("{;who}", ";who=fred");
|
||||
ASSERT_EXPANSION("{;half}", ";half=50%25");
|
||||
ASSERT_EXPANSION("{;empty}", ";empty");
|
||||
ASSERT_EXPANSION("{;v,empty,who}", ";v=6;empty;who=fred");
|
||||
ASSERT_EXPANSION("{;v,bar,who}", ";v=6;who=fred");
|
||||
ASSERT_EXPANSION("{;x,y}", ";x=1024;y=768");
|
||||
ASSERT_EXPANSION("{;x,y,empty}", ";x=1024;y=768;empty");
|
||||
ASSERT_EXPANSION("{;x,y,undef}", ";x=1024;y=768");
|
||||
|
||||
/* unreserved set is not really tested with provided set, so add a test for
|
||||
* that */
|
||||
ASSERT_EXPANSION("{;hello}", ";hello=Hello%20World%21");
|
||||
}
|
||||
|
||||
static void test_expand_str__query(void)
|
||||
{
|
||||
size_t res_buf_len;
|
||||
|
||||
/* see https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.8
|
||||
* except level 4 expansions */
|
||||
ASSERT_EXPANSION("{?who}", "?who=fred");
|
||||
ASSERT_EXPANSION("{?half}", "?half=50%25");
|
||||
ASSERT_EXPANSION("{?x,y}", "?x=1024&y=768");
|
||||
ASSERT_EXPANSION("{?x,y,empty}", "?x=1024&y=768&empty=");
|
||||
ASSERT_EXPANSION("{?x,y,undef}", "?x=1024&y=768");
|
||||
|
||||
/* unreserved set is not really tested with provided set, so add a test for
|
||||
* that */
|
||||
ASSERT_EXPANSION("{?hello}", "?hello=Hello%20World%21");
|
||||
}
|
||||
|
||||
static void test_expand_str__query_cont(void)
|
||||
{
|
||||
size_t res_buf_len;
|
||||
|
||||
/* see https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.8
|
||||
* except level 4 expansions */
|
||||
ASSERT_EXPANSION("{&who}", "&who=fred");
|
||||
ASSERT_EXPANSION("{&half}", "&half=50%25");
|
||||
ASSERT_EXPANSION("?fixed=yes{&x}", "?fixed=yes&x=1024");
|
||||
ASSERT_EXPANSION("{&x,y,empty}", "&x=1024&y=768&empty=");
|
||||
ASSERT_EXPANSION("{&x,y,undef}", "&x=1024&y=768");
|
||||
|
||||
/* unreserved set is not really tested with provided set, so add a test for
|
||||
* that */
|
||||
ASSERT_EXPANSION("{&hello}", "&hello=Hello%20World%21");
|
||||
}
|
||||
|
||||
static void test_expand_str__dup_open(void)
|
||||
{
|
||||
size_t res_buf_len;
|
||||
|
||||
ASSERT_EXPANSION("{half{who}", "{halffred");
|
||||
ASSERT_EXPANSION("{{half{who}", "{{halffred");
|
||||
ASSERT_EXPANSION("half{who}{{", "halffred{{");
|
||||
}
|
||||
|
||||
static void test_expand_str__dup_close(void)
|
||||
{
|
||||
size_t res_buf_len;
|
||||
|
||||
ASSERT_EXPANSION("{half{who}}", "{halffred}");
|
||||
ASSERT_EXPANSION("half{who}}}", "halffred}}");
|
||||
}
|
||||
|
||||
#define ASSERT_ENOBUFS_EXPANSION(template, res_len) \
|
||||
res_buf_len = (res_len); \
|
||||
TEST_ASSERT_EQUAL_INT(-ENOBUFS, ut_process_str_expand( \
|
||||
template, _test_vec, ARRAY_SIZE(_test_vec), _res_buf, &res_buf_len \
|
||||
))
|
||||
|
||||
static void test_expand_str__enobufs(void)
|
||||
{
|
||||
size_t res_buf_len;
|
||||
|
||||
ASSERT_ENOBUFS_EXPANSION("{half{who}}", sizeof("{half") - 1);
|
||||
ASSERT_ENOBUFS_EXPANSION("foo{#empty}", sizeof("fo") - 1);
|
||||
ASSERT_ENOBUFS_EXPANSION("{thisisonlyatest", sizeof("{thisi") - 1);
|
||||
ASSERT_ENOBUFS_EXPANSION("foo", sizeof("foo") - 1);
|
||||
ASSERT_ENOBUFS_EXPANSION("{?x,y,empty}", sizeof("?x=1024&") - 1);
|
||||
ASSERT_ENOBUFS_EXPANSION("{?half}", sizeof("?hal") - 1);
|
||||
ASSERT_ENOBUFS_EXPANSION("{?half}", sizeof("?half") - 1);
|
||||
ASSERT_ENOBUFS_EXPANSION("{?half}", sizeof("?half=50") - 1);
|
||||
ASSERT_ENOBUFS_EXPANSION("foo{#empty}", sizeof("foo") - 1);
|
||||
}
|
||||
|
||||
#define ASSERT_EINVAL_EXPANSION(template) \
|
||||
res_buf_len = sizeof(_res_buf); \
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, ut_process_str_expand( \
|
||||
template, _test_vec, ARRAY_SIZE(_test_vec), _res_buf, &res_buf_len \
|
||||
))
|
||||
|
||||
static void test_expand_str__einval(void)
|
||||
{
|
||||
size_t res_buf_len;
|
||||
|
||||
ASSERT_EINVAL_EXPANSION("{?x,,empty}");
|
||||
ASSERT_EINVAL_EXPANSION("{=x,y,empty}");
|
||||
ASSERT_EINVAL_EXPANSION("{?x,$,empty}");
|
||||
}
|
||||
|
||||
static Test *_ut_process_tests(void)
|
||||
{
|
||||
EMB_UNIT_TESTFIXTURES(fixtures) {
|
||||
new_TestFixture(test_expand_str__overview_example),
|
||||
new_TestFixture(test_expand_str__simple),
|
||||
new_TestFixture(test_expand_str__reserved),
|
||||
new_TestFixture(test_expand_str__fragment),
|
||||
new_TestFixture(test_expand_str__label),
|
||||
new_TestFixture(test_expand_str__path),
|
||||
new_TestFixture(test_expand_str__path_param),
|
||||
new_TestFixture(test_expand_str__query),
|
||||
new_TestFixture(test_expand_str__query_cont),
|
||||
new_TestFixture(test_expand_str__dup_open),
|
||||
new_TestFixture(test_expand_str__dup_close),
|
||||
new_TestFixture(test_expand_str__enobufs),
|
||||
new_TestFixture(test_expand_str__einval),
|
||||
};
|
||||
|
||||
EMB_UNIT_TESTCALLER(ut_process_tests, _setup, NULL, fixtures);
|
||||
|
||||
return (Test *)&ut_process_tests;
|
||||
}
|
||||
|
||||
void tests_ut_process(void)
|
||||
{
|
||||
TESTS_RUN(_ut_process_tests());
|
||||
}
|
||||
|
||||
/** @} */
|
||||
35
tests/unittests/tests-ut_process/tests-ut_process.h
Normal file
35
tests/unittests/tests-ut_process/tests-ut_process.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Freie Universität Berlin
|
||||
*
|
||||
* 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 Unit tests for the ut_process module
|
||||
*
|
||||
* @author Martine Lenders <m.lenders@fu-berlin.de>
|
||||
*/
|
||||
#ifndef TESTS_UT_PROCESS_H
|
||||
#define TESTS_UT_PROCESS_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief The entry point of this test suite.
|
||||
*/
|
||||
void test_uri_parser(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* TESTS_UT_PROCESS_H */
|
||||
/** @} */
|
||||
Loading…
x
Reference in New Issue
Block a user