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){schema, host, port, 0, path, query, frag})
33 #define DIFF(wanted, got, field) \
34 if (wanted->field == NULL || got->field == NULL || \
35 strcmp(wanted->field, got->field)) { \
36 fprintf(stderr, #field ":\n\tgot: %s\n\twanted: %s\n", \
37 got->field, wanted->field); \
38 return 0; \
39 }
41 #define PASS 0
42 #define FAIL 1
44 int
45 diff_iri(struct iri *p, struct iri *exp)
46 {
47 DIFF(p, exp, schema);
48 DIFF(p, exp, host);
49 DIFF(p, exp, port);
50 DIFF(p, exp, path);
51 DIFF(p, exp, query);
52 DIFF(p, exp, fragment);
53 return 1;
54 }
56 int
57 run_test(const char *iri, int should_fail, struct iri expected)
58 {
59 int failed, ok = 1;
60 char *iri_copy;
61 struct iri parsed;
62 const char *error;
64 if ((iri_copy = strdup(iri)) == NULL)
65 err(1, "strdup");
67 fprintf(stderr, "=> %s\n", iri);
68 failed = !parse_iri(iri_copy, &parsed, &error);
70 if (failed && should_fail)
71 goto done;
73 if (error != NULL)
74 fprintf(stderr, "> %s\n", error);
76 ok = !failed && !should_fail;
77 if (ok)
78 ok = diff_iri(&expected, &parsed);
80 done:
81 free(iri_copy);
82 return ok;
83 }
85 int
86 main(void)
87 {
88 struct iri empty = {"", "", "", PASS, "", "", ""};
90 TEST("http://omarpolo.com",
91 PASS,
92 IRI("http", "omarpolo.com", "", "", "", ""),
93 "can parse iri with empty path");
95 /* schema */
96 TEST("omarpolo.com", FAIL, empty, "FAIL when the schema is missing");
97 TEST("gemini:/omarpolo.com", FAIL, empty, "FAIL with invalid marker");
98 TEST("gemini//omarpolo.com", FAIL, empty, "FAIL with invalid marker");
99 TEST("h!!p://omarpolo.com", FAIL, empty, "FAIL with invalid schema");
100 TEST("GEMINI://omarpolo.com",
101 PASS,
102 IRI("gemini", "omarpolo.com", "", "", "", ""),
103 "Schemas are case insensitive.");
105 /* authority */
106 TEST("gemini://omarpolo.com",
107 PASS,
108 IRI("gemini", "omarpolo.com", "", "", "", ""),
109 "can parse authority with empty path");
110 TEST("gemini://omarpolo.com/",
111 PASS,
112 IRI("gemini", "omarpolo.com", "", "", "", ""),
113 "can parse authority with empty path (alt)")
114 TEST("gemini://omarpolo.com:1965",
115 PASS,
116 IRI("gemini", "omarpolo.com", "1965", "", "", ""),
117 "can parse with port and empty path");
118 TEST("gemini://omarpolo.com:1965/",
119 PASS,
120 IRI("gemini", "omarpolo.com", "1965", "", "", ""),
121 "can parse with port and empty path")
122 TEST("gemini://omarpolo.com:196s",
123 FAIL,
124 empty,
125 "FAIL with invalid port number");
126 TEST("gemini://OmArPoLo.CoM",
127 PASS,
128 IRI("gemini", "omarpolo.com", "", "", "", ""),
129 "host is case-insensitive");
131 /* path */
132 TEST("gemini://omarpolo.com/foo/bar/baz",
133 PASS,
134 IRI("gemini", "omarpolo.com", "", "foo/bar/baz", "", ""),
135 "parse simple paths");
136 TEST("gemini://omarpolo.com/foo//bar///baz",
137 PASS,
138 IRI("gemini", "omarpolo.com", "", "foo/bar/baz", "", ""),
139 "parse paths with multiple slashes");
140 TEST("gemini://omarpolo.com/foo/./bar/./././baz",
141 PASS,
142 IRI("gemini", "omarpolo.com", "", "foo/bar/baz", "", ""),
143 "parse paths with . elements");
144 TEST("gemini://omarpolo.com/foo/bar/../bar/baz",
145 PASS,
146 IRI("gemini", "omarpolo.com", "", "foo/bar/baz", "", ""),
147 "parse paths with .. elements");
148 TEST("gemini://omarpolo.com/foo/../foo/bar/../bar/baz/../baz",
149 PASS,
150 IRI("gemini", "omarpolo.com", "", "foo/bar/baz", "", ""),
151 "parse paths with multiple .. elements");
152 TEST("gemini://omarpolo.com/foo/..",
153 PASS,
154 IRI("gemini", "omarpolo.com", "", "", "", ""),
155 "parse paths with a trailing ..");
156 TEST("gemini://omarpolo.com/foo/../",
157 PASS,
158 IRI("gemini", "omarpolo.com", "", "", "", ""),
159 "parse paths with a trailing ..");
160 TEST("gemini://omarpolo.com/foo/../..",
161 FAIL,
162 empty,
163 "reject paths that would escape the root");
164 TEST("gemini://omarpolo.com/foo/../../",
165 FAIL,
166 empty,
167 "reject paths that would escape the root")
168 TEST("gemini://omarpolo.com/foo/../foo/../././/bar/baz/.././.././/",
169 PASS,
170 IRI("gemini", "omarpolo.com", "", "", "", ""),
171 "parse path with lots of cleaning available");
173 /* query */
174 TEST("foo://example.com/foo/?gne",
175 PASS,
176 IRI("foo", "example.com", "", "foo/", "gne", ""),
177 "parse query strings");
178 TEST("foo://example.com/foo/?gne&foo",
179 PASS,
180 IRI("foo", "example.com", "", "foo/", "gne&foo", ""),
181 "parse query strings");
182 TEST("foo://example.com/foo/?gne%2F",
183 PASS,
184 IRI("foo", "example.com", "", "foo/", "gne/", ""),
185 "parse query strings");
187 /* fragment */
188 TEST("foo://bar.co/#foo",
189 PASS,
190 IRI("foo", "bar.co", "", "", "", "foo"),
191 "can recognize fragments");
193 /* percent encoding */
194 TEST("foo://bar.com/caf%C3%A8.gmi",
195 PASS,
196 IRI("foo", "bar.com", "", "cafè.gmi", "", ""),
197 "can decode");
198 TEST("foo://bar.com/caff%C3%A8%20macchiato.gmi",
199 PASS,
200 IRI("foo", "bar.com", "", "caffè macchiato.gmi", "", ""),
201 "can decode");
202 TEST("foo://bar.com/caff%C3%A8+macchiato.gmi",
203 PASS,
204 IRI("foo", "bar.com", "", "caffè+macchiato.gmi", "", ""),
205 "can decode");
206 TEST("foo://bar.com/foo%2F..%2F..",
207 FAIL,
208 empty,
209 "conversion and checking are done in the correct order");
210 TEST("foo://bar.com/foo%00?baz",
211 FAIL,
212 empty,
213 "rejects %00");
215 /* IRI */
216 TEST("foo://bar.com/cafè.gmi",
217 PASS,
218 IRI("foo", "bar.com", "", "cafè.gmi", "" , ""),
219 "decode IRI (with a 2-byte utf8 seq)");
220 TEST("foo://bar.com/世界.gmi",
221 PASS,
222 IRI("foo", "bar.com", "", "世界.gmi", "" , ""),
223 "decode IRI");
224 TEST("foo://bar.com/😼.gmi",
225 PASS,
226 IRI("foo", "bar.com", "", "😼.gmi", "" , ""),
227 "decode IRI (with a 3-byte utf8 seq)");
228 TEST("foo://bar.com/😼/𤭢.gmi",
229 PASS,
230 IRI("foo", "bar.com", "", "😼/𤭢.gmi", "" , ""),
231 "decode IRI (with a 3-byte and a 4-byte utf8 seq)");
232 TEST("foo://bar.com/世界/\xC0\x80",
233 FAIL,
234 empty,
235 "reject invalid sequence (overlong NUL)");
237 return 0;