Browse Source

Merge branch 'master' into signals

Evan Almloff 1 year ago
parent
commit
0dc602eb32

+ 3 - 0
packages/cli/Cargo.toml

@@ -97,3 +97,6 @@ plugin = ["mlua"]
 [[bin]]
 [[bin]]
 path = "src/main.rs"
 path = "src/main.rs"
 name = "dx"
 name = "dx"
+
+[dev-dependencies]
+tempfile = "3.3"

+ 1 - 0
packages/cli/docs/src/cmd/README.md

@@ -15,6 +15,7 @@ Commands:
   serve      Build, watch & serve the Rust WASM app and all of its assets
   serve      Build, watch & serve the Rust WASM app and all of its assets
   create     Init a new project for Dioxus
   create     Init a new project for Dioxus
   clean      Clean output artifacts
   clean      Clean output artifacts
+  bundle     Bundle the Rust desktop app and all of its assets
   version    Print the version of this extension
   version    Print the version of this extension
   fmt        Format some rsx
   fmt        Format some rsx
   check      Check the Rust files in the project for issues
   check      Check the Rust files in the project for issues

+ 1 - 1
packages/cli/docs/src/configure.md

@@ -144,7 +144,7 @@ Configeration related to static resources your application uses in development:
 Configeration related to any proxies your application requires durring development. Proxies will forward requests to a new service
 Configeration related to any proxies your application requires durring development. Proxies will forward requests to a new service
 
 
 ```
 ```
-[web.proxy]
+[[web.proxy]]
 # configuration
 # configuration
 ```
 ```
 
 

+ 2 - 3
packages/cli/src/builder.rs

@@ -236,10 +236,9 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
         }
         }
     }
     }
 
 
-    let t_end = std::time::Instant::now();
     Ok(BuildResult {
     Ok(BuildResult {
         warnings: warning_messages,
         warnings: warning_messages,
-        elapsed_time: (t_end - t_start).as_millis(),
+        elapsed_time: t_start.elapsed().as_millis(),
     })
     })
 }
 }
 
 
@@ -360,7 +359,7 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResul
 
 
     Ok(BuildResult {
     Ok(BuildResult {
         warnings: warning_messages,
         warnings: warning_messages,
-        elapsed_time: (t_start - std::time::Instant::now()).as_millis(),
+        elapsed_time: t_start.elapsed().as_millis(),
     })
     })
 }
 }
 
 

+ 1 - 1
packages/cli/src/cli/autoformat.rs

@@ -6,7 +6,7 @@ use super::*;
 // For reference, the rustfmt main.rs file
 // For reference, the rustfmt main.rs file
 // https://github.com/rust-lang/rustfmt/blob/master/src/bin/main.rs
 // https://github.com/rust-lang/rustfmt/blob/master/src/bin/main.rs
 
 
