aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Buchanan <jonathan.russ.buchanan@gmail.com>2020-08-26 14:49:02 -0400
committerJonathan Buchanan <jonathan.russ.buchanan@gmail.com>2020-08-26 14:49:02 -0400
commit49d74db2e9bd8418f15b560e36e1f27661f65361 (patch)
tree48612892b643b0abbc63d99c8e2bcacfc1d6e396
parente36180a17c8da8d3639f42924ce56c11fec1fddd (diff)
downloadgnunet-49d74db2e9bd8418f15b560e36e1f27661f65361.tar.gz
gnunet-49d74db2e9bd8418f15b560e36e1f27661f65361.zip
use (and "GNUnet-ify") libyuarel as a basepoint for uri parsing
-rw-r--r--src/include/gnunet_uri_lib.h170
-rw-r--r--src/util/.gitignore3
-rw-r--r--src/util/Makefile.am7
-rw-r--r--src/util/test_uri.c837
-rw-r--r--src/util/uri.c344
5 files changed, 1288 insertions, 73 deletions
diff --git a/src/include/gnunet_uri_lib.h b/src/include/gnunet_uri_lib.h
index 48db0ac85..e5f144591 100644
--- a/src/include/gnunet_uri_lib.h
+++ b/src/include/gnunet_uri_lib.h
@@ -1,96 +1,122 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2020 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20/** 1/**
21 * @file include/gnunet_uri_lib.h 2 * Copyright (C) 2016 Jack Engqvist Johansson
22 * @brief generic parser for URIs 3 *
23 * @author Jonathan Buchanan 4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in all
12 * copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
24 */ 21 */
25
26#ifndef GNUNET_URI_LIB_H 22#ifndef GNUNET_URI_LIB_H
27#define GNUNET_URI_LIB_H 23#define GNUNET_URI_LIB_H
28 24
25
29/** 26/**
30 * A Universal Resource Identifier (URI). 27 * The struct where the parsed values will be stored:
28 *
29 * scheme ":" [ "//" ] [ username ":" password "@" ] host [ ":" port ] [ "/" ] [ path ] [ "?" query ]
30 *
31 * Note: to make sure that no strings are copied, the first slash "/" in the
32 * path will be used to null terminate the hostname if no port is supplied.
31 */ 33 */
32struct GNUNET_Uri 34struct GNUNET_uri {
33{ 35 char *scheme; /* scheme, without ":" and "//" */
34 /** 36 char *username; /* username, default: NULL */
35 * The scheme of the uri. 37 char *password; /* password, default: NULL */
36 */ 38 char *host; /* hostname or IP address */
37 char *scheme; 39 int port; /* port, default: 0 */
38 40 char *path; /* path, without leading "/", default: NULL */
39 41 char *query; /* query, default: NULL */
40 /** 42 char *fragment; /* fragment, default: NULL */
41 * The authority of the uri. If not present in the uri, NULL. 43};
42 */
43 char *authority;
44
45
46 /**
47 * The list of path segments in the URI. Note that if the path ends with a
48 * '/', then this array will end with an empty string to indicate the empty
49 * segment following the '/'.
50 */
51 char **path_segments;
52
53
54 /**
55 * The length of @e path_segments.
56 */
57 unsigned int path_segments_count;
58 44
59 45
60 /** 46/* A struct to hold the query string parameter values. */
61 * The query of the uri. If not present in the uri, NULL. 47struct GNUNET_uri_param {
62 */ 48 char *key;
63 char *query; 49 char *val;
50};
64 51
65 52
66 /** 53/**
67 * The fragment of the uri. If not present in the uri, NULL. 54 * Parse a URL to a struct.
68 */ 55 *
69 char *fragment; 56 * The URL string should be in one of the following formats:
70}; 57 *
58 * Absolute URL:
59 * scheme ":" [ "//" ] [ username ":" password "@" ] host [ ":" port ] [ "/" ] [ path ] [ "?" query ] [ "#" fragment ]
60 *
61 * Relative URL:
62 * path [ "?" query ] [ "#" fragment ]
63 *
64 * The following parts will be parsed to the corresponding struct member.
65 *
66 * *url: a pointer to the struct where to store the parsed values.
67 * *url_str: a pointer to the url to be parsed (null terminated). The string
68 * will be modified.
69 *
70 * Returns 0 on success, otherwise -1.
71 */
72int
73GNUNET_uri_parse (struct GNUNET_uri *url,
74 char *url_str);
71 75
72 76
73/** 77/**
74 * Parse a URI from a string into an internal representation. 78 * Split a path into several strings.
79 *
80 * No data is copied, the slashed are used as null terminators and then
81 * pointers to each path part will be stored in **parts. Double slashes will be
82 * treated as one.
83 *
84 * *path: the path to split. The string will be modified.
85 * **parts: a pointer to an array of (char *) where to store the result.
86 * max_parts: max number of parts to parse.
75 * 87 *
76 * @param uri string to parse 88 * Returns the number of parsed items. -1 on error.
77 * @param emsg where to store the parser error message (if any)
78 * @return handle to the internal representation of the URI, or NULL on error
79 */ 89 */
80struct GNUNET_Uri * 90int
81GNUNET_uri_parse (const char *uri, 91GNUNET_uri_split_path (char *path,
82 char **emsg); 92 char **parts,
93 int max_parts);
83 94
84 95
85/** 96/**
86 * Free URI. 97 * Parse a query string into a key/value struct.
87 * 98 *
88 * @param uri uri to free 99 * The query string should be a null terminated string of parameters separated by
100 * a delimiter. Each parameter are checked for the equal sign character. If it
101 * appears in the parameter, it will be used as a null terminator and the part
102 * that comes after it will be the value of the parameter.
103 *
104 * No data are copied, the equal sign and delimiters are used as null
105 * terminators and then pointers to each parameter key and value will be stored
106 * in the yuarel_param struct.
107 *
108 * *query: the query string to parse. The string will be modified.
109 * delimiter: the character that separates the key/value pairs from eachother.
110 * *params: an array of (struct yuarel_param) where to store the result.
111 * max_values: max number of parameters to parse.
112 *
113 * Returns the number of parsed items. -1 on error.
89 */ 114 */
90void 115int
91GNUNET_uri_destroy (struct GNUNET_Uri *uri); 116GNUNET_uri_parse_query (char *query,
117 char delimiter,
118 struct GNUNET_uri_param *params,
119 int max_params);
92 120
93 121
94#endif /* GNUNET_URI_LIB_H */ 122#endif /* GNUNET_URI_LIB_H */
95
96/* end of include/gnunet_uri_lib.h */
diff --git a/src/util/.gitignore b/src/util/.gitignore
index 05f187869..8556ee7b8 100644
--- a/src/util/.gitignore
+++ b/src/util/.gitignore
@@ -71,9 +71,10 @@ perf_crypto_hash
71perf_crypto_symmetric 71perf_crypto_symmetric
72perf_crypto_rsa 72perf_crypto_rsa
73perf_crypto_ecc_dlog 73perf_crypto_ecc_dlog
74test_hexcoder 74test_hexcoder
75test_regex 75test_regex
76test_tun 76test_tun
77test_uri
77gnunet-timeout 78gnunet-timeout
78python27_location 79python27_location
79perf_malloc 80perf_malloc
diff --git a/src/util/Makefile.am b/src/util/Makefile.am
index 83b3b9c3d..c5059bbb1 100644
--- a/src/util/Makefile.am
+++ b/src/util/Makefile.am
@@ -96,6 +96,7 @@ libgnunetutil_la_SOURCES = \
96 strings.c \ 96 strings.c \
97 time.c \ 97 time.c \
98 tun.c \ 98 tun.c \
99 uri.c \
99 speedup.c speedup.h \ 100 speedup.c speedup.h \
100 proc_compat.c 101 proc_compat.c
101 102
@@ -302,6 +303,7 @@ check_PROGRAMS = \
302 test_speedup \ 303 test_speedup \
303 test_time \ 304 test_time \
304 test_tun \ 305 test_tun \
306 test_uri \
305 $(BENCHMARKS) \ 307 $(BENCHMARKS) \
306 test_os_start_process \ 308 test_os_start_process \
307 test_common_logging_runtime_loglevels 309 test_common_logging_runtime_loglevels
@@ -573,6 +575,11 @@ test_speedup_SOURCES = \
573test_speedup_LDADD = \ 575test_speedup_LDADD = \
574 libgnunetutil.la 576 libgnunetutil.la
575 577
578test_uri_SOURCES = \
579 test_uri.c
580test_uri_LDADD = \
581 libgnunetutil.la
582
576perf_crypto_hash_SOURCES = \ 583perf_crypto_hash_SOURCES = \
577 perf_crypto_hash.c 584 perf_crypto_hash.c
578perf_crypto_hash_LDADD = \ 585perf_crypto_hash_LDADD = \
diff --git a/src/util/test_uri.c b/src/util/test_uri.c
new file mode 100644
index 000000000..f9e38383a
--- /dev/null
+++ b/src/util/test_uri.c
@@ -0,0 +1,837 @@
1#include <stdlib.h>
2#include <stdio.h>
3#include <string.h>
4#include "gnunet_uri_lib.h"
5
6#define KNRM "\x1B[0m"
7#define KBLU "\x1B[34m"
8#define KGRN "\x1B[32m"
9#define KERR "\x1B[5;31;50m"
10
11/* macro to print out the header for a new group of tests */
12#define mu_group(name) printf ("%s • %s%s\n", KBLU, name, KNRM)
13
14/* macro for asserting a statement */
15#define mu_assert(message, test) do { \
16 if (!(test)) { \
17 printf ("\t%s× %s%s\n", KERR, message, KNRM); \
18 return message; \
19 } \
20 printf ("\t%s• %s%s\n", KGRN, message, KNRM); \
21 } while (0)
22
23/* macro for asserting a statement without printing it unless it is a failure */
24#define mu_silent_assert(message, test) do { \
25 if (!(test)) { \
26 printf ("\t\t%s× %s%s\n", KERR, message, KNRM); \
27 return message; \
28 } \
29 } while (0)
30
31/* run a test function and return result */
32#define mu_run_test(test) do { \
33 char *message = test (); tests_run++; \
34 if (message) { return message; } \
35 } while (0)
36
37
38int tests_run;
39
40static int
41strcmp_wrap (const char *str,
42 const char *str2)
43{
44 if (NULL == str && NULL == str2) {
45 return 0;
46 }
47 if (NULL == str) {
48 return 1;
49 }
50 if (NULL == str2) {
51 return -1;
52 }
53
54 return strcmp (str, str2);
55}
56
57#define assert_struct(as_url, \
58 as_scheme, \
59 as_user, \
60 as_pass, \
61 as_host, \
62 as_port, \
63 as_path, \
64 as_query, \
65 as_fragment) \
66 mu_silent_assert ("should set the scheme attribute correctly", \
67 0 == strcmp_wrap (as_url.scheme, as_scheme)); \
68 mu_silent_assert ("should set the username attribute correctly", \
69 0 == strcmp_wrap (as_url.username, as_user)); \
70 mu_silent_assert ("should set the password attribute correctly", \
71 0 == strcmp_wrap (as_url.password, as_pass)); \
72 mu_silent_assert ("should set the host attribute correctly", \
73 0 == strcmp_wrap (as_url.host, as_host)); \
74 mu_silent_assert ("should set the port attribute correctly", \
75 as_port == as_url.port); \
76 mu_silent_assert ("should set the path attribute correctly", \
77 0 == strcmp_wrap (as_url.path, as_path)); \
78 mu_silent_assert ("should set the query attribute correctly", \
79 0 == strcmp_wrap (as_url.query, as_query)); \
80 mu_silent_assert ("should set the fragment attribute correctly", \
81 0 == strcmp_wrap (as_url.fragment, as_fragment));
82
83static char *
84test_parse_http_url_ok (void)
85{
86 int rc;
87 struct GNUNET_uri url;
88 char *url_string;
89
90 /* Minimal URL */
91 url_string = strdup ("http://example.com");
92 rc = GNUNET_uri_parse (&url,
93 url_string);
94 mu_assert ("minimal HTTP URL", -1 != rc);
95 assert_struct (url,
96 "http",
97 NULL,
98 NULL,
99 "example.com",
100 0,
101 NULL,
102 NULL,
103 NULL);
104 free (url_string);
105
106 /* With path (/) */
107 url_string = strdup ("http://example.com/");
108 rc = GNUNET_uri_parse (&url,
109 url_string);
110 mu_assert ("with path ('/')", -1 != rc);
111 assert_struct (url,
112 "http",
113 NULL,
114 NULL,
115 "example.com",
116 0,
117 "",
118 NULL,
119 NULL);
120 free (url_string);
121
122 /* With path */
123 url_string = strdup ("http://example.com/path");
124 rc = GNUNET_uri_parse (&url,
125 url_string);
126 mu_assert ("with path ('/path')", -1 != rc);
127 assert_struct (url,
128 "http",
129 NULL,
130 NULL,
131 "example.com",
132 0,
133 "path",
134 NULL,
135 NULL);
136 free (url_string);
137
138 /* With port */
139 url_string = strdup ("http://example.com:80");
140 rc = GNUNET_uri_parse (&url,
141 url_string);
142 mu_assert ("with port only",
143 -1 != rc);
144 assert_struct (url,
145 "http",
146 NULL,
147 NULL,
148 "example.com",
149 80,
150 NULL,
151 NULL,
152 NULL);
153 free (url_string);
154
155 /* With query */
156 url_string = strdup ("http://example.com?query=only");
157 rc = GNUNET_uri_parse (&url,
158 url_string);
159 mu_assert ("with query only",
160 -1 != rc);
161 assert_struct (url,
162 "http",
163 NULL,
164 NULL,
165 "example.com",
166 0,
167 NULL,
168 "query=only",
169 NULL);
170 free (url_string);
171
172 /* With fragment */
173 url_string = strdup ("http://example.com#frag=f1");
174 rc = GNUNET_uri_parse (&url,
175 url_string);
176 mu_assert ("with fragment only",
177 -1 != rc);
178 assert_struct (url,
179 "http",
180 NULL,
181 NULL,
182 "example.com",
183 0,
184 NULL,
185 NULL,
186 "frag=f1");
187 free (url_string);
188
189 /* With credentials */
190 url_string = strdup ("http://u:p@example.com");
191 rc = GNUNET_uri_parse (&url,
192 url_string);
193 mu_assert ("with credentials only",
194 -1 != rc);
195 assert_struct (url,
196 "http",
197 "u",
198 "p",
199 "example.com",
200 0,
201 NULL,
202 NULL,
203 NULL);
204 free (url_string);
205
206 /* With port and path */
207 url_string = strdup ("http://example.com:8080/port/and/path");
208 rc = GNUNET_uri_parse (&url,
209 url_string);
210 mu_assert ("with port and path",
211 -1 != rc);
212 assert_struct (url,
213 "http",
214 NULL,
215 NULL,
216 "example.com",
217 8080,
218 "port/and/path",
219 NULL,
220 NULL);
221 free (url_string);
222
223 /* With port and query */
224 url_string = strdup ("http://example.com:8080?query=portANDquery");
225 rc = GNUNET_uri_parse (&url,
226 url_string);
227 mu_assert ("with port and query",
228 -1 != rc);
229 assert_struct (url,
230 "http",
231 NULL,
232 NULL,
233 "example.com",
234 8080,
235 NULL,
236 "query=portANDquery",
237 NULL);
238 free (url_string);
239
240 /* With port and fragment */
241 url_string = strdup ("http://example.com:8080#f1");
242 rc = GNUNET_uri_parse (&url,
243 url_string);
244 mu_assert ("with port and fragment",
245 -1 != rc);
246 assert_struct (url,
247 "http",
248 NULL,
249 NULL,
250 "example.com",
251 8080,
252 NULL,
253 NULL,
254 "f1");
255 free (url_string);
256
257 /* With port and credentials */
258 url_string = strdup ("http://u:p@example.com:8080");
259 rc = GNUNET_uri_parse (&url,
260 url_string);
261 mu_assert ("with port and credentials",
262 -1 != rc);
263 assert_struct (url,
264 "http",
265 "u",
266 "p",
267 "example.com",
268 8080,
269 NULL,
270 NULL,
271 NULL);
272 free (url_string);
273
274 /* With path and query */
275 url_string = strdup ("http://example.com/path/and/query?q=yes");
276 rc = GNUNET_uri_parse (&url,
277 url_string);
278 mu_assert ("with path and query",
279 -1 != rc);
280 assert_struct (url,
281 "http",
282 NULL,
283 NULL,
284 "example.com",
285 0,
286 "path/and/query",
287 "q=yes",
288 NULL);
289 free (url_string);
290
291 /* With path and fragment */
292 url_string = strdup ("http://example.com/path/and#fragment");
293 rc = GNUNET_uri_parse (&url,
294 url_string);
295 mu_assert ("with path and fragment",
296 -1 != rc);
297 assert_struct (url,
298 "http",
299 NULL,
300 NULL,
301 "example.com",
302 0,
303 "path/and",
304 NULL,
305 "fragment");
306 free (url_string);
307
308 /* With query and fragment */
309 url_string = strdup ("http://example.com?q=yes#f1");
310 rc = GNUNET_uri_parse (&url,
311 url_string);
312 mu_assert ("with query and fragment",
313 -1 != rc);
314 assert_struct (url,
315 "http",
316 NULL,
317 NULL,
318 "example.com",
319 0,
320 NULL,
321 "q=yes",
322 "f1");
323 free (url_string);
324
325 /* With query and credentials */
326 url_string = strdup ("http://u:p@example.com?q=yes");
327 rc = GNUNET_uri_parse (&url,
328 url_string);
329 mu_assert ("with query and credentials",
330 -1 != rc);
331 assert_struct (url,
332 "http",
333 "u",
334 "p",
335 "example.com",
336 0,
337 NULL,
338 "q=yes",
339 NULL);
340 free (url_string);
341
342 /* With empty credentials */
343 url_string = strdup ("http://:@example.com");
344 rc = GNUNET_uri_parse (&url,
345 url_string);
346 mu_assert ("with empty credentials",
347 -1 != rc);
348 assert_struct (url,
349 "http",
350 "",
351 "",
352 "example.com",
353 0,
354 NULL,
355 NULL,
356 NULL);
357 free (url_string);
358
359 /* With empty credentials and port */
360 url_string = strdup ("http://:@example.com:89");
361 rc = GNUNET_uri_parse (&url,
362 url_string);
363 mu_assert ("with empty credentials and port",
364 -1 != rc);
365 assert_struct (url,
366 "http",
367 "",
368 "",
369 "example.com",
370 89,
371 NULL,
372 NULL,
373 NULL);
374 free (url_string);
375
376 /* Full URL */
377 url_string = strdup ("https://jack:password@localhost:8989/path/to/test?query=yes&q=jack#fragment1");
378 rc = GNUNET_uri_parse (&url,
379 url_string);
380 mu_assert ("with port, path and query",
381 -1 != rc);
382 assert_struct (url,
383 "https",
384 "jack",
385 "password",
386 "localhost",
387 8989,
388 "path/to/test",
389 "query=yes&q=jack",
390 "fragment1");
391 free (url_string);
392
393 return NULL;
394}
395
396static char *
397test_parse_http_rel_url_ok (void)
398{
399 int rc;
400 struct GNUNET_uri url;
401 char *url_string;
402
403 /* Minimal relative URL */
404 url_string = strdup ("/");
405 rc = GNUNET_uri_parse (&url,
406 url_string);
407 mu_assert ("minimal relative URL",
408 -1 != rc);
409 assert_struct (url,
410 NULL,
411 NULL,
412 NULL,
413 NULL,
414 0,
415 "",
416 NULL,
417 NULL);
418 free (url_string);
419
420 /* Path only */
421 url_string = strdup ("/hejsan");
422 rc = GNUNET_uri_parse (&url,
423 url_string);
424 mu_assert ("path only",
425 -1 != rc);
426 assert_struct (url,
427 NULL,
428 NULL,
429 NULL,
430 NULL,
431 0,
432 "hejsan",
433 NULL,
434 NULL);
435 free (url_string);
436
437 /* Path and query */
438 url_string = strdup ("/hejsan?q=yes");
439 rc = GNUNET_uri_parse (&url,
440 url_string);
441 mu_assert ("path only",
442 -1 != rc);
443 assert_struct (url,
444 NULL,
445 NULL,
446 NULL,
447 NULL,
448 0,
449 "hejsan",
450 "q=yes",
451 NULL);
452 free (url_string);
453
454 /* Path and fragment */
455 url_string = strdup ("/hejsan#fragment");
456 rc = GNUNET_uri_parse (&url,
457 url_string);
458 mu_assert ("path and fragment",
459 -1 != rc);
460 assert_struct (url,
461 NULL,
462 NULL,
463 NULL,
464 NULL,
465 0,
466 "hejsan",
467 NULL,
468 "fragment");
469 free (url_string);
470
471 /* Path, query and fragment */
472 url_string = strdup ("/?q=yes&q2=no#fragment");
473 rc = GNUNET_uri_parse (&url,
474 url_string);
475 mu_assert ("path, query and fragment",
476 -1 != rc);
477 assert_struct (url,
478 NULL,
479 NULL,
480 NULL,
481 NULL,
482 0,
483 "",
484 "q=yes&q2=no",
485 "fragment");
486 free (url_string);
487
488 return NULL;
489}
490
491static char *
492test_parse_url_fail (void)
493{
494 int rc;
495 struct GNUNET_uri url;
496 char *url_string;
497
498 /* Empty */
499 url_string = strdup ("");
500 rc = GNUNET_uri_parse (&url,
501 url_string);
502 mu_assert ("empty string should return -1",
503 -1 == rc);
504 free (url_string);
505
506 /* Scheme only */
507 url_string = strdup ("rtsp://");
508 rc = GNUNET_uri_parse (&url,
509 url_string);
510 mu_assert ("scheme only should return -1",
511 -1 == rc);
512 free (url_string);
513
514 /* Hostname only */
515 url_string = strdup ("hostname");
516 rc = GNUNET_uri_parse (&url,
517 url_string);
518 mu_assert ("hostname only should return -1",
519 -1 == rc);
520 free (url_string);
521
522 /* Query only */
523 url_string = strdup ("?query=only");
524 rc = GNUNET_uri_parse (&url,
525 url_string);
526 mu_assert ("query only should return -1",
527 -1 == rc);
528 free (url_string);
529
530 /* Missing scheme */
531 url_string = strdup ("://");
532 rc = GNUNET_uri_parse (&url,
533 url_string);
534 mu_assert ("missing scheme should return -1",
535 -1 == rc);
536 free (url_string);
537
538 /* Missing hostname */
539 url_string = strdup ("rtsp://:8910/path");
540 rc = GNUNET_uri_parse (&url,
541 url_string);
542 mu_assert ("missing hostname should return -1",
543 -1 == rc);
544 free (url_string);
545
546 /* Missing credentials */
547 url_string = strdup ("rtsp://@hostname:8910/path");
548 rc = GNUNET_uri_parse (&url,
549 url_string);
550 mu_assert ("missing credentials should return -1",
551 -1 == rc);
552 free (url_string);
553
554 return NULL;
555}
556
557static char *
558test_split_path_ok (void)
559{
560 int rc;
561 char *path;
562 char *parts[10];
563
564 /* Simple path */
565 path = strdup ("/this/is/a/path");
566 rc = GNUNET_uri_split_path (path,
567 parts,
568 10);
569 mu_assert ("should be able to parse a regular path",
570 4 == rc);
571 mu_silent_assert ("first part should be 'this'",
572 0 == strcmp ("this", parts[0]));
573 mu_silent_assert ("second part should be 'is'",
574 0 == strcmp ("is", parts[1]));
575 mu_silent_assert ("third part should be 'a'",
576 0 == strcmp ("a", parts[2]));
577 mu_silent_assert ("fourth part should be 'path'",
578 0 == strcmp ("path", parts[3]));
579 free (path);
580
581 /* Relative path */
582 path = strdup ("this/is/a/path");
583 rc = GNUNET_uri_split_path (path,
584 parts,
585 10);
586 mu_assert ("should be able to parse a relative path",
587 4 == rc);
588 mu_silent_assert ("first part should be 'this'",
589 0 == strcmp ("this", parts[0]));
590 mu_silent_assert ("second part should be 'is'",
591 0 == strcmp ("is", parts[1]));
592 mu_silent_assert ("third part should be 'a'",
593 0 == strcmp ("a", parts[2]));
594 mu_silent_assert ("fourth part should be 'path'",
595 0 == strcmp ("path", parts[3]));
596 free (path);
597
598 /* Path with empty parts */
599 path = strdup ("//this//is/a/path/");
600 rc = GNUNET_uri_split_path (path,
601 parts,
602 10);
603 mu_assert ("should treat multiple slashes as one",
604 4 == rc);
605 mu_silent_assert ("first part should be 'this'",
606 0 == strcmp("this", parts[0]));
607 mu_silent_assert ("second part should be 'is'",
608 0 == strcmp("is", parts[1]));
609 mu_silent_assert ("third part should be 'a'",
610 0 == strcmp("a", parts[2]));
611 mu_silent_assert ("fourth part should be 'path'",
612 0 == strcmp("path", parts[3]));
613 free (path);
614
615 /* Just one level */
616 path = strdup("/one_level");
617 rc = GNUNET_uri_split_path(path, parts, 10);
618 mu_assert("should be able to parse a path with one level", 1 == rc);
619 mu_silent_assert("first part should be 'this'", 0 == strcmp("one_level", parts[0]));
620 free(path);
621
622 return NULL;
623}
624
625static char *
626test_parse_query_ok (void)
627{
628 int rc;
629 char *q;
630 struct GNUNET_uri_param params[10];
631
632 /* One param query */
633 q = strdup ("q=yes");
634 rc = GNUNET_uri_parse_query (q,
635 '&',
636 params,
637 10);
638 mu_assert ("single parameter with value",
639 1 == rc);
640 mu_silent_assert ("first param key should be 'q'",
641 0 == strcmp ("q", params[0].key));
642 mu_silent_assert ("first param val should be 'yes'",
643 0 == strcmp ("yes", params[0].val));
644 free (q);
645
646 /* One param query without value */
647 q = strdup ("q");
648 rc = GNUNET_uri_parse_query (q,
649 '&',
650 params,
651 10);
652 mu_assert ("single parameter without value",
653 1 == rc);
654 mu_silent_assert ("first param key should be 'q'",
655 0 == strcmp ("q", params[0].key));
656 mu_silent_assert ("first param val should be NULL",
657 NULL == params[0].val);
658 free (q);
659
660 /* Two param query */
661 q = strdup ("query=yes&a1=hello");
662 rc = GNUNET_uri_parse_query (q,
663 '&',
664 params,
665 10);
666 mu_assert ("multiple params with value",
667 2 == rc);
668 mu_silent_assert ("first param key should be 'query'",
669 0 == strcmp ("query", params[0].key));
670 mu_silent_assert ("first param val should be 'yes'",
671 0 == strcmp ("yes", params[0].val));
672 mu_silent_assert ("second param key should be 'a1'",
673 0 == strcmp ("a1", params[1].key));
674 mu_silent_assert ("second param val should be 'hello'",
675 0 == strcmp ("hello", params[1].val));
676 free (q);
677
678 /* Two param query, one without value */
679 q = strdup ("query=yes&forceHttps");
680 rc = GNUNET_uri_parse_query (q,
681 '&',
682 params,
683 10);
684 mu_assert ("multiple params one without value",
685 2 == rc);
686 mu_silent_assert ("first param key should be 'query'",
687 0 == strcmp ("query", params[0].key));
688 mu_silent_assert ("first param val should be 'yes'",
689 0 == strcmp ("yes", params[0].val));
690 mu_silent_assert ("second param key should be 'forceHttps'",
691 0 == strcmp ("forceHttps", params[1].key));
692 mu_silent_assert ("second param val should be NULL",
693 NULL == params[1].val);
694 free (q);
695
696 /* Three param query, all without value */
697 q = strdup ("query&forceHttps&log");
698 rc = GNUNET_uri_parse_query (q,
699 '&',
700 params,
701 10);
702 mu_assert ("multiple params all without value",
703 3 == rc);
704 mu_silent_assert ("first param key should be 'query'",
705 0 == strcmp ("query", params[0].key));
706 mu_silent_assert ("first param val should be NULL",
707 NULL == params[0].val);
708 mu_silent_assert ("second param key should be 'forceHttps'",
709 0 == strcmp ("forceHttps", params[1].key));
710 mu_silent_assert ("second param val should be NULL",
711 NULL == params[1].val);
712 mu_silent_assert ("third param key should be 'log'",
713 0 == strcmp ("log", params[2].key));
714 mu_silent_assert ("third param val should be NULL",
715 NULL == params[2].val);
716 free (q);
717
718 /* Param with empty value */
719 q = strdup ("param=&query=no");
720 rc = GNUNET_uri_parse_query (q,
721 '&',
722 params,
723 10);
724 mu_assert ("param with empty value",
725 2 == rc);
726 mu_silent_assert ("first param key should be 'param'",
727 0 == strcmp ("param", params[0].key));
728 mu_silent_assert ("first param val should be ''",
729 0 == strcmp ("", params[0].val));
730 mu_silent_assert ("second param key should be 'query'",
731 0 == strcmp ("query", params[1].key));
732 mu_silent_assert ("second param val should be 'no'",
733 0 == strcmp ("no", params[1].val));
734 free (q);
735
736 /* Double delimiter */
737 q = strdup ("param=jack&&query=no");
738 rc = GNUNET_uri_parse_query (q,
739 '&',
740 params,
741 10);
742 mu_assert ("double delimiter",
743 3 == rc);
744 mu_silent_assert ("first param key should be 'param'",
745 0 == strcmp ("param", params[0].key));
746 mu_silent_assert ("first param val should be 'jack'",
747 0 == strcmp ("jack", params[0].val));
748 mu_silent_assert ("second param key should be ''",
749 0 == strcmp ("", params[1].key));
750 mu_silent_assert ("second param val should be NULL",
751 NULL == params[1].val);
752 mu_silent_assert ("third param key should be 'query'",
753 0 == strcmp ("query", params[2].key));
754 mu_silent_assert ("third param val should be 'no'",
755 0 == strcmp ("no", params[2].val));
756 free (q);
757
758 /* Delimiter in beginning */
759 q = strdup ("&param=jack&query=no");
760 rc = GNUNET_uri_parse_query (q,
761 '&',
762 params,
763 10);
764 mu_assert ("delimiter in beginning",
765 3 == rc);
766 mu_silent_assert ("first param key should be ''",
767 0 == strcmp ("", params[0].key));
768 mu_silent_assert ("first param val should be NULL",
769 NULL == params[0].val);
770 mu_silent_assert ("second param key should be 'param'",
771 0 == strcmp ("param", params[1].key));
772 mu_silent_assert ("second param val should be 'jack'",
773 0 == strcmp ("jack", params[1].val));
774 mu_silent_assert ("third param key should be 'query'",
775 0 == strcmp ("query", params[2].key));
776 mu_silent_assert ("third param val should be 'no'",
777 0 == strcmp ("no", params[2].val));
778 free (q);
779
780 /* Delimiter at the end */
781 q = strdup ("param=jack&query=no&");
782 rc = GNUNET_uri_parse_query (q,
783 '&',
784 params,
785 10);
786 mu_assert ("delimiter at the end",
787 3 == rc);
788 mu_silent_assert ("first param key should be 'param'",
789 0 == strcmp ("param", params[0].key));
790 mu_silent_assert ("first param val should be 'jack'",
791 0 == strcmp ("jack", params[0].val));
792 mu_silent_assert ("second param key should be 'query'",
793 0 == strcmp ("query", params[1].key));
794 mu_silent_assert ("second param val should be 'no'",
795 0 == strcmp ("no", params[1].val));
796 mu_silent_assert ("third param key should be ''",
797 0 == strcmp ("", params[2].key));
798 mu_silent_assert ("third param val should be NULL",
799 NULL == params[2].val);
800 free (q);
801
802 return NULL;
803}
804
805static char *
806all_tests (void)
807{
808 mu_group ("GNUNET_uri_parse () with an HTTP URL");
809 mu_run_test (test_parse_http_url_ok);
810
811 mu_group ("GNUNET_uri_parse () with an relative URL");
812 mu_run_test (test_parse_http_rel_url_ok);
813
814 mu_group ("GNUNET_uri_parse () with faulty values");
815 mu_run_test (test_parse_url_fail);
816
817 mu_group ("GNUNET_uri_split_path ()");
818 mu_run_test (test_split_path_ok);
819
820 mu_group ("GNUNET_uri_parse_query ()");
821 mu_run_test (test_parse_query_ok);
822
823 return NULL;
824}
825
826int
827main (void)
828{
829 char *result;
830
831 result = all_tests ();
832 if (result != NULL) {
833 exit (EXIT_FAILURE);
834 }
835
836 exit (EXIT_SUCCESS);
837}
diff --git a/src/util/uri.c b/src/util/uri.c
new file mode 100644
index 000000000..ba5c0f716
--- /dev/null
+++ b/src/util/uri.c
@@ -0,0 +1,344 @@
1/**
2 * Copyright (C) 2016,2017 Jack Engqvist Johansson
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in all
12 * copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22#include <stdlib.h>
23#include <stdio.h>
24#include <string.h>
25#include "gnunet_uri_lib.h"
26
27
28/**
29 * Parse a non null terminated string into an integer.
30 *
31 * str: the string containing the number.
32 * len: Number of characters to parse.
33 */
34static inline int
35natoi (const char *str,
36 size_t len)
37{
38 int i, r = 0;
39 for (i = 0; i < len; i++) {
40 r *= 10;
41 r += str[i] - '0';
42 }
43
44 return r;
45}
46
47
48/**
49 * Check if a URL is relative (no scheme and hostname).
50 *
51 * url: the string containing the URL to check.
52 *
53 * Returns 1 if relative, otherwise 0.
54 */
55static inline int
56is_relative (const char *url)
57{
58 return (*url == '/') ? 1 : 0;
59}
60
61
62/**
63 * Parse the scheme of a URL by inserting a null terminator after the scheme.
64 *
65 * str: the string containing the URL to parse. Will be modified.
66 *
67 * Returns a pointer to the hostname on success, otherwise NULL.
68 */
69static inline char *
70parse_scheme (char *str)
71{
72 char *s;
73
74 /* If not found or first in string, return error */
75 s = strchr (str, ':');
76 if (s == NULL || s == str) {
77 return NULL;
78 }
79
80 /* If not followed by two slashes, return error */
81 if (s[1] == '\0' || s[1] != '/' || s[2] == '\0' || s[2] != '/') {
82 return NULL;
83 }
84
85 *s = '\0'; // Replace ':' with NULL
86
87 return s + 3;
88}
89
90
91/**
92 * Find a character in a string, replace it with '\0' and return the next
93 * character in the string.
94 *
95 * str: the string to search in.
96 * find: the character to search for.
97 *
98 * Returns a pointer to the character after the one to search for. If not
99 * found, NULL is returned.
100 */
101static inline char *
102find_and_terminate (char *str,
103 char find)
104{
105 str = strchr(str, find);
106 if (NULL == str) {
107 return NULL;
108 }
109
110 *str = '\0';
111 return str + 1;
112}
113
114
115/* Yes, the following functions could be implemented as preprocessor macros
116 instead of inline functions, but I think that this approach will be more
117 clean in this case. */
118static inline char *
119find_fragment (char *str)
120{
121 return find_and_terminate (str, '#');
122}
123
124
125static inline char *
126find_query (char *str)
127{
128 return find_and_terminate (str, '?');
129}
130
131
132static inline char *
133find_path (char *str)
134{
135 return find_and_terminate (str, '/');
136}
137
138
139/**
140 * Parse a URL to a struct.
141 *
142 * The URL string should be in one of the following formats:
143 *
144 * Absolute URL:
145 * scheme ":" [ "//" ] [ username ":" password "@" ] host [ ":" port ] [ "/" ] [ path ] [ "?" query ] [ "#" fragment ]
146 *
147 * Relative URL:
148 * path [ "?" query ] [ "#" fragment ]
149 *
150 * The following parts will be parsed to the corresponding struct member.
151 *
152 * *url: a pointer to the struct where to store the parsed values.
153 * *url_str: a pointer to the url to be parsed (null terminated). The string
154 * will be modified.
155 *
156 * Returns 0 on success, otherwise -1.
157 */
158int
159GNUNET_uri_parse (struct GNUNET_uri *url,
160 char *u)
161{
162 if (NULL == url || NULL == u) {
163 return -1;
164 }
165
166 memset(url, 0, sizeof (struct GNUNET_uri));
167
168 /* (Fragment) */
169 url->fragment = find_fragment (u);
170
171 /* (Query) */
172 url->query = find_query (u);
173
174 /* Relative URL? Parse scheme and hostname */
175 if (!is_relative (u)) {
176 /* Scheme */
177 url->scheme = u;
178 u = parse_scheme (u);
179 if (u == NULL) {
180 return -1;
181 }
182
183 /* Host */
184 if ('\0' == *u) {
185 return -1;
186 }
187 url->host = u;
188
189 /* (Path) */
190 url->path = find_path (u);
191
192 /* (Credentials) */
193 u = strchr (url->host, '@');
194 if (NULL != u) {
195 /* Missing credentials? */
196 if (u == url->host) {
197 return -1;
198 }
199
200 url->username = url->host;
201 url->host = u + 1;
202 *u = '\0';
203
204 u = strchr (url->username, ':');
205 if (NULL == u) {
206 return -1;
207 }
208
209 url->password = u + 1;
210 *u = '\0';
211 }
212
213 /* Missing hostname? */
214 if ('\0' == *url->host) {
215 return -1;
216 }
217
218 /* (Port) */
219 u = strchr (url->host, ':');
220 if (NULL != u && (NULL == url->path || u < url->path)) {
221 *(u++) = '\0';
222 if ('\0' == *u) {
223 return -1;
224 }
225
226 if (url->path) {
227 url->port = natoi (u, url->path - u - 1);
228 } else {
229 url->port = atoi (u);
230 }
231 }
232
233 /* Missing hostname? */
234 if ('\0' == *url->host) {
235 return -1;
236 }
237 } else {
238 /* (Path) */
239 url->path = find_path (u);
240 }
241
242 return 0;
243}
244
245
246/**
247 * Split a path into several strings.
248 *
249 * No data is copied, the slashed are used as null terminators and then
250 * pointers to each path part will be stored in **parts. Double slashes will be
251 * treated as one.
252 *
253 * *path: the path to split. The string will be modified.
254 * **parts: a pointer to an array of (char *) where to store the result.
255 * max_parts: max number of parts to parse.
256 *
257 * Returns the number of parsed items. -1 on error.
258 */
259int
260GNUNET_uri_split_path (char *path,
261 char **parts,
262 int max_parts)
263{
264 int i = 0;
265
266 if (NULL == path || '\0' == *path) {
267 return -1;
268 }
269
270 do {
271 /* Forward to after slashes */
272 while (*path == '/') path++;
273
274 if ('\0' == *path) {
275 break;
276 }
277
278 parts[i++] = path;
279
280 path = strchr (path, '/');
281 if (NULL == path) {
282 break;
283 }
284
285 *(path++) = '\0';
286 } while (i < max_parts);
287
288 return i;
289}
290
291
292/**
293 * Parse a query string into a key/value struct.
294 *
295 * The query string should be a null terminated string of parameters separated by
296 * a delimiter. Each parameter are checked for the equal sign character. If it
297 * appears in the parameter, it will be used as a null terminator and the part
298 * that comes after it will be the value of the parameter.
299 *
300 * No data are copied, the equal sign and delimiters are used as null
301 * terminators and then pointers to each parameter key and value will be stored
302 * in the yuarel_param struct.
303 *
304 * *query: the query string to parse. The string will be modified.
305 * delimiter: the character that separates the key/value pairs from eachother.
306 * *params: an array of (struct yuarel_param) where to store the result.
307 * max_values: max number of parameters to parse.
308 *
309 * Returns the number of parsed items. -1 on error.
310 */
311int
312GNUNET_uri_parse_query (char *query,
313 char delimiter,
314 struct GNUNET_uri_param *params,
315 int max_params)
316{
317 int i = 0;
318
319 if (NULL == query || '\0' == *query) {
320 return -1;
321 }
322
323 params[i++].key = query;
324 while (i < max_params && NULL != (query = strchr (query, delimiter))) {
325 *query = '\0';
326 params[i].key = ++query;
327 params[i].val = NULL;
328
329 /* Go back and split previous param */
330 if (i > 0) {
331 if ((params[i - 1].val = strchr (params[i - 1].key, '=')) != NULL) {
332 *(params[i - 1].val)++ = '\0';
333 }
334 }
335 i++;
336 }
337
338 /* Go back and split last param */
339 if ((params[i - 1].val = strchr (params[i - 1].key, '=')) != NULL) {
340 *(params[i - 1].val)++ = '\0';
341 }
342
343 return i;
344}