141 lines
4.3 KiB
Zig
141 lines
4.3 KiB
Zig
const std = @import("std");
|
|
const mem = std.mem;
|
|
|
|
// http://tools.ietf.org/html/rfc3986#section-2.2
|
|
const reserved_chars = &[_]u8{
|
|
'!', '#', '$', '%', '&', '\'',
|
|
'(', ')', '*', '+', ',', ':',
|
|
';', '=', '?', '@', '[', ']',
|
|
};
|
|
|
|
const reserved_escapes = blk: {
|
|
var escapes: [reserved_chars.len][3]u8 = [_][3]u8{[_]u8{undefined} ** 3} ** reserved_chars.len;
|
|
|
|
for (reserved_chars) |c, i| {
|
|
escapes[i][0] = '%';
|
|
_ = std.fmt.bufPrint(escapes[i][1..], "{X}", .{c}) catch unreachable;
|
|
}
|
|
break :blk &escapes;
|
|
};
|
|
|
|
/// Returns a URI from a path, caller owns the memory allocated with `allocator`
|
|
pub fn fromPath(allocator: *mem.Allocator, path: []const u8) ![]const u8 {
|
|
if (path.len == 0) return "";
|
|
const prefix = if (std.builtin.os.tag == .windows) "file:///" else "file://";
|
|
|
|
var buf = std.ArrayList(u8).init(allocator);
|
|
try buf.appendSlice(prefix);
|
|
|
|
for (path) |char| {
|
|
if (char == std.fs.path.sep) {
|
|
try buf.append('/');
|
|
} else if (mem.indexOfScalar(u8, reserved_chars, char)) |reserved| {
|
|
try buf.appendSlice(&reserved_escapes[reserved]);
|
|
} else {
|
|
try buf.append(char);
|
|
}
|
|
}
|
|
|
|
// On windows, we need to lowercase the drive name.
|
|
if (std.builtin.os.tag == .windows) {
|
|
if (buf.items.len > prefix.len + 1 and
|
|
std.ascii.isAlpha(buf.items[prefix.len]) and
|
|
mem.startsWith(u8, buf.items[prefix.len + 1 ..], "%3A"))
|
|
{
|
|
buf.items[prefix.len] = std.ascii.toLower(buf.items[prefix.len]);
|
|
}
|
|
}
|
|
|
|
return buf.toOwnedSlice();
|
|
}
|
|
|
|
/// Move along `rel` from `base` with a single allocation.
|
|
/// `base` is a URI of a folder, `rel` is a raw relative path.
|
|
pub fn pathRelative(allocator: *mem.Allocator, base: []const u8, rel: []const u8) ![]const u8 {
|
|
const max_size = base.len + rel.len * 3 + 1;
|
|
|
|
var result = try allocator.alloc(u8, max_size);
|
|
errdefer allocator.free(result);
|
|
mem.copy(u8, result, base);
|
|
var result_index: usize = base.len;
|
|
|
|
var it = mem.tokenize(u8, rel, "/");
|
|
while (it.next()) |component| {
|
|
if (mem.eql(u8, component, ".")) {
|
|
continue;
|
|
} else if (mem.eql(u8, component, "..")) {
|
|
while (true) {
|
|
if (result_index == 0)
|
|
return error.UriBadScheme;
|
|
result_index -= 1;
|
|
if (result[result_index] == '/')
|
|
break;
|
|
}
|
|
} else {
|
|
result[result_index] = '/';
|
|
result_index += 1;
|
|
for (component) |char| {
|
|
if (mem.indexOfScalar(u8, reserved_chars, char)) |reserved| {
|
|
const escape = &reserved_escapes[reserved];
|
|
mem.copy(u8, result[result_index..], escape);
|
|
result_index += escape.len;
|
|
} else {
|
|
result[result_index] = char;
|
|
result_index += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return allocator.resize(result, result_index);
|
|
}
|
|
|
|
pub const UriParseError = error{
|
|
UriBadScheme,
|
|
UriBadHexChar,
|
|
UriBadEscape,
|
|
OutOfMemory,
|
|
};
|
|
|
|
// Original code: https://github.com/andersfr/zig-lsp/blob/master/uri.zig
|
|
fn parseHex(c: u8) !u8 {
|
|
return switch (c) {
|
|
'0'...'9' => c - '0',
|
|
'a'...'f' => c - 'a' + 10,
|
|
'A'...'F' => c - 'A' + 10,
|
|
else => return error.UriBadHexChar,
|
|
};
|
|
}
|
|
|
|
/// Caller should free memory
|
|
pub fn parse(allocator: *mem.Allocator, str: []const u8) ![]u8 {
|
|
if (str.len < 7 or !mem.eql(u8, "file://", str[0..7])) return error.UriBadScheme;
|
|
|
|
const uri = try allocator.alloc(u8, str.len - (if (std.fs.path.sep == '\\') 8 else 7));
|
|
errdefer allocator.free(uri);
|
|
|
|
const path = if (std.fs.path.sep == '\\') str[8..] else str[7..];
|
|
|
|
var i: usize = 0;
|
|
var j: usize = 0;
|
|
while (j < path.len) : (i += 1) {
|
|
if (path[j] == '%') {
|
|
if (j + 2 >= path.len) return error.UriBadEscape;
|
|
const upper = try parseHex(path[j + 1]);
|
|
const lower = try parseHex(path[j + 2]);
|
|
uri[i] = (upper << 4) + lower;
|
|
j += 3;
|
|
} else {
|
|
uri[i] = if (path[j] == '/') std.fs.path.sep else path[j];
|
|
j += 1;
|
|
}
|
|
}
|
|
|
|
// Remove trailing separator
|
|
if (i > 0 and uri[i - 1] == std.fs.path.sep) {
|
|
i -= 1;
|
|
}
|
|
|
|
return allocator.shrink(uri, i);
|
|
}
|