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