The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
pair_tokenize.c
Go to the documentation of this file.
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/** Parse attribute value pairs from text
18 *
19 * @file src/lib/util/pair_tokenize.c
20 *
21 * @copyright 2020 Network RADIUS SAS (legal@networkradius.com)
22 */
23RCSID("$Id: f0764a2fe69e8f28f0ea31a33d8b3f99fa85587f $")
24
25#include <freeradius-devel/util/pair.h>
26#include <freeradius-devel/util/value.h>
27
28/** Operators that may appear between an attribute name and a value
29 *
30 * fr_table_num_sorted_t requires the entries in sorted order, so keep the entries sorted.
31 */
33 { L("!*"), T_OP_CMP_FALSE },
34 { L("!="), T_OP_NE },
35 { L("!~"), T_OP_REG_NE },
36 { L("+="), T_OP_ADD_EQ },
37 { L(":="), T_OP_SET },
38 { L("<"), T_OP_LT },
39 { L("<="), T_OP_LE },
40 { L("="), T_OP_EQ },
41 { L("=*"), T_OP_CMP_TRUE },
42 { L("=="), T_OP_CMP_EQ },
43 { L("=~"), T_OP_REG_EQ },
44 { L(">"), T_OP_GT },
45 { L(">="), T_OP_GE }
46};
48
49/** A bare word value ends at whitespace or at the comma before the next pair
50 *
51 */
52static fr_sbuff_parse_rules_t const pair_value_bareword_rules = {
53 .terminals = &FR_SBUFF_TERMS(
54 L("\t"),
55 L("\n"),
56 L("\r"),
57 L(" "),
58 L(",")
59 )
60};
61
62/** Parse `Attribute op value` and append the pair to pair_ctx->list
63 *
64 * The function looks the attribute name up under pair_ctx->parent, and the
65 * attribute must be a direct child of pair_ctx->parent.
66 *
67 * @param[in,out] pair_ctx the parsing context.
68 * @param[in,out] in to parse. Advanced past the value on success.
69 * @return
70 * - >0 the number of bytes parsed.
71 * - <0 the negative offset of the error.
72 */
74{
75 fr_sbuff_t our_in = FR_SBUFF(in);
76 fr_sbuff_marker_t name_m;
77 fr_dict_attr_t const *da;
78 fr_token_t op;
79 size_t op_len;
80 char quote;
81 fr_sbuff_parse_rules_t const *rules;
83 fr_slen_t slen;
84
85 fr_sbuff_marker(&name_m, &our_in);
86 if (fr_dict_attr_by_name_substr(NULL, &da, pair_ctx->parent, &our_in, NULL) < 0) FR_SBUFF_ERROR_RETURN(&our_in);
87
88 if (da->parent != pair_ctx->parent) {
89 fr_strerror_printf("Unexpected attribute %s is not a child of %s", da->name, pair_ctx->parent->name);
90 FR_SBUFF_ERROR_RETURN(&name_m);
91 }
92
93 fr_sbuff_adv_past_whitespace(&our_in, SIZE_MAX, NULL);
94
96 if (op == T_INVALID) {
97 fr_strerror_const("Expected operator");
98 FR_SBUFF_ERROR_RETURN(&our_in);
99 }
100
101 fr_sbuff_adv_past_whitespace(&our_in, SIZE_MAX, NULL);
102
103 if (fr_sbuff_next_if_char(&our_in, '"')) {
104 quote = '"';
106 } else if (fr_sbuff_next_if_char(&our_in, '\'')) {
107 quote = '\'';
109 } else if (fr_sbuff_is_char(&our_in, '`')) {
110 fr_strerror_const("Invalid string quotation");
111 FR_SBUFF_ERROR_RETURN(&our_in);
112 } else {
113 quote = '\0';
115 }
116
117 vp = fr_pair_afrom_da(pair_ctx->ctx, da);
118 if (unlikely(!vp)) FR_SBUFF_ERROR_RETURN(&name_m);
119 vp->op = op;
120
121 slen = fr_value_box_from_substr(vp, &vp->data, da->type, da, &our_in, rules);
122 if (slen < 0) {
123 error:
125 FR_SBUFF_ERROR_RETURN(&our_in);
126 }
127
128 if (quote && !fr_sbuff_next_if_char(&our_in, quote)) {
129 fr_strerror_const("Unterminated string");
130 goto error;
131 }
132
133 FR_PAIR_APPEND(pair_ctx->list, vp);
134
135 FR_SBUFF_SET_RETURN(in, &our_in);
136}
137
138/** Set a new parent from a dotted attribute reference
139 *
140 * The first component must be a direct child of pair_ctx->parent, and each
141 * later component must be a direct child of the component before. The last
142 * component becomes the new pair_ctx->parent. The function stops at the end
143 * of the input or at a comma. The comma remains in the buffer for the caller.
144 *
145 * @param[in,out] pair_ctx the parsing context.
146 * @param[in,out] in to parse. Advanced past the reference on success.
147 * @return
148 * - >0 the number of bytes parsed.
149 * - <0 the negative offset of the error.
150 *
151 * @todo - allow for child contexts, so that the parser can parse TLVs into vp->vp_children.
152 * The change requires nested cursors, but not necessarily nested contexts.
153 * One design is a `fr_dlist_t` of pair_ctx, where the parser always operates
154 * on the last pair_ctx in the list. When the context changes to the parent of
155 * an attribute, pair_ctx also changes to a parent context. The list resembles
156 * the da stack, but with child cursors as well.
157 *
158 * The parser also needs to emulate the previous behavior of group attributes,
159 * based on the parent and an increasing child_num. When the parser reads a
160 * series of "attr-foo = bar" pairs, the parser watches the parent context, and
161 * creates a new parent VP when a child has a SMALLER attribute number than the
162 * previous child. Emulating the previous behavior keeps existing configurations
163 * working.
164 */
166{
167 fr_sbuff_t our_in = FR_SBUFF(in);
168 fr_sbuff_marker_t name_m;
169 fr_dict_attr_t const *da;
170 fr_dict_attr_t const *parent = pair_ctx->parent;
171
172 for (;;) {
173 fr_sbuff_marker(&name_m, &our_in);
174 if (fr_dict_attr_by_name_substr(NULL, &da, parent, &our_in, NULL) < 0) FR_SBUFF_ERROR_RETURN(&our_in);
175
176 if (da->parent != parent) {
177 fr_strerror_printf("Unexpected attribute %s is not a child of %s", da->name, parent->name);
178 FR_SBUFF_ERROR_RETURN(&name_m);
179 }
180 parent = da;
181
182 if (!fr_sbuff_extend(&our_in) || fr_sbuff_is_char(&our_in, ',')) break;
183
184 if (!fr_sbuff_next_if_char(&our_in, '.')) {
185 fr_strerror_const("Unexpected text after attribute");
186 FR_SBUFF_ERROR_RETURN(&our_in);
187 }
188 }
189
190 pair_ctx->parent = parent;
191
192 FR_SBUFF_SET_RETURN(in, &our_in);
193}
194
195/** Parse one pair, or one context change, from a string
196 *
197 * The function stops at the end of the input or at a comma. The comma remains
198 * in the buffer for the caller.
199 *
200 * The syntax is:
201 *
202 * - `Attribute = value`
203 * The function resets the context to the parent of Attribute, a top level
204 * attribute, then parses the attribute and the value.
205 *
206 * - `Attribute`
207 * The function resets the context to Attribute, a top level structural
208 * attribute (a group, a TLV, or a struct).
209 *
210 * - `Attribute.Child`
211 * The function resets the context to Child, where each component is a
212 * structural attribute and a direct child of the component before.
213 *
214 * - `.Attribute = value`, `.Attribute` and `.Attribute.Child`
215 * As above, with the lookup starting at the current context.
216 *
217 * - `..Attribute = value`, `..Attribute` and `..Attribute.Child`
218 * As above, with the lookup starting at the parent of the current
219 * context. Each further '.' starts one level further up, and a
220 * reference of dots alone only moves the context up.
221 *
222 * @param[in,out] pair_ctx the parsing context.
223 * @param[in,out] in to parse. Advanced past what was parsed on success.
224 * @return
225 * - >=0 the number of bytes parsed.
226 * - <0 the negative offset of the error.
227 */
229{
230 fr_sbuff_t our_in = FR_SBUFF(in);
231 fr_sbuff_marker_t name_m;
232 fr_dict_attr_t const *parent;
233 fr_dict_attr_t const *da;
234 fr_slen_t slen;
235
236 fr_sbuff_adv_past_whitespace(&our_in, SIZE_MAX, NULL);
237 if (!fr_sbuff_extend(&our_in)) FR_SBUFF_SET_RETURN(in, &our_in);
238
239 /*
240 * The reference has no leading '.', so the lookup
241 * starts at the dictionary root.
242 */
243 if (!fr_sbuff_is_char(&our_in, '.')) {
244 parent = fr_dict_root(pair_ctx->parent->dict);
245 /*
246 * The first '.' starts the lookup at the current
247 * context, and each further '.' walks one level up.
248 */
249 } else {
250 fr_sbuff_advance(&our_in, 1);
251 parent = pair_ctx->parent;
252
253 while (fr_sbuff_is_char(&our_in, '.')) {
254 if (!parent->parent) {
255 fr_strerror_const("No parent above the dictionary root");
256 FR_SBUFF_ERROR_RETURN(&our_in);
257 }
258 parent = parent->parent;
259 fr_sbuff_advance(&our_in, 1);
260 }
261
262 if (!fr_sbuff_extend(&our_in) || fr_sbuff_is_char(&our_in, ',')) {
263 pair_ctx->parent = parent;
264 FR_SBUFF_SET_RETURN(in, &our_in);
265 }
266 }
267
268 fr_sbuff_marker(&name_m, &our_in);
269 if (fr_dict_attr_by_name_substr(NULL, &da, parent, &our_in, NULL) < 0) FR_SBUFF_ERROR_RETURN(&our_in);
270
271 switch (da->type) {
272 /*
273 * Structural types have no values, so a bare
274 * structural attribute changes the context.
275 */
276 case FR_TYPE_GROUP:
277 case FR_TYPE_TLV:
278 case FR_TYPE_STRUCT:
279 pair_ctx->parent = da;
280
281 if (!fr_sbuff_extend(&our_in) || fr_sbuff_is_char(&our_in, ',')) FR_SBUFF_SET_RETURN(in, &our_in);
282
283 if (!fr_sbuff_next_if_char(&our_in, '.')) {
284 fr_strerror_const("Unexpected text after attribute");
285 FR_SBUFF_ERROR_RETURN(&our_in);
286 }
287
288 slen = pair_ctx_set_from_substr(pair_ctx, &our_in);
289 if (slen < 0) FR_SBUFF_ERROR_RETURN(&our_in);
290 break;
291
292 /*
293 * Leaf types have values, so the context becomes
294 * the parent of the leaf, and pair_afrom_substr()
295 * parses the leaf again from the start of the name.
296 */
297 default:
298 pair_ctx->parent = parent;
299 fr_sbuff_set(&our_in, &name_m);
300
301 slen = pair_afrom_substr(pair_ctx, &our_in);
302 if (slen < 0) FR_SBUFF_ERROR_RETURN(&our_in);
303 break;
304 }
305
306 FR_SBUFF_SET_RETURN(in, &our_in);
307}
308
309/** Reset a pair_ctx to the dictionary root
310 *
311 * Callers reset the context when the parsed text switches to a different
312 * attribute list, for example from request.foo to reply.bar.
313 *
314 * The function will need to reset child contexts once pairs store children
315 * in vp->vp_children.
316 *
317 * @param[in,out] pair_ctx the parsing context.
318 * @param[in] dict whose root becomes the parent.
319 */
320void fr_pair_ctx_reset(fr_pair_ctx_t *pair_ctx, fr_dict_t const *dict)
321{
322 pair_ctx->parent = fr_dict_root(dict);
323}
#define RCSID(id)
Definition build.h:603
#define L(_str)
Helper for initialising arrays of string literals.
Definition build.h:245
#define unlikely(_x)
Definition build.h:486
#define NUM_ELEMENTS(_t)
Definition build.h:437
fr_dict_t * dict
Definition common.c:31
fr_slen_t fr_dict_attr_by_name_substr(fr_dict_attr_err_t *err, fr_dict_attr_t const **out, fr_dict_attr_t const *parent, fr_sbuff_t *name, fr_sbuff_term_t const *tt))
fr_dict_attr_t const * fr_dict_root(fr_dict_t const *dict)
Return the root attribute of a dictionary.
Definition dict_util.c:2717
talloc_free(hp)
@ FR_TYPE_TLV
Contains nested attributes.
@ FR_TYPE_STRUCT
like TLV, but without T or L, and fixed-width children
@ FR_TYPE_GROUP
A grouping of other attributes.
ssize_t fr_slen_t
fr_pair_t * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute and assign a fr_dict_attr_t.
Definition pair.c:284
fr_slen_t fr_pair_ctx_afrom_substr(fr_pair_ctx_t *pair_ctx, fr_sbuff_t *in)
Parse one pair, or one context change, from a string.
static fr_slen_t pair_ctx_set_from_substr(fr_pair_ctx_t *pair_ctx, fr_sbuff_t *in)
Set a new parent from a dotted attribute reference.
void fr_pair_ctx_reset(fr_pair_ctx_t *pair_ctx, fr_dict_t const *dict)
Reset a pair_ctx to the dictionary root.
fr_table_num_sorted_t const fr_pair_comparison_op_table[]
Operators that may appear between an attribute name and a value.
static fr_slen_t pair_afrom_substr(fr_pair_ctx_t *pair_ctx, fr_sbuff_t *in)
Parse Attribute op value and append the pair to pair_ctx->list.
size_t fr_pair_comparison_op_table_len
static fr_sbuff_parse_rules_t const pair_value_bareword_rules
A bare word value ends at whitespace or at the comma before the next pair.
bool fr_sbuff_next_if_char(fr_sbuff_t *sbuff, char c)
Return true if the current char matches, and if it does, advance.
Definition sbuff.c:2247
#define fr_sbuff_out_by_longest_prefix(_match_len, _out, _table, _sbuff, _def)
#define fr_sbuff_set(_dst, _src)
#define fr_sbuff_adv_past_whitespace(_sbuff, _len, _tt)
#define FR_SBUFF_TERMS(...)
Initialise a terminal structure with a list of sorted strings.
Definition sbuff.h:191
#define fr_sbuff_extend(_sbuff_or_marker)
#define fr_sbuff_is_char(_sbuff_or_marker, _c)
#define FR_SBUFF_ERROR_RETURN(_sbuff_or_marker)
#define FR_SBUFF_SET_RETURN(_dst, _src)
#define FR_SBUFF(_sbuff_or_marker)
#define fr_sbuff_advance(_sbuff_or_marker, _len)
fr_pair_t * vp
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
An element in a lexicographically sorted array of name to num mappings.
Definition table.h:49
@ T_INVALID
Definition token.h:37
@ T_OP_CMP_TRUE
Definition token.h:102
@ T_OP_EQ
Definition token.h:81
@ T_OP_SET
Definition token.h:82
@ T_OP_NE
Definition token.h:95
@ T_OP_ADD_EQ
Definition token.h:67
@ T_OP_CMP_FALSE
Definition token.h:103
@ T_OP_REG_EQ
Definition token.h:100
@ T_OP_CMP_EQ
Definition token.h:104
@ T_OP_LE
Definition token.h:98
@ T_OP_GE
Definition token.h:96
@ T_OP_GT
Definition token.h:97
@ T_OP_LT
Definition token.h:99
@ T_OP_REG_NE
Definition token.h:101
enum fr_token fr_token_t
TALLOC_CTX * ctx
to allocate VPs in
Definition pair.h:891
fr_pair_list_t * list
of VPs to add
Definition pair.h:893
#define FR_PAIR_APPEND
Definition pair.h:218
fr_dict_attr_t const * parent
current attribute to allocate VPs in
Definition pair.h:892
static fr_slen_t parent
Definition pair.h:864
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
#define fr_strerror_const(_msg)
Definition strerror.h:223
fr_sbuff_parse_rules_t const value_parse_rules_single_quoted
Definition value.c:567
fr_slen_t fr_value_box_from_substr(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_type_t dst_type, fr_dict_attr_t const *dst_enumv, fr_sbuff_t *in, fr_sbuff_parse_rules_t const *rules)
Convert string value to a fr_value_box_t type.
Definition value.c:5523
fr_sbuff_parse_rules_t const value_parse_rules_double_quoted
Definition value.c:561
static fr_sbuff_err_t char size_t fr_sbuff_t * in
Definition value.h:1061