Skip to content

Commit 57b480f

Browse files
authored
Fix various URI fragment issues (#39)
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent e47e588 commit 57b480f

3 files changed

Lines changed: 258 additions & 3 deletions

File tree

schemas/ietf/uri/url.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"allOf": [
1919
{
2020
"$comment": "Valid authority structure - host can be empty",
21-
"pattern": "^[^:]+://(?:[^@/]*@)?(?:\\[[^\\]]+\\]|[^:/]*)(?::[0-9]*)?(?:/.*)?$"
21+
"pattern": "^[^:]+://(?:[^@/]*@)?(?:\\[[^\\]]+\\]|[^:/]*)(?::[0-9]*)?(?:/.*|[?#].*)?$"
2222
}
2323
],
2424
"anyOf": [
@@ -35,14 +35,14 @@
3535
"not": {
3636
"pattern": "^[^:]+://(?:[^@]*@)?(?:(?:25[6-9]|2[6-9][0-9]|[3-9][0-9]{2}|[1-9][0-9]{3,})[.]|[.](?:25[6-9]|2[6-9][0-9]|[3-9][0-9]{2}|[1-9][0-9]{3,})[.]|[.](?:25[6-9]|2[6-9][0-9]|[3-9][0-9]{2}|[1-9][0-9]{3,})[.]|[.](?:25[6-9]|2[6-9][0-9]|[3-9][0-9]{2}|[1-9][0-9]{3,})(?:[:/]|$))"
3737
},
38-
"pattern": "^[^:]+://(?:[^@]*@)?[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+(?:[:/]|$)"
38+
"pattern": "^[^:]+://(?:[^@]*@)?[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+(?:[:/?#]|$)"
3939
},
4040
{
4141
"$comment": "Domain or reg-name (not IPv4)",
4242
"not": {
4343
"pattern": "^[^:]+://(?:[^@]*@)?[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+(?:[:/]|$)"
4444
},
45-
"pattern": "^[^:]+://(?:[^@]*@)?[A-Za-z0-9.-]+(?:[:/]|$)"
45+
"pattern": "^[^:]+://(?:[^@]*@)?[A-Za-z0-9.-]+(?:[:/?#]|$)"
4646
}
4747
],
4848
"pattern": "^[A-Za-z][A-Za-z0-9+\\-.]*://"

test/ietf/uri/uri-reference.test.json

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,161 @@
556556
"description": "Fragment with hash in value",
557557
"data": "#fragment#with#hashes",
558558
"valid": true
559+
},
560+
{
561+
"description": "URL with fragment without path",
562+
"data": "http://example.com#fragment",
563+
"valid": true
564+
},
565+
{
566+
"description": "URL with query without path",
567+
"data": "http://example.com?query=value",
568+
"valid": true
569+
},
570+
{
571+
"description": "URL with query and fragment without path",
572+
"data": "http://example.com?query=value#fragment",
573+
"valid": true
574+
},
575+
{
576+
"description": "URL with empty fragment without path",
577+
"data": "http://example.com#",
578+
"valid": true
579+
},
580+
{
581+
"description": "URL with empty query without path",
582+
"data": "http://example.com?",
583+
"valid": true
584+
},
585+
{
586+
"description": "HTTPS with fragment without path",
587+
"data": "https://secure.example.com#section",
588+
"valid": true
589+
},
590+
{
591+
"description": "HTTPS with query without path",
592+
"data": "https://secure.example.com?token=abc123",
593+
"valid": true
594+
},
595+
{
596+
"description": "FTP with fragment without path",
597+
"data": "ftp://ftp.example.com#readme",
598+
"valid": true
599+
},
600+
{
601+
"description": "FTP with query without path",
602+
"data": "ftp://ftp.example.com?mode=binary",
603+
"valid": true
604+
},
605+
{
606+
"description": "Network path with query without path",
607+
"data": "//example.com?search=test",
608+
"valid": true
609+
},
610+
{
611+
"description": "Network path with fragment without path",
612+
"data": "//example.com#top",
613+
"valid": true
614+
},
615+
{
616+
"description": "Network path with query and fragment without path",
617+
"data": "//example.com?q=1#section",
618+
"valid": true
619+
},
620+
{
621+
"description": "IPv4 URL with fragment without path",
622+
"data": "http://192.168.1.1#section",
623+
"valid": true
624+
},
625+
{
626+
"description": "IPv4 URL with query without path",
627+
"data": "http://192.168.1.1?param=value",
628+
"valid": true
629+
},
630+
{
631+
"description": "IPv6 URL with fragment without path",
632+
"data": "http://[::1]#anchor",
633+
"valid": true
634+
},
635+
{
636+
"description": "IPv6 URL with query without path",
637+
"data": "http://[::1]?key=val",
638+
"valid": true
639+
},
640+
{
641+
"description": "URL with port and fragment without path",
642+
"data": "http://example.com:8080#section",
643+
"valid": true
644+
},
645+
{
646+
"description": "URL with port and query without path",
647+
"data": "http://example.com:8080?search=term",
648+
"valid": true
649+
},
650+
{
651+
"description": "URL with userinfo and fragment without path",
652+
"data": "http://user@example.com#top",
653+
"valid": true
654+
},
655+
{
656+
"description": "URL with userinfo and query without path",
657+
"data": "http://user@example.com?q=test",
658+
"valid": true
659+
},
660+
{
661+
"description": "URL with full userinfo and query without path",
662+
"data": "http://user:pass@example.com?param=value",
663+
"valid": true
664+
},
665+
{
666+
"description": "URL with full userinfo and fragment without path",
667+
"data": "http://user:pass@example.com#anchor",
668+
"valid": true
669+
},
670+
{
671+
"description": "Complex URL with all authority components and query without path",
672+
"data": "http://user:pass@example.com:8080?key=value",
673+
"valid": true
674+
},
675+
{
676+
"description": "Complex URL with all authority components and fragment without path",
677+
"data": "http://user:pass@example.com:8080#section",
678+
"valid": true
679+
},
680+
{
681+
"description": "URL with multiple query parameters without path",
682+
"data": "http://example.com?a=1&b=2&c=3",
683+
"valid": true
684+
},
685+
{
686+
"description": "URL with percent-encoded query without path",
687+
"data": "http://example.com?key=value%20here",
688+
"valid": true
689+
},
690+
{
691+
"description": "URL with complex query encoding without path",
692+
"data": "http://example.com?a=b&c=%2Fd%2Fe",
693+
"valid": true
694+
},
695+
{
696+
"description": "URL with fragment containing special characters without path",
697+
"data": "http://example.com#section-1.2",
698+
"valid": true
699+
},
700+
{
701+
"description": "URL with empty query and non-empty fragment without path",
702+
"data": "http://example.com?#fragment",
703+
"valid": true
704+
},
705+
{
706+
"description": "Custom scheme with fragment without path",
707+
"data": "custom://example.com#section",
708+
"valid": true
709+
},
710+
{
711+
"description": "Custom scheme with query without path",
712+
"data": "custom://example.com?param=value",
713+
"valid": true
559714
}
560715
]
561716
}

