Blob


1 /*
2 * Copyright (c) 2020 Omar Polo <op@omarpolo.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include <err.h>
18 #include <stdio.h>
19 #include <string.h>
21 #include "../gmid.h"
23 #define TEST(iri, fail, exp, descr) \
24 if (!run_test(iri, fail, exp)) { \
25 fprintf(stderr, "%s:%d: error: %s\n", \
26 __FILE__, __LINE__, descr); \
27 exit(1); \
28 }
30 #define IRI(schema, host, port, path, query, frag) \
31 ((struct iri){(char*)schema, (char*)host, (char*)port, \
32 0, (char*)path, (char*)query, \
33 (char*)frag})
35 #define DIFF(wanted, got, field) \
36 if (wanted->field == NULL || got->field == NULL || \
37 strcmp(wanted->field, got->field)) { \
38 fprintf(stderr, #field ":\n\tgot: %s\n\twanted: %s\n", \
39 got->field, wanted->field); \
40 return 0; \
41 }
43 #define PASS 0
44 #define FAIL 1
46 int diff_iri(struct iri*, struct iri*);
47 int run_test(const char*, int, struct iri);
49 int
50 diff_iri(struct iri *p, struct iri *exp)
51 {
52 DIFF(p, exp, schema);
53 DIFF(p, exp, host);
54 DIFF(p, exp, port);
55 DIFF(p, exp, path);
56 DIFF(p, exp, query);
57 DIFF(p, exp, fragment);
58 return 1;
59 }
61 int
62 run_test(const char *iri, int should_fail, struct iri expected)
63 {
64 int failed, ok = 1;
65 char *iri_copy;
66 struct iri parsed;
67 const char *error;
69 if ((iri_copy = strdup(iri)) == NULL)
70 err(1, "strdup");
72 fprintf(stderr, "=> %s\n", iri);
73 failed = !parse_iri(iri_copy, &parsed, &error);
75 if (failed && should_fail)
76 goto done;
78 if (error != NULL)
79 fprintf(stderr, "> %s\n", error);
81 ok = !failed && !should_fail;
82 if (ok)
83 ok = diff_iri(&expected, &parsed);
85 done:
86 free(iri_copy);
87 return ok;
88 }
90 int
91 main(void)
92 {
93 struct iri empty = IRI("", "", "", "", "", "");
95 TEST("http://omarpolo.com",
96 PASS,
97 IRI("http", "omarpolo.com", "", "", "", ""),
98 "can parse iri with empty path");
100 /* schema */
101 TEST("omarpolo.com", FAIL, empty, "FAIL when the schema is missing");
102 TEST("gemini:/omarpolo.com", FAIL, empty, "FAIL with invalid marker");
103 TEST("gemini//omarpolo.com", FAIL, empty, "FAIL with invalid marker");
104 TEST("h!!p://omarpolo.com", FAIL, empty, "FAIL with invalid schema");
105 TEST("GEMINI://omarpolo.com",
106 PASS,
107 IRI("gemini", "omarpolo.com", "", "", "", ""),
108 "Schemas are case insensitive.");
110 /* authority */
111 TEST("gemini://omarpolo.com",
112 PASS,
113 IRI("gemini", "omarpolo.com", "", "", "", ""),
114 "can parse authority with empty path");
115 TEST("gemini://omarpolo.com/",
116 PASS,
117 IRI("gemini", "omarpolo.com", "", "", "", ""),
118 "can parse authority with empty path (alt)")
119 TEST("gemini://omarpolo.com:1965",
120 PASS,
121 IRI("gemini", "omarpolo.com", "1965", "", "", ""),
122 "can parse with port and empty path");
123 TEST("gemini://omarpolo.com:1965/",
124 PASS,
125 IRI("gemini", "omarpolo.com", "1965", "", "", ""),
126 "can parse with port and empty path")
127 TEST("gemini://omarpolo.com:196s",
128 FAIL,
129 empty,
130 "FAIL with invalid port number");
131 TEST("gemini://OmArPoLo.CoM",
132 PASS,
133 IRI("gemini", "omarpolo.com", "", "", "", ""),
134 "host is case-insensitive");
136 /* path */
137 TEST("gemini://omarpolo.com/foo/bar/baz",
138 PASS,
139 IRI("gemini", "omarpolo.com", "", "foo/bar/baz", "", ""),
140 "parse simple paths");
141 TEST("gemini://omarpolo.com/foo//bar///baz",
142 PASS,
143 IRI("gemini", "omarpolo.com", "", "foo/bar/baz", "", ""),
144 "parse paths with multiple slashes");
145 TEST("gemini://omarpolo.com/foo/./bar/./././baz",
146 PASS,
147 IRI("gemini", "omarpolo.com", "", "foo/bar/baz", "", ""),
148 "parse paths with . elements");
149 TEST("gemini://omarpolo.com/foo/bar/../bar/baz",
150 PASS,
151 IRI("gemini", "omarpolo.com", "", "foo/bar/baz", "", ""),
152 "parse paths with .. elements");
153 TEST("gemini://omarpolo.com/foo/../foo/bar/../bar/baz/../baz",
154 PASS,
155 IRI("gemini", "omarpolo.com", "", "foo/bar/baz", "", ""),
156 "parse paths with multiple .. elements");
157 TEST("gemini://omarpolo.com/foo/..",
158 PASS,
159 IRI("gemini", "omarpolo.com", "", "", "", ""),
160 "parse paths with a trailing ..");
161 TEST("gemini://omarpolo.com/foo/../",
162 PASS,
163 IRI("gemini", "omarpolo.com", "", "", "", ""),
164 "parse paths with a trailing ..");
165 TEST("gemini://omarpolo.com/foo/../..",
166 FAIL,
167 empty,
168 "reject paths that would escape the root");
169 TEST("gemini://omarpolo.com/foo/../../",
170 FAIL,
171 empty,
172 "reject paths that would escape the root")
173 TEST("gemini://omarpolo.com/foo/../foo/../././/bar/baz/.././.././/",
174 PASS,
175 IRI("gemini", "omarpolo.com", "", "", "", ""),
176 "parse path with lots of cleaning available");
177 TEST("gemini://omarpolo.com//foo",
178 PASS,
179 IRI("gemini", "omarpolo.com", "", "foo", "", ""),
180 "Trim initial slashes");
181 TEST("gemini://omarpolo.com/////foo",
182 PASS,
183 IRI("gemini", "omarpolo.com", "", "foo", "", ""),
184 "Trim initial slashes (pt. 2)");
186 /* query */
187 TEST("foo://example.com/foo/?gne",
188 PASS,
189 IRI("foo", "example.com", "", "foo/", "gne", ""),
190 "parse query strings");
191 TEST("foo://example.com/foo/?gne&foo",
192 PASS,
193 IRI("foo", "example.com", "", "foo/", "gne&foo", ""),
194 "parse query strings");
195 TEST("foo://example.com/foo/?gne%2F",
196 PASS,
197 IRI("foo", "example.com", "", "foo/", "gne/", ""),
198 "parse query strings");
200 /* fragment */
201 TEST("foo://bar.co/#foo",
202 PASS,
203 IRI("foo", "bar.co", "", "", "", "foo"),
204 "can recognize fragments");
206 /* percent encoding */
207 TEST("foo://bar.com/caf%C3%A8.gmi",
208 PASS,
209 IRI("foo", "bar.com", "", "cafè.gmi", "", ""),
210 "can decode");
211 TEST("foo://bar.com/caff%C3%A8%20macchiato.gmi",
212 PASS,
213 IRI("foo", "bar.com", "", "caffè macchiato.gmi", "", ""),
214 "can decode");
215 TEST("foo://bar.com/caff%C3%A8+macchiato.gmi",
216 PASS,
217 IRI("foo", "bar.com", "", "caffè+macchiato.gmi", "", ""),
218 "can decode");
219 TEST("foo://bar.com/foo%2F..%2F..",
220 FAIL,
221 empty,
222 "conversion and checking are done in the correct order");
223 TEST("foo://bar.com/foo%00?baz",
224 FAIL,
225 empty,
226 "rejects %00");
228 /* IRI */
229 TEST("foo://bar.com/cafè.gmi",
230 PASS,
231 IRI("foo", "bar.com", "", "cafè.gmi", "" , ""),
232 "decode IRI (with a 2-byte utf8 seq)");
233 TEST("foo://bar.com/世界.gmi",
234 PASS,
235 IRI("foo", "bar.com", "", "世界.gmi", "" , ""),
236 "decode IRI");
237 TEST("foo://bar.com/😼.gmi",
238 PASS,
239 IRI("foo", "bar.com", "", "😼.gmi", "" , ""),
240 "decode IRI (with a 3-byte utf8 seq)");
241 TEST("foo://bar.com/😼/𤭢.gmi",
242 PASS,
243 IRI("foo", "bar.com", "", "😼/𤭢.gmi", "" , ""),
244 "decode IRI (with a 3-byte and a 4-byte utf8 seq)");
245 TEST("foo://bar.com/世界/\xC0\x80",
246 FAIL,
247 empty,
248 "reject invalid sequence (overlong NUL)");
250 return 0;