From 88a82ab8df5339665008643bc41475620434e413 Mon Sep 17 00:00:00 2001 From: Christophe CURIS Date: Mon, 18 Jun 2012 00:49:53 +0200 Subject: [PATCH] Remove dependency to CPP: support for #include directive The parser is prepared to handle '#' directives, starting with file inclusion. The search path for the file are taken from what was actually given to CPP. There is an arbitrary limit to the inclusion nesting, which is actually not a design limitation but a security to avoid infinite include loops. --- WINGs/WINGs/WUtil.h | 2 +- WINGs/menuparser.c | 181 ++++++++++++++++++++++++++++++++++++++++++-- WINGs/menuparser.h | 4 + src/rootmenu.c | 4 +- 4 files changed, 183 insertions(+), 8 deletions(-) diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index e62b72bc..0d3343c1 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -872,7 +872,7 @@ extern char *WMUserDefaultsDidChangeNotification; typedef struct w_menu_parser *WMenuParser; -WMenuParser WMenuParserCreate(const char *file_name, void *file); +WMenuParser WMenuParserCreate(const char *file_name, void *file, const char *include_default_paths); void WMenuParserError(WMenuParser parser, const char *msg, ...) __attribute__ ((format (printf, 2, 3))); diff --git a/WINGs/menuparser.c b/WINGs/menuparser.c index f1ff1f41..e0212154 100644 --- a/WINGs/menuparser.c +++ b/WINGs/menuparser.c @@ -28,29 +28,47 @@ #include "menuparser.h" -static WMenuParser menu_parser_create_new(const char *file_name, void *file); +static WMenuParser menu_parser_create_new(const char *file_name, void *file, + const char *include_default_paths); static char *menu_parser_isolate_token(WMenuParser parser); +static void menu_parser_get_directive(WMenuParser parser); +static Bool menu_parser_include_file(WMenuParser parser); /***** Constructor and Destructor for the Menu Parser object *****/ -WMenuParser WMenuParserCreate(const char *file_name, void *file) +WMenuParser WMenuParserCreate(const char *file_name, void *file, + const char *include_default_paths) { WMenuParser parser; - parser = menu_parser_create_new(file_name, file); + parser = menu_parser_create_new(file_name, file, include_default_paths); return parser; } void WMenuParserDelete(WMenuParser parser) { + if (parser->include_file) { + /* Trick: the top parser's data are not wmalloc'd, we point on the + provided data so we do not wfree it; however for include files + we did wmalloc them. + This code should not be used as the wfree is done when we reach + the end of an include file; however this may not happen when an + early exit occurs (typically when 'readMenuFile' does not find + its expected command). */ + fclose(parser->include_file->file_handle); + wfree((char *) parser->include_file->file_name); + WMenuParserDelete(parser->include_file); + } wfree(parser); } -static WMenuParser menu_parser_create_new(const char *file_name, void *file) +static WMenuParser menu_parser_create_new(const char *file_name, void *file, + const char *include_default_paths) { WMenuParser parser; parser = wmalloc(sizeof(*parser)); + parser->include_default_paths = include_default_paths; parser->file_name = file_name; parser->file_handle = file; parser->rd = parser->line_buffer; @@ -68,11 +86,20 @@ void WMenuParserError(WMenuParser parser, const char *msg, ...) { char buf[MAXLINE]; va_list args; + WMenuParser parent; + + while (parser->include_file) + parser = parser->include_file; va_start(args, msg); vsnprintf(buf, sizeof(buf), msg, args); va_end(args); __wmessage("WMenuParser", parser->file_name, parser->line_number, WMESSAGE_TYPE_WARNING, buf); + + for (parent = parser->parent_file; parent != NULL; parent = parent->parent_file) + __wmessage("WMenuParser", parser->file_name, parser->line_number, WMESSAGE_TYPE_WARNING, + _(" included from file \"%s\" at line %d"), + parent->file_name, parent->line_number); } /***** Read one line from file and split content *****/ @@ -92,11 +119,24 @@ Bool WMenuParserGetLine(WMenuParser top_parser, char **title, char **command, ch *shortcut = NULL; scanmode = GET_TITLE; + read_next_line_with_filechange: cur_parser = top_parser; + while (cur_parser->include_file) + cur_parser = cur_parser->include_file; read_next_line: if (fgets(cur_parser->line_buffer, sizeof(cur_parser->line_buffer), cur_parser->file_handle) == NULL) { - return False; + if (cur_parser->parent_file == NULL) + /* Not inside an included file -> we have reached the end */ + return False; + + /* We have only reached the end of an included file -> go back to calling file */ + fclose(cur_parser->file_handle); + wfree((char *) cur_parser->file_name); + cur_parser = cur_parser->parent_file; + wfree(cur_parser->include_file); + cur_parser->include_file = NULL; + goto read_next_line_with_filechange; } cur_parser->line_number++; cur_parser->rd = cur_parser->line_buffer; @@ -109,6 +149,11 @@ Bool WMenuParserGetLine(WMenuParser top_parser, char **title, char **command, ch else break; // Finished reading current line -> return it to caller } + if ((scanmode == GET_TITLE) && (*cur_parser->rd == '#')) { + cur_parser->rd++; + menu_parser_get_directive(cur_parser); + goto read_next_line_with_filechange; + } /* Found a word */ token = menu_parser_isolate_token(cur_parser); @@ -259,3 +304,129 @@ static char *menu_parser_isolate_token(WMenuParser parser) return token; } + +/***** Processing of special # directives *****/ +static void menu_parser_get_directive(WMenuParser parser) +{ + char *command; + + /* Isolate the command */ + while (isspace(*parser->rd)) + parser->rd++; + command = parser->rd; + while (*parser->rd) + if (isspace(*parser->rd)) { + *parser->rd++ = '\0'; + break; + } else parser->rd++; + + if (strcmp(command, "include") == 0) { + if (!menu_parser_include_file(parser)) return; + + } else { + WMenuParserError(parser, _("unknow directive '#%s'"), command); + return; + } + + if (menu_parser_skip_spaces_and_comments(parser)) + WMenuParserError(parser, _("extra text after '#' command is ignored: \"%.16s...\""), + parser->rd); +} + +/* Extract the file name, search for it in known directories + and create a sub-parser to handle it. + Returns False if the file could not be found */ +static Bool menu_parser_include_file(WMenuParser parser) +{ + char buffer[MAXLINE]; + char *req_filename, *fullfilename, *p; + char eot; + FILE *fh; + + if (!menu_parser_skip_spaces_and_comments(parser)) { + WMenuParserError(parser, _("no file name found for #include") ); + return False; + } + switch (*parser->rd++) { + case '<': eot = '>'; break; + case '"': eot = '"'; break; + default: + WMenuParserError(parser, _("file name must be enclosed in brackets or double-quotes for #define") ); + return False; + } + req_filename = parser->rd; + while (*parser->rd) + if (*parser->rd == eot) { + *parser->rd++ = '\0'; + goto found_end_define_fname; + } else parser->rd++; + WMenuParserError(parser, _("missing closing '%c' in filename specification"), eot); + return False; + found_end_define_fname: + + { /* Check we are not nesting too many includes */ + WMenuParser p; + int count; + + count = 0; + for (p = parser; p->parent_file; p = p->parent_file) + count++; + if (count > MAX_NESTED_INCLUDES) { + WMenuParserError(parser, _("too many nested includes") ); + return False; + } + } + + /* Absolute paths */ + fullfilename = req_filename; + if (req_filename[0] != '/') { + /* Search first in the same directory as the current file */ + p = strrchr(parser->file_name, '/'); + if (p != NULL) { + int len; + + len = p - parser->file_name + 1; + if (len > sizeof(buffer) - 1) len = sizeof(buffer) - 1; + strncpy(buffer, parser->file_name, len); + strncpy(buffer+len, req_filename, sizeof(buffer) - len - 1); + buffer[sizeof(buffer) - 1] = '\0'; + fullfilename = buffer; + } + } + fh = fopen(fullfilename, "rb"); + + /* Not found? Search in wmaker's known places */ + if (fh == NULL) { + if (req_filename[0] != '/') { + const char *src; + + fullfilename = buffer; + src = parser->include_default_paths; + while (*src != '\0') { + p = buffer; + if (*src == '~') { + char *home = wgethomedir(); + while (*home != '\0') + *p++ = *home++; + src++; + } + while ((*src != '\0') && (*src != ':')) + *p++ = *src++; + *p++ = '/'; + strncpy(p, req_filename, sizeof(buffer) - (p - buffer - 1)); + buffer[sizeof(buffer) - 1] = '\0'; + + fh = fopen(fullfilename, "rb"); + if (fh != NULL) goto found_valid_file; + } + } + WMenuParserError(parser, _("could not find file \"%s\" for include"), req_filename); + return False; + } + + /* Found the file, make it our new source */ + found_valid_file: + parser->include_file = menu_parser_create_new(wstrdup(req_filename), fh, parser->include_default_paths); + parser->include_file->parent_file = parser; + return True; +} diff --git a/WINGs/menuparser.h b/WINGs/menuparser.h index 594f581e..5e7bf94c 100644 --- a/WINGs/menuparser.h +++ b/WINGs/menuparser.h @@ -28,8 +28,12 @@ */ #define MAXLINE 1024 +#define MAX_NESTED_INCLUDES 16 // To avoid infinite includes case struct w_menu_parser { + WMenuParser include_file; + WMenuParser parent_file; + const char *include_default_paths; const char *file_name; FILE *file_handle; int line_number; diff --git a/src/rootmenu.c b/src/rootmenu.c index 932a3625..165ab92c 100644 --- a/src/rootmenu.c +++ b/src/rootmenu.c @@ -969,7 +969,7 @@ static WMenu *readMenuFile(WScreen * scr, char *file_name) return NULL; } } - parser = WMenuParserCreate(file_name, file); + parser = WMenuParserCreate(file_name, file, DEF_CONFIG_PATHS); while (WMenuParserGetLine(parser, &title, &command, ¶ms, &shortcut)) { @@ -1060,7 +1060,7 @@ static WMenu *readMenuPipe(WScreen * scr, char **file_name) return NULL; } } - parser = WMenuParserCreate(flat_file, file); + parser = WMenuParserCreate(flat_file, file, DEF_CONFIG_PATHS); while (WMenuParserGetLine(parser, &title, &command, ¶ms, &shortcut)) {