From 5d248ce5a77b3425cc2338f64eb63ddd76ec302b Mon Sep 17 00:00:00 2001
From: Christophe CURIS <[email protected]>
Date: Mon, 18 Jun 2012 00:49:53 +0200
Subject: [PATCH 06/10] 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 e62b72b..0d3343c 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 f1ff1f4..e021215 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 594f581..5e7bf94 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 932a362..165ab92 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, &params, &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, &params, &shortcut)) {
 
-- 
1.7.10.4

Reply via email to