1 /*
2 	Copyright (C) 2004, 2005 Stephen Bach
3 	This file is part of the Viewglob package.
4 
5 	Viewglob is free software; you can redistribute it and/or modify
6 	it under the terms of the GNU General Public License as published by
7 	the Free Software Foundation; either version 2 of the License, or
8 	(at your option) any later version.
9 
10 	Viewglob is distributed in the hope that it will be useful,
11 	but WITHOUT ANY WARRANTY; without even the implied warranty of
12 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 	GNU General Public License for more details.
14 
15 	You should have received a copy of the GNU General Public License
16 	along with Viewglob; if not, write to the Free Software
17 	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19 
20 #include "config.h"
21 
22 #include "common.h"
23 #include "sanitize.h"
24 
25 
26 enum quote_type {
27 	QT_DUMMY,
28 	QT_SINGLE,
29 	QT_DOUBLE,
30 	QT_EXTGLOB_PAREN,
31 };
32 
33 
34 struct quote_list {
35 	enum quote_type qt;
36 	struct quote_list* next;
37 };
38 
39 
40 struct sane_cmd {
41 	gboolean last_char_backslash;
42 	gboolean last_char_exclamation;
43 	gboolean last_char_dollar;
44 	gboolean skip_word;
45 	struct quote_list* ql;
46 	GString* command;
47 };
48 
49 
50 static void       add_char(struct sane_cmd* s, gchar c);
51 static void       delete_current_word(struct sane_cmd* s);
52 static void       backspace(struct sane_cmd* s);
53 static gboolean   last_char(struct sane_cmd* s, gchar c);
54 
55 static gboolean         in_quote(struct sane_cmd* s, enum quote_type qt);
56 static enum quote_type  ql_pop(struct sane_cmd* s);
57 static void             ql_push(struct sane_cmd* s, enum quote_type);
58 
59 
sanitize(GString * string)60 gchar* sanitize(GString* string) {
61 	struct sane_cmd s;
62 	gint i;
63 	gchar c;
64 
65 	s.last_char_backslash = FALSE;
66 	s.last_char_exclamation = FALSE;
67 	s.last_char_dollar = FALSE;
68 	s.skip_word = FALSE;
69 	s.command = g_string_sized_new(string->len);
70 	s.ql = NULL;
71 
72 	for (i = 0; i < string->len; i++) {
73 		c = *(string->str + i);
74 
75 		if (s.last_char_exclamation) {
76 			/* Don't allow history expansion. */
77 			if ( (c != '(') && (c != ' ') && (c != '\t') && (c != '\n') ) {
78 				s.skip_word = TRUE;
79 				s.last_char_exclamation = FALSE;
80 				/* Remove the ! */
81 				backspace(&s);
82 				continue;
83 			}
84 		}
85 		else if (s.last_char_dollar) {
86 			/* Don't allow $ constructs (variables, command substitution,
87 			   etc. */
88 			if ( (c != ' ') && (c != '\t') && (c != '\n') ) {
89 				backspace(&s);
90 				s.last_char_dollar = FALSE;
91 				i = string->len;   /* Break out of loop. */
92 				continue;
93 			}
94 			else {
95 				/* A lone $ is acceptable. */
96 				s.last_char_dollar = FALSE;
97 			}
98 		}
99 
100 		if (s.skip_word) {
101 			if ( (c == ' ') || (c == '\t') )
102 				s.skip_word = FALSE;
103 			else
104 				continue;
105 		}
106 
107 		switch (c) {
108 			case ('\''):
109 				if (in_quote(&s, QT_SINGLE)) {
110 					ql_pop(&s);
111 					add_char(&s, c);
112 				}
113 				else if (in_quote(&s, QT_DOUBLE))
114 					add_char(&s, c);
115 				else if (s.last_char_backslash) {
116 					s.last_char_backslash = FALSE;
117 					add_char(&s, c);
118 				}
119 				else {
120 					ql_push(&s, QT_SINGLE);
121 					add_char(&s, c);
122 				}
123 				break;
124 
125 			case ('\"'):
126 				if (in_quote(&s, QT_SINGLE))
127 					add_char(&s, c);
128 				else if (in_quote(&s, QT_DOUBLE)) {
129 					ql_pop(&s);
130 					add_char(&s, c);
131 				}
132 				else if (s.last_char_backslash) {
133 					s.last_char_backslash = FALSE;
134 					add_char(&s, c);
135 				}
136 				else {
137 					ql_push(&s, QT_DOUBLE);
138 					add_char(&s, c);
139 				}
140 				break;
141 
142 			case ('\\'):
143 				if (in_quote(&s, QT_SINGLE) || in_quote(&s, QT_DOUBLE))
144 					add_char(&s, c);
145 				else if (s.last_char_backslash) {
146 					s.last_char_backslash = FALSE;
147 					add_char(&s, c);
148 				}
149 				else {
150 					s.last_char_backslash = TRUE;
151 					add_char(&s, c);
152 				}
153 				break;
154 
155 			case ('$'):
156 				if (in_quote(&s, QT_SINGLE))
157 					add_char(&s, c);
158 				else if (s.last_char_backslash) {
159 					s.last_char_backslash = FALSE;
160 					add_char(&s, c);
161 				}
162 				else {
163 					s.last_char_dollar = TRUE;
164 					add_char(&s, c);
165 				}
166 
167 				break;
168 
169 
170 			case ('!'):      /* Gotta be careful about history expansion. */
171 				if (in_quote(&s, QT_SINGLE))
172 					add_char(&s, c);
173 				else if (in_quote(&s, QT_EXTGLOB_PAREN))
174 					/* No ! allowed in ?( ) constructs. */
175 					break;
176 				else if (s.last_char_backslash) {
177 					s.last_char_backslash = FALSE;
178 					add_char(&s, c);
179 				}
180 				else {
181 					s.last_char_exclamation = TRUE;
182 					add_char(&s, c);
183 				}
184 				break;
185 
186 			case (' '):
187 			case ('\t'):
188 				if (s.last_char_exclamation)
189 					s.last_char_exclamation = FALSE;
190 				else if (s.last_char_backslash)
191 					s.last_char_backslash = FALSE;
192 				else if (s.skip_word) {
193 					/* Only ' ' and \t can turn off skip_word. */
194 					s.skip_word = FALSE;
195 				}
196 				add_char(&s, c);
197 				break;
198 
199 			/* Only allow ( in the *(blah), ?(blah), etc. forms, or when
200 			   quoted. */
201 			case ('('):
202 				if (in_quote(&s, QT_SINGLE))
203 					add_char(&s, c);
204 				else if (s.last_char_backslash) {
205 					s.last_char_backslash = FALSE;
206 					add_char(&s, c);
207 				}
208 				else if (s.last_char_exclamation) {
209 					if (in_quote(&s, QT_DOUBLE)) {
210 						/* It sucks that ! is such a multiuse character.
211 						   There's no good way to deal here, so just give
212 						   up. */
213 						backspace(&s);
214 						s.last_char_exclamation = FALSE;
215 						i = string->len;
216 						break;
217 					}
218 					s.last_char_exclamation = FALSE;
219 					ql_push(&s, QT_EXTGLOB_PAREN);
220 					add_char(&s, c);
221 				}
222 				else if (in_quote(&s, QT_DOUBLE))
223 					add_char(&s, c);
224 				else if (last_char(&s, '?') || last_char(&s, '*') ||
225 				         last_char(&s, '+') || last_char(&s, '@')) {
226 					ql_push(&s, QT_EXTGLOB_PAREN);
227 					add_char(&s, c);
228 				}
229 				else
230 					i = string->len;    /* Break out of loop. */
231 				break;
232 
233 			case (')'):
234 				if (in_quote(&s, QT_SINGLE) || in_quote(&s, QT_DOUBLE))
235 					add_char(&s, c);
236 				else if (s.last_char_backslash) {
237 					s.last_char_backslash = FALSE;
238 					add_char(&s, c);
239 				}
240 				else if (in_quote(&s, QT_EXTGLOB_PAREN)) {
241 					ql_pop(&s);
242 					add_char(&s, c);
243 				}
244 				/* Skip ) otherwise. */
245 				break;
246 
247 			case ('`'):  /* Backtick */
248 				if (in_quote(&s, QT_SINGLE))
249 					add_char(&s, c);
250 				else if (s.last_char_backslash) {
251 					s.last_char_backslash = FALSE;
252 					add_char(&s, c);
253 				}
254 				else
255 					i = string->len;    /* Break out of loop. */
256 				break;
257 
258 			case (';'):     /* These are command finishers. */
259 			case ('&'):     /* Must be careful not to include one if not */
260 			case ('|'):     /* escaped, and to stop processing if so. */
261 				if (in_quote(&s, QT_SINGLE) || in_quote(&s, QT_DOUBLE) ||
262 						in_quote(&s, QT_EXTGLOB_PAREN))
263 					add_char(&s, c);
264 				else if (s.last_char_backslash) {
265 					s.last_char_backslash = FALSE;
266 					add_char(&s, c);
267 				}
268 				else
269 					i = string->len;    /* Break out of loop. */
270 				break;
271 
272 			case ('\015'):  /* Carriage return */
273 			case ('\n'):    /* Should never see this, but just in case. */
274 				break;      /* Skip 'em. */
275 
276 			case ('>'):
277 			case ('<'):
278 				if (in_quote(&s, QT_SINGLE) || in_quote(&s, QT_DOUBLE) ||
279 						in_quote(&s, QT_EXTGLOB_PAREN))
280 					add_char(&s, c);
281 				else if (s.last_char_backslash) {
282 					s.last_char_backslash = FALSE;
283 					add_char(&s, c);
284 				}
285 				else {    /* Break out of loop. */
286 					/* Note: this isn't entirely correct, as bash only
287 					   interprets a few characters as being part of the
288 					   redirection construct. */
289 					delete_current_word(&s);
290 					i = string->len;
291 				}
292 				break;
293 
294 			default:
295 				if (s.last_char_backslash)
296 					s.last_char_backslash = FALSE;
297 				add_char(&s, c);
298 				break;
299 		}
300 	}
301 
302 	if (s.last_char_backslash) {
303 		/* Can't have a trailing backslash. */
304 		backspace(&s);
305 		s.last_char_backslash = FALSE;
306 	}
307 
308 	/* Close unclosed quotes. */
309 	enum quote_type qt;
310 	while ( (qt = ql_pop(&s)) != QT_DUMMY ) {
311 
312 		if (s.last_char_exclamation) {
313 			/* This exclamation could be interpreted as special because of
314 			   the following quote characters. */
315 			backspace(&s);
316 			s.last_char_exclamation = FALSE;
317 		}
318 
319 		switch (qt) {
320 			case QT_SINGLE:
321 				c = '\'';
322 				break;
323 			case QT_DOUBLE:
324 				c = '\"';
325 				break;
326 			case QT_EXTGLOB_PAREN:
327 				c = ')';
328 				break;
329 			default:
330 				/* Should never get here, but just in case. */
331 				c = ' ';
332 				break;
333 		}
334 		add_char(&s, c);
335 	}
336 
337 	gchar* retval = s.command->str;
338 	g_string_free(s.command, FALSE);
339 	return retval;
340 }
341 
342 
last_char(struct sane_cmd * s,gchar c)343 static gboolean last_char(struct sane_cmd* s, gchar c) {
344 	if ( (s->command->len > 0) &&
345 			(*(s->command->str + s->command->len - 1) == c) )
346 		return TRUE;
347 	else
348 		return FALSE;
349 }
350 
351 
delete_current_word(struct sane_cmd * s)352 static void delete_current_word(struct sane_cmd* s) {
353 
354 	gchar c = *(s->command->str + s->command->len);
355 	while (c != ' ' && c != '\t' && c != '\n' && s->command->len > 0) {
356 		backspace(s);
357 		c = *(s->command->str + s->command->len);
358 	}
359 }
360 
361 
add_char(struct sane_cmd * s,gchar c)362 static void add_char(struct sane_cmd* s, gchar c) {
363 	s->command = g_string_append_c(s->command, c);
364 }
365 
366 
ql_pop(struct sane_cmd * s)367 static enum quote_type ql_pop(struct sane_cmd* s) {
368 	enum quote_type popped;
369 	if (s->ql) {
370 		struct quote_list* tmp = s->ql->next;
371 		popped = s->ql->qt;
372 		g_free(s->ql);
373 		s->ql = tmp;
374 	}
375 	else
376 		popped = QT_DUMMY;
377 
378 	return popped;
379 }
380 
381 
ql_push(struct sane_cmd * s,enum quote_type new_qt)382 static void ql_push(struct sane_cmd* s, enum quote_type new_qt) {
383 	struct quote_list* new_ql;
384 
385 	new_ql = g_new(struct quote_list, 1);
386 	new_ql->qt = new_qt;
387 	new_ql->next = s->ql;
388 	s->ql = new_ql;
389 }
390 
391 
in_quote(struct sane_cmd * s,enum quote_type qt)392 static gboolean in_quote(struct sane_cmd* s, enum quote_type qt) {
393 	if (s->ql) {
394 		if (s->ql->qt == qt)
395 			return TRUE;
396 		else
397 			return FALSE;
398 	}
399 	else
400 		return FALSE;
401 }
402 
403 
backspace(struct sane_cmd * s)404 static void backspace(struct sane_cmd* s) {
405 	g_return_if_fail(s != NULL);
406 
407 	if (s->command->len > 0)
408 		s->command = g_string_truncate(s->command, s->command->len - 1);
409 }
410 
411