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