@@ -16,15 +16,27 @@ fragment: ?[]const u8,
1616
1717/// Applies URI encoding and replaces all reserved characters with their respective %XX code.
1818pub fn escapeString (allocator : std.mem.Allocator , input : []const u8 ) error {OutOfMemory }! []const u8 {
19+ return escapeStringWithFn (allocator , input , isUnreserved );
20+ }
21+
22+ pub fn escapePath (allocator : std.mem.Allocator , input : []const u8 ) error {OutOfMemory }! []const u8 {
23+ return escapeStringWithFn (allocator , input , isPathChar );
24+ }
25+
26+ pub fn escapeQuery (allocator : std.mem.Allocator , input : []const u8 ) error {OutOfMemory }! []const u8 {
27+ return escapeStringWithFn (allocator , input , isQueryChar );
28+ }
29+
30+ pub fn escapeStringWithFn (allocator : std.mem.Allocator , input : []const u8 , comptime keepUnescaped : fn (c : u8 ) bool ) std.mem.Allocator.Error ! []const u8 {
1931 var outsize : usize = 0 ;
2032 for (input ) | c | {
21- outsize += if (isUnreserved (c )) @as (usize , 1 ) else 3 ;
33+ outsize += if (keepUnescaped (c )) @as (usize , 1 ) else 3 ;
2234 }
2335 var output = try allocator .alloc (u8 , outsize );
2436 var outptr : usize = 0 ;
2537
2638 for (input ) | c | {
27- if (isUnreserved (c )) {
39+ if (keepUnescaped (c )) {
2840 output [outptr ] = c ;
2941 outptr += 1 ;
3042 } else {
@@ -94,13 +106,14 @@ pub fn unescapeString(allocator: std.mem.Allocator, input: []const u8) error{Out
94106
95107pub const ParseError = error { UnexpectedCharacter , InvalidFormat , InvalidPort };
96108
97- /// Parses the URI or returns an error.
109+ /// Parses the URI or returns an error. This function is not compliant, but is required to parse
110+ /// some forms of URIs in the wild. Such as HTTP Location headers.
98111/// The return value will contain unescaped strings pointing into the
99112/// original `text`. Each component that is provided, will be non-`null`.
100- pub fn parse (text : []const u8 ) ParseError ! Uri {
113+ pub fn parseWithoutScheme (text : []const u8 ) ParseError ! Uri {
101114 var reader = SliceReader { .slice = text };
102115 var uri = Uri {
103- .scheme = reader . readWhile ( isSchemeChar ) ,
116+ .scheme = "" ,
104117 .user = null ,
105118 .password = null ,
106119 .host = null ,
@@ -110,14 +123,6 @@ pub fn parse(text: []const u8) ParseError!Uri {
110123 .fragment = null ,
111124 };
112125
113- // after the scheme, a ':' must appear
114- if (reader .get ()) | c | {
115- if (c != ':' )
116- return error .UnexpectedCharacter ;
117- } else {
118- return error .InvalidFormat ;
119- }
120-
121126 if (reader .peekPrefix ("//" )) { // authority part
122127 std .debug .assert (reader .get ().? == '/' );
123128 std .debug .assert (reader .get ().? == '/' );
@@ -179,6 +184,76 @@ pub fn parse(text: []const u8) ParseError!Uri {
179184 return uri ;
180185}
181186
187+ /// Parses the URI or returns an error.
188+ /// The return value will contain unescaped strings pointing into the
189+ /// original `text`. Each component that is provided, will be non-`null`.
190+ pub fn parse (text : []const u8 ) ParseError ! Uri {
191+ var reader = SliceReader { .slice = text };
192+ const scheme = reader .readWhile (isSchemeChar );
193+
194+ // after the scheme, a ':' must appear
195+ if (reader .get ()) | c | {
196+ if (c != ':' )
197+ return error .UnexpectedCharacter ;
198+ } else {
199+ return error .InvalidFormat ;
200+ }
201+
202+ var uri = try parseWithoutScheme (reader .readUntilEof ());
203+ uri .scheme = scheme ;
204+
205+ return uri ;
206+ }
207+
208+ /// Resolves a URI against a base URI, conforming to RFC 3986, Section 5.
209+ /// arena owns any memory allocated by this function.
210+ pub fn resolve (Base : Uri , R : Uri , strict : bool , arena : std.mem.Allocator ) ! Uri {
211+ var T : Uri = undefined ;
212+
213+ if (R .scheme .len > 0 and ! ((! strict ) and (std .mem .eql (u8 , R .scheme , Base .scheme )))) {
214+ T .scheme = R .scheme ;
215+ T .user = R .user ;
216+ T .host = R .host ;
217+ T .port = R .port ;
218+ T .path = try std .fs .path .resolvePosix (arena , &.{ "/" , R .path });
219+ T .query = R .query ;
220+ } else {
221+ if (R .host ) | host | {
222+ T .user = R .user ;
223+ T .host = host ;
224+ T .port = R .port ;
225+ T .path = R .path ;
226+ T .path = try std .fs .path .resolvePosix (arena , &.{ "/" , R .path });
227+ T .query = R .query ;
228+ } else {
229+ if (R .path .len == 0 ) {
230+ T .path = Base .path ;
231+ if (R .query ) | query | {
232+ T .query = query ;
233+ } else {
234+ T .query = Base .query ;
235+ }
236+ } else {
237+ if (R .path [0 ] == '/' ) {
238+ T .path = try std .fs .path .resolvePosix (arena , &.{ "/" , R .path });
239+ } else {
240+ T .path = try std .fs .path .resolvePosix (arena , &.{ "/" , Base .path , R .path });
241+ }
242+ T .query = R .query ;
243+ }
244+
245+ T .user = Base .user ;
246+ T .host = Base .host ;
247+ T .port = Base .port ;
248+ }
249+ T .scheme = Base .scheme ;
250+ }
251+
252+ T .fragment = R .fragment ;
253+
254+ return T ;
255+ }
256+
182257const SliceReader = struct {
183258 const Self = @This ();
184259
@@ -284,6 +359,14 @@ fn isPathSeparator(c: u8) bool {
284359 };
285360}
286361
362+ fn isPathChar (c : u8 ) bool {
363+ return isUnreserved (c ) or isSubLimit (c ) or c == '/' or c == ':' or c == '@' ;
364+ }
365+
366+ fn isQueryChar (c : u8 ) bool {
367+ return isPathChar (c ) or c == '?' ;
368+ }
369+
287370fn isQuerySeparator (c : u8 ) bool {
288371 return switch (c ) {
289372 '#' = > true ,
0 commit comments