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