1 /*
2  * Copyright (c) 2008-2013 Zmanda, Inc.  All Rights Reserved.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
17  *
18  * Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300
19  * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
20  *
21  * Author: Dustin J. Mitchell <dustin@zmanda.com>
22  */
23 
24 #include "amanda.h"
25 #include "testutils.h"
26 #include "util.h"
27 
28 /* Utilities */
29 
30 static char *
safestr(const char * str)31 safestr(const char *str) {
32     static char hex[] = "0123456789abcdef";
33     const char *p;
34     char *result = malloc(3 + strlen(str) * 3);
35     char *r = result;
36 
37     *(r++) = '|';
38     for (p = str; *p; p++) {
39 	if (isprint((int)*p)) {
40 	    *(r++) = *p;
41 	} else {
42 	    *(r++) = '#';
43 	    *(r++) = hex[((*p)&0xf0) >> 4];
44 	    *(r++) = hex[(*p)&0xf];
45 	}
46     }
47     *(r++) = '|';
48     *(r++) = '\0';
49 
50     return result;
51 }
52 
53 char * quotable_strings[] = {
54     "",
55     "simple",
56     "sp a ces",
57     "\"foo bar\"",
58     "back\\slash",
59     "escaped\\ space",
60     "escaped\\\"quote",
61     "balanced \"internal\" quotes",
62     "\"already quoted\" string",
63     "string that's \"already quoted\"",
64     "internal\"quote",
65     "bs-end\\",
66     "backslash\\nletter",
67     "backslash\\tletter",
68     "\t", "\r", "\n", "\f", "\004",
69     "new\nline",
70     "newline-end\n",
71     "ta\tb",
72     "tab-end\t",
73     "\\\\\\\\",
74     "\"",
75     NULL
76 };
77 
78 /****
79  * Round-trip testing of quoting functions
80  */
81 
82 static gboolean
test_round_trip(void)83 test_round_trip(void)
84 {
85     char **strp;
86     gboolean success = TRUE;
87 
88     for (strp = quotable_strings; *strp; strp++) {
89 	char *quoted, *unquoted;
90 
91 	quoted = quote_string(*strp);
92 	unquoted = unquote_string(quoted);
93 
94 	/* if they're not the same, complain */
95 	if (0 != strcmp(*strp, unquoted)) {
96 	    char *safe_orig = safestr(*strp);
97 	    char *safe_quoted = safestr(quoted);
98 	    char *safe_unquoted = safestr(unquoted);
99 
100 	    printf("  bad round-trip: %s -quote_string-> %s -unquote_string-> %s\n",
101 		safe_orig, safe_quoted, safe_unquoted);
102 
103 	    amfree(safe_orig);
104 	    amfree(safe_quoted);
105 	    amfree(safe_unquoted);
106 
107 	    success = FALSE;
108 	}
109 
110 	amfree(quoted);
111 	amfree(unquoted);
112     }
113 
114     return success;
115 }
116 
117 /***
118  * Test that the new split_quoted_strings acts identically to
119  * the old split(), albeit with a different set of arguments and
120  * return value.  Note that we only test with a delimiter of " ",
121  * as split() is not used with any other delimiter.
122  */
123 
124 static gboolean
compare_strv(const char ** exp,char ** got,const char * source,const char * original)125 compare_strv(
126     const char **exp,
127     char **got,
128     const char *source,
129     const char *original)
130 {
131     const char **a = exp;
132     char **b = got;
133     while (*a && *b) {
134 	if (0 != strcmp(*a, *b))
135 	    break;
136 	a++; b++;
137     }
138 
139     /* did we exit the loop early, or were they different lengths? */
140     if (*a || *b) {
141 	char *safe;
142 
143 	safe = safestr(original);
144 	g_printf("  %s: expected [", safe);
145 	amfree(safe);
146 	for (a = exp; *a; a++) {
147 	    safe = safestr(*a);
148 	    g_printf("%s%s", safe, *(a+1)? ", " : "");
149 	    amfree(safe);
150 	}
151 	g_printf("] but got [");
152 	for (b = got; *b; b++) {
153 	    safe = safestr(*b);
154 	    g_printf("%s%s", safe, *(b+1)? ", " : "");
155 	    amfree(safe);
156 	}
157 	g_printf("] using %s.\n", source);
158 
159 	return FALSE;
160     }
161 
162     return TRUE;
163 }
164 
165 static gboolean
test_split_quoted_strings(void)166 test_split_quoted_strings(void)
167 {
168     char **iter1, **iter2, **iter3;
169     gboolean success = TRUE;
170     char *middle_strings[] = {
171 	"",
172 	"foo",
173 	"\"foo\"",
174 	"sp aces",
175 	NULL,
176     };
177 
178     /* the idea here is to loop over all triples of strings, forming a
179      * string by quoting them with quote_string and inserting a space, then
180      * re-splitting with split_quoted_string.  This should get us back to our
181      * starting point. */
182 
183     for (iter1 = quotable_strings; *iter1; iter1++) {
184 	for (iter2 = middle_strings; *iter2; iter2++) {
185 	    for (iter3 = quotable_strings; *iter3; iter3++) {
186 		char *q1 = quote_string(*iter1);
187 		char *q2 = quote_string(*iter2);
188 		char *q3 = quote_string(*iter3);
189 		const char *expected[4] = { *iter1, *iter2, *iter3, NULL };
190 		char *combined = vstralloc(q1, " ", q2, " ", q3, NULL);
191 		char **tokens;
192 
193 		tokens = split_quoted_strings(combined);
194 
195 		success = compare_strv(expected, tokens, "split_quoted_strings", combined)
196 			&& success;
197 
198 		amfree(q1);
199 		amfree(q2);
200 		amfree(q3);
201 		amfree(combined);
202 		g_strfreev(tokens);
203 	    }
204 	}
205     }
206 
207     return success;
208 }
209 
210 /****
211  * Test splitting some edge cases and invalid strings
212  */
213 
214 struct trial {
215     const char *combined;
216     const char *expected[5];
217 };
218 
219 static gboolean
test_split_quoted_strings_edge(void)220 test_split_quoted_strings_edge(void)
221 {
222     gboolean success = TRUE;
223     struct trial trials[] = {
224 	{ "", { "", NULL, } },
225 	{ " ", { "", "", NULL } },
226 	{ " x", { "", "x", NULL } },
227 	{ "x ", { "x", "", NULL } },
228 	{ "x\\ y", { "x y", NULL } },
229 	{ "\\", { "", NULL } }, /* inv */
230 	{ "z\\", { "z", NULL } }, /* inv */
231 	{ "z\"", { "z", NULL } }, /* inv */
232 	{ "\" \" \"", { " ", "", NULL } }, /* inv */
233 	{ NULL, { NULL, } },
234     };
235     struct trial *trial = trials;
236 
237     while (trial->combined) {
238 	char **tokens = split_quoted_strings(trial->combined);
239 
240 	success = compare_strv(trial->expected, tokens,
241 			       "split_quoted_strings", trial->combined)
242 	    && success;
243 
244 	g_strfreev(tokens);
245 	trial++;
246     }
247 
248     return success;
249 }
250 
251 /****
252  * Test unquoting of some pathological strings
253  */
254 static gboolean
test_unquote_string(void)255 test_unquote_string(void)
256 {
257     gboolean success = TRUE;
258     char *tests[] = {
259 	"simple",              "simple",
260 	"\"quoted\"",          "quoted",
261 	"s p a c e",           "s p a c e",
262 
263 	/* special escape characters */
264 	"esc \\\" quote",      "esc \" quote",
265 	"esc \\t tab",         "esc \t tab",
266 	"esc \\\\ esc",        "esc \\ esc",
267 	"esc \\02 oct",         "esc \02 oct",
268 	"esc \\7 oct",         "esc \7 oct",
269 	"esc \\17 oct",        "esc \17 oct",
270 	"esc \\117 oct",       "esc \117 oct",
271 	"esc \\1117 oct",      "esc \1117 oct", /* '7' is distinct char */
272 
273 	/* same, but pre-quoted */
274 	"\"esc \\\" quote\"",  "esc \" quote",
275 	"\"esc \\t tab\"",     "esc \t tab",
276 	"\"esc \\\\ esc\"",    "esc \\ esc",
277 	"\"esc \\02 oct\"",     "esc \02 oct",
278 	"\"esc \\7 oct\"",     "esc \7 oct",
279 	"\"esc \\17 oct\"",    "esc \17 oct",
280 	"\"esc \\117 oct\"",   "esc \117 oct",
281 	"\"esc \\1117 oct\"",  "esc \1117 oct", /* '7' is distinct char */
282 
283 	/* strips balanced quotes, even inside the string */
284 	">>\"x\"<<",           ">>x<<",
285 	">>\"x\"-\"y\"<<",     ">>x-y<<",
286 
287 	/* pathological, but valid */
288 	"\\\\",                "\\",
289 	"\"\\\"\"",            "\"",
290 	"\"\\\\\"",            "\\",
291 	"--\\\"",              "--\"",
292 	"\\\"--",              "\"--",
293 
294 	/* invalid strings (handling here is arbitrary, but these tests
295 	 * will alert us if the handling changes) */
296 	"\\",                  "", /* trailing backslash is ignored */
297 	"xx\\",                "xx", /* ditto */
298 	"\\\\\\\\\\\\\\",      "\\\\\\",   /* ditto */
299 	"\\777",               "\377", /* 0777 & 0xff = 0xff */
300 	"\"--",                "--", /* leading quote is dropped */
301 	"--\"",                "--", /* trailing quote is dropped */
302 
303 	NULL, NULL,
304     };
305     char **strp;
306 
307     for (strp = tests; *strp;) {
308 	char *quoted = *(strp++);
309 	char *expected = *(strp++);
310 	char *unquoted = unquote_string(quoted);
311 
312 	/* if they're not the same, complain */
313 	if (0 != strcmp(expected, unquoted)) {
314 	    char *safe_quoted = safestr(quoted);
315 	    char *safe_unquoted = safestr(unquoted);
316 	    char *safe_expected = safestr(expected);
317 
318 	    printf("  %s unquoted to %s; expected %s.\n",
319 		safe_quoted, safe_unquoted, safe_expected);
320 
321 	    amfree(safe_quoted);
322 	    amfree(safe_unquoted);
323 	    amfree(safe_expected);
324 
325 	    success = FALSE;
326 	}
327 
328 	amfree(unquoted);
329     }
330 
331     return success;
332 }
333 
334 /****
335  * Test the strquotedstr function
336  */
337 static gboolean
test_strquotedstr_skipping(void)338 test_strquotedstr_skipping(void)
339 {
340     char **iter1, **iter2;
341     gboolean success = TRUE;
342 
343     /* the idea here is to loop over all pairs of strings, forming a
344      * string by quoting them with quote_string and inserting a space, then
345      * re-splitting with strquotedstr.  This should get us back to our
346      * starting point. Note that we have to begin with a non-quoted identifier,
347      * becuse strquotedstr requires that strtok_r has already been called. */
348 
349     for (iter1 = quotable_strings; *iter1; iter1++) {
350 	for (iter2 = quotable_strings; *iter2; iter2++) {
351 	    char *q1 = quote_string(*iter1);
352 	    char *q2 = quote_string(*iter2);
353 	    char *combined = vstralloc("START ", q1, " ", q2, NULL);
354 	    char *copy = g_strdup(combined);
355 	    char *saveptr = NULL;
356 	    char *tok;
357 	    int i;
358 
359 	    tok = strtok_r(copy, " ", &saveptr);
360 
361 	    for (i = 1; i <= 2; i++) {
362 		char *expected = (i == 1)? q1:q2;
363 		tok = strquotedstr(&saveptr);
364 		if (!tok) {
365 		    g_fprintf(stderr, "while parsing '%s', call %d to strquotedstr returned NULL\n",
366 			      combined, i);
367 		    success = FALSE;
368 		    goto next;
369 		}
370 		if (0 != strcmp(tok, expected)) {
371 		    char *safe = safestr(tok);
372 
373 		    g_fprintf(stderr, "while parsing '%s', call %d to strquotedstr returned '%s' "
374 			      "but '%s' was expected.\n",
375 			      combined, i, safe, expected);
376 		    success = FALSE;
377 		    goto next;
378 		}
379 	    }
380 
381 	    if (strquotedstr(&saveptr) != NULL) {
382 		g_fprintf(stderr, "while parsing '%s', call 3 to strquotedstr did not return NULL\n",
383 			  combined);
384 		success = FALSE;
385 		goto next;
386 	    }
387 next:
388 	    amfree(q1);
389 	    amfree(q2);
390 	    amfree(copy);
391 	    amfree(combined);
392 	}
393     }
394 
395     return success;
396 }
397 
398 static gboolean
test_strquotedstr_edge_invalid(void)399 test_strquotedstr_edge_invalid(void)
400 {
401     gboolean success = TRUE;
402     char *invalid[] = {
403 	"X \"abc", /* unterminated */
404 	"X \"ab cd", /* unterminated second token */
405 	"X a\"b cd", /* unterminated second token with internal quote */
406 	"X b\\", /* trailing backslash */
407 	"X \"b\\", /* trailing backslash in quote */
408 	"X \"b\\\"", /* backslash'd ending quote */
409 	NULL
410     };
411     char **iter;
412 
413     /* run strquotedstr on a bunch of invalid tokens.  It should return NULL */
414 
415     for (iter = invalid; *iter; iter++) {
416 	char *copy = g_strdup(*iter);
417 	char *tok;
418 	char *saveptr = NULL;
419 
420 	tok = strtok_r(copy, " ", &saveptr);
421 	tok = strquotedstr(&saveptr);
422 	if (tok != NULL) {
423 	    g_fprintf(stderr, "while parsing invalid '%s', strquotedstr did not return NULL\n",
424 		      *iter);
425 	    success = FALSE;
426 	}
427 
428 	amfree(copy);
429     }
430 
431     return success;
432 }
433 
434 static gboolean
test_strquotedstr_edge_valid(void)435 test_strquotedstr_edge_valid(void)
436 {
437     gboolean success = TRUE;
438     char *valid[] = {
439 	/* input */	    /* expected (omitting "X") */
440 	"X abc\\ def",      "abc\\ def", /* backslashed space */
441 	"X \"abc\\ def\"",  "\"abc\\ def\"", /* quoted, backslashed space */
442 	"X a\"  \"b",       "a\"  \"b", /* quoted spaces */
443 	NULL, NULL
444     };
445     char **iter;
446 
447     /* run strquotedstr on a bunch of valid, but tricky, tokens.  It should return NULL */
448 
449     for (iter = valid; *iter; iter += 2) {
450 	char *copy = g_strdup(*iter);
451 	char *expected = *(iter+1);
452 	char *tok;
453 	char *saveptr = NULL;
454 
455 	tok = strtok_r(copy, " ", &saveptr);
456 	tok = strquotedstr(&saveptr);
457 	if (tok == NULL) {
458 	    g_fprintf(stderr, "while parsing valid '%s', strquotedstr returned NULL\n",
459 		      *iter);
460 	    success = FALSE;
461 	} else if (0 != strcmp(tok, expected)) {
462 	    g_fprintf(stderr, "while parsing valid '%s', strquotedstr returned '%s' while "
463 		      "'%s' was expected\n",
464 		      *iter, tok, expected);
465 	    success = FALSE;
466 	}
467 
468 	amfree(copy);
469     }
470 
471     return success;
472 }
473 
474 /*
475  * Main driver
476  */
477 
478 int
main(int argc,char ** argv)479 main(int argc, char **argv)
480 {
481     static TestUtilsTest tests[] = {
482 	TU_TEST(test_round_trip, 90),
483 	TU_TEST(test_unquote_string, 90),
484 	TU_TEST(test_split_quoted_strings, 90),
485 	TU_TEST(test_split_quoted_strings_edge, 90),
486 	TU_TEST(test_strquotedstr_skipping, 90),
487 	TU_TEST(test_strquotedstr_edge_invalid, 90),
488 	TU_TEST(test_strquotedstr_edge_valid, 90),
489 	TU_END()
490     };
491 
492     return testutils_run_tests(argc, argv, tests);
493 }
494