aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src-tauri/src/utils
diff options
context:
space:
mode:
authorBegonia, HE <163421589+BegoniaHe@users.noreply.github.com>2026-01-16 08:29:23 +0100
committerBegonia, HE <163421589+BegoniaHe@users.noreply.github.com>2026-01-16 08:29:23 +0100
commite38f3b0ac1277f3b918ceb5a819f98d598b1a419 (patch)
tree2702b3499ee105c9e366ccd313d44ff2aed06204 /src-tauri/src/utils
parent46ea4d96c205276ecfb84928736ef02df8c104ff (diff)
downloadDropOut-e38f3b0ac1277f3b918ceb5a819f98d598b1a419.tar.gz
DropOut-e38f3b0ac1277f3b918ceb5a819f98d598b1a419.zip
fix(path): resolve critical java path validation bug on unix
Fix a critical bug in normalize_java_path where Unix implementation would return Ok(non-existent path) when java_path == "java" and the `which` command failed to find Java in PATH. This caused game launch failures with confusing error messages. Key changes: - Add strip_unc_prefix helper for Windows UNC path handling - Fix Unix bug: explicitly return error when PATH search fails - Apply canonicalize + strip_unc_prefix pattern to both platforms - Enhanced error messages distinguishing PATH vs specific path failures - Add comprehensive unit tests covering edge cases - Update documentation to reflect actual behavior Resolves issue where Windows and Unix users could not launch games when Java path resolution failed silently. Reviewed-by: Claude Sonnet 4.5
Diffstat (limited to 'src-tauri/src/utils')
-rw-r--r--src-tauri/src/utils/path.rs160
1 files changed, 149 insertions, 11 deletions
diff --git a/src-tauri/src/utils/path.rs b/src-tauri/src/utils/path.rs
index 453064e..deaebb5 100644
--- a/src-tauri/src/utils/path.rs
+++ b/src-tauri/src/utils/path.rs
@@ -1,21 +1,43 @@
/// Path utilities for cross-platform compatibility
use std::path::PathBuf;
+/// Helper to strip UNC prefix on Windows (\\?\)
+/// This is needed because std::fs::canonicalize adds UNC prefix on Windows
+#[cfg(target_os = "windows")]
+fn strip_unc_prefix(path: PathBuf) -> PathBuf {
+ let s = path.to_string_lossy().to_string();
+ if s.starts_with(r"\\?\") {
+ return PathBuf::from(&s[4..]);
+ }
+ path
+}
+
+#[cfg(not(target_os = "windows"))]
+fn strip_unc_prefix(path: PathBuf) -> PathBuf {
+ path
+}
+
/// Normalize a Java executable path for the current platform.
///
+/// This function handles platform-specific requirements and validates that
+/// the resulting path points to an executable Java binary.
+///
/// On Windows:
/// - Adds .exe extension if missing
/// - Attempts to locate java.exe in PATH if only "java" is provided
+/// - Resolves symlinks and strips UNC prefix
/// - Validates that the path exists
///
/// On Unix:
-/// - Returns the path as-is
+/// - Attempts to locate java in PATH using `which` if only "java" is provided
+/// - Resolves symlinks to get canonical path
+/// - Validates that the path exists
///
/// # Arguments
-/// * `java_path` - The Java executable path to normalize
+/// * `java_path` - The Java executable path to normalize (can be relative, absolute, or "java")
///
/// # Returns
-/// * `Ok(PathBuf)` - Normalized path that exists
+/// * `Ok(PathBuf)` - Canonicalized, validated path to Java executable
/// * `Err(String)` - Error if the path cannot be found or validated
#[cfg(target_os = "windows")]
pub fn normalize_java_path(java_path: &str) -> Result<PathBuf, String> {
@@ -37,9 +59,17 @@ pub fn normalize_java_path(java_path: &str) -> Result<PathBuf, String> {
}
}
}
+
+ // If still not found after PATH search, return specific error
+ if !path.exists() {
+ return Err(
+ "Java not found in PATH. Please install Java or configure the full path in Settings."
+ .to_string(),
+ );
+ }
}
- // Verify the path exists
+ // Verify the path exists before canonicalization
if !path.exists() {
return Err(format!(
"Java executable not found at: {}\nPlease configure a valid Java path in Settings.",
@@ -47,38 +77,75 @@ pub fn normalize_java_path(java_path: &str) -> Result<PathBuf, String> {
));
}
- Ok(path)
+ // Canonicalize and strip UNC prefix for clean path
+ let canonical = std::fs::canonicalize(&path)
+ .map_err(|e| format!("Failed to resolve Java path '{}': {}", path.display(), e))?;
+
+ Ok(strip_unc_prefix(canonical))
}
#[cfg(not(target_os = "windows"))]
pub fn normalize_java_path(java_path: &str) -> Result<PathBuf, String> {
- let path = PathBuf::from(java_path);
+ let mut path = PathBuf::from(java_path);
+ // If path doesn't exist and it's just "java", try to find java in PATH
if !path.exists() && java_path == "java" {
- // Try to find java in PATH
if let Ok(output) = std::process::Command::new("which").arg("java").output() {
if output.status.success() {
let path_str = String::from_utf8_lossy(&output.stdout);
if let Some(first_path) = path_str.lines().next() {
- return Ok(PathBuf::from(first_path.trim()));
+ path = PathBuf::from(first_path.trim());
}
}
}
+
+ // If still not found after PATH search, return specific error
+ if !path.exists() {
+ return Err(
+ "Java not found in PATH. Please install Java or configure the full path in Settings."
+ .to_string(),
+ );
+ }
}
- if !path.exists() && java_path != "java" {
+ // Verify the path exists before canonicalization
+ if !path.exists() {
return Err(format!(
"Java executable not found at: {}\nPlease configure a valid Java path in Settings.",
path.display()
));
}
- Ok(path)
+ // Canonicalize to resolve symlinks and get absolute path
+ let canonical = std::fs::canonicalize(&path)
+ .map_err(|e| format!("Failed to resolve Java path '{}': {}", path.display(), e))?;
+
+ Ok(strip_unc_prefix(canonical))
}
#[cfg(test)]
mod tests {
use super::*;
+ use std::fs;
+ use std::io::Write;
+
+ #[test]
+ #[cfg(target_os = "windows")]
+ fn test_normalize_nonexistent_path_windows() {
+ // Non-existent path should return error
+ let result = normalize_java_path("C:\\NonExistent\\Path\\java.exe");
+ assert!(result.is_err());
+ assert!(result.unwrap_err().contains("not found"));
+ }
+
+ #[test]
+ #[cfg(not(target_os = "windows"))]
+ fn test_normalize_nonexistent_path_unix() {
+ // Non-existent path should return error
+ let result = normalize_java_path("/nonexistent/path/java");
+ assert!(result.is_err());
+ assert!(result.unwrap_err().contains("not found"));
+ }
#[test]
#[cfg(target_os = "windows")]
@@ -90,7 +157,7 @@ mod tests {
}
#[test]
- fn test_normalize_existing_path() {
+ fn test_normalize_existing_path_returns_canonical() {
// Test with a path that should exist on most systems
#[cfg(target_os = "windows")]
let test_path = "C:\\Windows\\System32\\cmd.exe";
@@ -100,6 +167,77 @@ mod tests {
if std::path::Path::new(test_path).exists() {
let result = normalize_java_path(test_path);
assert!(result.is_ok());
+ let normalized = result.unwrap();
+ // Should be absolute path after canonicalization
+ assert!(normalized.is_absolute());
+ // Should not contain UNC prefix on Windows
+ #[cfg(target_os = "windows")]
+ assert!(!normalized.to_string_lossy().starts_with(r"\\?\"));
+ }
+ }
+
+ #[test]
+ fn test_normalize_java_not_in_path() {
+ // When "java" is provided but not in PATH, should return error
+ // This test may pass if java IS in PATH, so we check error message format
+ let result = normalize_java_path("java");
+ if result.is_err() {
+ let err = result.unwrap_err();
+ assert!(
+ err.contains("not found in PATH") || err.contains("not found at"),
+ "Expected PATH error, got: {}",
+ err
+ );
+ }
+ // If Ok, java was found in PATH - test passes
+ }
+
+ #[test]
+ fn test_normalize_with_temp_file() {
+ // Create a temporary file to test with an actual existing path
+ let temp_dir = std::env::temp_dir();
+
+ #[cfg(target_os = "windows")]
+ let temp_file = temp_dir.join("test_java_normalize.exe");
+ #[cfg(not(target_os = "windows"))]
+ let temp_file = temp_dir.join("test_java_normalize");
+
+ // Create the file
+ if let Ok(mut file) = fs::File::create(&temp_file) {
+ let _ = file.write_all(b"#!/bin/sh\necho test");
+ drop(file);
+
+ // Test normalization
+ let result = normalize_java_path(temp_file.to_str().unwrap());
+
+ // Clean up
+ let _ = fs::remove_file(&temp_file);
+
+ // Verify result
+ assert!(result.is_ok(), "Failed to normalize temp file path");
+ let normalized = result.unwrap();
+ assert!(normalized.is_absolute());
+ }
+ }
+
+ #[test]
+ fn test_strip_unc_prefix() {
+ #[cfg(target_os = "windows")]
+ {
+ let unc_path = PathBuf::from(r"\\?\C:\Windows\System32\cmd.exe");
+ let stripped = strip_unc_prefix(unc_path);
+ assert_eq!(stripped.to_string_lossy(), r"C:\Windows\System32\cmd.exe");
+
+ let normal_path = PathBuf::from(r"C:\Windows\System32\cmd.exe");
+ let unchanged = strip_unc_prefix(normal_path.clone());
+ assert_eq!(unchanged, normal_path);
+ }
+
+ #[cfg(not(target_os = "windows"))]
+ {
+ let path = PathBuf::from("/usr/bin/java");
+ let unchanged = strip_unc_prefix(path.clone());
+ assert_eq!(unchanged, path);
}
}
}