Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ notify = "6.0"
tokio = { version = "1.0", features = ["fs"] }
html-escape = "0.2"
regex = "1.0"
percent-encoding = "2.3"

32 changes: 30 additions & 2 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use syntect::parsing::SyntaxSet;
use syntect::highlighting::ThemeSet;
use syntect::html::highlighted_html_for_string;
use regex;
use percent_encoding::percent_decode_str;


// Security constants
Expand Down Expand Up @@ -129,6 +130,31 @@ fn create_secure_regex(pattern: &str) -> Result<regex::Regex, String> {
.map_err(|e| format!("Failed to create regex: {}", e))
}

// Decode file:// URL using percent_encoding crate (already included via Tauri dependencies)
// Supports UTF-8 multi-byte characters including Chinese characters
fn decode_file_url(url_str: &str) -> String {
// Step 1: Decode URL-encoded string to UTF-8 using percent_encoding crate
// This automatically handles UTF-8 multi-byte characters (including Chinese)
let decoded_url = percent_decode_str(url_str)
.decode_utf8_lossy()
.to_string();

// Step 2: Now handle file:// prefix and format on the decoded string
let mut path = decoded_url.trim_start_matches("file://").to_string();

// Handle file:/// or file://localhost/ formats
// macOS typically uses file:///absolute/path format (three slashes)
if path.starts_with("//") {
// file:////path -> /path (remove extra slashes)
path = path.trim_start_matches("//").to_string();
} else if path.starts_with("localhost/") {
// file://localhost/path -> path
path = path.trim_start_matches("localhost/").to_string();
}

path
}

// Security validation functions
fn validate_file_path(file_path: &str) -> Result<PathBuf, String> {
let path = Path::new(file_path);
Expand Down Expand Up @@ -746,10 +772,9 @@ pub fn run() {
let app_handle = _app_handle;
// Find the first markdown file in the opened URLs
for url in urls {
// Convert URL to string and handle file:// URLs
let url_str = url.as_str();
let file_path = if url_str.starts_with("file://") {
url_str.trim_start_matches("file://").to_string()
decode_file_url(url_str)
} else {
url_str.to_string()
};
Expand All @@ -766,6 +791,9 @@ pub fn run() {

// Also try to emit the event to the frontend if it's ready
let _ = app_handle.emit("file-opened-via-os", &validated_str);
} else {
// Add error log for debugging
eprintln!("Failed to validate file path: {}", file_path);
}
break;
}
Expand Down