From b8310968c796abef5cfb993e81bf68b4fb691a5e Mon Sep 17 00:00:00 2001
From: Christophe CURIS <[email protected]>
Date: Mon, 18 Jun 2012 00:49:53 +0200
Subject: [PATCH 3/7] 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.
---
 src/rootmenu.h       |    2 +
 src/rootmenuparser.c |  167 +++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 168 insertions(+), 1 deletion(-)

diff --git a/src/rootmenu.h b/src/rootmenu.h
index b990560..0592000 100644
--- a/src/rootmenu.h
+++ b/src/rootmenu.h
@@ -58,6 +58,8 @@ void __menu_parser_warning(WMenuParser parser, const char *src_func, const char
 #ifdef MENU_PARSER_INTERNALS
 
 struct menu_parser {
+	WMenuParser include_file;
+	WMenuParser parent_file;
 	const char *file_name;
 	FILE *file_handle;
 	int num_line;
diff --git a/src/rootmenuparser.c b/src/rootmenuparser.c
index 3dd2bd7..d932d4c 100644
--- a/src/rootmenuparser.c
+++ b/src/rootmenuparser.c
@@ -28,6 +28,8 @@
 #include "WindowMaker.h"
 #include "rootmenu.h"
 
+#define MAX_NESTED_INCLUDES 16  // To avoid infinite includes case
+
 
 /***** Root parser functions *****/
 WMenuParser menu_parser_create(const char *file_name, FILE *file)
@@ -44,6 +46,18 @@ WMenuParser menu_parser_create(const char *file_name, FILE *file)
 
 void menu_parser_delete(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);
+		menu_parser_delete(parser->include_file);
+	}
 	wfree(parser);
 }
 
@@ -60,6 +74,8 @@ void __menu_parser_warning(WMenuParser parser, const char *src_func, const char
 	va_list args;
 
 	va_start(args, msg);
+	while (parser->include_file)
+		parser = parser->include_file;
 	snprintf(buf, sizeof(buf), "%s:%d: ", parser->file_name, parser->num_line);
 	for (pos=0; buf[pos] != '\0'; pos++) ;
 	vsnprintf(buf+pos, sizeof(buf)-pos, msg, args);
@@ -69,6 +85,10 @@ void __menu_parser_warning(WMenuParser parser, const char *src_func, const char
 
 /***** Reading from the file *****/
 static char *menu_parser_isolate_token(WMenuParser parser);
+static void menu_parser_get_directive(WMenuParser parser);
+
+/***** Handling of '#' directives *****/
+static Bool menu_parser_include_file(WMenuParser parser);
 
 /***** Main parsing from the file *****/
 /* The function returns False when the end of file is reached */
@@ -87,11 +107,24 @@ Bool menu_parser_get_line(WMenuParser top_parser, char **title, char **command,
 	*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->num_line++;
 	cur_parser->rd = cur_parser->line_buffer;
@@ -104,6 +137,11 @@ Bool menu_parser_get_line(WMenuParser top_parser, char **title, char **command,
 			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);
@@ -254,3 +292,130 @@ 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 {
+		menu_parser_warning(parser, _("unknow directive '%s'"), command);
+		return;
+	}
+
+	if (menu_parser_skip_spaces_and_comments(parser))
+		menu_parser_warning(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)) {
+		menu_parser_warning(parser, _("no file name found for #include") );
+		return False;
+	}
+	switch (*parser->rd++) {
+	case '<': eot = '>'; break;
+	case '"': eot = '"'; break;
+	default:
+		menu_parser_warning(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++;
+	menu_parser_warning(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) {
+			menu_parser_warning(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] != '/') {
+			static const char *DefaultPath = DEF_CONFIG_PATHS;
+			const char *src;
+
+			fullfilename = buffer;
+			src = DefaultPath;
+			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;
+			}
+		}
+		menu_parser_warning(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(wstrdup(req_filename), fh);
+	parser->include_file->parent_file = parser;
+	return True;
+}
-- 
1.7.10

Reply via email to