Skip to content

Commit ff6b8b2

Browse files
committed
always add an authority component to URIs
Even if it is technically valid to have a URI like `file:/foo/bar`, we cannot guarantee that the client is able to parse these kinds of Uris. To be safe, we will just always add an authority component with an empty host.
1 parent f31f240 commit ff6b8b2

File tree

4 files changed

+54
-47
lines changed

4 files changed

+54
-47
lines changed

src/Uri.zig

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const Uri = @This();
77
/// - consistent percent encoding (implementations may escape differently)
88
/// - consistent casing of the Windows drive letter
99
/// - consistent path seperator on Windows (convert '\\' to '/')
10+
/// - always add an authority component even if unnecessary
1011
raw: []const u8,
1112

1213
pub fn parse(allocator: std.mem.Allocator, text: []const u8) (std.Uri.ParseError || error{OutOfMemory})!Uri {
@@ -22,9 +23,8 @@ fn parseWithOs(
2223

2324
const capacity = capacity: {
2425
var capacity: usize = 0;
25-
capacity += uri.scheme.len + ":".len;
26+
capacity += uri.scheme.len + ":".len + "//".len;
2627
if (uri.host) |host| {
27-
capacity += "//".len;
2828
if (uri.user) |user| {
2929
capacity += user.percent_encoded.len;
3030
if (uri.password) |password| {
@@ -47,9 +47,8 @@ fn parseWithOs(
4747
errdefer result.deinit(allocator);
4848

4949
result.appendSliceAssumeCapacity(uri.scheme);
50-
result.appendAssumeCapacity(':');
50+
result.appendSliceAssumeCapacity("://");
5151
if (uri.host) |host| {
52-
result.appendSliceAssumeCapacity("//");
5352
if (uri.user) |user| {
5453
normalizePercentEncoded(&result, user.percent_encoded, &isUserChar);
5554
if (uri.password) |password| {
@@ -92,15 +91,15 @@ fn parseWithOs(
9291
}
9392

9493
test "parse (posix)" {
95-
const uri: Uri = try .parseWithOs(std.testing.allocator, "file:/foo/main.zig", false);
94+
const uri: Uri = try .parseWithOs(std.testing.allocator, "file:///foo/main.zig", false);
9695
defer uri.deinit(std.testing.allocator);
97-
try std.testing.expectEqualStrings("file:/foo/main.zig", uri.raw);
96+
try std.testing.expectEqualStrings("file:///foo/main.zig", uri.raw);
9897
}
9998

10099
test "parse (windows)" {
101-
const uri: Uri = try .parseWithOs(std.testing.allocator, "file:/C:/foo\\main.zig", true);
100+
const uri: Uri = try .parseWithOs(std.testing.allocator, "file:///C:/foo\\main.zig", true);
102101
defer uri.deinit(std.testing.allocator);
103-
try std.testing.expectEqualStrings("file:/c:/foo/main.zig", uri.raw);
102+
try std.testing.expectEqualStrings("file:///c:/foo/main.zig", uri.raw);
104103
}
105104

106105
test "parse - UNC (windows)" {
@@ -109,34 +108,40 @@ test "parse - UNC (windows)" {
109108
try std.testing.expectEqualStrings("file://wsl.localhost/foo/main.zig", uri.raw);
110109
}
111110

111+
test "parse - always add authority component (posix)" {
112+
const uri: Uri = try .parseWithOs(std.testing.allocator, "file:/foo/main.zig", false);
113+
defer uri.deinit(std.testing.allocator);
114+
try std.testing.expectEqualStrings("file:///foo/main.zig", uri.raw);
115+
}
116+
112117
test "parse - normalize percent encoding (posix)" {
113-
const uri: Uri = try .parseWithOs(std.testing.allocator, "file:/foo%5cmain%2ezig", false);
118+
const uri: Uri = try .parseWithOs(std.testing.allocator, "file:///foo%5cmain%2ezig", false);
114119
defer uri.deinit(std.testing.allocator);
115-
try std.testing.expectEqualStrings("file:/foo%5Cmain.zig", uri.raw);
120+
try std.testing.expectEqualStrings("file:///foo%5Cmain.zig", uri.raw);
116121
}
117122

118123
test "parse - convert percent encoded '\\' to '/' (windows)" {
119-
const uri: Uri = try .parseWithOs(std.testing.allocator, "file:/C:%5Cmain.zig", true);
124+
const uri: Uri = try .parseWithOs(std.testing.allocator, "file:///C:%5Cmain.zig", true);
120125
defer uri.deinit(std.testing.allocator);
121-
try std.testing.expectEqualStrings("file:/c:/main.zig", uri.raw);
126+
try std.testing.expectEqualStrings("file:///c:/main.zig", uri.raw);
122127
}
123128

124129
test "parse - preserve percent encoded '\\' (posix)" {
125-
const uri: Uri = try .parseWithOs(std.testing.allocator, "file:/foo%5Cmain.zig", false);
130+
const uri: Uri = try .parseWithOs(std.testing.allocator, "file:///foo%5Cmain.zig", false);
126131
defer uri.deinit(std.testing.allocator);
127-
try std.testing.expectEqualStrings("file:/foo%5Cmain.zig", uri.raw);
132+
try std.testing.expectEqualStrings("file:///foo%5Cmain.zig", uri.raw);
128133
}
129134

130135
test "parse - percent encoded drive letter (windows)" {
131-
const uri: Uri = try .parseWithOs(std.testing.allocator, "file:/%43%3a%5Cfoo\\main.zig", true);
136+
const uri: Uri = try .parseWithOs(std.testing.allocator, "file:///%43%3a%5Cfoo\\main.zig", true);
132137
defer uri.deinit(std.testing.allocator);
133-
try std.testing.expectEqualStrings("file:/c:/foo/main.zig", uri.raw);
138+
try std.testing.expectEqualStrings("file:///c:/foo/main.zig", uri.raw);
134139
}
135140

136141
test "parse - windows like path on posix" {
137142
const uri: Uri = try .parseWithOs(std.testing.allocator, "file:///C:%5Cmain.zig", false);
138143
defer uri.deinit(std.testing.allocator);
139-
try std.testing.expectEqualStrings("file:/C:%5Cmain.zig", uri.raw);
144+
try std.testing.expectEqualStrings("file:///C:%5Cmain.zig", uri.raw);
140145
}
141146

142147
pub fn deinit(uri: Uri, allocator: std.mem.Allocator) void {
@@ -181,7 +186,7 @@ fn fromPathWithOs(
181186
path: []const u8,
182187
comptime is_windows: bool,
183188
) error{OutOfMemory}!Uri {
184-
var buf: std.ArrayList(u8) = try .initCapacity(allocator, path.len + 6);
189+
var buf: std.ArrayList(u8) = try .initCapacity(allocator, path.len + "file:///".len);
185190
errdefer buf.deinit(allocator);
186191

187192
buf.appendSliceAssumeCapacity("file:");
@@ -192,7 +197,9 @@ fn fromPathWithOs(
192197
{
193198
// UNC path
194199
} else if (!std.mem.startsWith(u8, path, "/")) {
195-
buf.appendAssumeCapacity('/');
200+
buf.appendSliceAssumeCapacity("///");
201+
} else {
202+
buf.appendSliceAssumeCapacity("//");
196203
}
197204

198205
var value = path;
@@ -226,7 +233,7 @@ test "fromPath (posix)" {
226233
const uri = try fromPathWithOs(std.testing.allocator, "/home/main.zig", false);
227234
defer uri.deinit(std.testing.allocator);
228235

229-
try std.testing.expectEqualStrings("file:/home/main.zig", uri.raw);
236+
try std.testing.expectEqualStrings("file:///home/main.zig", uri.raw);
230237

231238
const reparsed_uri: Uri = try .parseWithOs(std.testing.allocator, uri.raw, false);
232239
defer reparsed_uri.deinit(std.testing.allocator);
@@ -237,7 +244,7 @@ test "fromPath (windows)" {
237244
const uri = try fromPathWithOs(std.testing.allocator, "C:/main.zig", true);
238245
defer uri.deinit(std.testing.allocator);
239246

240-
try std.testing.expectEqualStrings("file:/c:/main.zig", uri.raw);
247+
try std.testing.expectEqualStrings("file:///c:/main.zig", uri.raw);
241248

242249
const reparsed_uri: Uri = try .parseWithOs(std.testing.allocator, uri.raw, true);
243250
defer reparsed_uri.deinit(std.testing.allocator);
@@ -259,7 +266,7 @@ test "fromPath - preserve '\\' (posix)" {
259266
const uri = try fromPathWithOs(std.testing.allocator, "/home\\main.zig", false);
260267
defer uri.deinit(std.testing.allocator);
261268

262-
try std.testing.expectEqualStrings("file:/home%5Cmain.zig", uri.raw);
269+
try std.testing.expectEqualStrings("file:///home%5Cmain.zig", uri.raw);
263270

264271
const reparsed_uri: Uri = try .parseWithOs(std.testing.allocator, uri.raw, false);
265272
defer reparsed_uri.deinit(std.testing.allocator);
@@ -270,7 +277,7 @@ test "fromPath - convert '\\' to '/' (windows)" {
270277
const uri = try fromPathWithOs(std.testing.allocator, "C:\\main.zig", true);
271278
defer uri.deinit(std.testing.allocator);
272279

273-
try std.testing.expectEqualStrings("file:/c:/main.zig", uri.raw);
280+
try std.testing.expectEqualStrings("file:///c:/main.zig", uri.raw);
274281

275282
const reparsed_uri: Uri = try .parseWithOs(std.testing.allocator, uri.raw, true);
276283
defer reparsed_uri.deinit(std.testing.allocator);
@@ -281,7 +288,7 @@ test "fromPath - root directory (posix)" {
281288
const uri = try fromPathWithOs(std.testing.allocator, "/", false);
282289
defer uri.deinit(std.testing.allocator);
283290

284-
try std.testing.expectEqualStrings("file:/", uri.raw);
291+
try std.testing.expectEqualStrings("file:///", uri.raw);
285292

286293
const reparsed_uri: Uri = try .parseWithOs(std.testing.allocator, uri.raw, false);
287294
defer reparsed_uri.deinit(std.testing.allocator);
@@ -292,7 +299,7 @@ test "fromPath - root directory (windows)" {
292299
const uri = try fromPathWithOs(std.testing.allocator, "C:/", true);
293300
defer uri.deinit(std.testing.allocator);
294301

295-
try std.testing.expectEqualStrings("file:/c:/", uri.raw);
302+
try std.testing.expectEqualStrings("file:///c:/", uri.raw);
296303

297304
const reparsed_uri: Uri = try .parseWithOs(std.testing.allocator, uri.raw, true);
298305
defer reparsed_uri.deinit(std.testing.allocator);
@@ -303,7 +310,7 @@ test "fromPath - windows like path on posix" {
303310
const uri = try fromPathWithOs(std.testing.allocator, "/C:\\main.zig", false);
304311
defer uri.deinit(std.testing.allocator);
305312

306-
try std.testing.expectEqualStrings("file:/C:%5Cmain.zig", uri.raw);
313+
try std.testing.expectEqualStrings("file:///C:%5Cmain.zig", uri.raw);
307314

308315
const reparsed_uri: Uri = try .parseWithOs(std.testing.allocator, uri.raw, false);
309316
defer reparsed_uri.deinit(std.testing.allocator);

tests/context.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,15 @@ pub const Context = struct {
8989
mode: std.zig.Ast.Mode = .zig,
9090
}) !zls.Uri {
9191
const fmt = switch (builtin.os.tag) {
92-
.windows => "file:/c:/Untitled-{d}.{t}",
93-
else => "file:/Untitled-{d}.{t}",
92+
.windows => "file:///c:/Untitled-{d}.{t}",
93+
else => "file:///Untitled-{d}.{t}",
9494
};
9595

9696
const arena = self.arena.allocator();
9797
const path = if (options.use_file_scheme)
9898
try std.fmt.allocPrint(arena, fmt, .{ self.file_id, options.mode })
9999
else
100-
try std.fmt.allocPrint(arena, "untitled:/Untitled-{d}.{t}", .{ self.file_id, options.mode });
100+
try std.fmt.allocPrint(arena, "untitled:///Untitled-{d}.{t}", .{ self.file_id, options.mode });
101101
const uri: zls.Uri = try .parse(arena, path);
102102

103103
const params: types.TextDocument.DidOpenParams = .{

tests/lsp_features/diagnostics.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ test "autofix comment" {
143143
.relatedInformation = &.{
144144
.{
145145
.location = .{
146-
.uri = "untitled:/Untitled-0.zig",
146+
.uri = "untitled:///Untitled-0.zig",
147147
.range = .{
148148
.start = .{ .line = 1, .character = 10 },
149149
.end = .{ .line = 1, .character = 13 },

tests/lsp_features/hover.zig

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ test "root struct" {
337337
\\(Untitled-0)
338338
\\```
339339
\\
340-
\\Go to [Untitled-0](untitled:/Untitled-0.zig#L1)
340+
\\Go to [Untitled-0](untitled:///Untitled-0.zig#L1)
341341
);
342342
}
343343

@@ -353,7 +353,7 @@ test "inferred struct init" {
353353
\\(type)
354354
\\```
355355
\\
356-
\\Go to [S](untitled:/Untitled-0.zig#L1)
356+
\\Go to [S](untitled:///Untitled-0.zig#L1)
357357
);
358358
try testHover(
359359
\\const S = struct { foo: u32 };
@@ -367,7 +367,7 @@ test "inferred struct init" {
367367
\\(type)
368368
\\```
369369
\\
370-
\\Go to [S](untitled:/Untitled-0.zig#L1)
370+
\\Go to [S](untitled:///Untitled-0.zig#L1)
371371
);
372372
}
373373

@@ -385,7 +385,7 @@ test "decl literal" {
385385
\\(S)
386386
\\```
387387
\\
388-
\\Go to [S](untitled:/Untitled-0.zig#L1)
388+
\\Go to [S](untitled:///Untitled-0.zig#L1)
389389
);
390390
try testHover(
391391
\\const S = struct {
@@ -410,7 +410,7 @@ test "decl literal function" {
410410
\\(fn () S)
411411
\\```
412412
\\
413-
\\Go to [S](untitled:/Untitled-0.zig#L1)
413+
\\Go to [S](untitled:///Untitled-0.zig#L1)
414414
);
415415

416416
try testHover(
@@ -428,7 +428,7 @@ test "decl literal function" {
428428
\\(fn () !S)
429429
\\```
430430
\\
431-
\\Go to [S](untitled:/Untitled-0.zig#L1)
431+
\\Go to [S](untitled:///Untitled-0.zig#L1)
432432
);
433433
try testHover(
434434
\\const Inner = struct {
@@ -448,7 +448,7 @@ test "decl literal function" {
448448
\\(fn () Inner)
449449
\\```
450450
\\
451-
\\Go to [Inner](untitled:/Untitled-0.zig#L1)
451+
\\Go to [Inner](untitled:///Untitled-0.zig#L1)
452452
);
453453
}
454454

@@ -471,7 +471,7 @@ test "decl literal on generic type" {
471471
\\(Box(u8))
472472
\\```
473473
\\
474-
\\Go to [Box](untitled:/Untitled-0.zig#L1)
474+
\\Go to [Box](untitled:///Untitled-0.zig#L1)
475475
);
476476
}
477477

@@ -495,7 +495,7 @@ test "decl literal on generic type - alias" {
495495
\\(Box(u8))
496496
\\```
497497
\\
498-
\\Go to [Box](untitled:/Untitled-0.zig#L1)
498+
\\Go to [Box](untitled:///Untitled-0.zig#L1)
499499
);
500500
}
501501

@@ -585,7 +585,7 @@ test "enum member" {
585585
\\(Enum)
586586
\\```
587587
\\
588-
\\Go to [Enum](untitled:/Untitled-0.zig#L1)
588+
\\Go to [Enum](untitled:///Untitled-0.zig#L1)
589589
);
590590
}
591591

@@ -607,7 +607,7 @@ test "generic type" {
607607
\\(GenericType(StructType,EnumType))
608608
\\```
609609
\\
610-
\\Go to [GenericType](untitled:/Untitled-0.zig#L3) | [StructType](untitled:/Untitled-0.zig#L1) | [EnumType](untitled:/Untitled-0.zig#L2)
610+
\\Go to [GenericType](untitled:///Untitled-0.zig#L3) | [StructType](untitled:///Untitled-0.zig#L1) | [EnumType](untitled:///Untitled-0.zig#L2)
611611
);
612612
}
613613

@@ -652,7 +652,7 @@ test "enum literal" {
652652
\\(E)
653653
\\```
654654
\\
655-
\\Go to [E](untitled:/Untitled-0.zig#L1)
655+
\\Go to [E](untitled:///Untitled-0.zig#L1)
656656
);
657657
}
658658

@@ -671,7 +671,7 @@ test "function" {
671671
\\(fn (A, B) error{A,B}!C)
672672
\\```
673673
\\
674-
\\Go to [A](untitled:/Untitled-0.zig#L1) | [B](untitled:/Untitled-0.zig#L2) | [C](untitled:/Untitled-0.zig#L3)
674+
\\Go to [A](untitled:///Untitled-0.zig#L1) | [B](untitled:///Untitled-0.zig#L2) | [C](untitled:///Untitled-0.zig#L3)
675675
);
676676
try testHover(
677677
\\const S = struct { a: i32 };
@@ -685,7 +685,7 @@ test "function" {
685685
\\(fn (S, S) error{A,B}!S)
686686
\\```
687687
\\
688-
\\Go to [S](untitled:/Untitled-0.zig#L1)
688+
\\Go to [S](untitled:///Untitled-0.zig#L1)
689689
);
690690
try testHover(
691691
\\const E = error { A, B, C };
@@ -768,7 +768,7 @@ test "optional" {
768768
\\(?S)
769769
\\```
770770
\\
771-
\\Go to [S](untitled:/Untitled-0.zig#L1)
771+
\\Go to [S](untitled:///Untitled-0.zig#L1)
772772
);
773773
}
774774

@@ -785,7 +785,7 @@ test "error union" {
785785
\\(error{A,B}!S)
786786
\\```
787787
\\
788-
\\Go to [S](untitled:/Untitled-0.zig#L1)
788+
\\Go to [S](untitled:///Untitled-0.zig#L1)
789789
);
790790
}
791791

@@ -1130,7 +1130,7 @@ test "escaped identifier in enum literal" {
11301130
\\(E)
11311131
\\```
11321132
\\
1133-
\\Go to [E](untitled:/Untitled-0.zig#L1)
1133+
\\Go to [E](untitled:///Untitled-0.zig#L1)
11341134
, .{
11351135
.highlight = "@\"hello world\"",
11361136
.markup_kind = .markdown,

0 commit comments

Comments
 (0)