85bd91227beabaa62e4e6263b375ad759f2ee486
[idra/krb5.git] / src / util / profile / prof_parse.c
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 #include "prof_int.h"
3
4 #include <sys/types.h>
5 #include <stdio.h>
6 #include <string.h>
7 #ifdef HAVE_STDLIB_H
8 #include <stdlib.h>
9 #endif
10 #include <errno.h>
11 #include <ctype.h>
12 #include <dirent.h>
13
14 #define SECTION_SEP_CHAR '/'
15
16 #define STATE_INIT_COMMENT      1
17 #define STATE_STD_LINE          2
18 #define STATE_GET_OBRACE        3
19
20 struct parse_state {
21     int     state;
22     int     group_level;
23     struct profile_node *root_section;
24     struct profile_node *current_section;
25 };
26
27 static errcode_t parse_file(FILE *f, struct parse_state *state);
28
29 static char *skip_over_blanks(char *cp)
30 {
31     while (*cp && isspace((int) (*cp)))
32         cp++;
33     return cp;
34 }
35
36 static void strip_line(char *line)
37 {
38     char *p = line + strlen(line);
39     while (p > line && (p[-1] == '\n' || p[-1] == '\r'))
40         *--p = 0;
41 }
42
43 static void parse_quoted_string(char *str)
44 {
45     char *to, *from;
46
47     to = from = str;
48
49     for (to = from = str; *from && *from != '"'; to++, from++) {
50         if (*from == '\\') {
51             from++;
52             switch (*from) {
53             case 'n':
54                 *to = '\n';
55                 break;
56             case 't':
57                 *to = '\t';
58                 break;
59             case 'b':
60                 *to = '\b';
61                 break;
62             default:
63                 *to = *from;
64             }
65             continue;
66         }
67         *to = *from;
68     }
69     *to = '\0';
70 }
71
72
73 static errcode_t parse_std_line(char *line, struct parse_state *state)
74 {
75     char    *cp, ch, *tag, *value;
76     char    *p;
77     errcode_t retval;
78     struct profile_node     *node;
79     int do_subsection = 0;
80     void *iter = 0;
81
82     if (*line == 0)
83         return 0;
84     cp = skip_over_blanks(line);
85     if (cp[0] == ';' || cp[0] == '#')
86         return 0;
87     strip_line(cp);
88     ch = *cp;
89     if (ch == 0)
90         return 0;
91     if (ch == '[') {
92         if (state->group_level > 0)
93             return PROF_SECTION_NOTOP;
94         cp++;
95         p = strchr(cp, ']');
96         if (p == NULL)
97             return PROF_SECTION_SYNTAX;
98         *p = '\0';
99         retval = profile_find_node_subsection(state->root_section,
100                                               cp, &iter, 0,
101                                               &state->current_section);
102         if (retval == PROF_NO_SECTION) {
103             retval = profile_add_node(state->root_section,
104                                       cp, 0,
105                                       &state->current_section);
106             if (retval)
107                 return retval;
108         } else if (retval)
109             return retval;
110
111         /*
112          * Finish off the rest of the line.
113          */
114         cp = p+1;
115         if (*cp == '*') {
116             profile_make_node_final(state->current_section);
117             cp++;
118         }
119         /*
120          * A space after ']' should not be fatal
121          */
122         cp = skip_over_blanks(cp);
123         if (*cp)
124             return PROF_SECTION_SYNTAX;
125         return 0;
126     }
127     if (ch == '}') {
128         if (state->group_level == 0)
129             return PROF_EXTRA_CBRACE;
130         if (*(cp+1) == '*')
131             profile_make_node_final(state->current_section);
132         retval = profile_get_node_parent(state->current_section,
133                                          &state->current_section);
134         if (retval)
135             return retval;
136         state->group_level--;
137         return 0;
138     }
139     /*
140      * Parse the relations
141      */
142     tag = cp;
143     cp = strchr(cp, '=');
144     if (!cp)
145         return PROF_RELATION_SYNTAX;
146     if (cp == tag)
147         return PROF_RELATION_SYNTAX;
148     *cp = '\0';
149     p = tag;
150     /* Look for whitespace on left-hand side.  */
151     while (p < cp && !isspace((int)*p))
152         p++;
153     if (p < cp) {
154         /* Found some sort of whitespace.  */
155         *p++ = 0;
156         /* If we have more non-whitespace, it's an error.  */
157         while (p < cp) {
158             if (!isspace((int)*p))
159                 return PROF_RELATION_SYNTAX;
160             p++;
161         }
162     }
163     cp = skip_over_blanks(cp+1);
164     value = cp;
165     if (value[0] == '"') {
166         value++;
167         parse_quoted_string(value);
168     } else if (value[0] == 0) {
169         do_subsection++;
170         state->state = STATE_GET_OBRACE;
171     } else if (value[0] == '{' && *(skip_over_blanks(value+1)) == 0)
172         do_subsection++;
173     else {
174         cp = value + strlen(value) - 1;
175         while ((cp > value) && isspace((int) (*cp)))
176             *cp-- = 0;
177     }
178     if (do_subsection) {
179         p = strchr(tag, '*');
180         if (p)
181             *p = '\0';
182         retval = profile_add_node(state->current_section,
183                                   tag, 0, &state->current_section);
184         if (retval)
185             return retval;
186         if (p)
187             profile_make_node_final(state->current_section);
188         state->group_level++;
189         return 0;
190     }
191     p = strchr(tag, '*');
192     if (p)
193         *p = '\0';
194     profile_add_node(state->current_section, tag, value, &node);
195     if (p)
196         profile_make_node_final(node);
197     return 0;
198 }
199
200 /* Open and parse an included profile file. */
201 static errcode_t parse_include_file(char *filename, struct parse_state *state)
202 {
203     FILE    *fp;
204     errcode_t retval = 0;
205     struct parse_state incstate;
206
207     /* Create a new state so that fragments are syntactically independent,
208      * sharing the root section with the existing state. */
209     incstate.state = STATE_INIT_COMMENT;
210     incstate.group_level = 0;
211     incstate.root_section = state->root_section;
212     incstate.current_section = NULL;
213
214     fp = fopen(filename, "r");
215     if (fp == NULL)
216         return PROF_FAIL_INCLUDE_FILE;
217     retval = parse_file(fp, &incstate);
218     fclose(fp);
219     return retval;
220 }
221
222 /* Return non-zero if filename contains only alphanumeric characters, dashes,
223  * and underscores. */
224 static int valid_name(const char *filename)
225 {
226     const char *p;
227
228     for (p = filename; *p != '\0'; p++) {
229         if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_')
230             return 0;
231     }
232     return 1;
233 }
234
235 /*
236  * Include files within dirname.  Only files with names consisting entirely of
237  * alphanumeric chracters, dashes, and underscores are included, in order to
238  * avoid including editor backup files, .rpmsave files, and the like.
239  */
240 static errcode_t parse_include_dir(char *dirname, struct parse_state *state)
241 {
242     DIR     *dir;
243     char    *pathname;
244     errcode_t retval = 0;
245     struct dirent *ent;
246
247     dir = opendir(dirname);
248     if (dir == NULL)
249         return PROF_FAIL_INCLUDE_DIR;
250     while ((ent = readdir(dir)) != NULL) {
251         if (!valid_name(ent->d_name))
252             continue;
253         if (asprintf(&pathname, "%s/%s", dirname, ent->d_name) < 0) {
254             retval = ENOMEM;
255             break;
256         }
257         retval = parse_include_file(pathname, state);
258         free(pathname);
259         if (retval)
260             break;
261     }
262     closedir(dir);
263     return retval;
264 }
265
266 static errcode_t parse_line(char *line, struct parse_state *state)
267 {
268     char    *cp;
269
270     if (strncmp(line, "include", 7) == 0 && isspace(line[7])) {
271         cp = skip_over_blanks(line + 7);
272         strip_line(cp);
273         return parse_include_file(cp, state);
274     }
275     if (strncmp(line, "includedir", 10) == 0 && isspace(line[10])) {
276         cp = skip_over_blanks(line + 10);
277         strip_line(cp);
278         return parse_include_dir(cp, state);
279     }
280     switch (state->state) {
281     case STATE_INIT_COMMENT:
282         if (line[0] != '[')
283             return 0;
284         state->state = STATE_STD_LINE;
285     case STATE_STD_LINE:
286         return parse_std_line(line, state);
287     case STATE_GET_OBRACE:
288         cp = skip_over_blanks(line);
289         if (*cp != '{')
290             return PROF_MISSING_OBRACE;
291         state->state = STATE_STD_LINE;
292     }
293     return 0;
294 }
295
296 static errcode_t parse_file(FILE *f, struct parse_state *state)
297 {
298 #define BUF_SIZE        2048
299     char *bptr;
300     errcode_t retval;
301
302     bptr = malloc (BUF_SIZE);
303     if (!bptr)
304         return ENOMEM;
305
306     while (!feof(f)) {
307         if (fgets(bptr, BUF_SIZE, f) == NULL)
308             break;
309 #ifndef PROFILE_SUPPORTS_FOREIGN_NEWLINES
310         retval = parse_line(bptr, state);
311         if (retval) {
312             free (bptr);
313             return retval;
314         }
315 #else
316         {
317             char *p, *end;
318
319             if (strlen(bptr) >= BUF_SIZE - 1) {
320                 /* The string may have foreign newlines and
321                    gotten chopped off on a non-newline
322                    boundary.  Seek backwards to the last known
323                    newline.  */
324                 long offset;
325                 char *c = bptr + strlen (bptr);
326                 for (offset = 0; offset > -BUF_SIZE; offset--) {
327                     if (*c == '\r' || *c == '\n') {
328                         *c = '\0';
329                         fseek (f, offset, SEEK_CUR);
330                         break;
331                     }
332                     c--;
333                 }
334             }
335
336             /* First change all newlines to \n */
337             for (p = bptr; *p != '\0'; p++) {
338                 if (*p == '\r')
339                     *p = '\n';
340             }
341             /* Then parse all lines */
342             p = bptr;
343             end = bptr + strlen (bptr);
344             while (p < end) {
345                 char* newline;
346                 char* newp;
347
348                 newline = strchr (p, '\n');
349                 if (newline != NULL)
350                     *newline = '\0';
351
352                 /* parse_line modifies contents of p */
353                 newp = p + strlen (p) + 1;
354                 retval = parse_line (p, state);
355                 if (retval) {
356                     free (bptr);
357                     return retval;
358                 }
359
360                 p = newp;
361             }
362         }
363 #endif
364     }
365
366     free (bptr);
367     return 0;
368 }
369
370 errcode_t profile_parse_file(FILE *f, struct profile_node **root)
371 {
372     struct parse_state state;
373     errcode_t retval;
374
375     *root = NULL;
376
377     /* Initialize parsing state with a new root node. */
378     state.state = STATE_INIT_COMMENT;
379     state.group_level = 0;
380     state.current_section = NULL;
381     retval = profile_create_node("(root)", 0, &state.root_section);
382     if (retval)
383         return retval;
384
385     retval = parse_file(f, &state);
386     if (retval) {
387         profile_free_node(state.root_section);
388         return retval;
389     }
390     *root = state.root_section;
391     return 0;
392 }
393
394 /*
395  * Return TRUE if the string begins or ends with whitespace
396  */
397 static int need_double_quotes(char *str)
398 {
399     if (!str)
400         return 0;
401     if (str[0] == '\0')
402         return 1;
403     if (isspace((int) (*str)) ||isspace((int) (*(str + strlen(str) - 1))))
404         return 1;
405     if (strchr(str, '\n') || strchr(str, '\t') || strchr(str, '\b'))
406         return 1;
407     return 0;
408 }
409
410 /*
411  * Output a string with double quotes, doing appropriate backquoting
412  * of characters as necessary.
413  */
414 static void output_quoted_string(char *str, void (*cb)(const char *,void *),
415                                  void *data)
416 {
417     char    ch;
418     char buf[2];
419
420     cb("\"", data);
421     if (!str) {
422         cb("\"", data);
423         return;
424     }
425     buf[1] = 0;
426     while ((ch = *str++)) {
427         switch (ch) {
428         case '\\':
429             cb("\\\\", data);
430             break;
431         case '\n':
432             cb("\\n", data);
433             break;
434         case '\t':
435             cb("\\t", data);
436             break;
437         case '\b':
438             cb("\\b", data);
439             break;
440         default:
441             /* This would be a lot faster if we scanned
442                forward for the next "interesting"
443                character.  */
444             buf[0] = ch;
445             cb(buf, data);
446             break;
447         }
448     }
449     cb("\"", data);
450 }
451
452
453
454 #if defined(_WIN32)
455 #define EOL "\r\n"
456 #endif
457
458 #ifndef EOL
459 #define EOL "\n"
460 #endif
461
462 /* Errors should be returned, not ignored!  */
463 static void dump_profile(struct profile_node *root, int level,
464                          void (*cb)(const char *, void *), void *data)
465 {
466     int i;
467     struct profile_node *p;
468     void *iter;
469     long retval;
470     char *name, *value;
471
472     iter = 0;
473     do {
474         retval = profile_find_node_relation(root, 0, &iter,
475                                             &name, &value);
476         if (retval)
477             break;
478         for (i=0; i < level; i++)
479             cb("\t", data);
480         if (need_double_quotes(value)) {
481             cb(name, data);
482             cb(" = ", data);
483             output_quoted_string(value, cb, data);
484             cb(EOL, data);
485         } else {
486             cb(name, data);
487             cb(" = ", data);
488             cb(value, data);
489             cb(EOL, data);
490         }
491     } while (iter != 0);
492
493     iter = 0;
494     do {
495         retval = profile_find_node_subsection(root, 0, &iter,
496                                               &name, &p);
497         if (retval)
498             break;
499         if (level == 0) { /* [xxx] */
500             cb("[", data);
501             cb(name, data);
502             cb("]", data);
503             cb(profile_is_node_final(p) ? "*" : "", data);
504             cb(EOL, data);
505             dump_profile(p, level+1, cb, data);
506             cb(EOL, data);
507         } else {        /* xxx = { ... } */
508             for (i=0; i < level; i++)
509                 cb("\t", data);
510             cb(name, data);
511             cb(" = {", data);
512             cb(EOL, data);
513             dump_profile(p, level+1, cb, data);
514             for (i=0; i < level; i++)
515                 cb("\t", data);
516             cb("}", data);
517             cb(profile_is_node_final(p) ? "*" : "", data);
518             cb(EOL, data);
519         }
520     } while (iter != 0);
521 }
522
523 static void dump_profile_to_file_cb(const char *str, void *data)
524 {
525     fputs(str, data);
526 }
527
528 errcode_t profile_write_tree_file(struct profile_node *root, FILE *dstfile)
529 {
530     dump_profile(root, 0, dump_profile_to_file_cb, dstfile);
531     return 0;
532 }
533
534 struct prof_buf {
535     char *base;
536     size_t cur, max;
537     int err;
538 };
539
540 static void add_data_to_buffer(struct prof_buf *b, const void *d, size_t len)
541 {
542     if (b->err)
543         return;
544     if (b->max - b->cur < len) {
545         size_t newsize;
546         char *newptr;
547
548         newsize = b->max + (b->max >> 1) + len + 1024;
549         newptr = realloc(b->base, newsize);
550         if (newptr == NULL) {
551             b->err = 1;
552             return;
553         }
554         b->base = newptr;
555         b->max = newsize;
556     }
557     memcpy(b->base + b->cur, d, len);
558     b->cur += len;          /* ignore overflow */
559 }
560
561 static void dump_profile_to_buffer_cb(const char *str, void *data)
562 {
563     add_data_to_buffer((struct prof_buf *)data, str, strlen(str));
564 }
565
566 errcode_t profile_write_tree_to_buffer(struct profile_node *root,
567                                        char **buf)
568 {
569     struct prof_buf prof_buf = { 0, 0, 0, 0 };
570
571     dump_profile(root, 0, dump_profile_to_buffer_cb, &prof_buf);
572     if (prof_buf.err) {
573         *buf = NULL;
574         return ENOMEM;
575     }
576     add_data_to_buffer(&prof_buf, "", 1); /* append nul */
577     if (prof_buf.max - prof_buf.cur > (prof_buf.max >> 3)) {
578         char *newptr = realloc(prof_buf.base, prof_buf.cur);
579         if (newptr)
580             prof_buf.base = newptr;
581     }
582     *buf = prof_buf.base;
583     return 0;
584 }