Rewild Your Web
web browser dweb
16
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 246ded04a91d5de0e500ff85a8615fad03c8d10f 159 lines 4.4 kB view raw
1// SPDX-License-Identifier: AGPL-3.0-or-later 2 3//! vhost http server to serve local content. 4 5use std::path::PathBuf; 6use std::sync::Arc; 7 8use axum::Router; 9use axum::extract::{Request, State}; 10use axum::http::Request as HttpRequest; 11use axum::routing::get; 12use log::error; 13use parking_lot::Mutex; 14use servo::prefs::{PreferencesObserver, get_embedder_pref}; 15use servo::{PrefValue, prefs}; 16use tower_http::compression::CompressionLayer; 17use tower_http::cors::{Any, CorsLayer}; 18use tower_http::services::ServeFile; 19use tower_http::services::fs::ServeFileSystemResponseBody; 20use url::Host::Domain; 21use url::Url; 22 23use crate::resources::resources_dir_path; 24 25fn vhost_handler(state: ServerState, request: Request) -> Option<PathBuf> { 26 let Some(host) = request.headers().get("Host") else { 27 println!("No Host header!"); 28 return None; 29 }; 30 31 let Ok(url) = format!("http://{}", host.to_str().unwrap_or_default()).parse::<Url>() else { 32 println!( 33 "Failed to parse http://{}", 34 host.to_str().unwrap_or_default() 35 ); 36 return None; 37 }; 38 39 let Some(Domain(domain)) = url.host() else { 40 return None; 41 }; 42 43 let parts: Vec<String> = domain.split(".").map(|s| s.to_owned()).collect(); 44 45 if parts.len() != 2 || parts[1] != "localhost" { 46 return None; 47 } 48 49 let app = &parts[0]; 50 51 // Currently recognized: system, shared, keyboard, homescreen, theme 52 // TODO: don't hardcode 53 if app != "homescreen" && 54 app != "keyboard" && 55 app != "settings" && 56 app != "shared" && 57 app != "system" && 58 app != "theme" 59 { 60 return None; 61 } 62 63 let uri_parts: Vec<String> = request 64 .uri() 65 .path() 66 .split("/") 67 .filter_map(|s| { 68 if s.is_empty() || s.contains("..") { 69 None 70 } else { 71 Some(s.to_owned()) 72 } 73 }) 74 .collect(); 75 76 if app == "theme" { 77 let theme = state.theme.lock(); 78 Some( 79 resources_dir_path() 80 .join("browserhtml") 81 .join("themes") 82 .join(&*theme) 83 .join(uri_parts.join("/")), 84 ) 85 } else { 86 Some( 87 resources_dir_path() 88 .join("browserhtml") 89 .join(app) 90 .join(uri_parts.join("/")), 91 ) 92 } 93} 94 95#[derive(Clone)] 96struct ServerState { 97 theme: Arc<Mutex<String>>, 98} 99 100impl PreferencesObserver for ServerState { 101 fn prefs_changed(&self, changes: &[(&'static str, PrefValue)]) { 102 for (name, value) in changes { 103 if *name == "browserhtml.theme" { 104 let PrefValue::Str(theme) = value else { 105 error!("Unexpected value for browserhtml.theme: {value:?}"); 106 return; 107 }; 108 *self.theme.lock() = theme.to_string(); 109 } 110 } 111 } 112} 113 114async fn get_file( 115 State(state): State<ServerState>, 116 request: Request, 117) -> impl axum::response::IntoResponse { 118 let path = vhost_handler(state, request).unwrap_or_default(); 119 let service = ServeFile::new(path); 120 <ServeFile as tower::ServiceExt<HttpRequest<ServeFileSystemResponseBody>>>::oneshot( 121 service, 122 HttpRequest::default(), 123 ) 124 .await 125} 126 127pub(crate) fn start_vhost(port: u16) { 128 // Get the initial value from the embedder preferences. 129 let theme = match get_embedder_pref("browserhtml.theme") { 130 Some(PrefValue::Str(value)) => value, 131 _ => "default".to_owned(), 132 }; 133 let state = ServerState { 134 theme: Arc::new(Mutex::new(theme)), 135 }; 136 137 prefs::add_observer(Box::new(state.clone())); 138 139 // Configure CORS to allow cross-origin requests between localhost subdomains 140 let cors = CorsLayer::new() 141 .allow_origin(Any) 142 .allow_methods(Any) 143 .allow_headers(Any); 144 145 let compression = CompressionLayer::new().zstd(true); 146 147 let app = Router::new() 148 .route("/{*key}", get(get_file)) 149 .layer(cors) 150 .layer(compression) 151 .with_state(state); 152 153 servo::Servo::spawn_task(async move { 154 let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{port}")) 155 .await 156 .expect("Failed to bind on 127.0.0.1:{port}"); 157 let _ = axum::serve(listener, app).await; 158 }); 159}