xref: /third_party/libcoap/src/coap_uri.c (revision c87c5fba)
1/* coap_uri.c -- helper functions for URI treatment
2 *
3 * Copyright (C) 2010--2012,2015-2016,2022-2023 Olaf Bergmann <bergmann@tzi.org>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 *
7 * This file is part of the CoAP library libcoap. Please see
8 * README for terms of use.
9 */
10
11/**
12 * @file coap_uri.c
13 * @brief URI handling functions
14 */
15
16#include "coap3/coap_internal.h"
17
18#if defined(HAVE_LIMITS_H)
19#include <limits.h>
20#endif
21
22#include <stdint.h>
23#include <stdio.h>
24#include <string.h>
25#include <ctype.h>
26
27/**
28 * A length-safe version of strchr(). This function returns a pointer
29 * to the first occurrence of @p c  in @p s, or @c NULL if not found.
30 *
31 * @param s   The string to search for @p c.
32 * @param len The length of @p s.
33 * @param c   The character to search.
34 *
35 * @return A pointer to the first occurence of @p c, or @c NULL
36 * if not found.
37 */
38COAP_STATIC_INLINE const uint8_t *
39strnchr(const uint8_t *s, size_t len, unsigned char c) {
40  while (len && *s++ != c)
41    --len;
42
43  return len ? s : NULL;
44}
45
46typedef enum coap_uri_check_t {
47  COAP_URI_CHECK_URI,
48  COAP_URI_CHECK_PROXY
49} coap_uri_check_t;
50
51coap_uri_info_t coap_uri_scheme[COAP_URI_SCHEME_LAST] = {
52  { "coap",       COAP_DEFAULT_PORT,  0, COAP_URI_SCHEME_COAP },
53  { "coaps",      COAPS_DEFAULT_PORT, 0, COAP_URI_SCHEME_COAPS },
54  { "coap+tcp",   COAP_DEFAULT_PORT,  0, COAP_URI_SCHEME_COAP_TCP },
55  { "coaps+tcp",  COAPS_DEFAULT_PORT, 0, COAP_URI_SCHEME_COAPS_TCP },
56  { "http",         80,               1, COAP_URI_SCHEME_HTTP },
57  { "https",       443,               1, COAP_URI_SCHEME_HTTPS },
58  { "coap+ws",      80,               0, COAP_URI_SCHEME_COAP_WS },
59  { "coaps+ws",    443,               0, COAP_URI_SCHEME_COAPS_WS }
60};
61
62static int
63coap_split_uri_sub(const uint8_t *str_var,
64                   size_t len,
65                   coap_uri_t *uri,
66                   coap_uri_check_t check_proxy) {
67  const uint8_t *p, *q;
68  int res = 0;
69  size_t i;
70  int is_unix_domain = 0;
71
72  if (!str_var || !uri || len == 0)
73    return -1;
74
75  memset(uri, 0, sizeof(coap_uri_t));
76  uri->port = COAP_DEFAULT_PORT;
77
78  /* search for scheme */
79  p = str_var;
80  if (*p == '/') {
81    /* no scheme, host or port */
82    if (check_proxy == COAP_URI_CHECK_PROXY) {
83      /* Must have ongoing host if proxy definition */
84      return -1;
85    }
86    q = p;
87    goto path;
88  }
89
90  /* find scheme terminating :// */
91  while (len >= 3 && !(p[0] == ':' && p[1] == '/' && p[2] == '/')) {
92    ++p;
93    --len;
94  }
95  if (len < 3) {
96    /* scheme not defined with a :// terminator */
97    res = -2;
98    goto error;
99  }
100  for (i = 0; i < COAP_URI_SCHEME_LAST; i++) {
101    if ((p - str_var) == (int)strlen(coap_uri_scheme[i].name) &&
102        memcmp(str_var, coap_uri_scheme[i].name, p - str_var) == 0) {
103      if (check_proxy != COAP_URI_CHECK_PROXY && coap_uri_scheme[i].proxy_only) {
104        coap_log_err("%.*s URI scheme not enabled (not a proxy)\n",
105                     (int)(p - str_var), str_var);
106        return -1;
107      }
108      uri->scheme = coap_uri_scheme[i].scheme;
109      uri->port = coap_uri_scheme[i].port;
110      break;
111    }
112  }
113  if (i == COAP_URI_SCHEME_LAST) {
114    /* scheme unknown */
115    coap_log_err("%.*s URI scheme unknown\n", (int)(p - str_var), str_var);
116    res = -1;
117    goto error;
118  }
119  switch (uri->scheme) {
120  case COAP_URI_SCHEME_COAP:
121    break;
122  case COAP_URI_SCHEME_COAPS:
123    if (!coap_dtls_is_supported()) {
124      coap_log_err("coaps URI scheme not supported in this version of libcoap\n");
125      return -1;
126    }
127    break;
128  case COAP_URI_SCHEME_COAP_TCP:
129    if (!coap_tcp_is_supported()) {
130      coap_log_err("coap+tcp URI scheme not supported in this version of libcoap\n");
131      return -1;
132    }
133    break;
134  case COAP_URI_SCHEME_COAPS_TCP:
135    if (!coap_tcp_is_supported()) {
136      coap_log_err("coaps+tcp URI scheme not supported in this version of libcoap\n");
137      return -1;
138    }
139    break;
140  case COAP_URI_SCHEME_COAP_WS:
141    if (!coap_ws_is_supported()) {
142      coap_log_err("coap+ws URI scheme not supported in this version of libcoap\n");
143      return -1;
144    }
145    break;
146  case COAP_URI_SCHEME_COAPS_WS:
147    if (!coap_wss_is_supported()) {
148      coap_log_err("coaps+ws URI scheme not supported in this version of libcoap\n");
149      return -1;
150    }
151    break;
152  case COAP_URI_SCHEME_HTTP:
153  case COAP_URI_SCHEME_HTTPS:
154  case COAP_URI_SCHEME_LAST:
155  default:
156    coap_log_warn("Unsupported URI type %d\n", uri->scheme);
157    return -1;
158  }
159  /* skip :// */
160  p += 3;
161  len -= 3;
162
163  /* p points to beginning of Uri-Host */
164  q = p;
165  if (len && *p == '[') {
166    /* IPv6 address reference */
167    ++p;
168
169    while (len && *q != ']') {
170      ++q;
171      --len;
172    }
173
174    if (!len || *q != ']' || p == q) {
175      res = -3;
176      goto error;
177    }
178
179    COAP_SET_STR(&uri->host, q - p, p);
180    ++q;
181    --len;
182  } else {
183    /* IPv4 address, FQDN or Unix domain socket */
184    if (len >= 3 && p[0] == '%' && p[1] == '2' &&
185        (p[2] == 'F' || p[2] == 'f')) {
186      /* Unix domain definition */
187      uri->port = 0;
188      is_unix_domain = 1;
189    }
190    while (len && *q != ':' && *q != '/' && *q != '?') {
191      ++q;
192      --len;
193    }
194
195    if (p == q) {
196      res = -3;
197      goto error;
198    }
199
200    COAP_SET_STR(&uri->host, q - p, p);
201  }
202
203  /* check for Uri-Port (invalid for Unix) */
204  if (len && *q == ':') {
205    if (is_unix_domain) {
206      res = -5;
207      goto error;
208    }
209    p = ++q;
210    --len;
211
212    while (len && isdigit(*q)) {
213      ++q;
214      --len;
215    }
216
217    if (p < q) {                /* explicit port number given */
218      int uri_port = 0;
219
220      while ((p < q) && (uri_port <= UINT16_MAX))
221        uri_port = uri_port * 10 + (*p++ - '0');
222
223      /* check if port number is in allowed range */
224      if (uri_port > UINT16_MAX) {
225        res = -4;
226        goto error;
227      }
228
229      uri->port = (uint16_t)uri_port;
230    }
231  }
232
233path:                 /* at this point, p must point to an absolute path */
234
235  if (!len)
236    goto end;
237
238  if (*q == '/') {
239    p = ++q;
240    --len;
241
242    while (len && *q != '?') {
243      ++q;
244      --len;
245    }
246
247    if (p < q) {
248      COAP_SET_STR(&uri->path, q - p, p);
249      p = q;
250    }
251  }
252
253  /* Uri_Query */
254  if (len && *p == '?') {
255    ++p;
256    --len;
257    COAP_SET_STR(&uri->query, len, p);
258    len = 0;
259  }
260
261end:
262  return len ? -1 : 0;
263
264error:
265  return res;
266}
267
268int
269coap_split_uri(const uint8_t *str_var, size_t len, coap_uri_t *uri) {
270  return coap_split_uri_sub(str_var, len, uri, COAP_URI_CHECK_URI);
271}
272
273int
274coap_split_proxy_uri(const uint8_t *str_var, size_t len, coap_uri_t *uri) {
275  return coap_split_uri_sub(str_var, len, uri, COAP_URI_CHECK_PROXY);
276}
277
278int
279coap_uri_into_options(const coap_uri_t *uri, const coap_address_t *dst,
280                      coap_optlist_t **optlist_chain, int create_port_host_opt,
281                      uint8_t *_buf, size_t _buflen) {
282  int res;
283  unsigned char *buf = _buf;
284  size_t buflen = _buflen;
285
286  if (create_port_host_opt && !coap_host_is_unix_domain(&uri->host)) {
287    int add_option = 0;
288
289    if (dst && uri->host.length) {
290#if !defined(WITH_LWIP) && !defined(WITH_CONTIKI)
291      char addr[INET6_ADDRSTRLEN];
292#else /* WITH_LWIP || WITH_CONTIKI */
293      char addr[40];
294#endif /* WITH_LWIP || WITH_CONTIKI */
295
296      /* Add in UriHost if not match (need to strip off &iface) */
297      size_t uri_host_len = uri->host.length;
298      const uint8_t *cp = uri->host.s;
299
300      /* Unfortunately not null terminated */
301      for (size_t i = 0; i < uri_host_len; i++) {
302        if (cp[i] == '%') {
303          /* %iface specified in host name */
304          uri_host_len = i;
305          break;
306        }
307      }
308
309      if (coap_print_ip_addr(dst, addr, sizeof(addr)) &&
310          (strlen(addr) != uri_host_len ||
311           memcmp(addr, uri->host.s, uri_host_len) != 0)) {
312        /* add Uri-Host */
313        coap_insert_optlist(optlist_chain,
314                            coap_new_optlist(COAP_OPTION_URI_HOST,
315                                             uri->host.length,
316                                             uri->host.s));
317      }
318    }
319    /* Add in UriPort if not default */
320    switch ((int)uri->scheme) {
321    case COAP_URI_SCHEME_HTTP:
322    case COAP_URI_SCHEME_COAP_WS:
323      if (uri->port != 80)
324        add_option = 1;
325      break;
326    case COAP_URI_SCHEME_HTTPS:
327    case COAP_URI_SCHEME_COAPS_WS:
328      if (uri->port != 443)
329        add_option = 1;
330      break;
331    default:
332      if (uri->port != (coap_uri_scheme_is_secure(uri) ? COAPS_DEFAULT_PORT :
333                        COAP_DEFAULT_PORT))
334        add_option = 1;
335      break;
336    }
337    if (add_option)
338      coap_insert_optlist(optlist_chain,
339                          coap_new_optlist(COAP_OPTION_URI_PORT,
340                                           coap_encode_var_safe(buf, 4,
341                                                                (uri->port & 0xffff)),
342                                           buf));
343  }
344
345  if (uri->path.length) {
346    if (uri->path.length > buflen)
347      coap_log_warn("URI path will be truncated (max buffer %zu)\n",
348                    buflen);
349    res = coap_split_path(uri->path.s, uri->path.length, buf, &buflen);
350    if (res < 0)
351      return -1;
352
353    while (res--) {
354      coap_insert_optlist(optlist_chain,
355                          coap_new_optlist(COAP_OPTION_URI_PATH,
356                                           coap_opt_length(buf),
357                                           coap_opt_value(buf)));
358
359      buf += coap_opt_size(buf);
360    }
361  }
362
363  if (uri->query.length) {
364    buflen = _buflen;
365    buf = _buf;
366    if (uri->query.length > buflen)
367      coap_log_warn("URI query will be truncated (max buffer %zu)\n",
368                    buflen);
369    res = coap_split_query(uri->query.s, uri->query.length, buf, &buflen);
370    if (res < 0)
371      return -1;
372
373    while (res--) {
374      coap_insert_optlist(optlist_chain,
375                          coap_new_optlist(COAP_OPTION_URI_QUERY,
376                                           coap_opt_length(buf),
377                                           coap_opt_value(buf)));
378
379      buf += coap_opt_size(buf);
380    }
381  }
382  return 0;
383}
384
385int
386coap_host_is_unix_domain(const coap_str_const_t *host) {
387  if (host->length >= 3 && host->s[0] == '%' &&
388      host->s[1] == '2' &&
389      (host->s[2] == 'F' || host->s[2] == 'f')) {
390    return 1;
391  }
392  if (host->length >= 1 && host->s[0] == '/')
393    return 1;
394  return 0;
395}
396
397/**
398 * Calculates decimal value from hexadecimal ASCII character given in
399 * @p c. The caller must ensure that @p c actually represents a valid
400 * heaxdecimal character, e.g. with isxdigit(3).
401 *
402 * @hideinitializer
403 */
404#define hexchar_to_dec(c) ((c) & 0x40 ? ((c) & 0x0F) + 9 : ((c) & 0x0F))
405
406/**
407 * Decodes percent-encoded characters while copying the string @p seg
408 * of size @p length to @p buf. The caller of this function must
409 * ensure that the percent-encodings are correct (i.e. the character
410 * '%' is always followed by two hex digits. and that @p buf provides
411 * sufficient space to hold the result. This function is supposed to
412 * be called by make_decoded_option() only.
413 *
414 * @param seg     The segment to decode and copy.
415 * @param length  Length of @p seg.
416 * @param buf     The result buffer.
417 */
418static void
419decode_segment(const uint8_t *seg, size_t length, unsigned char *buf) {
420
421  while (length--) {
422
423    if (*seg == '%') {
424      *buf = (hexchar_to_dec(seg[1]) << 4) + hexchar_to_dec(seg[2]);
425
426      seg += 2;
427      length -= 2;
428    } else {
429      *buf = *seg;
430    }
431
432    ++buf;
433    ++seg;
434  }
435}
436
437/**
438 * Runs through the given path (or query) segment and checks if
439 * percent-encodings are correct. This function returns @c 0 on success
440 * and @c -1 on error.
441 */
442static int
443check_segment(const uint8_t *s, size_t length, size_t *segment_size) {
444  size_t n = 0;
445
446  while (length) {
447    if (*s == '%') {
448      if (length < 2 || !(isxdigit(s[1]) && isxdigit(s[2])))
449        return -1;
450
451      s += 2;
452      length -= 2;
453    }
454
455    ++s;
456    ++n;
457    --length;
458  }
459
460  *segment_size = n;
461
462  return 0;
463}
464
465/**
466 * Writes a coap option from given string @p s to @p buf. @p s should
467 * point to a (percent-encoded) path or query segment of a coap_uri_t
468 * object.  The created option will have type @c 0, and the length
469 * parameter will be set according to the size of the decoded string.
470 * On success, this function returns @c 0 and sets @p optionsize to the option's
471 * size. On error the function returns a value less than zero. This function
472 * must be called from coap_split_path_impl() only.
473 *
474 * @param s           The string to decode.
475 * @param length      The size of the percent-encoded string @p s.
476 * @param buf         The buffer to store the new coap option.
477 * @param buflen      The maximum size of @p buf.
478 * @param optionsize  The option's size.
479 *
480 * @return @c 0 on success and @c -1 on error.
481 *
482 * @bug This function does not split segments that are bigger than 270
483 * bytes.
484 */
485static int
486make_decoded_option(const uint8_t *s, size_t length,
487                    unsigned char *buf, size_t buflen, size_t *optionsize) {
488  int res;
489  size_t segmentlen;
490  size_t written;
491
492  if (!buflen) {
493    coap_log_debug("make_decoded_option(): buflen is 0!\n");
494    return -1;
495  }
496
497  res = check_segment(s, length, &segmentlen);
498  if (res < 0)
499    return -1;
500
501  /* write option header using delta 0 and length res */
502  written = coap_opt_setheader(buf, buflen, 0, segmentlen);
503
504  assert(written <= buflen);
505
506  if (!written)                        /* encoding error */
507    return -1;
508
509  buf += written;                /* advance past option type/length */
510  buflen -= written;
511
512  if (buflen < segmentlen) {
513    coap_log_debug("buffer too small for option\n");
514    return -1;
515  }
516
517  decode_segment(s, length, buf);
518
519  *optionsize = written + segmentlen;
520
521  return 0;
522}
523
524
525#ifndef min
526#define min(a,b) ((a) < (b) ? (a) : (b))
527#endif
528
529typedef void (*segment_handler_t)(const uint8_t *, size_t, void *);
530
531/**
532 * Checks if path segment @p s consists of one or two dots.
533 */
534COAP_STATIC_INLINE int
535dots(const uint8_t *s, size_t len) {
536  return len && *s == '.' && (len == 1 || (len == 2 && *(s+1) == '.'));
537}
538
539/**
540 * Splits the given string into segments. You should call one of the
541 * macros coap_split_path() or coap_split_query() instead.
542 *
543 * @param s      The URI string to be tokenized.
544 * @param length The length of @p s.
545 * @param h      A handler that is called with every token.
546 * @param data   Opaque data that is passed to @p h when called.
547 *
548 * @return The number of characters that have been parsed from @p s.
549 */
550static size_t
551coap_split_path_impl(const uint8_t *s, size_t length,
552                     segment_handler_t h, void *data) {
553
554  const uint8_t *p, *q;
555
556  p = q = s;
557  while (length > 0 && !strnchr((const uint8_t *)"?#", 2, *q)) {
558    if (*q == '/') {                /* start new segment */
559
560      if (!dots(p, q - p)) {
561        h(p, q - p, data);
562      }
563
564      p = q + 1;
565    }
566
567    q++;
568    length--;
569  }
570
571  /* write last segment */
572  if (!dots(p, q - p)) {
573    h(p, q - p, data);
574  }
575
576  return q - s;
577}
578
579struct cnt_str {
580  coap_string_t buf;
581  int n;
582};
583
584static void
585write_option(const uint8_t *s, size_t len, void *data) {
586  struct cnt_str *state = (struct cnt_str *)data;
587  int res;
588  size_t optionsize;
589  assert(state);
590
591  res = make_decoded_option(s, len, state->buf.s, state->buf.length, &optionsize);
592  if (res == 0) {
593    state->buf.s += optionsize;
594    state->buf.length -= optionsize;
595    state->n++;
596  }
597}
598
599int
600coap_split_path(const uint8_t *s, size_t length,
601                unsigned char *buf, size_t *buflen) {
602  struct cnt_str tmp = { { *buflen, buf }, 0 };
603
604  coap_split_path_impl(s, length, write_option, &tmp);
605
606  *buflen = *buflen - tmp.buf.length;
607
608  return tmp.n;
609}
610
611int
612coap_split_query(const uint8_t *s, size_t length,
613                 unsigned char *buf, size_t *buflen) {
614  struct cnt_str tmp = { { *buflen, buf }, 0 };
615  const uint8_t *p;
616
617  p = s;
618  while (length > 0 && *s != '#') {
619    if (*s == '&') {                /* start new query element */
620      write_option(p, s - p, &tmp);
621      p = s + 1;
622    }
623
624    s++;
625    length--;
626  }
627
628  /* write last query element */
629  write_option(p, s - p, &tmp);
630
631  *buflen = *buflen - tmp.buf.length;
632  return tmp.n;
633}
634
635#define URI_DATA(uriobj) ((unsigned char *)(uriobj) + sizeof(coap_uri_t))
636
637coap_uri_t *
638coap_new_uri(const uint8_t *uri, unsigned int length) {
639  unsigned char *result;
640
641  result = (unsigned char *)coap_malloc_type(COAP_STRING, length + 1 + sizeof(coap_uri_t));
642
643  if (!result)
644    return NULL;
645
646  memcpy(URI_DATA(result), uri, length);
647  URI_DATA(result)[length] = '\0'; /* make it zero-terminated */
648
649  if (coap_split_uri(URI_DATA(result), length, (coap_uri_t *)result) < 0) {
650    coap_free_type(COAP_STRING, result);
651    return NULL;
652  }
653  return (coap_uri_t *)result;
654}
655
656coap_uri_t *
657coap_clone_uri(const coap_uri_t *uri) {
658  coap_uri_t *result;
659  uint8_t *p;
660
661  if (!uri)
662    return  NULL;
663
664  result = (coap_uri_t *)coap_malloc_type(COAP_STRING,  uri->query.length + uri->host.length +
665                                          uri->path.length + sizeof(coap_uri_t) + 1);
666
667  if (!result)
668    return NULL;
669
670  memset(result, 0, sizeof(coap_uri_t));
671
672  result->port = uri->port;
673
674  if (uri->host.length) {
675    result->host.s = p = URI_DATA(result);
676    result->host.length = uri->host.length;
677
678    memcpy(p, uri->host.s, uri->host.length);
679  }
680
681  if (uri->path.length) {
682    result->path.s = p = URI_DATA(result) + uri->host.length;
683    result->path.length = uri->path.length;
684
685    memcpy(p, uri->path.s, uri->path.length);
686  }
687
688  if (uri->query.length) {
689    result->query.s = p = URI_DATA(result) + uri->host.length + uri->path.length;
690    result->query.length = uri->query.length;
691
692    memcpy(p, uri->query.s, uri->query.length);
693  }
694
695  return result;
696}
697
698void
699coap_delete_uri(coap_uri_t *uri) {
700  coap_free_type(COAP_STRING, uri);
701}
702
703COAP_STATIC_INLINE int
704is_unescaped_in_path(const uint8_t c) {
705  return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
706         (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' ||
707         c == '~' || c == '!' || c == '$' || c == '\'' || c == '(' ||
708         c == ')' || c == '*' || c == '+' || c == ',' || c == ';' ||
709         c=='=' || c==':' || c=='@' || c == '&';
710}
711
712COAP_STATIC_INLINE int
713is_unescaped_in_query(const uint8_t c) {
714  return is_unescaped_in_path(c) || c=='/' || c=='?';
715}
716
717coap_string_t *
718coap_get_query(const coap_pdu_t *request) {
719  coap_opt_iterator_t opt_iter;
720  coap_opt_filter_t f;
721  coap_opt_t *q;
722  coap_string_t *query = NULL;
723  size_t length = 0;
724  static const uint8_t hex[] = "0123456789ABCDEF";
725
726  coap_option_filter_clear(&f);
727  coap_option_filter_set(&f, COAP_OPTION_URI_QUERY);
728  coap_option_iterator_init(request, &opt_iter, &f);
729  while ((q = coap_option_next(&opt_iter))) {
730    uint16_t seg_len = coap_opt_length(q), i;
731    const uint8_t *seg= coap_opt_value(q);
732    for (i = 0; i < seg_len; i++) {
733      if (is_unescaped_in_query(seg[i]))
734        length += 1;
735      else
736        length += 3;
737    }
738    length += 1;
739  }
740  if (length > 0)
741    length -= 1;
742  if (length > 0) {
743    query = coap_new_string(length);
744    if (query) {
745      query->length = length;
746      unsigned char *s = query->s;
747      coap_option_iterator_init(request, &opt_iter, &f);
748      while ((q = coap_option_next(&opt_iter))) {
749        if (s != query->s)
750          *s++ = '&';
751        uint16_t seg_len = coap_opt_length(q), i;
752        const uint8_t *seg= coap_opt_value(q);
753        for (i = 0; i < seg_len; i++) {
754          if (is_unescaped_in_query(seg[i])) {
755            *s++ = seg[i];
756          } else {
757            *s++ = '%';
758            *s++ = hex[seg[i]>>4];
759            *s++ = hex[seg[i]&0x0F];
760          }
761        }
762      }
763    }
764  }
765  return query;
766}
767
768coap_string_t *
769coap_get_uri_path(const coap_pdu_t *request) {
770  coap_opt_iterator_t opt_iter;
771  coap_opt_filter_t f;
772  coap_opt_t *q;
773  coap_string_t *uri_path = NULL;
774  size_t length = 0;
775  static const uint8_t hex[] = "0123456789ABCDEF";
776
777  q = coap_check_option(request, COAP_OPTION_PROXY_URI, &opt_iter);
778  if (q) {
779    coap_uri_t uri;
780
781    if (coap_split_proxy_uri(coap_opt_value(q),
782                             coap_opt_length(q), &uri) < 0) {
783      return NULL;
784    }
785    uri_path = coap_new_string(uri.path.length);
786    if (uri_path) {
787      memcpy(uri_path->s, uri.path.s, uri.path.length);
788    }
789    return uri_path;
790  }
791
792  coap_option_filter_clear(&f);
793  coap_option_filter_set(&f, COAP_OPTION_URI_PATH);
794  coap_option_iterator_init(request, &opt_iter, &f);
795  while ((q = coap_option_next(&opt_iter))) {
796    uint16_t seg_len = coap_opt_length(q), i;
797    const uint8_t *seg= coap_opt_value(q);
798    for (i = 0; i < seg_len; i++) {
799      if (is_unescaped_in_path(seg[i]))
800        length += 1;
801      else
802        length += 3;
803    }
804    /* bump for the leading "/" */
805    length += 1;
806  }
807  /* The first entry does not have a leading "/" */
808  if (length > 0)
809    length -= 1;
810
811  /* if 0, either no URI_PATH Option, or the first one was empty */
812  uri_path = coap_new_string(length);
813  if (uri_path) {
814    uri_path->length = length;
815    unsigned char *s = uri_path->s;
816    int n = 0;
817    coap_option_iterator_init(request, &opt_iter, &f);
818    while ((q = coap_option_next(&opt_iter))) {
819      if (n++) {
820        *s++ = '/';
821      }
822      uint16_t seg_len = coap_opt_length(q), i;
823      const uint8_t *seg= coap_opt_value(q);
824      for (i = 0; i < seg_len; i++) {
825        if (is_unescaped_in_path(seg[i])) {
826          *s++ = seg[i];
827        } else {
828          *s++ = '%';
829          *s++ = hex[seg[i]>>4];
830          *s++ = hex[seg[i]&0x0F];
831        }
832      }
833    }
834  }
835  return uri_path;
836}
837