1#![allow(dead_code)]
2
3use crate::mapping::Mapping;
4use crate::util::UrlType::{Absolute, PathAbsolute, PathRelative, SchemeRelative};
5use lazy_static::lazy_static;
6use regex::Regex;
7use std::collections::LinkedList;
8use url::Url;
9
10pub fn strcmp(a: Option<String>, b: Option<String>) -> i32 {
11 if a == b {
12 return 0;
13 }
14
15 if a.is_none() {
16 return 1;
17 }
18
19 if b.is_none() {
20 return -1;
21 }
22
23 if a.unwrap() > b.unwrap() {
24 return 1;
25 }
26
27 -1
28}
29
30pub fn compare_by_generated_pos_inflated(a: &Mapping, b: &Mapping) -> i32 {
31 let mut cmp = a.generated.line - b.generated.line;
32 if cmp != 0 {
33 return cmp;
34 }
35
36 cmp = a.generated.column - b.generated.column;
37 if cmp != 0 {
38 return cmp;
39 }
40
41 cmp = strcmp(a.source.clone(), b.source.clone());
42 if cmp != 0 {
43 return cmp;
44 }
45 if a.original.is_some() && b.original.is_some() {
46 cmp = a.original.as_ref().unwrap().line - b.original.as_ref().unwrap().line;
47 if cmp != 0 {
48 return cmp;
49 }
50
51 cmp = a.original.as_ref().unwrap().column - b.original.as_ref().unwrap().column;
52 if cmp != 0 {
53 return cmp;
54 }
55 }
56
57 strcmp(a.name.clone(), b.name.clone())
58}
59
60const PROTOCOL: &str = "http:";
65const ABSOLUTE_SCHEME: &str = r"^[A-Za-z0-9\+\-\.]+:/";
66lazy_static! {
67 static ref ABSOLUTE_SCHEME_REGEXP: Regex = Regex::new(ABSOLUTE_SCHEME).unwrap();
68 static ref PROTOCOL_AND_HOST: String = format!("{}//host", PROTOCOL);
69}
70
71#[derive(Debug, Clone, PartialEq)]
72enum UrlType {
73 Absolute,
74 PathAbsolute,
75 PathRelative,
76 SchemeRelative,
77}
78
79fn get_url_type(input: &str) -> UrlType {
80 let mut iter = input.chars();
81 let first = iter.next().unwrap();
82 let second = iter.next().unwrap();
83 if first == '/' {
84 if second == '/' {
85 return SchemeRelative;
86 }
87 return PathAbsolute;
88 }
89 if ABSOLUTE_SCHEME_REGEXP.is_match(input) {
90 Absolute
91 } else {
92 PathRelative
93 }
94}
95
96fn build_unique_segment(prefix: &str, input: &str) -> String {
97 let mut id = 0;
98 loop {
99 let ident = format!("{}{}", prefix, id);
100 id += 1;
101 if !input.contains(ident.as_str()) {
102 return ident;
103 }
104 }
105}
106
107fn build_safe_base(input: &str) -> String {
108 let max_dot_parts = input.split("..").count() - 1;
109
110 let segment = build_unique_segment("p", input);
123
124 let mut base = format!("{}/", *PROTOCOL_AND_HOST);
125 for _ in 0..max_dot_parts {
126 base.push_str(&segment);
127 base.push('/');
128 }
129
130 base
131}
132
133fn compute_relative_url(root_url: &str, target_url: &str) -> String {
134 let root_url = Url::parse(root_url).unwrap();
135 let target_url = Url::parse(target_url).unwrap();
136
137 let mut target_parts = target_url.path().split('/').collect::<LinkedList<_>>();
138 let mut root_parts = root_url.path().split('/').collect::<LinkedList<_>>();
139
140 if !root_parts.is_empty() && root_parts.back().unwrap().is_empty() {
141 root_parts.pop_back();
142 }
143
144 while !target_parts.is_empty()
145 && !root_parts.is_empty()
146 && target_parts.front().unwrap() == root_parts.front().unwrap()
147 {
148 target_parts.pop_front();
149 root_parts.pop_front();
150 }
151
152 let mut relative_path: String = root_parts
153 .iter()
154 .map(|_| "..")
155 .chain(target_parts)
156 .collect::<Vec<_>>()
157 .join("/");
158
159 if let Some(query) = target_url.query() {
160 relative_path.push_str(query);
161 }
162
163 if let Some(frag) = target_url.fragment() {
164 relative_path.push_str(frag);
165 }
166 relative_path
167}
168
169fn create_safe_handler(cb: Box<dyn Fn(&mut Url) + Sync>) -> Box<dyn Fn(String) -> String + Sync> {
170 Box::new(move |input: String| -> String {
171 let t = get_url_type(input.as_str());
172 let base = build_safe_base(input.as_str());
173 let urlx = Url::parse(base.as_str()).unwrap();
174 let mut urlx = urlx.join(input.as_str()).unwrap();
175
176 cb(&mut urlx);
177
178 let result = urlx.to_string();
179
180 match t {
181 Absolute => result,
182 SchemeRelative => result.chars().skip(PROTOCOL.len()).collect(),
183 PathAbsolute => result.chars().skip(PROTOCOL_AND_HOST.len()).collect(),
184 _ => compute_relative_url(base.as_str(), result.as_str()),
185 }
186 })
187}
188
189type UtilityFn = Box<dyn Fn(String) -> String + Sync>;
190
191lazy_static! {
192 static ref ENSURE_DIRECTORY: UtilityFn = create_safe_handler(Box::new(|url| {
193 let reg = Regex::new("/?$").unwrap();
195
196 let path = reg.replace(url.path(), "/").to_string();
197 url.set_path(&path);
198 }));
199
200 static ref TRIM_FILENAME: UtilityFn = create_safe_handler(Box::new(|url| {
201 let path = url.path().to_string();
202 let mut path = path.split('/').collect::<Vec<_>>();
203
204 if !path.last().unwrap().is_empty() {
205 path.pop();
206 }
207
208 url.set_path(&path.join(""));
209 }));
210
211 static ref NORMALIZE: UtilityFn = create_safe_handler(Box::new(|_url|{}));
212}
213
214fn replace_if_possible(root: &str, target: &str) -> Option<String> {
215 let url_type = get_url_type(root);
216 if url_type != get_url_type(target) {
217 return None;
218 }
219
220 let base = build_safe_base(&format!("{}{}", root, target));
221 let root_url = Url::parse(&base).unwrap();
222 let root_url = root_url.join(root).unwrap();
223 let target_url = Url::parse(&base).unwrap();
224 let target_url = target_url.join(target).unwrap();
225 match target_url.join("") {
226 Ok(_) => {}
227 Err(_) => return None,
228 };
229
230 if target_url.scheme() != root_url.scheme()
231 || target_url.username() != root_url.username()
232 || target_url.password() != root_url.password()
233 || target_url.host() != root_url.host()
234 || target_url.port() != root_url.port()
235 {
236 return None;
237 }
238
239 Some(compute_relative_url(root, target))
240}
241
242pub fn relative(root: String, target: String) -> String {
243 match replace_if_possible(root.as_str(), target.as_str()) {
244 None => NORMALIZE(target),
245 Some(r) => r,
246 }
247}
248
249fn with_base(url: &str, base: Option<&str>) -> String {
250 match base {
251 Some(base) => Url::parse(base)
252 .unwrap()
253 .join(url)
254 .unwrap()
255 .as_str()
256 .to_string(),
257 None => url.to_string(),
258 }
259}
260
261pub fn join(root: &str, path: &str) -> String {
262 let path_t = get_url_type(path);
263 let root_t = get_url_type(root);
264
265 let root = ENSURE_DIRECTORY(root.to_owned());
266
267 if path_t == Absolute {
268 return with_base(path, None);
269 }
270
271 if root_t == Absolute {
272 return with_base(path, Some(root.as_str()));
273 }
274
275 if path_t == SchemeRelative {
276 return NORMALIZE(path.to_string());
277 }
278
279 if root_t == SchemeRelative {
280 return with_base(
281 path,
282 Some(with_base(root.as_str(), Some(PROTOCOL_AND_HOST.as_str())).as_str()),
283 )
284 .chars()
285 .skip(PROTOCOL.len())
286 .collect();
287 }
288
289 if path_t == PathAbsolute {
290 return NORMALIZE(path.to_string());
291 }
292
293 if root_t == PathAbsolute {
294 return with_base(
295 path,
296 Some(with_base(root.as_str(), Some(PROTOCOL_AND_HOST.as_str())).as_str()),
297 )
298 .chars()
299 .skip(PROTOCOL_AND_HOST.len())
300 .collect();
301 }
302
303 let base = build_safe_base(format!("{}{}", path, root).as_str());
304 let new_path = with_base(
305 path,
306 Some(with_base(root.as_str(), Some(base.as_str())).as_str()),
307 );
308 compute_relative_url(base.as_str(), new_path.as_str())
309}
310
311pub fn compute_source_url(
312 source_root: Option<&str>,
313 source_url: &str,
314 source_map_url: Option<&str>,
315) -> String {
316 let mut source_url = source_url;
337 let after = source_url.replacen("/", "", 1);
338
339 if source_root.is_some() && get_url_type(source_url) == PathAbsolute {
340 source_url = after.as_str();
342 }
343
344 let mut url = NORMALIZE(source_url.to_string());
345
346 if let Some(source_root) = source_root {
347 url = join(source_root, url.as_str());
348 }
349
350 if let Some(source_map_url) = source_map_url {
351 url = join(
352 TRIM_FILENAME(source_map_url.to_string()).as_str(),
353 url.as_str(),
354 );
355 }
356
357 url
358}