test/ietf/uri/url.test.json

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,106 @@
216216
"description": "URI with empty authority and path",
217217
"data": "scheme:///path",
218218
"valid": true
219+
},
220+
{
221+
"description": "Fragment without path",
222+
"data": "http://example.com#fragment",
223+
"valid": true
224+
},
225+
{
226+
"description": "Query without path",
227+
"data": "http://example.com?query=value",
228+
"valid": true
229+
},
230+
{
231+
"description": "Query and fragment without path",
232+
"data": "http://example.com?query=value#fragment",
233+
"valid": true
234+
},
235+
{
236+
"description": "Empty fragment without path",
237+
"data": "http://example.com#",
238+
"valid": true
239+
},
240+
{
241+
"description": "Empty query without path",
242+
"data": "http://example.com?",
243+
"valid": true
244+
},
245+
{
246+
"description": "Multiple query parameters without path",
247+
"data": "http://example.com?a=1&b=2&c=3",
248+
"valid": true
249+
},
250+
{
251+
"description": "Query with percent-encoding without path",
252+
"data": "http://example.com?key=value%20here",
253+
"valid": true
254+
},
255+
{
256+
"description": "Complex query without path",
257+
"data": "http://example.com?a=b&c=%2Fd%2Fe",
258+
"valid": true
259+
},
260+
{
261+
"description": "Fragment with special characters without path",
262+
"data": "http://example.com#section-1.2",
263+
"valid": true
264+
},
265+
{
266+
"description": "IPv4 with fragment without path",
267+
"data": "http://192.168.1.1#anchor",
268+
"valid": true
269+
},
270+
{
271+
"description": "IPv4 with query without path",
272+
"data": "http://192.168.1.1?param=value",
273+
"valid": true
274+
},
275+
{
276+
"description": "IPv6 with fragment without path",
277+
"data": "http://[2001:db8::1]#section",
278+
"valid": true
279+
},
280+
{
281+
"description": "IPv6 with query without path",
282+
"data": "http://[2001:db8::1]?key=val",
283+
"valid": true
284+
},
285+
{
286+
"description": "Port with fragment without path",
287+
"data": "http://example.com:8080#top",
288+
"valid": true
289+
},
290+
{
291+
"description": "Port with query without path",
292+
"data": "http://example.com:8080?search=term",
293+
"valid": true
294+
},
295+
{
296+
"description": "Userinfo with fragment without path",
297+
"data": "http://user@example.com#section",
298+
"valid": true
299+
},
300+
{
301+
"description": "Userinfo with query without path",
302+
"data": "http://user@example.com?q=test",
303+
"valid": true
304+
},
305+
{
306+
"description": "Complex authority with query without path",
307+
"data": "http://user:pass@example.com:8080?param=value",
308+
"valid": true
309+
},
310+
{
311+
"description": "Complex authority with fragment without path",
312+
"data": "http://user:pass@example.com:8080#anchor",
313+
"valid": true
314+
},
315+
{
316+
"description": "Complex authority with query and fragment without path",
317+
"data": "http://user:pass@example.com:8080?q=1#top",
318+
"valid": true
219319
}
220320
]
221321
}

0 commit comments

Comments
 (0)