simplefiles

A lightweight C CGI web application for managing files.
git clone https://scm.kuandu.systems/git-raw/simplefiles.git
Log | Files | Refs | README | LICENSE

url.c (5003B)


      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <string.h>
      4 
      5 #include "url.h"
      6 
      7 struct encode_test_case {
      8 	const char *name;
      9 	const char *input;
     10 	const char *expected;
     11 };
     12 
     13 struct decode_test_case {
     14 	const char *name;
     15 	const char *input;
     16 	const char *expected;
     17 	int expected_result;
     18 };
     19 
     20 static const struct encode_test_case encode_tests[] = {
     21 	/* Unreserved characters should not be encoded. */
     22 	{ "simple filename", "file.txt", "file.txt" },
     23 	{ "alphanumeric", "file123", "file123" },
     24 	{ "hyphen", "my-file.txt", "my-file.txt" },
     25 	{ "underscore", "my_file.txt", "my_file.txt" },
     26 	{ "dot", "file.txt", "file.txt" },
     27 	{ "tilde", "~file.txt", "~file.txt" },
     28 
     29 	/* Characters requiring encoding. */
     30 	{ "space", "my file.txt", "my%20file.txt" },
     31 	{ "ampersand", "&file.txt", "%26file.txt" },
     32 	{ "plus", "a+b.txt", "a%2Bb.txt" },
     33 	{ "percent", "100%.txt", "100%25.txt" },
     34 	{ "question mark", "file?.txt", "file%3F.txt" },
     35 	{ "equals", "file=name.txt", "file%3Dname.txt" },
     36 	{ "slash", "foo/bar.txt", "foo%2Fbar.txt" },
     37 	{ "backslash", "foo\\bar.txt", "foo%5Cbar.txt" },
     38 	{ "hash", "file#1.txt", "file%231.txt" },
     39 	{ "quote", "file\".txt", "file%22.txt" },
     40 
     41 	/* Empty string. */
     42 	{ "empty", "", "" },
     43 };
     44 
     45 static const struct decode_test_case decode_tests[] = {
     46 	/* Plain strings. */
     47 	{ "simple filename", "file.txt", "file.txt", 0 },
     48 	{ "alphanumeric", "file123", "file123", 0 },
     49 	{ "hyphen", "my-file.txt", "my-file.txt", 0 },
     50 	{ "underscore", "my_file.txt", "my_file.txt", 0 },
     51 	{ "dot", "file.txt", "file.txt", 0 },
     52 	{ "tilde", "~file.txt", "~file.txt", 0 },
     53 
     54 	/* Encoded characters. */
     55 	{ "space", "my%20file.txt", "my file.txt", 0 },
     56 	{ "plus", "my+file.txt", "my file.txt", 0 },
     57 	{ "ampersand", "%26file.txt", "&file.txt", 0 },
     58 	{ "percent", "100%25.txt", "100%.txt", 0 },
     59 	{ "question mark", "file%3F.txt", "file?.txt", 0 },
     60 	{ "equals", "file%3Dname.txt", "file=name.txt", 0 },
     61 	{ "slash", "foo%2Fbar.txt", "foo/bar.txt", 0 },
     62 	{ "backslash", "foo%5Cbar.txt", "foo\\bar.txt", 0 },
     63 	{ "hash", "file%23.txt", "file#.txt", 0 },
     64 	{ "quote", "file%22.txt", "file\".txt", 0 },
     65 
     66 	/* Hexadecimal case should both work. */
     67 	{ "lowercase hex", "%2f", "/", 0 },
     68 	{ "uppercase hex", "%2F", "/", 0 },
     69 	{ "mixed hex", "%2f%2F", "//", 0 },
     70 
     71 	/* Encoded traversal must decode successfully;
     72 	 * valid_filename() is responsible for rejecting it. */
     73 	{ "encoded dot", "%2e", ".", 0 },
     74 	{ "encoded dot dot", "%2e%2e", "..", 0 },
     75 	{ "encoded traversal", "%2e%2e%2ffile.txt",
     76 	    "../file.txt", 0 },
     77 
     78 	/* Empty string. */
     79 	{ "empty", "", "", 0 },
     80 
     81 	/* Invalid percent encodings. */
     82 	{ "trailing percent", "%", NULL, -1 },
     83 	{ "one hex digit", "%2", NULL, -1 },
     84 	{ "invalid hex", "%ZZ", NULL, -1 },
     85 	{ "invalid high digit", "%G0", NULL, -1 },
     86 	{ "invalid low digit", "%0G", NULL, -1 },
     87 	{ "incomplete encoding", "file%2", NULL, -1 },
     88 
     89 	/* Encoded NUL must be rejected. */
     90 	{ "encoded NUL", "file%00.txt", NULL, -1 },
     91 };
     92 
     93 static int
     94 test_encode(void)
     95 {
     96 	size_t i;
     97 	int failures = 0;
     98 
     99 	for (i = 0;
    100 	    i < sizeof(encode_tests) / sizeof(encode_tests[0]);
    101 	    i++) {
    102 		char *got;
    103 
    104 		got = url_encode(encode_tests[i].input);
    105 
    106 		if (got == NULL) {
    107 			fprintf(stderr,
    108 			    "FAIL: %s: url_encode() returned NULL\n",
    109 			    encode_tests[i].name);
    110 			failures++;
    111 			continue;
    112 		}
    113 
    114 		if (strcmp(got, encode_tests[i].expected) != 0) {
    115 			fprintf(stderr,
    116 			    "FAIL: %s: url_encode(%s) returned %s, "
    117 			    "expected %s\n",
    118 			    encode_tests[i].name,
    119 			    encode_tests[i].input,
    120 			    got,
    121 			    encode_tests[i].expected);
    122 			failures++;
    123 		} else {
    124 			printf("ok: encode %s\n",
    125 			    encode_tests[i].name);
    126 		}
    127 
    128 		free(got);
    129 	}
    130 
    131 	return failures;
    132 }
    133 
    134 static int
    135 test_decode(void)
    136 {
    137 	size_t i;
    138 	int failures = 0;
    139 
    140 	for (i = 0;
    141 	    i < sizeof(decode_tests) / sizeof(decode_tests[0]);
    142 	    i++) {
    143 		char *input;
    144 		int got;
    145 
    146 		input = malloc(strlen(decode_tests[i].input) + 1);
    147 		if (input == NULL) {
    148 			fprintf(stderr,
    149 			    "FAIL: %s: malloc failed\n",
    150 			    decode_tests[i].name);
    151 			failures++;
    152 			continue;
    153 		}
    154 
    155 		strcpy(input, decode_tests[i].input);
    156 
    157 		got = url_decode(input);
    158 
    159 		if (got != decode_tests[i].expected_result) {
    160 			fprintf(stderr,
    161 			    "FAIL: %s: url_decode() returned %d, "
    162 			    "expected %d\n",
    163 			    decode_tests[i].name,
    164 			    got,
    165 			    decode_tests[i].expected_result);
    166 			failures++;
    167 			free(input);
    168 			continue;
    169 		}
    170 
    171 		if (got == 0 &&
    172 		    strcmp(input, decode_tests[i].expected) != 0) {
    173 			fprintf(stderr,
    174 			    "FAIL: %s: url_decode(%s) returned %s, "
    175 			    "expected %s\n",
    176 			    decode_tests[i].name,
    177 			    decode_tests[i].input,
    178 			    input,
    179 			    decode_tests[i].expected);
    180 			failures++;
    181 		} else {
    182 			printf("ok: decode %s\n",
    183 			    decode_tests[i].name);
    184 		}
    185 
    186 		free(input);
    187 	}
    188 
    189 	return failures;
    190 }
    191 
    192 int
    193 main(void)
    194 {
    195 	int failures;
    196 
    197 	failures = 0;
    198 
    199 	failures += test_encode();
    200 	failures += test_decode();
    201 
    202 	if (failures != 0) {
    203 		fprintf(stderr,
    204 		    "%d test(s) failed\n",
    205 		    failures);
    206 		return EXIT_FAILURE;
    207 	}
    208 
    209 	printf("all tests passed\n");
    210 	return EXIT_SUCCESS;
    211 }
    212