hoist unquoted case to top of the loop
[freeradius.git] / src / lib / token.c
1 /*
2  * token.c      Read the next token from a string.
3  *              Yes it's pretty primitive but effective.
4  *
5  * Version:     $Id$
6  *
7  *   This library is free software; you can redistribute it and/or
8  *   modify it under the terms of the GNU Lesser General Public
9  *   License as published by the Free Software Foundation; either
10  *   version 2.1 of the License, or (at your option) any later version.
11  *
12  *   This library is distributed in the hope that it will be useful,
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  *   Lesser General Public License for more details.
16  *
17  *   You should have received a copy of the GNU Lesser General Public
18  *   License along with this library; if not, write to the Free Software
19  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  *
21  * Copyright 2000,2006  The FreeRADIUS server project
22  */
23
24 RCSID("$Id$")
25
26 #include <freeradius-devel/libradius.h>
27
28 #include <ctype.h>
29
30 const FR_NAME_NUMBER fr_tokens[] = {
31         { "=~", T_OP_REG_EQ,    }, /* order is important! */
32         { "!~", T_OP_REG_NE,    },
33         { "{",  T_LCBRACE,      },
34         { "}",  T_RCBRACE,      },
35         { "(",  T_LBRACE,       },
36         { ")",  T_RBRACE,       },
37         { ",",  T_COMMA,        },
38         { "++", T_OP_INCRM,     },
39         { "+=", T_OP_ADD,       },
40         { "-=", T_OP_SUB,       },
41         { ":=", T_OP_SET,       },
42         { "=*", T_OP_CMP_TRUE,  },
43         { "!*", T_OP_CMP_FALSE, },
44         { "==", T_OP_CMP_EQ,    },
45         { "=",  T_OP_EQ,        },
46         { "!=", T_OP_NE,        },
47         { ">=", T_OP_GE,        },
48         { ">",  T_OP_GT,        },
49         { "<=", T_OP_LE,        },
50         { "<",  T_OP_LT,        },
51         { "#",  T_HASH,         },
52         { ";",  T_SEMICOLON,    },
53         { NULL, 0,              },
54 };
55
56 const bool fr_assignment_op[] = {
57         false,          /* invalid token */
58         false,          /* end of line */
59         false,          /* { */
60         false,          /* } */
61         false,          /* ( */
62         false,          /* )             5 */
63         false,          /* , */
64         false,          /* ; */
65
66         true,           /* ++ */
67         true,           /* += */
68         true,           /* -=           10 */
69         true,           /* := */
70         true,           /* = */
71         false,          /* != */
72         false,          /* >= */
73         false,          /* >            15 */
74         false,          /* <= */
75         false,          /* < */
76         false,          /* =~ */
77         false,          /* !~ */
78         false,          /* =*           20 */
79         false,          /* !* */
80         false,          /* == */
81         false,                          /* # */
82         false,          /* bare word */
83         false,          /* "foo"        25 */
84         false,          /* 'foo' */
85         false,          /* `foo` */
86         false
87 };
88
89 const bool fr_equality_op[] = {
90         false,          /* invalid token */
91         false,          /* end of line */
92         false,          /* { */
93         false,          /* } */
94         false,          /* ( */
95         false,          /* )             5 */
96         false,          /* , */
97         false,          /* ; */
98
99         false,          /* ++ */
100         false,          /* += */
101         false,          /* -=           10 */
102         false,          /* := */
103         false,          /* = */
104         true,           /* != */
105         true,           /* >= */
106         true,           /* >            15 */
107         true,           /* <= */
108         true,           /* < */
109         true,           /* =~ */
110         true,           /* !~ */
111         true,           /* =*           20 */
112         true,           /* !* */
113         true,           /* == */
114         false,                          /* # */
115         false,          /* bare word */
116         false,          /* "foo"        25 */
117         false,          /* 'foo' */
118         false,          /* `foo` */
119         false
120 };
121
122 const bool fr_str_tok[] = {
123         false,          /* invalid token */
124         false,          /* end of line */
125         false,          /* { */
126         false,          /* } */
127         false,          /* ( */
128         false,          /* )             5 */
129         false,          /* , */
130         false,          /* ; */
131
132         false,          /* ++ */
133         false,          /* += */
134         false,          /* -=           10 */
135         false,          /* := */
136         false,          /* = */
137         false,          /* != */
138         false,          /* >= */
139         false,          /* >            15 */
140         false,          /* <= */
141         false,          /* < */
142         false,          /* =~ */
143         false,          /* !~ */
144         false,          /* =*           20 */
145         false,          /* !* */
146         false,          /* == */
147         false,                          /* # */
148         true,           /* bare word */
149         true,           /* "foo"        25 */
150         true,           /* 'foo' */
151         true,           /* `foo` */
152         false
153 };
154
155 /*
156  *      This works only as long as special tokens
157  *      are max. 2 characters, but it's fast.
158  */
159 #define TOKEN_MATCH(bptr, tptr) \
160         ( (tptr)[0] == (bptr)[0] && \
161          ((tptr)[1] == (bptr)[1] || (tptr)[1] == 0))
162
163 /*
164  *      Read a word from a buffer and advance pointer.
165  *      This function knows about escapes and quotes.
166  *
167  *      At end-of-line, buf[0] is set to '\0'.
168  *      Returns 0 or special token value.
169  */
170 static FR_TOKEN getthing(char const **ptr, char *buf, int buflen, bool tok,
171                          FR_NAME_NUMBER const *tokenlist, bool unescape)
172 {
173         char                    *s;
174         char const              *p;
175         char                    quote;
176         bool                    end = false;
177         unsigned int            x;
178         FR_NAME_NUMBER const    *t;
179         FR_TOKEN rcode;
180
181         buf[0] = '\0';
182
183         /* Skip whitespace */
184         p = *ptr;
185
186         while (*p && isspace((int) *p)) p++;
187
188         if (!*p) {
189                 *ptr = p;
190                 return T_EOL;
191         }
192
193         /*
194          *      Might be a 1 or 2 character token.
195          */
196         if (tok) for (t = tokenlist; t->name; t++) {
197                 if (TOKEN_MATCH(p, t->name)) {
198                         strcpy(buf, t->name);
199                         p += strlen(t->name);
200
201                         rcode = t->number;
202                         goto done;
203                 }
204         }
205
206         /* Read word. */
207         quote = '\0';
208         switch (*p) {
209         default:
210                 rcode = T_BARE_WORD;
211                 break;
212
213         case '\'':
214                 rcode = T_SINGLE_QUOTED_STRING;
215                 break;
216
217         case '"':
218                 rcode = T_DOUBLE_QUOTED_STRING;
219                 break;
220
221         case '`':
222                 rcode = T_BACK_QUOTED_STRING;
223                 break;
224         }
225
226         if (rcode != T_BARE_WORD) {
227                 quote = *p;
228                 end = false;
229                 p++;
230         }
231         s = buf;
232
233         while (*p && buflen-- > 1) {
234                 /*
235                  *      We're looking for strings.  Stop on spaces, or
236                  *      (if given a token list), on a token, or on a
237                  *      comma.
238                  */
239                 if (!quote) {
240                         if (isspace((int) *p)) {
241                                 break;
242                         }
243
244                         if (tok) {
245                                 for (t = tokenlist; t->name; t++) {
246                                         if (TOKEN_MATCH(p, t->name)) {
247                                                 *s++ = 0;
248                                                 goto done;
249                                         }
250                                 }
251                         }
252                         if (*p == ',') break;
253
254                         /*
255                          *      Copy the character over.
256                          */
257                         *s++ = *p++;
258                         continue;
259                 }
260
261                 if (unescape && quote && (*p == '\\')) {
262                         p++;
263
264                         switch (*p) {
265                                 case 'r':
266                                         *s++ = '\r';
267                                         break;
268                                 case 'n':
269                                         *s++ = '\n';
270                                         break;
271                                 case 't':
272                                         *s++ = '\t';
273                                         break;
274                                 case '\0':
275                                         *s++ = '\\';
276                                         p--; /* force EOS */
277                                         break;
278                                 default:
279                                         if (*p >= '0' && *p <= '9' &&
280                                             sscanf(p, "%3o", &x) == 1) {
281                                                 *s++ = x;
282                                                 p += 2;
283                                         } else
284                                                 *s++ = *p;
285                                         break;
286                         }
287                         p++;
288                         continue;
289                 }
290
291                 /*
292                  *      Deal with quotes and escapes, but don't mash
293                  *      escaped characters into their non-escaped
294                  *      equivalent.
295                  */
296                 if (!unescape && quote && (*p == '\\')) {
297                         if (!p[1]) continue; /* force end of string */
298
299                         if (p[1] == quote) { /* convert '\'' --> ' */
300                                 p++;
301                         } else {
302                                 *(s++) = *(p++);
303                         }
304                         *(s++) = *(p++);
305                         continue;
306                 }
307
308                 if (quote && (*p == quote)) {
309                         end = true;
310                         p++;
311                         break;
312                 }
313                 *s++ = *p++;
314         }
315
316         *s++ = 0;
317
318         if (quote && !end) {
319                 fr_strerror_printf("Unterminated string");
320                 return T_INVALID;
321         }
322
323 done:
324         /* Skip whitespace again. */
325         while (*p && isspace((int) *p)) p++;
326
327         *ptr = p;
328
329         return rcode;
330 }
331
332 /*
333  *      Read a "word" - this means we don't honor
334  *      tokens as delimiters.
335  */
336 int getword(char const **ptr, char *buf, int buflen, bool unescape)
337 {
338         return getthing(ptr, buf, buflen, false, fr_tokens, unescape) == T_EOL ? 0 : 1;
339 }
340
341
342 /*
343  *      Read the next word, use tokens as delimiters.
344  */
345 FR_TOKEN gettoken(char const **ptr, char *buf, int buflen, bool unescape)
346 {
347         return getthing(ptr, buf, buflen, true, fr_tokens, unescape);
348 }
349
350 /*
351  *      Expect an operator.
352  */
353 FR_TOKEN getop(char const **ptr)
354 {
355         char op[3];
356         FR_TOKEN rcode;
357
358         rcode = getthing(ptr, op, sizeof(op), true, fr_tokens, false);
359         if (!fr_assignment_op[rcode] && !fr_equality_op[rcode]) {
360                 fr_strerror_printf("Expected operator");
361                 return T_INVALID;
362         }
363         return rcode;
364 }
365
366 /*
367  *      Expect a string.
368  */
369 FR_TOKEN getstring(char const **ptr, char *buf, int buflen, bool unescape)
370 {
371         char const *p;
372
373         if (!ptr || !*ptr || !buf) return T_INVALID;
374
375         p = *ptr;
376
377         while (*p && (isspace((int)*p))) p++;
378
379         *ptr = p;
380
381         if ((*p == '"') || (*p == '\'') || (*p == '`')) {
382                 return gettoken(ptr, buf, buflen, unescape);
383         }
384
385         return getthing(ptr, buf, buflen, false, fr_tokens, unescape);
386 }
387
388 /*
389  *      Convert a string to an integer
390  */
391 int fr_str2int(FR_NAME_NUMBER const *table, char const *name, int def)
392 {
393         FR_NAME_NUMBER const *this;
394
395         if (!name) {
396                 return def;
397         }
398
399         for (this = table; this->name != NULL; this++) {
400                 if (strcasecmp(this->name, name) == 0) {
401                         return this->number;
402                 }
403         }
404
405         return def;
406 }
407
408 /*
409  *      Convert a string matching part of name to an integer.
410  */
411 int fr_substr2int(FR_NAME_NUMBER const *table, char const *name, int def, int len)
412 {
413         FR_NAME_NUMBER const *this;
414         size_t max;
415
416         if (!name) {
417                 return def;
418         }
419
420         for (this = table; this->name != NULL; this++) {
421                 size_t tlen;
422
423                 tlen = strlen(this->name);
424
425                 /*
426                  *      Don't match "request" to user input "req".
427                  */
428                 if ((len > 0) && (len < (int) tlen)) continue;
429
430                 /*
431                  *      Match up to the length of the table entry if len is < 0.
432                  */
433                 max = (len < 0) ? tlen : (unsigned)len;
434
435                 if (strncasecmp(this->name, name, max) == 0) {
436                         return this->number;
437                 }
438         }
439
440         return def;
441 }
442
443 /*
444  *      Convert an integer to a string.
445  */
446 char const *fr_int2str(FR_NAME_NUMBER const *table, int number,
447                          char const *def)
448 {
449         FR_NAME_NUMBER const *this;
450
451         for (this = table; this->name != NULL; this++) {
452                 if (this->number == number) {
453                         return this->name;
454                 }
455         }
456
457         return def;
458 }
459
460 char const *fr_token_name(int token)
461 {
462         return fr_int2str(fr_tokens, token, "???");
463 }