1use crate::constants::{
2 RAW_DOMAINS, RESPONSE_STATUS_CODE_200, RESPONSE_STATUS_CODE_404, ROOT_404_HTML,
3 ROOT_INDEX_HTML, ROOT_PATH,
4};
5use crate::http::types::HeaderField;
6use crate::rewrites::{is_root_path, redirect_url, rewrite_url};
7use crate::strategies::StorageStateStrategy;
8use crate::types::config::StorageConfigRawAccess;
9use crate::types::http_request::{
10 MapUrl, Routing, RoutingDefault, RoutingRedirect, RoutingRedirectRaw, RoutingRewrite,
11};
12use crate::types::state::FullPath;
13use crate::types::store::Asset;
14use crate::url::{map_alternative_paths, map_url};
15use junobuild_collections::types::rules::Memory;
16use junobuild_shared::ic::api::id;
17
18pub fn get_routing(
19 url: String,
20 req_headers: &[HeaderField],
21 include_alternative_routing: bool,
22 storage_state: &impl StorageStateStrategy,
23) -> Result<Routing, &'static str> {
24 if url.is_empty() {
25 return Err("No url provided.");
26 }
27
28 let redirect_raw = get_routing_redirect_raw(&url, req_headers, storage_state);
30
31 match redirect_raw {
32 None => (),
33 Some(redirect_raw) => {
34 return Ok(redirect_raw);
35 }
36 }
37
38 let MapUrl { path, token } = map_url(&url)?;
45
46 let asset: Option<(Asset, Memory)> =
48 storage_state.get_public_asset(path.clone(), token.clone());
49
50 match asset {
51 None => (),
52 Some(_) => {
53 return Ok(Routing::Default(RoutingDefault { url: path, asset }));
54 }
55 }
56
57 let alternative_asset = get_alternative_asset(&path, &token, storage_state);
61 match alternative_asset {
62 None => (),
63 Some(alternative_asset) => {
64 return Ok(Routing::Default(RoutingDefault {
65 url: path.clone(),
66 asset: Some(alternative_asset),
67 }));
68 }
69 }
70
71 if include_alternative_routing {
72 let redirect = get_routing_redirect(&path, storage_state);
74
75 match redirect {
76 None => (),
77 Some(redirect) => {
78 return Ok(redirect);
79 }
80 }
81
82 let rewrite = get_routing_rewrite(&path, &token, storage_state);
84
85 match rewrite {
86 None => (),
87 Some(rewrite) => {
88 return Ok(rewrite);
89 }
90 }
91
92 let root_rewrite = get_routing_root_rewrite(&path, storage_state);
94
95 match root_rewrite {
96 None => (),
97 Some(root_rewrite) => {
98 return Ok(root_rewrite);
99 }
100 }
101 }
102
103 Ok(Routing::Default(RoutingDefault {
104 url: path,
105 asset: None,
106 }))
107}
108
109fn get_alternative_asset(
110 path: &String,
111 token: &Option<String>,
112 storage_state: &impl StorageStateStrategy,
113) -> Option<(Asset, Memory)> {
114 let alternative_paths = map_alternative_paths(path);
115
116 for alternative_path in alternative_paths {
117 let asset: Option<(Asset, Memory)> =
118 storage_state.get_public_asset(alternative_path, token.clone());
119
120 match asset {
122 None => (),
123 Some(_) => {
124 return asset;
125 }
126 }
127 }
128
129 None
130}
131
132fn get_routing_rewrite(
133 path: &FullPath,
134 token: &Option<String>,
135 storage_state: &impl StorageStateStrategy,
136) -> Option<Routing> {
137 let rewrite = rewrite_url(path, &storage_state.get_config());
140
141 match rewrite {
142 None => (),
143 Some(rewrite) => {
144 let (source, destination) = rewrite;
145
146 let rewrite_asset = get_alternative_asset(&destination, token, storage_state);
149
150 match rewrite_asset {
151 None => (),
152 Some(_) => {
153 return Some(Routing::Rewrite(RoutingRewrite {
154 url: path.clone(),
155 asset: rewrite_asset,
156 source,
157 status_code: RESPONSE_STATUS_CODE_200,
158 }));
159 }
160 }
161
162 let rewrite_absolute_asset: Option<(Asset, Memory)> =
165 storage_state.get_public_asset(destination.clone(), token.clone());
166
167 match rewrite_absolute_asset {
168 None => (),
169 Some(_) => {
170 return Some(Routing::Rewrite(RoutingRewrite {
171 url: path.clone(),
172 asset: rewrite_absolute_asset,
173 source,
174 status_code: RESPONSE_STATUS_CODE_200,
175 }));
176 }
177 }
178 }
179 }
180
181 None
182}
183
184fn get_routing_root_rewrite(
185 path: &FullPath,
186 storage_state: &impl StorageStateStrategy,
187) -> Option<Routing> {
188 if !is_root_path(path) {
189 let asset_404: Option<(Asset, Memory)> =
191 storage_state.get_public_asset(ROOT_404_HTML.to_string(), None);
192
193 match asset_404 {
194 None => (),
195 Some(_) => {
196 return Some(Routing::Rewrite(RoutingRewrite {
197 url: path.clone(),
198 asset: asset_404,
199 source: ROOT_PATH.to_string(),
200 status_code: RESPONSE_STATUS_CODE_404,
201 }));
202 }
203 }
204
205 let asset_index: Option<(Asset, Memory)> =
207 storage_state.get_public_asset(ROOT_INDEX_HTML.to_string(), None);
208
209 match asset_index {
210 None => (),
211 Some(_) => {
212 return Some(Routing::Rewrite(RoutingRewrite {
213 url: path.clone(),
214 asset: asset_index,
215 source: ROOT_PATH.to_string(),
216 status_code: RESPONSE_STATUS_CODE_200,
217 }));
218 }
219 }
220 }
221
222 None
223}
224
225fn get_routing_redirect(
226 path: &FullPath,
227 storage_state: &impl StorageStateStrategy,
228) -> Option<Routing> {
229 let config = storage_state.get_config();
230 let redirect = redirect_url(path, &config);
231
232 match redirect {
233 None => (),
234 Some(redirect) => {
235 return Some(Routing::Redirect(RoutingRedirect {
236 url: path.clone(),
237 redirect,
238 iframe: config.unwrap_iframe(),
239 }));
240 }
241 }
242
243 None
244}
245
246fn get_routing_redirect_raw(
247 url: &String,
248 req_headers: &[HeaderField],
249 storage_state: &impl StorageStateStrategy,
250) -> Option<Routing> {
251 let raw = req_headers.iter().any(|HeaderField(key, value)| {
252 key.eq_ignore_ascii_case("Host") && RAW_DOMAINS.iter().any(|domain| value.contains(domain))
253 });
254
255 let config = storage_state.get_config();
256
257 if raw {
258 let allow_raw_access = config.unwrap_raw_access();
259
260 match allow_raw_access {
261 StorageConfigRawAccess::Deny => {
262 return Some(Routing::RedirectRaw(RoutingRedirectRaw {
263 redirect_url: format!("https://{}.icp0.io{}", id().to_text(), url),
264 iframe: config.unwrap_iframe(),
265 }));
266 }
267 StorageConfigRawAccess::Allow => (),
268 }
269 }
270
271 None
272}