Coverage Report

Created: 2026-04-12 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/util/uri.c
Line
Count
Source
1
/*
2
 *   This library is free software; you can redistribute it and/or
3
 *   modify it under the terms of the GNU Lesser General Public
4
 *   License as published by the Free Software Foundation; either
5
 *   version 2.1 of the License, or (at your option) any later version.
6
 *
7
 *   This library is distributed in the hope that it will be useful,
8
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
 *   Lesser General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU Lesser General Public
13
 *   License along with this library; if not, write to the Free Software
14
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15
 */
16
17
/** Functions for dealing with URIs
18
 *
19
 * @file src/lib/util/uri.c
20
 *
21
 * @copyright 2021 The FreeRADIUS server project
22
 */
23
RCSID("$Id: 55fdcb691a654af04c0bc5ac608352b11dd11848 $")
24
25
#include <freeradius-devel/util/sbuff.h>
26
#include <freeradius-devel/util/value.h>
27
28
#include "uri.h"
29
30
/** Escapes an individual value box that's part of a URI, advancing the pointer to uri_parts
31
 *
32
 * @note This function has a signature compatible with fr_uri_escape_func_t.
33
 *
34
 * @note This function may modify the type of boxes, as all boxes in the list are
35
 *       cast to strings before parsing.
36
 *
37
 * @param[in,out] uri_vb  to escape
38
 * @param[in] uctx    A fr_uri_escape_ctx_t containing the initial fr_uri_part_t
39
 *        and the uctx to pass to the escaping function.
40
 * @return
41
 *  - 0 on success.
42
 *  - -1 on failure.
43
 */
44
int fr_uri_escape(fr_value_box_t *uri_vb, void *uctx)
45
0
{
46
0
  fr_uri_escape_ctx_t *ctx = uctx;
47
0
  fr_sbuff_t    sbuff;
48
0
  uint8_t     adv;
49
50
  /*
51
   *  Ensure boxes are strings before attempting to escape.
52
   */
53
0
  if (unlikely(uri_vb->type != FR_TYPE_STRING)) {
54
0
    if (unlikely(fr_value_box_cast_in_place(uri_vb, uri_vb, FR_TYPE_STRING, uri_vb->enumv) < 0)) {
55
0
      fr_strerror_printf_push("Unable to cast %pV to a string", uri_vb);
56
0
      return -1;
57
0
    }
58
0
  }
59
60
  /*
61
   *  Tainted boxes can only belong to a single part of the URI
62
   */
63
0
  if ((ctx->uri_part->safe_for > 0) && !fr_value_box_is_safe_for(uri_vb, ctx->uri_part->safe_for)) {
64
0
    if (ctx->uri_part->func) {
65
      /*
66
       *  Escaping often ends up breaking the vb's list pointers
67
       *  so remove it from the list and re-insert after the escaping
68
       *  has been done
69
       */
70
0
      fr_value_box_entry_t entry = uri_vb->entry;
71
0
      if (ctx->uri_part->func(uri_vb, ctx->uctx) < 0) {
72
0
        fr_strerror_printf_push("Unable to escape tainted input %pV", uri_vb);
73
0
        return -1;
74
0
      }
75
0
      fr_value_box_mark_safe_for(uri_vb, ctx->uri_part->safe_for);
76
0
      uri_vb->entry = entry;
77
0
    } else {
78
0
      fr_strerror_printf_push("Unsafe input \"%pV\" not allowed in URI part %s", uri_vb, ctx->uri_part->name);
79
0
      return -1;
80
0
    }
81
0
    return 0;
82
0
  }
83
84
  /*
85
   *  This URI part has no term chars - so no need to look for them
86
   */
87
0
  if (!ctx->uri_part->terminals) return 0;
88
89
  /*
90
   *  Zero length box - no terminators here
91
   */
92
0
  if (uri_vb->vb_length == 0) return 0;
93
94
  /*
95
   *  Look for URI part terminator
96
   */
97
0
  fr_sbuff_init_in(&sbuff, uri_vb->vb_strvalue, uri_vb->vb_length);
98
0
  do {
99
0
    fr_sbuff_adv_until(&sbuff, SIZE_MAX, ctx->uri_part->terminals, '\0');
100
101
    /*
102
     *  We've not found a terminal in the current box
103
     */
104
0
    adv = ctx->uri_part->part_adv[fr_sbuff_char(&sbuff, '\0')];
105
0
    if (adv == 0) continue;
106
107
    /*
108
     *  This terminator has trailing characters to skip
109
     */
110
0
    if (ctx->uri_part->extra_skip) fr_sbuff_advance(&sbuff, ctx->uri_part->extra_skip);
111
112
    /*
113
     *  Move to the next part
114
     */
115
0
    ctx->uri_part += adv;
116
0
    if (!ctx->uri_part->terminals) break;
117
0
  } while (fr_sbuff_advance(&sbuff, 1) > 0);
118
119
0
  return 0;
120
0
}
121
122
/** Parse a list of value boxes representing a URI
123
 *
124
 * Reads a URI from a list of value boxes and parses it according to the
125
 * definition in uri_parts.  Tainted values, where allowed, are escaped
126
 * using the function specified for the uri part.
127
 *
128
 * @note This function may modify the type of boxes, as all boxes in the list are
129
 *       cast to strings before parsing.
130
 *
131
 * @param uri   to parse.  A list of string type value boxes containing
132
 *      fragments of a URI.
133
 * @param uri_parts definition of URI structure.  Should point to the start
134
 *      of the array of uri parts.
135
 * @param uctx    to pass to escaping function
136
 * @return
137
 *  - 0 on success
138
 *  - -1 on failure
139
 */
