atmos/os_lib/web_engine/
url_resolve.rs1use alloc::format;
6use alloc::string::String;
7use alloc::vec::Vec;
8
9pub fn resolve_url(base_url: &str, rel: &str) -> String {
15 let rel = rel.trim();
16 if rel.is_empty() {
17 return String::from(rel);
18 }
19 if rel.starts_with("http://") || rel.starts_with("https://") || rel.starts_with("data:") {
20 return String::from(rel);
21 }
22 let (scheme, rest) = if let Some(r) = base_url.strip_prefix("https://") {
23 ("https://", r)
24 } else if let Some(r) = base_url.strip_prefix("http://") {
25 ("http://", r)
26 } else {
27 return String::from(rel);
28 };
29 let host = match rest.find('/') {
30 Some(idx) => rest.get(..idx).unwrap_or(rest),
31 None => rest,
32 };
33 if let Some(no_scheme) = rel.strip_prefix("//") {
34 return format!("{}{}", scheme, no_scheme);
35 }
36 if let Some(abs_path) = rel.strip_prefix('/') {
37 return format!("{}{}/{}", scheme, host, abs_path);
38 }
39 let base_path = match rest.find('/') {
41 Some(idx) => rest.get(idx..).unwrap_or("/"),
42 None => "/",
43 };
44 let mut dir_segments: Vec<&str> = base_path.split('/').collect();
45 dir_segments.pop(); for seg in rel.split('/') {
47 match seg {
48 "." | "" => {}
49 ".." => {
50 if dir_segments.len() > 1 {
51 dir_segments.pop();
52 }
53 }
54 s => dir_segments.push(s),
55 }
56 }
57 let joined_path = dir_segments.join("/");
58 format!("{}{}{}", scheme, host, joined_path)
59}
60
61pub fn rewrite_css_urls(css: &str, css_base_url: &str) -> String {
66 if !css.contains("url(") {
67 return String::from(css);
68 }
69 let mut out = String::with_capacity(css.len());
70 let mut rest = css;
71 while let Some(idx) = rest.find("url(") {
72 if let Some(prefix) = rest.get(..idx) {
73 out.push_str(prefix);
74 }
75 out.push_str("url(");
76 let after_url = match rest.get(idx + 4..) {
77 Some(s) => s,
78 None => break,
79 };
80 let bytes = after_url.as_bytes();
81 let mut depth = 1i32;
82 let mut i = 0usize;
83 while i < bytes.len() && depth > 0 {
84 match bytes[i] {
85 b'(' => depth += 1,
86 b')' => depth -= 1,
87 _ => {}
88 }
89 if depth > 0 {
90 i += 1;
91 }
92 }
93 let inner = after_url.get(..i).unwrap_or("");
94 let trimmed = inner.trim();
95 let (quote, unquoted) = if (trimmed.starts_with('"') && trimmed.ends_with('"'))
96 || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
97 {
98 let q = trimmed.get(..1).unwrap_or("");
99 (q, trimmed.get(1..trimmed.len() - 1).unwrap_or(""))
100 } else {
101 ("", trimmed)
102 };
103 let resolved = resolve_url(css_base_url, unquoted);
104 out.push_str(quote);
105 out.push_str(&resolved);
106 out.push_str(quote);
107 if i < bytes.len() {
108 out.push(')');
109 i += 1;
110 }
111 rest = after_url.get(i..).unwrap_or("");
112 }
113 out.push_str(rest);
114 out
115}
116
117pub fn truncate_to_balanced_css(css: &str) -> &str {
126 let bytes = css.as_bytes();
127 let mut depth: i32 = 0;
128 let mut last_balanced_end = 0usize;
129 let mut i = 0usize;
130 while i < bytes.len() {
131 match bytes[i] {
132 b'/' if bytes.get(i + 1) == Some(&b'*') => {
133 i += 2;
135 while i < bytes.len() && !(bytes[i] == b'*' && bytes.get(i + 1) == Some(&b'/')) {
136 i += 1;
137 }
138 i = (i + 2).min(bytes.len());
139 continue;
140 }
141 b'"' | b'\'' => {
142 let quote = bytes[i];
144 i += 1;
145 while i < bytes.len() && bytes[i] != quote {
146 if bytes[i] == b'\\' {
147 i += 1;
148 }
149 i += 1;
150 }
151 i = (i + 1).min(bytes.len());
152 continue;
153 }
154 b'{' => {
155 depth += 1;
156 i += 1;
157 }
158 b'}' => {
159 depth -= 1;
160 i += 1;
161 if depth <= 0 {
162 depth = 0;
163 last_balanced_end = i;
164 }
165 }
166 _ => {
167 i += 1;
168 }
169 }
170 }
171 if depth == 0 {
172 css
173 } else {
174 css.get(..last_balanced_end).unwrap_or("")
175 }
176}
177
178pub fn split_target(
193 url: &str,
194 current_is_https: bool,
195 current_host: &str,
196) -> (bool, String, String) {
197 fn split_host_path(rest: &str) -> (String, String) {
198 match rest.find('/') {
199 Some(idx) => (
200 String::from(rest.get(..idx).unwrap_or(rest)),
201 String::from(rest.get(idx..).unwrap_or("/")),
202 ),
203 None => (String::from(rest), String::from("/")),
204 }
205 }
206 let url = match url.split_once('#') {
208 Some((before, _)) => before,
209 None => url,
210 };
211 if let Some(rest) = url.strip_prefix("https://") {
212 let (h, p) = split_host_path(rest);
213 (true, h, p)
214 } else if let Some(rest) = url.strip_prefix("http://") {
215 let (h, p) = split_host_path(rest);
216 (false, h, p)
217 } else if let Some(rest) = url.strip_prefix("//") {
218 let (h, p) = split_host_path(rest);
221 (current_is_https, h, p)
222 } else if url.starts_with('/') {
223 (current_is_https, String::from(current_host), String::from(url))
224 } else {
225 (current_is_https, String::from(current_host), String::from(url))
226 }
227}