-/// Build the Rust WASM app and all of its assets.
+/// Format some rsx
 #[derive(Clone, Debug, Parser)]
 #[derive(Clone, Debug, Parser)]
 pub struct Autoformat {
 pub struct Autoformat {
     /// Run in 'check' mode. Exits with 0 if input is formatted correctly. Exits
     /// Run in 'check' mode. Exits with 0 if input is formatted correctly. Exits

+ 1 - 1
packages/cli/src/cli/bundle.rs

@@ -6,7 +6,7 @@ use tauri_bundler::{BundleSettings, PackageSettings, SettingsBuilder};
 use super::*;
 use super::*;
 use crate::{build_desktop, cfg::ConfigOptsBundle};
 use crate::{build_desktop, cfg::ConfigOptsBundle};
 
 
-/// Build the Rust WASM app and all of its assets.
+/// Bundle the Rust desktop app and all of its assets
 #[derive(Clone, Debug, Parser)]
 #[derive(Clone, Debug, Parser)]
 #[clap(name = "bundle")]
 #[clap(name = "bundle")]
 pub struct Bundle {
 pub struct Bundle {

+ 0 - 7
packages/cli/src/cli/cfg.rs

@@ -101,13 +101,6 @@ pub enum Platform {
     Desktop,
     Desktop,
 }
 }
 
 
-/// Ensure the given value for `--public-url` is formatted correctly.
-pub fn parse_public_url(val: &str) -> String {
-    let prefix = if !val.starts_with('/') { "/" } else { "" };
-    let suffix = if !val.ends_with('/') { "/" } else { "" };
-    format!("{}{}{}", prefix, val, suffix)
-}
-
 /// Config options for the bundling system.
 /// Config options for the bundling system.
 #[derive(Clone, Debug, Default, Deserialize, Parser)]
 #[derive(Clone, Debug, Default, Deserialize, Parser)]
 pub struct ConfigOptsBundle {
 pub struct ConfigOptsBundle {

+ 0 - 0
packages/cli/src/cli/check/mod.rs → packages/cli/src/cli/check.rs


+ 1 - 1
packages/cli/src/cli/config.rs

@@ -1,6 +1,6 @@
 use super::*;
 use super::*;
 
 
-/// Build the Rust WASM app and all of its assets.
+/// Dioxus config file controls
 #[derive(Clone, Debug, Deserialize, Subcommand)]
 #[derive(Clone, Debug, Deserialize, Subcommand)]
 #[clap(name = "config")]
 #[clap(name = "config")]
 pub enum Config {
 pub enum Config {

+ 4 - 4
packages/cli/src/cli/create.rs

@@ -46,15 +46,15 @@ impl Create {
             toml.as_table_mut().fmt();
             toml.as_table_mut().fmt();
 
 
             let as_string = toml.to_string();
             let as_string = toml.to_string();
-            let new_string = remove_tripple_newlines(&as_string);
+            let new_string = remove_triple_newlines(&as_string);
             let mut file = std::fs::File::create(toml_path)?;
             let mut file = std::fs::File::create(toml_path)?;
             file.write_all(new_string.as_bytes())?;
             file.write_all(new_string.as_bytes())?;
         }
         }
 
 
-        // remove any tripple newlines from the readme
+        // remove any triple newlines from the readme
         let readme_path = path.join("README.md");
         let readme_path = path.join("README.md");
         let readme = std::fs::read_to_string(&readme_path)?;
         let readme = std::fs::read_to_string(&readme_path)?;
-        let new_readme = remove_tripple_newlines(&readme);
+        let new_readme = remove_triple_newlines(&readme);
         let mut file = std::fs::File::create(readme_path)?;
         let mut file = std::fs::File::create(readme_path)?;
         file.write_all(new_readme.as_bytes())?;
         file.write_all(new_readme.as_bytes())?;
 
 
@@ -64,7 +64,7 @@ impl Create {
     }
     }
 }
 }
 
 
-fn remove_tripple_newlines(string: &str) -> String {
+fn remove_triple_newlines(string: &str) -> String {
     let mut new_string = String::new();
     let mut new_string = String::new();
     for char in string.chars() {
     for char in string.chars() {
         if char == '\n' && new_string.ends_with("\n\n") {
         if char == '\n' && new_string.ends_with("\n\n") {

+ 1 - 0
packages/cli/src/cli/mod.rs

@@ -72,6 +72,7 @@ pub enum Commands {
     #[clap(name = "fmt")]
     #[clap(name = "fmt")]
     Autoformat(autoformat::Autoformat),
     Autoformat(autoformat::Autoformat),
 
 
+    /// Check the Rust files in the project for issues.
     #[clap(name = "check")]
     #[clap(name = "check")]
     Check(check::Check),
     Check(check::Check),
 
 

+ 1 - 1
packages/cli/src/cli/plugin.rs

@@ -2,7 +2,7 @@
 
 
 use super::*;
 use super::*;
 
 
-/// Build the Rust WASM app and all of its assets.
+/// Manage plugins for dioxus cli
 #[derive(Clone, Debug, Deserialize, Subcommand)]
 #[derive(Clone, Debug, Deserialize, Subcommand)]
 #[clap(name = "plugin")]
 #[clap(name = "plugin")]
 pub enum Plugin {
 pub enum Plugin {

+ 0 - 64
packages/cli/src/cli/tool.rs

@@ -1,64 +0,0 @@
-use crate::tools;
-
-use super::*;
-
-/// Build the Rust WASM app and all of its assets.
-#[derive(Clone, Debug, Deserialize, Subcommand)]
-#[clap(name = "tool")]
-pub enum Tool {
-    /// Return all dioxus-cli support tools.
-    List {},
-    /// Get default app install path.
-    AppPath {},
-    /// Install a new tool.
-    Add { name: String },
-}
-
-impl Tool {
-    pub async fn tool(self) -> Result<()> {
-        match self {
-            Tool::List {} => {
-                for item in tools::tool_list() {
-                    if tools::Tool::from_str(item).unwrap().is_installed() {
-                        println!("- {item} [installed]");
-                    } else {
-                        println!("- {item}");
-                    }
-                }
-            }
-            Tool::AppPath {} => {
-                if let Some(v) = tools::tools_path().to_str() {
-                    println!("{}", v);
-                } else {
-                    return custom_error!("Tools path get failed.");
-                }
-            }
-            Tool::Add { name } => {
-                let tool_list = tools::tool_list();
-
-                if !tool_list.contains(&name.as_str()) {
-                    return custom_error!("Tool {name} not found.");
-                }
-                let target_tool = tools::Tool::from_str(&name).unwrap();
-
-                if target_tool.is_installed() {
-                    log::warn!("Tool {name} is installed.");
-                    return Ok(());
-                }
-
-                log::info!("Start to download tool package...");
-                if let Err(e) = target_tool.download_package().await {
-                    return custom_error!("Tool download failed: {e}");
-                }
-
-                log::info!("Start to install tool package...");
-                if let Err(e) = target_tool.install_package().await {
-                    return custom_error!("Tool install failed: {e}");
-                }
-
-                log::info!("Tool {name} installed successfully!");
-            }
-        }
-        Ok(())
-    }
-}

+ 1 - 1
packages/cli/src/cli/translate.rs

@@ -4,7 +4,7 @@ use dioxus_rsx::{BodyNode, CallBody};
 
 
 use super::*;
 use super::*;
 
 
-/// Build the Rust WASM app and all of its assets.
+/// Translate some source file into Dioxus code
 #[derive(Clone, Debug, Parser)]
 #[derive(Clone, Debug, Parser)]
 #[clap(name = "translate")]
 #[clap(name = "translate")]
 pub struct Translate {
 pub struct Translate {

+ 1 - 1
packages/cli/src/cli/version.rs

@@ -1,6 +1,6 @@
 use super::*;
 use super::*;
 
 
-/// Build the Rust WASM app and all of its assets.
+/// Print the version of this extension
 #[derive(Clone, Debug, Parser)]
 #[derive(Clone, Debug, Parser)]
 #[clap(name = "version")]
 #[clap(name = "version")]
 pub struct Version {}
 pub struct Version {}

+ 3 - 0
packages/cli/src/error.rs

@@ -35,6 +35,9 @@ pub enum Error {
     #[error("Invalid proxy URL: {0}")]
     #[error("Invalid proxy URL: {0}")]
     InvalidProxy(#[from] hyper::http::uri::InvalidUri),
     InvalidProxy(#[from] hyper::http::uri::InvalidUri),
 
 
+    #[error("Failed to establish proxy: {0}")]
+    ProxySetupError(String),
+
     #[error("Error proxying request: {0}")]
     #[error("Error proxying request: {0}")]
     ProxyRequestError(hyper::Error),
     ProxyRequestError(hyper::Error),
 
 

+ 29 - 2
packages/cli/src/server/web/proxy.rs

@@ -48,6 +48,16 @@ impl ProxyClient {
 pub fn add_proxy(mut router: Router, proxy: &WebProxyConfig) -> Result<Router> {
 pub fn add_proxy(mut router: Router, proxy: &WebProxyConfig) -> Result<Router> {
     let url: Uri = proxy.backend.parse()?;
     let url: Uri = proxy.backend.parse()?;
     let path = url.path().to_string();
     let path = url.path().to_string();
+    let trimmed_path = path.trim_end_matches('/');
+
+    if trimmed_path.is_empty() {
+        return Err(crate::Error::ProxySetupError(format!(
+            "Proxy backend URL must have a non-empty path, e.g. {}/api instead of {}",
+            proxy.backend.trim_end_matches('/'),
+            proxy.backend
+        )));
+    }
+
     let client = ProxyClient::new(url);
     let client = ProxyClient::new(url);
 
 
     // We also match everything after the path using a wildcard matcher.
     // We also match everything after the path using a wildcard matcher.
@@ -56,7 +66,7 @@ pub fn add_proxy(mut router: Router, proxy: &WebProxyConfig) -> Result<Router> {
     router = router.route(
     router = router.route(
         // Always remove trailing /'s so that the exact route
         // Always remove trailing /'s so that the exact route
         // matches.
         // matches.
-        path.trim_end_matches('/'),
+        trimmed_path,
         any(move |req| async move {
         any(move |req| async move {
             client
             client
                 .send(req)
                 .send(req)
@@ -68,7 +78,7 @@ pub fn add_proxy(mut router: Router, proxy: &WebProxyConfig) -> Result<Router> {
     // Wildcard match anything else _after_ the backend URL's path.
     // Wildcard match anything else _after_ the backend URL's path.
     // Note that we know `path` ends with a trailing `/` in this branch,
     // Note that we know `path` ends with a trailing `/` in this branch,
     // so `wildcard` will look like `http://localhost/api/*proxywildcard`.
     // so `wildcard` will look like `http://localhost/api/*proxywildcard`.
-    let wildcard = format!("{}/*proxywildcard", path.trim_end_matches('/'));
+    let wildcard = format!("{}/*proxywildcard", trimmed_path);
     router = router.route(
     router = router.route(
         &wildcard,
         &wildcard,
         any(move |req| async move {
         any(move |req| async move {
@@ -168,4 +178,21 @@ mod test {
     async fn add_proxy_trailing_slash() {
     async fn add_proxy_trailing_slash() {
         test_proxy_requests("/api/".to_string()).await;
         test_proxy_requests("/api/".to_string()).await;
     }
     }
+
+    #[test]
+    fn add_proxy_empty_path() {
+        let config = WebProxyConfig {
+            backend: "http://localhost:8000".to_string(),
+        };
+        let router = super::add_proxy(Router::new(), &config);
+        match router.unwrap_err() {
+            crate::Error::ProxySetupError(e) => {
+                assert_eq!(
+                    e,
+                    "Proxy backend URL must have a non-empty path, e.g. http://localhost:8000/api instead of http://localhost:8000"
+                );
+            }
+            e => panic!("Unexpected error type: {}", e),
+        }
+    }
 }
 }

+ 68 - 11
packages/cli/src/tools.rs

@@ -328,22 +328,79 @@ pub fn extract_zip(file: &Path, target: &Path) -> anyhow::Result<()> {
     }
     }
 
 
     for i in 0..zip.len() {
     for i in 0..zip.len() {
-        let mut file = zip.by_index(i)?;
-        if file.is_dir() {
-            // dir
-            let target = target.join(Path::new(&file.name().replace('\\', "")));
-            std::fs::create_dir_all(target)?;
+        let mut zip_entry = zip.by_index(i)?;
+
+        // check for dangerous paths
+        // see https://docs.rs/zip/latest/zip/read/struct.ZipFile.html#warnings
+        let Some(enclosed_name) = zip_entry.enclosed_name() else {
+            return Err(anyhow::anyhow!(
+                "Refusing to unpack zip entry with potentially dangerous path: zip={} entry={:?}",
+                file.display(),
+                zip_entry.name()
+            ));
+        };
+
+        let output_path = target.join(enclosed_name);
+        if zip_entry.is_dir() {
+            std::fs::create_dir_all(output_path)?;
         } else {
         } else {
-            // file
-            let file_path = target.join(Path::new(file.name()));
-            let mut target_file = if !file_path.exists() {
-                std::fs::File::create(file_path)?
+            // create parent dirs if needed
+            if let Some(parent) = output_path.parent() {
+                std::fs::create_dir_all(parent)?;
+            }
+
+            // extract file
+            let mut target_file = if !output_path.exists() {
+                std::fs::File::create(output_path)?
             } else {
             } else {
-                std::fs::File::open(file_path)?
+                std::fs::File::open(output_path)?
             };
             };
-            let _num = std::io::copy(&mut file, &mut target_file)?;
+            let _num = std::io::copy(&mut zip_entry, &mut target_file)?;
         }
         }
     }
     }
 
 
     Ok(())
     Ok(())
 }
 }
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::path::PathBuf;
+    use tempfile::tempdir;
+
+    #[test]
+    fn test_extract_zip() -> anyhow::Result<()> {
+        let path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
+            .join("tests/fixtures/test.zip");
+        let temp_dir = tempdir()?;
+        let temp_path = temp_dir.path();
+
+        extract_zip(path.as_path(), temp_path)?;
+
+        let expected_files = vec!["file1.txt", "file2.txt", "dir/file3.txt"];
+        for file in expected_files {
+            let path = temp_path.join(file);
+            assert!(path.exists(), "File not found: {:?}", path);
+        }
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_extract_zip_dangerous_path() -> anyhow::Result<()> {
+        let path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
+            .join("tests/fixtures/dangerous.zip");
+        let temp_dir = tempdir()?;
+        let temp_path = temp_dir.path();
+
+        let result = extract_zip(path.as_path(), temp_path);
+
+        let err = result.unwrap_err();
+        assert!(err
+            .to_string()
+            .contains("Refusing to unpack zip entry with potentially dangerous path: zip="));
+        assert!(err.to_string().contains("entry=\"/etc/passwd\""));
+
+        Ok(())
+    }
+}

BIN
packages/cli/tests/fixtures/dangerous.zip


BIN
packages/cli/tests/fixtures/test.zip


+ 1 - 1
packages/hot-reload/src/file_watcher.rs

@@ -248,7 +248,7 @@ pub fn init<Ctx: HotReloadingContext + Send + 'static>(cfg: Config<Ctx>) {
                                 return shutdown;
                                 return shutdown;
                             } else if log {
                             } else if log {
                                 println!(
                                 println!(
-                                    "Rebuild needed... shutting down hot reloading.\nManually rebuild the application to view futher changes."
+                                    "Rebuild needed... shutting down hot reloading.\nManually rebuild the application to view further changes."
                                 );
                                 );
                             }
                             }
                             true
                             true

+ 1 - 1
packages/html/src/events/mounted.rs

@@ -1,4 +1,4 @@
-//! Handles quering data from the renderer
+//! Handles querying data from the renderer
 
 
 use euclid::Rect;
 use euclid::Rect;
 
 

+ 1 - 1
packages/web/Cargo.toml

@@ -11,7 +11,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 
 
 [dependencies]
 [dependencies]
 dioxus-core = { workspace = true, features = ["serialize"] }
 dioxus-core = { workspace = true, features = ["serialize"] }
-dioxus-html = { workspace = true, features = ["wasm-bind"], default-features = false }
+dioxus-html = { workspace = true, features = ["wasm-bind"] }
 dioxus-interpreter-js = { workspace = true, features = [
 dioxus-interpreter-js = { workspace = true, features = [
     "sledgehammer",
     "sledgehammer",
     "minimal_bindings",
     "minimal_bindings",