forked from oven-sh/bun
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathurl_path.zig
163 lines (139 loc) · 5.82 KB
/
url_path.zig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
const bun = @import("../global.zig");
const string = bun.string;
const Output = bun.Output;
const toMutable = bun.constStrToU8;
const Global = bun.Global;
const Environment = bun.Environment;
const strings = bun.strings;
const MutableString = bun.MutableString;
const stringZ = bun.stringZ;
const default_allocator = bun.default_allocator;
const C = bun.C;
const PercentEncoding = @import("../url.zig").PercentEncoding;
const std = @import("std");
const allocators = @import("../allocators.zig");
const URLPath = @This();
extname: string = "",
path: string = "",
pathname: string = "",
first_segment: string = "",
query_string: string = "",
needs_redirect: bool = false,
/// Treat URLs as non-sourcemap URLS
/// Then at the very end, we check.
is_source_map: bool = false,
pub fn isRoot(this: *const URLPath, asset_prefix: string) bool {
const without = this.pathWithoutAssetPrefix(asset_prefix);
if (without.len == 1 and without[0] == '.') return true;
return strings.eqlComptime(without, "index");
}
// TODO: use a real URL parser
// this treats a URL like /_next/ identically to /
pub fn pathWithoutAssetPrefix(this: *const URLPath, asset_prefix: string) string {
if (asset_prefix.len == 0) return this.path;
const leading_slash_offset: usize = if (asset_prefix[0] == '/') 1 else 0;
const base = this.path;
const origin = asset_prefix[leading_slash_offset..];
const out = if (base.len >= origin.len and strings.eql(base[0..origin.len], origin)) base[origin.len..] else base;
if (this.is_source_map and strings.endsWithComptime(out, ".map")) {
return out[0 .. out.len - 4];
}
return out;
}
// optimization: very few long strings will be URL-encoded
// we're allocating virtual memory here, so if we never use it, it won't be allocated
// and even when they're, they're probably rarely going to be > 1024 chars long
// so we can have a big and little one and almost always use the little one
threadlocal var temp_path_buf: [1024]u8 = undefined;
threadlocal var big_temp_path_buf: [16384]u8 = undefined;
pub fn parse(possibly_encoded_pathname_: string) !URLPath {
var decoded_pathname = possibly_encoded_pathname_;
var needs_redirect = false;
if (strings.containsChar(decoded_pathname, '%')) {
var possibly_encoded_pathname = switch (decoded_pathname.len) {
0...1024 => &temp_path_buf,
else => &big_temp_path_buf,
};
possibly_encoded_pathname = possibly_encoded_pathname[0..std.math.min(
possibly_encoded_pathname_.len,
possibly_encoded_pathname.len,
)];
std.mem.copy(u8, possibly_encoded_pathname, possibly_encoded_pathname_[0..possibly_encoded_pathname.len]);
var clone = possibly_encoded_pathname[0..possibly_encoded_pathname.len];
var fbs = std.io.fixedBufferStream(
// This is safe because:
// - this comes from a non-const buffer
// - percent *decoding* will always be <= length of the original string (no buffer overflow)
toMutable(
possibly_encoded_pathname,
),
);
var writer = fbs.writer();
decoded_pathname = possibly_encoded_pathname[0..try PercentEncoding.decodeFaultTolerant(@TypeOf(writer), writer, clone, &needs_redirect, true)];
}
var question_mark_i: i16 = -1;
var period_i: i16 = -1;
var first_segment_end: i16 = std.math.maxInt(i16);
var last_slash: i16 = -1;
var i: i16 = @intCast(i16, decoded_pathname.len) - 1;
while (i >= 0) : (i -= 1) {
const c = decoded_pathname[@intCast(usize, i)];
switch (c) {
'?' => {
question_mark_i = @maximum(question_mark_i, i);
if (question_mark_i < period_i) {
period_i = -1;
}
if (last_slash > question_mark_i) {
last_slash = -1;
}
},
'.' => {
period_i = @maximum(period_i, i);
},
'/' => {
last_slash = @maximum(last_slash, i);
if (i > 0) {
first_segment_end = @minimum(first_segment_end, i);
}
},
else => {},
}
}
if (last_slash > period_i) {
period_i = -1;
}
// .js.map
// ^
const extname = brk: {
if (question_mark_i > -1 and period_i > -1) {
period_i += 1;
break :brk decoded_pathname[@intCast(usize, period_i)..@intCast(usize, question_mark_i)];
} else if (period_i > -1) {
period_i += 1;
break :brk decoded_pathname[@intCast(usize, period_i)..];
} else {
break :brk &([_]u8{});
}
};
var path = if (question_mark_i < 0) decoded_pathname[1..] else decoded_pathname[1..@intCast(usize, question_mark_i)];
const first_segment = decoded_pathname[1..@minimum(@intCast(usize, first_segment_end), decoded_pathname.len)];
const is_source_map = strings.eqlComptime(extname, "map");
var backup_extname: string = extname;
if (is_source_map and path.len > ".map".len) {
if (std.mem.lastIndexOfScalar(u8, path[0 .. path.len - ".map".len], '.')) |j| {
backup_extname = path[j + 1 ..];
backup_extname = backup_extname[0 .. backup_extname.len - ".map".len];
path = path[0 .. j + backup_extname.len + 1];
}
}
return URLPath{
.extname = if (!is_source_map) extname else backup_extname,
.is_source_map = is_source_map,
.pathname = decoded_pathname,
.first_segment = first_segment,
.path = if (decoded_pathname.len == 1) "." else path,
.query_string = if (question_mark_i > -1) decoded_pathname[@intCast(usize, question_mark_i)..@intCast(usize, decoded_pathname.len)] else "",
.needs_redirect = needs_redirect,
};
}