mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-17 06:30:50 +01:00
Fix Android safe-area handling and window maximization
- Fix extension signature verification on Android by canonicalizing paths (symlink compatibility) - Implement proper safe-area-inset handling for mobile devices - Add reactive header height measurement to UI store - Fix maximized window positioning to respect safe-areas and header - Create reusable HaexDebugOverlay component for mobile debugging - Fix Swiper navigation by using absolute positioning instead of flex-1 - Remove debug logging after Android compatibility confirmed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -155,13 +155,35 @@ impl ExtensionManager {
|
||||
fn extract_and_validate_extension(
|
||||
bytes: Vec<u8>,
|
||||
temp_prefix: &str,
|
||||
app_handle: &AppHandle,
|
||||
) -> Result<ExtractedExtension, ExtensionError> {
|
||||
let temp = std::env::temp_dir().join(format!("{}_{}", temp_prefix, uuid::Uuid::new_v4()));
|
||||
// Use app_cache_dir for better Android compatibility
|
||||
let cache_dir = app_handle
|
||||
.path()
|
||||
.app_cache_dir()
|
||||
.map_err(|e| ExtensionError::InstallationFailed {
|
||||
reason: format!("Cannot get app cache dir: {}", e),
|
||||
})?;
|
||||
|
||||
let temp_id = uuid::Uuid::new_v4();
|
||||
let temp = cache_dir.join(format!("{}_{}", temp_prefix, temp_id));
|
||||
let zip_file_path = cache_dir.join(format!("{}_{}_{}.haextension", temp_prefix, temp_id, "temp"));
|
||||
|
||||
// Write bytes to a temporary ZIP file first (important for Android file system)
|
||||
fs::write(&zip_file_path, &bytes).map_err(|e| {
|
||||
ExtensionError::filesystem_with_path(zip_file_path.display().to_string(), e)
|
||||
})?;
|
||||
|
||||
// Create extraction directory
|
||||
fs::create_dir_all(&temp)
|
||||
.map_err(|e| ExtensionError::filesystem_with_path(temp.display().to_string(), e))?;
|
||||
|
||||
let mut archive = ZipArchive::new(Cursor::new(bytes)).map_err(|e| {
|
||||
// Open ZIP file from disk (more reliable on Android than from memory)
|
||||
let zip_file = fs::File::open(&zip_file_path).map_err(|e| {
|
||||
ExtensionError::filesystem_with_path(zip_file_path.display().to_string(), e)
|
||||
})?;
|
||||
|
||||
let mut archive = ZipArchive::new(zip_file).map_err(|e| {
|
||||
ExtensionError::InstallationFailed {
|
||||
reason: format!("Invalid ZIP: {}", e),
|
||||
}
|
||||
@ -173,6 +195,9 @@ impl ExtensionManager {
|
||||
reason: format!("Cannot extract ZIP: {}", e),
|
||||
})?;
|
||||
|
||||
// Clean up temporary ZIP file
|
||||
let _ = fs::remove_file(&zip_file_path);
|
||||
|
||||
// Read haextension_dir from config if it exists, otherwise use default
|
||||
let config_path = temp.join("haextension.config.json");
|
||||
let haextension_dir = if config_path.exists() {
|
||||
@ -491,9 +516,10 @@ impl ExtensionManager {
|
||||
|
||||
pub async fn preview_extension_internal(
|
||||
&self,
|
||||
app_handle: &AppHandle,
|
||||
file_bytes: Vec<u8>,
|
||||
) -> Result<ExtensionPreview, ExtensionError> {
|
||||
let extracted = Self::extract_and_validate_extension(file_bytes, "haexhub_preview")?;
|
||||
let extracted = Self::extract_and_validate_extension(file_bytes, "haexhub_preview", app_handle)?;
|
||||
|
||||
let is_valid_signature = ExtensionCrypto::verify_signature(
|
||||
&extracted.manifest.public_key,
|
||||
@ -518,7 +544,7 @@ impl ExtensionManager {
|
||||
custom_permissions: EditablePermissions,
|
||||
state: &State<'_, AppState>,
|
||||
) -> Result<String, ExtensionError> {
|
||||
let extracted = Self::extract_and_validate_extension(file_bytes, "haexhub_ext")?;
|
||||
let extracted = Self::extract_and_validate_extension(file_bytes, "haexhub_ext", &app_handle)?;
|
||||
|
||||
// Signatur verifizieren (bei Installation wird ein Fehler geworfen, nicht nur geprüft)
|
||||
ExtensionCrypto::verify_signature(
|
||||
|
||||
@ -48,7 +48,9 @@ impl ExtensionCrypto {
|
||||
let relative = path.strip_prefix(dir)
|
||||
.unwrap_or(&path)
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
.to_string()
|
||||
// Normalisiere Pfad-Separatoren zu Unix-Style (/) für plattformübergreifende Konsistenz
|
||||
.replace('\\', "/");
|
||||
(relative, path)
|
||||
})
|
||||
.collect();
|
||||
@ -56,16 +58,30 @@ impl ExtensionCrypto {
|
||||
// 3. Sortiere nach relativen Pfaden
|
||||
relative_files.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
println!("=== Files to hash ({}): ===", relative_files.len());
|
||||
for (rel, _) in &relative_files {
|
||||
println!(" - {}", rel);
|
||||
}
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
|
||||
// Canonicalize manifest path for comparison (important on Android where symlinks may differ)
|
||||
// Also ensure the canonical path is still within the allowed directory (security check)
|
||||
let canonical_manifest_path = manifest_path.canonicalize()
|
||||
.unwrap_or_else(|_| manifest_path.to_path_buf());
|
||||
|
||||
// Security: Verify canonical manifest path is still within dir
|
||||
let canonical_dir = dir.canonicalize()
|
||||
.unwrap_or_else(|_| dir.to_path_buf());
|
||||
|
||||
if !canonical_manifest_path.starts_with(&canonical_dir) {
|
||||
return Err(ExtensionError::ManifestError {
|
||||
reason: format!("Manifest path resolves outside of extension directory (potential path traversal)"),
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Inhalte der sortierten Dateien hashen
|
||||
for (_relative, file_path) in relative_files {
|
||||
if file_path == manifest_path {
|
||||
// Canonicalize file_path for comparison
|
||||
let canonical_file_path = file_path.canonicalize()
|
||||
.unwrap_or_else(|_| file_path.clone());
|
||||
|
||||
if canonical_file_path == canonical_manifest_path {
|
||||
// FÜR DIE MANIFEST.JSON:
|
||||
let content_str = fs::read_to_string(&file_path)
|
||||
.map_err(|e| ExtensionError::Filesystem { source: e })?;
|
||||
@ -94,8 +110,12 @@ impl ExtensionCrypto {
|
||||
reason: format!("Failed to serialize manifest: {}", e),
|
||||
}
|
||||
})?;
|
||||
println!("canonical_manifest_content: {}", canonical_manifest_content);
|
||||
hasher.update(canonical_manifest_content.as_bytes());
|
||||
|
||||
// Normalisiere Zeilenenden zu Unix-Style (\n), wie Node.js JSON.stringify es macht
|
||||
// Dies ist wichtig für plattformübergreifende Konsistenz (Desktop vs Android)
|
||||
let normalized_content = canonical_manifest_content.replace("\r\n", "\n");
|
||||
|
||||
hasher.update(normalized_content.as_bytes());
|
||||
} else {
|
||||
// FÜR ALLE ANDEREN DATEIEN:
|
||||
let content =
|
||||
|
||||
@ -82,12 +82,13 @@ pub async fn get_all_extensions(
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn preview_extension(
|
||||
app_handle: AppHandle,
|
||||
state: State<'_, AppState>,
|
||||
file_bytes: Vec<u8>,
|
||||
) -> Result<ExtensionPreview, ExtensionError> {
|
||||
state
|
||||
.extension_manager
|
||||
.preview_extension_internal(file_bytes)
|
||||
.preview_extension_internal(&app_handle, file_bytes)
|
||||
.await
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user