laden von erweiterungen implementiert

This commit is contained in:
Martin Drechsel
2025-05-15 09:28:45 +02:00
parent a653111071
commit ad3aa4293a
40 changed files with 1454 additions and 784 deletions

View File

@ -1,44 +0,0 @@
{
"db_name": "SQLite",
"query": "\n SELECT id, extension_id, resource, operation, path \n FROM haex_extensions_permissions \n WHERE extension_id = ? AND resource = ? AND operation = ?\n ",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "extension_id",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "resource",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "operation",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "path",
"ordinal": 4,
"type_info": "Text"
}
],
"parameters": {
"Right": 3
},
"nullable": [
false,
true,
true,
true,
true
]
},
"hash": "a73e92ff12dca9b046a6440b9a68b002662b594f7f569ee71de11e00c23ca625"
}

5
src-tauri/Cargo.lock generated
View File

@ -1536,6 +1536,7 @@ version = "0.1.0"
dependencies = [
"base64 0.22.1",
"fs_extra",
"hex",
"mime",
"mime_guess",
"rusqlite",
@ -4516,9 +4517,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.44.1"
version = "1.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
dependencies = [
"backtrace",
"bytes",

View File

@ -24,15 +24,16 @@ rusqlite = { version = "0.35.0", features = [
] }
#libsqlite3-sys = { version = "0.28", features = ["bundled-sqlcipher"] }
#sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] }
tokio = { version = "1.44", features = ["macros", "rt-multi-thread"] }
tokio = { version = "1.45", features = ["macros", "rt-multi-thread"] }
serde = { version = "1", features = ["derive"] }
hex = "0.4"
serde_json = "1"
base64 = "0.22"
mime_guess = "2.0"
mime = "0.3"
fs_extra = "1.3.0"
sqlparser = { version = "0.56.0", features = [] }
tauri = { version = "2.5", features = ["protocol-asset", "custom-protocol"] }
tauri = { version = "2.5", features = ["protocol-asset"] }
tauri-plugin-dialog = "2.2"
tauri-plugin-fs = "2.2.0"
tauri-plugin-opener = "2.2"

View File

@ -227,5 +227,5 @@ pub fn open_encrypted_database(
let mut db = state.0.lock().map_err(|e| e.to_string())?;
*db = Some(conn);
Ok(format!("Verschlüsselte CRDT-Datenbank geöffnet: {}", path))
Ok(format!("success"))
}

View File

@ -1,12 +1,70 @@
use mime;
use serde::Deserialize;
use std::fmt;
use std::fs;
use std::path::PathBuf;
use std::str::FromStr;
use tauri::{
http::{Request, Response, Uri},
AppHandle, Error as TauriError, Manager, Runtime,
http::{Request, Response},
AppHandle, Error as TauriError, Manager, Runtime, UriSchemeContext,
};
#[derive(Deserialize, Debug)]
struct ExtensionInfo {
id: String,
version: String,
}
#[derive(Debug)]
enum DataProcessingError {
HexDecoding(hex::FromHexError),
Utf8Conversion(std::string::FromUtf8Error),
JsonParsing(serde_json::Error),
}
// Implementierung von Display für benutzerfreundliche Fehlermeldungen
impl fmt::Display for DataProcessingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DataProcessingError::HexDecoding(e) => write!(f, "Hex-Dekodierungsfehler: {}", e),
DataProcessingError::Utf8Conversion(e) => {
write!(f, "UTF-8-Konvertierungsfehler: {}", e)
}
DataProcessingError::JsonParsing(e) => write!(f, "JSON-Parsing-Fehler: {}", e),
}
}
}
// Implementierung von std::error::Error (optional, aber gute Praxis für bibliotheksähnlichen Code)
impl std::error::Error for DataProcessingError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
DataProcessingError::HexDecoding(e) => Some(e),
DataProcessingError::Utf8Conversion(e) => Some(e),
DataProcessingError::JsonParsing(e) => Some(e),
}
}
}
// Implementierung von From-Traits für einfache Verwendung des '?'-Operators
impl From<hex::FromHexError> for DataProcessingError {
fn from(err: hex::FromHexError) -> Self {
DataProcessingError::HexDecoding(err)
}
}
impl From<std::string::FromUtf8Error> for DataProcessingError {
fn from(err: std::string::FromUtf8Error) -> Self {
DataProcessingError::Utf8Conversion(err)
}
}
impl From<serde_json::Error> for DataProcessingError {
fn from(err: serde_json::Error) -> Self {
DataProcessingError::JsonParsing(err)
}
}
pub fn copy_directory(source: String, destination: String) -> Result<(), String> {
println!(
"Kopiere Verzeichnis von '{}' nach '{}'",
@ -46,6 +104,7 @@ pub fn copy_directory(source: String, destination: String) -> Result<(), String>
pub fn resolve_secure_extension_asset_path<R: Runtime>(
app_handle: &AppHandle<R>,
extension_id: &str,
extension_version: &str,
requested_asset_path: &str,
) -> Result<PathBuf, String> {
// 1. Validiere die Extension ID
@ -57,6 +116,17 @@ pub fn resolve_secure_extension_asset_path<R: Runtime>(
return Err(format!("Ungültige Extension ID: {}", extension_id));
}
if extension_version.is_empty()
|| !extension_version
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '.')
{
return Err(format!(
"Ungültige Extension Version: {}",
extension_version
));
}
// 2. Bestimme das Basisverzeichnis für alle Erweiterungen (Resource Directory)
let base_extensions_dir = app_handle
.path()
@ -66,7 +136,8 @@ pub fn resolve_secure_extension_asset_path<R: Runtime>(
.join("extensions");
// 3. Verzeichnis für die spezifische Erweiterung
let specific_extension_dir = base_extensions_dir.join(extension_id);
let specific_extension_dir =
base_extensions_dir.join(format!("{}/{}", extension_id, extension_version));
// 4. Bereinige den angeforderten Asset-Pfad
let clean_relative_path = requested_asset_path
@ -112,70 +183,106 @@ pub fn resolve_secure_extension_asset_path<R: Runtime>(
}
}
pub fn handle_extension_protocol<R: Runtime>(
app_handle: &AppHandle<R>,
pub fn extension_protocol_handler<R: Runtime>(
context: &UriSchemeContext<'_, R>,
request: &Request<Vec<u8>>,
) -> Result<Response<Vec<u8>>, Box<dyn std::error::Error>> {
let uri_ref = request.uri(); // uri_ref ist &Uri
let uri_ref = request.uri();
println!("Protokoll Handler für: {}", uri_ref);
let uri_string = uri_ref.to_string(); // Konvertiere zu String
let parsed_uri = Uri::from_str(&uri_string)?; // Parse aus &str
let extension_id = parsed_uri
let host = uri_ref
.host()
.ok_or("Kein Host (Extension ID) in URI gefunden")?
.to_string();
let requested_asset_path = parsed_uri.path();
let path_str = uri_ref.path();
let segments_iter = path_str.split('/').filter(|s| !s.is_empty());
let resource_segments: Vec<&str> = segments_iter.collect();
let raw_asset_path = resource_segments.join("/");
let asset_path_to_load = if requested_asset_path == "/" || requested_asset_path.is_empty() {
"index.html"
} else {
requested_asset_path
};
match process_hex_encoded_json(&host) {
Ok(info) => {
println!("Daten erfolgreich verarbeitet:");
println!(" ID: {}", info.id);
println!(" Version: {}", info.version);
let absolute_secure_path = resolve_secure_extension_asset_path(
context.app_handle(),
&info.id,
&info.version,
&raw_asset_path,
)?;
// Sicheren Dateisystempfad auflösen (nutzt jetzt AppHandle)
let absolute_secure_path =
resolve_secure_extension_asset_path(app_handle, &extension_id, asset_path_to_load)?;
println!("absolute_secure_path: {}", absolute_secure_path.display());
// Datei lesen und Response erstellen (Code wie vorher)
match fs::read(&absolute_secure_path) {
Ok(content) => {
let mime_type = mime_guess::from_path(&absolute_secure_path)
.first_or(mime::APPLICATION_OCTET_STREAM)
.to_string();
println!(
"Liefere {} ({}) für Extension '{}'",
absolute_secure_path.display(),
mime_type,
extension_id
);
// *** KORREKTUR: Verwende Response::builder() ***
Response::builder()
.status(200)
.header("Content-Type", mime_type) // Setze Header über .header()
.body(content) // body() gibt Result<Response<Vec<u8>>, Error> zurück
.map_err(|e| e.into()) // Wandle http::Error in Box<dyn Error> um
if absolute_secure_path.exists() && absolute_secure_path.is_file() {
match fs::read(&absolute_secure_path) {
Ok(content) => {
let mime_type = mime_guess::from_path(&absolute_secure_path)
.first_or(mime::APPLICATION_OCTET_STREAM)
.to_string();
println!(
"Liefere {} ({}) ",
absolute_secure_path.display(),
mime_type
);
Response::builder()
.status(200)
.header("Content-Type", mime_type)
.body(content)
.map_err(|e| e.into())
}
Err(e) => {
eprintln!(
"Fehler beim Lesen der Datei {}: {}",
absolute_secure_path.display(),
e
);
let status_code = if e.kind() == std::io::ErrorKind::NotFound {
404
} else if e.kind() == std::io::ErrorKind::PermissionDenied {
403
} else {
500
};
Response::builder()
.status(status_code)
.body(Vec::new()) // Leerer Body für Fehler
.map_err(|e| e.into()) // Wandle http::Error in Box<dyn Error> um
}
}
} else {
// Datei nicht gefunden oder es ist keine Datei
eprintln!(
"Asset nicht gefunden oder ist kein File: {}",
absolute_secure_path.display()
);
Response::builder()
.status(404) // HTTP 404 Not Found
.body(Vec::new())
.map_err(|e| e.into())
}
}
Err(e) => {
eprintln!(
"Fehler beim Lesen der Datei {}: {}",
absolute_secure_path.display(),
e
);
let status_code = if e.kind() == std::io::ErrorKind::NotFound {
404
} else if e.kind() == std::io::ErrorKind::PermissionDenied {
403
} else {
500
};
// *** KORREKTUR: Verwende Response::builder() auch für Fehler ***
eprintln!("Fehler bei der Datenverarbeitung: {}", e);
Response::builder()
.status(status_code)
.status("500")
.body(Vec::new()) // Leerer Body für Fehler
.map_err(|e| e.into()) // Wandle http::Error in Box<dyn Error> um
.map_err(|e| e.into())
}
}
}
fn process_hex_encoded_json(hex_input: &str) -> Result<ExtensionInfo, DataProcessingError> {
// Schritt 1: Hex-String zu Bytes dekodieren
let bytes = hex::decode(hex_input)?; // Konvertiert hex::FromHexError automatisch
// Schritt 2: Bytes zu UTF-8-String konvertieren
let json_string = String::from_utf8(bytes)?; // Konvertiert FromUtf8Error automatisch
// Schritt 3: JSON-String zu Struktur parsen
let extension_info: ExtensionInfo = serde_json::from_str(&json_string)?; // Konvertiert serde_json::Error automatisch
Ok(extension_info)
}

View File

@ -12,30 +12,37 @@ pub fn run() {
let protocol_name = "haex-extension";
tauri::Builder::default()
/* .register_uri_scheme_protocol(protocol_name, move |app_handle, request| {
// Extrahiere den Request aus dem Kontext
//let request = context.request();
// Rufe die Handler-Logik auf
match extension::core::handle_extension_protocol(0, &request) {
Ok(response) => response, // Gib die erfolgreiche Response zurück
.register_uri_scheme_protocol(protocol_name, move |context, request| {
match extension::core::extension_protocol_handler(&context, &request) {
Ok(response) => response, // Wenn der Handler Ok ist, gib die Response direkt zurück
Err(e) => {
// Logge den Fehler
eprintln!("Fehler im Protokoll-Handler für '{}': {}", request.uri(), e);
// Gib eine generische 500er Fehler-Response zurück
Response::builder()
.status(500)
.mimetype("text/plain") // Einfacher Text für die Fehlermeldung
.body(format!("Internal Server Error: {}", e).into_bytes()) // Body als Vec<u8>
.unwrap() // .body() kann hier nicht fehlschlagen
// Wenn der Handler einen Fehler zurückgibt, logge ihn und erstelle eine Fehler-Response
eprintln!(
"Fehler im Custom Protocol Handler für URI '{}': {}",
request.uri(),
e
);
// Erstelle eine HTTP 500 Fehler-Response
// Du kannst hier auch spezifischere Fehler-Responses bauen, falls gewünscht.
tauri::http::Response::builder()
.status(500)
.header("Content-Type", "text/plain") // Optional, aber gut für Klarheit
.body(Vec::from(format!(
"Interner Serverfehler im Protokollhandler: {}",
e
)))
.unwrap_or_else(|build_err| {
// Fallback, falls selbst das Erstellen der Fehler-Response fehlschlägt
eprintln!("Konnte Fehler-Response nicht erstellen: {}", build_err);
tauri::http::Response::builder()
.status(500)
.body(Vec::new())
.expect("Konnte minimale Fallback-Response nicht erstellen")
})
}
}
}) */
/* .setup(move |app| {
// Der .setup Hook ist jetzt nur noch für andere Initialisierungen da
// Der AppHandle ist hier nicht mehr nötig für die Protokoll-Registrierung
println!("App Setup abgeschlossen.");
Ok(())
}) */
//extension::core::extension_protocol_handler(&context, &request)
})
.plugin(tauri_plugin_http::init())
.manage(DbConnection(Mutex::new(None)))
.manage(ExtensionState::default())
@ -57,3 +64,29 @@ pub fn run() {
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
/* fn extension_protocol_handler(
app_handle: &tauri::AppHandle, // Beachten Sie die Signaturänderung in neueren Tauri-Versionen
request: &tauri::http::Request<Vec<u8>>,
) -> Result<tauri::http::Response<Vec<u8>>, Box<dyn std::error::Error + Send + Sync>> {
let uri_str = request.uri().to_string();
let parsed_url = match Url::parse(&uri_str) {
Ok(url) => url,
Err(e) => {
eprintln!("Fehler beim Parsen der URL '{}': {}", uri_str, e);
return Ok(tauri::http::ResponseBuilder::new().status(400).body(Vec::from("Ungültige URL"))?);
}
};
let plugin_id = parsed_url.host_str().ok_or_else(|| "Fehlende Plugin-ID in der URL".to_string())?;
let path_segments: Vec<&str> = parsed_url.path_segments().ok_or_else(|| "URL hat keinen Pfad".to_string())?.collect();
if path_segments.len() < 2 {
eprintln!("Unvollständiger Pfad in URL: {}", uri_str);
return Ok(tauri::http::Response::new().status(400).body(Vec::from("Unvollständiger Pfad"))?);
}
let version = path_segments;
let file_path = path_segments[1..].join("/");
Ok(tauri::http::Response::builder()::new().status(404).body(Vec::new())?)
} */

View File

@ -7,7 +7,7 @@
"beforeDevCommand": "pnpm dev",
"devUrl": "http://localhost:3003",
"beforeBuildCommand": "pnpm generate",
"frontendDist": "../dist"
"frontendDist": "../.output/public"
},
"app": {
"windows": [
@ -18,7 +18,14 @@
}
],
"security": {
"csp": "default-src 'self' ipc: http://ipc.localhost; img-src 'self' asset: http://asset.localhost",
"csp": {
"default-src": ["'self'", "haex-extensions"],
"script-src": ["'self'", "haex-extensions"],
"style-src": ["'self'", "haex-extensions"],
"connect-src": ["'self'", "haex-extensions"],
"img-src": ["'self'", "haex-extensions", "data:"],
"font-src": ["'self'", "haex-extensions", "data:"]
},
"assetProtocol": {
"enable": true,
"scope": ["$RESOURCE/extensions/**"]