140
int fr_uri_escape_list(fr_value_box_list_t *uri, fr_uri_part_t const *uri_parts, void *uctx)
141
0
{
142
0
  fr_uri_escape_ctx_t ctx = {
143
0
    .uri_part = uri_parts,
144
0
    .uctx = uctx,
145
0
  };
146
147
0
  fr_strerror_clear();
148
149
0
  fr_value_box_list_foreach(uri, uri_vb) {
150
0
    if (unlikely(fr_uri_escape(uri_vb, &ctx)) < 0) return -1;
151
0
  }
152
153
0
  return 0;
154
0
}
155
156
/** Searches for a matching scheme in the table of schemes, using a list of value boxes representing the URI
157
 *
158
 * @note Unlikel
159
 *
160
 * @param uri   to parse.  A list of string type value boxes containing
161
 *      fragments of a URI.
162
 * @param schemes Table of schemes to search.
163
 * @param schemes_len Number of schemes in the table.
164
 * @param def   Default scheme to use if none is found.
165
 * @return The matching scheme, or def if none is found.
166
 */
167
int fr_uri_has_scheme(fr_value_box_list_t *uri, fr_table_num_sorted_t const *schemes, size_t schemes_len, int def)
168
0
{
169
0
  char scheme_buff[20]; /* hopefully no schemes over 20 bytes */
170
0
  fr_sbuff_t sbuff = FR_SBUFF_OUT(scheme_buff, sizeof(scheme_buff));
171
172
  /*
173
   *  Fill the scheme buffer with at most sizeof(scheme_buff) - 1 bytes of string data.
174
   */
175
0
  fr_value_box_list_foreach(uri, vb) {
176
0
    fr_value_box_t tmp;
177
0
    int ret;
178
179
0
    if (unlikely(vb->type != FR_TYPE_STRING)) {
180
0
      if (unlikely(fr_value_box_cast(NULL, &tmp, FR_TYPE_STRING, vb->enumv, vb) < 0)) {
181
0
        fr_strerror_printf_push("Unable to cast %pV to a string", vb);
182
0
        return 0;
183
0
      }
184
0
      ret = fr_sbuff_in_bstrncpy(&sbuff, tmp.vb_strvalue,
185
0
               fr_sbuff_remaining(&sbuff) > tmp.vb_length ? tmp.vb_length : fr_sbuff_remaining(&sbuff));
186
0
      fr_value_box_clear_value(&tmp);
187
0
    } else {
188
0
      ret = fr_sbuff_in_bstrncpy(&sbuff, vb->vb_strvalue,
189
0
               fr_sbuff_remaining(&sbuff) > vb->vb_length ? vb->vb_length : fr_sbuff_remaining(&sbuff));
190
0
    }
191
192
0
    if (unlikely(ret < 0)) return -1;
193
0
  }
194
195
  /*
196
   *  Ensure the first box is a valid scheme
197
   */
198
0
  return fr_table_value_by_longest_prefix(NULL, schemes, fr_sbuff_start(&sbuff), fr_sbuff_used(&sbuff), def);
199
0
}