From d69eab8807249356bb600312b84c4bf61ed8b4b5 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 12 May 2026 20:31:08 +0200 Subject: [PATCH] Migrated to axum (#757) --- .../Assistants/I18N/allTexts.lua | 12 +- .../Pages/Information.razor | 6 +- .../plugin.lua | 12 +- .../plugin.lua | 12 +- .../Tools/Services/RustService.FileSystem.cs | 3 +- .../Tools/Services/RustService.Retrieval.cs | 9 + runtime/Cargo.lock | 794 ++++-------------- runtime/Cargo.toml | 5 +- runtime/src/app_window.rs | 108 ++- runtime/src/clipboard.rs | 7 +- runtime/src/dotnet.rs | 5 +- runtime/src/encryption.rs | 31 +- runtime/src/environment.rs | 24 +- runtime/src/file_actions.rs | 27 +- runtime/src/file_data.rs | 128 ++- runtime/src/log.rs | 29 +- runtime/src/main.rs | 2 - runtime/src/qdrant.rs | 8 +- runtime/src/runtime_api.rs | 154 ++-- runtime/src/runtime_api_token.rs | 32 +- runtime/src/secret.rs | 12 +- 21 files changed, 488 insertions(+), 932 deletions(-) diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index 07569e09..2c340b17 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -6028,9 +6028,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1924365263"] = "This library is -- Encryption secret: is configured UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1931141322"] = "Encryption secret: is configured" --- We use Rocket to implement the runtime API. This is necessary because the runtime must be able to communicate with the user interface (IPC). Rocket is a great framework for implementing web APIs in Rust. -UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1943216839"] = "We use Rocket to implement the runtime API. This is necessary because the runtime must be able to communicate with the user interface (IPC). Rocket is a great framework for implementing web APIs in Rust." - -- Copies the following to the clipboard UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2029659664"] = "Copies the following to the clipboard" @@ -6133,6 +6130,12 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3178730036"] = "Have feature ide -- Hide Details UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3183837919"] = "Hide Details" +-- Axum server runs the internal axum service over a secure local connection. This helps AI Studio protect the communication between the Rust runtime and the user interface. +UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3208719461"] = "Axum server runs the internal axum service over a secure local connection. This helps AI Studio protect the communication between the Rust runtime and the user interface." + +-- Rustls helps secure the internal connection between the app's user interface and the Rust runtime. This protects the local communication that AI Studio needs while it is running. +UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3239817808"] = "Rustls helps secure the internal connection between the app's user interface and the Rust runtime. This protects the local communication that AI Studio needs while it is running." + -- Update Pandoc UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3249965383"] = "Update Pandoc" @@ -6226,6 +6229,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T836298648"] = "Provided by confi -- We use this library to be able to read PowerPoint files. This allows us to insert content from slides into prompts and take PowerPoint files into account in RAG processes. We thank Nils Kruthoff for his work on this Rust crate. UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T855925638"] = "We use this library to be able to read PowerPoint files. This allows us to insert content from slides into prompts and take PowerPoint files into account in RAG processes. We thank Nils Kruthoff for his work on this Rust crate." +-- Axum is used to provide the small internal service that connects the Rust runtime with the app's user interface. This lets both parts of AI Studio exchange information while the app is running. +UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T864851737"] = "Axum is used to provide the small internal service that connects the Rust runtime with the app's user interface. This lets both parts of AI Studio exchange information while the app is running." + -- For some data transfers, we need to encode the data in base64. This Rust library is great for this purpose. UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T870640199"] = "For some data transfers, we need to encode the data in base64. This Rust library is great for this purpose." diff --git a/app/MindWork AI Studio/Pages/Information.razor b/app/MindWork AI Studio/Pages/Information.razor index 3170be0f..244e8f3e 100644 --- a/app/MindWork AI Studio/Pages/Information.razor +++ b/app/MindWork AI Studio/Pages/Information.razor @@ -279,7 +279,9 @@ - + + + @@ -312,4 +314,4 @@ - \ No newline at end of file + diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua index b65b6552..b74ec6a3 100644 --- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua @@ -6030,9 +6030,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1924365263"] = "Diese Bibliothek -- Encryption secret: is configured UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1931141322"] = "Geheimnis für die Verschlüsselung: ist konfiguriert" --- We use Rocket to implement the runtime API. This is necessary because the runtime must be able to communicate with the user interface (IPC). Rocket is a great framework for implementing web APIs in Rust. -UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1943216839"] = "Wir verwenden Rocket zur Implementierung der Runtime-API. Dies ist notwendig, da die Runtime mit der Benutzeroberfläche (IPC) kommunizieren muss. Rocket ist ein ausgezeichnetes Framework zur Umsetzung von Web-APIs in Rust." - -- Copies the following to the clipboard UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2029659664"] = "Kopiert Folgendes in die Zwischenablage" @@ -6135,6 +6132,12 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3178730036"] = "Haben Sie Ideen -- Hide Details UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3183837919"] = "Details ausblenden" +-- Axum server runs the internal axum service over a secure local connection. This helps AI Studio protect the communication between the Rust runtime and the user interface. +UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3208719461"] = "Der Axum-Server führt den internen Axum-Dienst über eine sichere lokale Verbindung aus. Dadurch kann AI Studio die Kommunikation zwischen der Rust-Laufzeitumgebung und der Benutzeroberfläche schützen." + +-- Rustls helps secure the internal connection between the app's user interface and the Rust runtime. This protects the local communication that AI Studio needs while it is running. +UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3239817808"] = "Rustls hilft dabei, die interne Verbindung zwischen der Benutzeroberfläche der App und der Rust-Laufzeitumgebung abzusichern. Dadurch wird die lokale Kommunikation geschützt, die AI Studio während der Ausführung benötigt." + -- Update Pandoc UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3249965383"] = "Pandoc aktualisieren" @@ -6228,6 +6231,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T836298648"] = "Bereitgestellt vo -- We use this library to be able to read PowerPoint files. This allows us to insert content from slides into prompts and take PowerPoint files into account in RAG processes. We thank Nils Kruthoff for his work on this Rust crate. UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T855925638"] = "Wir verwenden diese Bibliothek, um PowerPoint-Dateien lesen zu können. So ist es möglich, Inhalte aus Folien in Prompts einzufügen und PowerPoint-Dateien in RAG-Prozessen zu berücksichtigen. Wir danken Nils Kruthoff für seine Arbeit an diesem Rust-Crate." +-- Axum is used to provide the small internal service that connects the Rust runtime with the app's user interface. This lets both parts of AI Studio exchange information while the app is running. +UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T864851737"] = "Axum wird verwendet, um den kleinen internen Dienst bereitzustellen, der die Rust-Laufzeitumgebung mit der Benutzeroberfläche der App verbindet. So können beide Teile von AI Studio Informationen austauschen, während die App läuft." + -- For some data transfers, we need to encode the data in base64. This Rust library is great for this purpose. UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T870640199"] = "Für einige Datenübertragungen müssen wir die Daten in Base64 kodieren. Diese Rust-Bibliothek eignet sich dafür hervorragend." diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index 434c6aa3..b46a21d9 100644 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -6030,9 +6030,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1924365263"] = "This library is -- Encryption secret: is configured UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1931141322"] = "Encryption secret: is configured" --- We use Rocket to implement the runtime API. This is necessary because the runtime must be able to communicate with the user interface (IPC). Rocket is a great framework for implementing web APIs in Rust. -UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1943216839"] = "We use Rocket to implement the runtime API. This is necessary because the runtime must be able to communicate with the user interface (IPC). Rocket is a great framework for implementing web APIs in Rust." - -- Copies the following to the clipboard UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2029659664"] = "Copies the following to the clipboard" @@ -6135,6 +6132,12 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3178730036"] = "Have feature ide -- Hide Details UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3183837919"] = "Hide Details" +-- Axum server runs the internal axum service over a secure local connection. This helps AI Studio protect the communication between the Rust runtime and the user interface. +UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3208719461"] = "Axum server runs the internal axum service over a secure local connection. This helps AI Studio protect the communication between the Rust runtime and the user interface." + +-- Rustls helps secure the internal connection between the app's user interface and the Rust runtime. This protects the local communication that AI Studio needs while it is running. +UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3239817808"] = "Rustls helps secure the internal connection between the app's user interface and the Rust runtime. This protects the local communication that AI Studio needs while it is running." + -- Update Pandoc UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3249965383"] = "Update Pandoc" @@ -6228,6 +6231,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T836298648"] = "Provided by confi -- We use this library to be able to read PowerPoint files. This allows us to insert content from slides into prompts and take PowerPoint files into account in RAG processes. We thank Nils Kruthoff for his work on this Rust crate. UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T855925638"] = "We use this library to be able to read PowerPoint files. This allows us to insert content from slides into prompts and take PowerPoint files into account in RAG processes. We thank Nils Kruthoff for his work on this Rust crate." +-- Axum is used to provide the small internal service that connects the Rust runtime with the app's user interface. This lets both parts of AI Studio exchange information while the app is running. +UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T864851737"] = "Axum is used to provide the small internal service that connects the Rust runtime with the app's user interface. This lets both parts of AI Studio exchange information while the app is running." + -- For some data transfers, we need to encode the data in base64. This Rust library is great for this purpose. UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T870640199"] = "For some data transfers, we need to encode the data in base64. This Rust library is great for this purpose." diff --git a/app/MindWork AI Studio/Tools/Services/RustService.FileSystem.cs b/app/MindWork AI Studio/Tools/Services/RustService.FileSystem.cs index e44dfa7f..4a066843 100644 --- a/app/MindWork AI Studio/Tools/Services/RustService.FileSystem.cs +++ b/app/MindWork AI Studio/Tools/Services/RustService.FileSystem.cs @@ -7,7 +7,8 @@ public sealed partial class RustService public async Task SelectDirectory(string title, string? initialDirectory = null) { PreviousDirectory? previousDirectory = initialDirectory is null ? null : new (initialDirectory); - var result = await this.http.PostAsJsonAsync($"/select/directory?title={title}", previousDirectory, this.jsonRustSerializerOptions); + var encodedTitle = Uri.EscapeDataString(title); + var result = await this.http.PostAsJsonAsync($"/select/directory?title={encodedTitle}", previousDirectory, this.jsonRustSerializerOptions); if (!result.IsSuccessStatusCode) { this.logger!.LogError($"Failed to select a directory: '{result.StatusCode}'"); diff --git a/app/MindWork AI Studio/Tools/Services/RustService.Retrieval.cs b/app/MindWork AI Studio/Tools/Services/RustService.Retrieval.cs index 6d63f022..4a3f59d5 100644 --- a/app/MindWork AI Studio/Tools/Services/RustService.Retrieval.cs +++ b/app/MindWork AI Studio/Tools/Services/RustService.Retrieval.cs @@ -13,7 +13,16 @@ public sealed partial class RustService var response = await this.http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); if (!response.IsSuccessStatusCode) + { + var responseBody = await response.Content.ReadAsStringAsync(); + this.logger?.LogError( + "Failed to read arbitrary file data from Rust runtime. Status: {StatusCode}, reason: '{ReasonPhrase}', path: '{Path}', body: '{Body}'", + response.StatusCode, + response.ReasonPhrase, + path, + responseBody); return string.Empty; + } var resultBuilder = new StringBuilder(); diff --git a/runtime/Cargo.lock b/runtime/Cargo.lock index 6d03ec12..5f07c21c 100644 --- a/runtime/Cargo.lock +++ b/runtime/Cargo.lock @@ -99,6 +99,15 @@ dependencies = [ "x11rb", ] +[[package]] +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] + [[package]] name = "asn1-rs" version = "0.7.1" @@ -323,21 +332,6 @@ dependencies = [ "debug_unsafe", ] -[[package]] -name = "atomic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" - -[[package]] -name = "atomic" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" -dependencies = [ - "bytemuck", -] - [[package]] name = "atomic-waker" version = "1.1.2" @@ -372,6 +366,80 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-server" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1df331683d982a0b9492b38127151e6453639cd34926eb9c07d4cd8c6d22bfc" +dependencies = [ + "arc-swap", + "bytes", + "either", + "fs-err", + "http", + "http-body", + "hyper", + "hyper-util", + "pin-project-lite", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "base64" version = "0.21.7" @@ -384,12 +452,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "binascii" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" - [[package]] name = "bit-set" version = "0.8.0" @@ -812,7 +874,6 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ - "percent-encoding", "time", "version_check", ] @@ -1144,39 +1205,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "devise" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d90b0c4c777a2cad215e3c7be59ac7c15adf45cf76317009b7d096d46f651d" -dependencies = [ - "devise_codegen", - "devise_core", -] - -[[package]] -name = "devise_codegen" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71b28680d8be17a570a2334922518be6adc3f58ecc880cbb404eaeb8624fd867" -dependencies = [ - "devise_core", - "quote", -] - -[[package]] -name = "devise_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7" -dependencies = [ - "bitflags 2.6.0", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.117", -] - [[package]] name = "digest" version = "0.10.7" @@ -1483,20 +1511,6 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "figment" -version = "0.10.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" -dependencies = [ - "atomic 0.6.0", - "pear", - "serde", - "toml 0.8.2", - "uncased", - "version_check", -] - [[package]] name = "file-format" version = "0.29.0" @@ -1614,6 +1628,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-err" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fde052dbfc920003cfd2c8e2c6e6d4cc7c1091538c3a24226cec0665ab08c0" +dependencies = [ + "autocfg", + "tokio", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -1820,19 +1844,6 @@ dependencies = [ "x11", ] -[[package]] -name = "generator" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "windows 0.48.0", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -2072,35 +2083,16 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.26" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.14.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "h2" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.1.0", + "http", "indexmap 2.14.0", "slab", "tokio", @@ -2157,12 +2149,6 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - [[package]] name = "hermit-abi" version = "0.5.2" @@ -2194,17 +2180,6 @@ dependencies = [ "markup5ever", ] -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.1.0" @@ -2216,17 +2191,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -2234,7 +2198,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.1.0", + "http", ] [[package]] @@ -2245,8 +2209,8 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.1.0", - "http-body 1.0.1", + "http", + "http-body", "pin-project-lite", ] @@ -2264,43 +2228,21 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.30" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ + "atomic-waker", "bytes", "futures-channel", "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", + "h2", + "http", + "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.10", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2 0.4.5", - "http 1.1.0", - "http-body 1.0.1", - "httparse", - "itoa", - "pin-project-lite", "smallvec", "tokio", "want", @@ -2313,13 +2255,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", - "http 1.1.0", - "hyper 1.6.0", + "http", + "hyper", "hyper-util", - "rustls 0.23.28", + "rustls", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.1", + "tokio-rustls", "tower-service", ] @@ -2331,7 +2273,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.6.0", + "hyper", "hyper-util", "native-tls", "tokio", @@ -2341,28 +2283,27 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", - "futures-core", "futures-util", - "http 1.1.0", - "http-body 1.0.1", - "hyper 1.6.0", + "http", + "http-body", + "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2", "system-configuration", "tokio", "tower-service", "tracing", - "windows-registry 0.5.3", + "windows-registry", ] [[package]] @@ -2614,12 +2555,6 @@ dependencies = [ "cfb", ] -[[package]] -name = "inlinable_string" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" - [[package]] name = "inout" version = "0.1.3" @@ -2655,17 +2590,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "is-terminal" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" -dependencies = [ - "hermit-abi 0.4.0", - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "is-wsl" version = "0.4.0" @@ -2887,7 +2811,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -2949,21 +2873,6 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -[[package]] -name = "loom" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] - [[package]] name = "lru-slab" version = "0.1.2" @@ -3003,13 +2912,10 @@ dependencies = [ ] [[package]] -name = "matchers" -version = "0.2.0" +name = "matchit" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "maybe-owned" @@ -3045,6 +2951,8 @@ dependencies = [ "aes", "arboard", "async-stream", + "axum", + "axum-server", "base64 0.22.1", "bytes", "calamine", @@ -3064,7 +2972,7 @@ dependencies = [ "rand_chacha 0.10.0", "rcgen", "reqwest", - "rocket", + "rustls", "serde", "serde_json", "sha2", @@ -3080,10 +2988,9 @@ dependencies = [ "tauri-plugin-updater", "tauri-plugin-window-state", "tempfile", - "time", "tokio", "tokio-stream", - "windows-registry 0.6.1", + "windows-registry", ] [[package]] @@ -3151,25 +3058,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "multer" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http 1.1.0", - "httparse", - "memchr", - "mime", - "spin", - "tokio", - "tokio-util", - "version_check", -] - [[package]] name = "native-tls" version = "0.2.12" @@ -3324,16 +3212,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.9", - "libc", -] - [[package]] name = "num_enum" version = "0.7.6" @@ -3837,29 +3715,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "pear" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" -dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi", -] - -[[package]] -name = "pear_codegen" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.117", -] - [[package]] name = "pem" version = "3.0.4" @@ -4115,19 +3970,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", - "version_check", - "yansi", -] - [[package]] name = "qoi" version = "0.4.1" @@ -4168,8 +4010,8 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.28", - "socket2 0.6.2", + "rustls", + "socket2", "thiserror 2.0.12", "tokio", "tracing", @@ -4189,7 +4031,7 @@ dependencies = [ "rand 0.9.1", "ring", "rustc-hash", - "rustls 0.23.28", + "rustls", "rustls-pki-types", "slab", "thiserror 2.0.12", @@ -4207,7 +4049,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.2", + "socket2", "tracing", "windows-sys 0.60.2", ] @@ -4383,26 +4225,6 @@ dependencies = [ "thiserror 2.0.12", ] -[[package]] -name = "ref-cast" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "regex" version = "1.10.5" @@ -4443,11 +4265,11 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.4.5", - "http 1.1.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "http-body-util", - "hyper 1.6.0", + "hyper", "hyper-rustls", "hyper-tls", "hyper-util", @@ -4458,7 +4280,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.28", + "rustls", "rustls-pki-types", "rustls-platform-verifier", "serde", @@ -4466,7 +4288,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-rustls 0.26.1", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -4516,91 +4338,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rocket" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a516907296a31df7dc04310e7043b61d71954d703b603cc6867a026d7e72d73f" -dependencies = [ - "async-stream", - "async-trait", - "atomic 0.5.3", - "binascii", - "bytes", - "either", - "figment", - "futures", - "indexmap 2.14.0", - "log", - "memchr", - "multer", - "num_cpus", - "parking_lot", - "pin-project-lite", - "rand 0.8.5", - "ref-cast", - "rocket_codegen", - "rocket_http", - "serde", - "serde_json", - "state", - "tempfile", - "time", - "tokio", - "tokio-stream", - "tokio-util", - "ubyte", - "version_check", - "yansi", -] - -[[package]] -name = "rocket_codegen" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" -dependencies = [ - "devise", - "glob", - "indexmap 2.14.0", - "proc-macro2", - "quote", - "rocket_http", - "syn 2.0.117", - "unicode-xid", - "version_check", -] - -[[package]] -name = "rocket_http" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e274915a20ee3065f611c044bd63c40757396b6dbc057d6046aec27f14f882b9" -dependencies = [ - "cookie", - "either", - "futures", - "http 0.2.12", - "hyper 0.14.30", - "indexmap 2.14.0", - "log", - "memchr", - "pear", - "percent-encoding", - "pin-project-lite", - "ref-cast", - "rustls 0.21.12", - "rustls-pemfile", - "serde", - "smallvec", - "stable-pattern", - "state", - "time", - "tokio", - "tokio-rustls 0.24.1", - "uncased", -] - [[package]] name = "roxmltree" version = "0.20.0" @@ -4657,18 +4394,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - [[package]] name = "rustls" version = "0.23.28" @@ -4679,7 +4404,7 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.10", + "rustls-webpki", "subtle", "zeroize", ] @@ -4696,15 +4421,6 @@ dependencies = [ "security-framework 3.5.1", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - [[package]] name = "rustls-pki-types" version = "1.14.0" @@ -4726,10 +4442,10 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.28", + "rustls", "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki 0.103.10", + "rustls-webpki", "security-framework 3.5.1", "security-framework-sys", "webpki-root-certs", @@ -4744,19 +4460,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.101.7" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -4770,6 +4476,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "same-file" version = "1.0.6" @@ -4815,28 +4527,12 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "security-framework" version = "2.11.1" @@ -4967,6 +4663,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_repr" version = "0.1.19" @@ -4996,6 +4703,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "3.9.0" @@ -5079,15 +4798,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - [[package]] name = "shared_child" version = "1.0.0" @@ -5140,16 +4850,6 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.2" @@ -5208,36 +4908,12 @@ dependencies = [ "system-deps", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "stable-pattern" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" -dependencies = [ - "memchr", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "state" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8" -dependencies = [ - "loom", -] - [[package]] name = "string_cache" version = "0.9.0" @@ -5364,9 +5040,9 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ "bitflags 2.6.0", "core-foundation 0.9.4", @@ -5480,7 +5156,7 @@ dependencies = [ "glob", "gtk", "heck 0.5.0", - "http 1.1.0", + "http", "jni", "libc", "log", @@ -5703,14 +5379,14 @@ dependencies = [ "dirs", "flate2", "futures-util", - "http 1.1.0", + "http", "infer", "log", "minisign-verify", "osakit", "percent-encoding", "reqwest", - "rustls 0.23.28", + "rustls", "semver", "serde", "serde_json", @@ -5750,7 +5426,7 @@ dependencies = [ "cookie", "dpi", "gtk", - "http 1.1.0", + "http", "jni", "objc2 0.6.4", "objc2-ui-kit", @@ -5773,7 +5449,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3989df2ae1c476404fe0a2e8ffc4cfbde97e51efd613c2bb5355fbc9ab52cf0" dependencies = [ "gtk", - "http 1.1.0", + "http", "jni", "log", "objc2 0.6.4", @@ -5805,7 +5481,7 @@ dependencies = [ "dom_query", "dunce", "glob", - "http 1.1.0", + "http", "infer", "json-patch", "log", @@ -5904,16 +5580,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "tiff" version = "0.9.1" @@ -5992,7 +5658,7 @@ dependencies = [ "mio", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.2", + "socket2", "tokio-macros", "windows-sys 0.61.2", ] @@ -6018,23 +5684,13 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ - "rustls 0.23.28", + "rustls", "tokio", ] @@ -6195,6 +5851,7 @@ dependencies = [ "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -6206,8 +5863,8 @@ dependencies = [ "bitflags 2.6.0", "bytes", "futures-util", - "http 1.1.0", - "http-body 1.0.1", + "http", + "http-body", "iri-string", "pin-project-lite", "tower", @@ -6233,6 +5890,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -6256,36 +5914,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", ] [[package]] @@ -6334,15 +5962,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "ubyte" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f720def6ce1ee2fc44d40ac9ed6d3a59c361c80a75a7aa8e75bb9baed31cf2ea" -dependencies = [ - "serde", -] - [[package]] name = "uds_windows" version = "1.2.1" @@ -6354,16 +5973,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "uncased" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" -dependencies = [ - "serde", - "version_check", -] - [[package]] name = "unic-char-property" version = "0.9.0" @@ -6491,12 +6100,6 @@ dependencies = [ "serde", ] -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - [[package]] name = "vcpkg" version = "0.2.15" @@ -6871,15 +6474,6 @@ dependencies = [ "windows-version", ] -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows" version = "0.61.3" @@ -7034,17 +6628,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-registry" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" -dependencies = [ - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - [[package]] name = "windows-registry" version = "0.6.1" @@ -7566,7 +7149,7 @@ dependencies = [ "dunce", "gdkx11", "gtk", - "http 1.1.0", + "http", "javascriptcore-rs", "jni", "libc", @@ -7676,15 +7259,6 @@ dependencies = [ "lzma-sys", ] -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" -dependencies = [ - "is-terminal", -] - [[package]] name = "yasna" version = "0.5.2" diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 97328e92..df26409f 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -25,7 +25,9 @@ async-stream = "0.3.6" flexi_logger = "0.31.8" log = { version = "0.4.29", features = ["kv"] } once_cell = "1.21.4" -rocket = { version = "0.5.1", features = ["json", "tls"] } +axum = { version = "0.8.9", features = ["http2", "json", "query", "tokio"] } +axum-server = { version = "0.8.0", features = ["tls-rustls"] } +rustls = { version = "0.23.28", default-features = false, features = ["aws_lc_rs"] } rand = "0.10.1" rand_chacha = "0.10.0" base64 = "0.22.1" @@ -46,7 +48,6 @@ strum_macros = "0.28.0" sysinfo = "0.38.4" # Fixes security vulnerability downstream, where the upstream is not fixed yet: -time = "0.3.47" # -> Rocket bytes = "1.11.1" # -> almost every dependency [target.'cfg(target_os = "linux")'.dependencies] diff --git a/runtime/src/app_window.rs b/runtime/src/app_window.rs index d53a2f6d..1abd7951 100644 --- a/runtime/src/app_window.rs +++ b/runtime/src/app_window.rs @@ -1,13 +1,16 @@ use std::collections::HashMap; +use std::convert::Infallible; use std::sync::Mutex; use std::time::Duration; +use async_stream::stream; +use axum::body::Body; +use axum::http::header::CONTENT_TYPE; +use axum::response::{IntoResponse, Response}; +use axum::Json; +use bytes::Bytes; use log::{debug, error, info, trace, warn}; use once_cell::sync::Lazy; -use rocket::{get, post}; -use rocket::response::stream::TextStream; -use rocket::serde::json::Json; -use rocket::serde::Serialize; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use strum_macros::Display; use tauri::{DragDropEvent,RunEvent, Manager, WindowEvent, generate_context}; use tauri::path::PathResolver; @@ -256,8 +259,7 @@ fn should_open_in_system_browser(webview: &tauri::Webview, /// When the client disconnects, the stream is closed. But we try to not lose events in between. /// The client is expected to reconnect automatically when the connection is closed and continue /// listening for events. -#[get("/events")] -pub async fn get_event_stream(_token: APIToken) -> TextStream![String] { +pub async fn get_event_stream(_token: APIToken) -> Response { // Get the lock to the event broadcast sender: let event_broadcast_lock = EVENT_BROADCAST.lock().unwrap(); @@ -269,8 +271,7 @@ pub async fn get_event_stream(_token: APIToken) -> TextStream![String] { // Drop the lock to allow other access to the sender: drop(event_broadcast_lock); - // Create the event stream: - TextStream! { + let stream = stream! { loop { // Wait at most 3 seconds for an event: match time::timeout(Duration::from_secs(3), event_receiver.recv()).await { @@ -281,11 +282,11 @@ pub async fn get_event_stream(_token: APIToken) -> TextStream![String] { // is serialized as a single line so that the client can parse it // correctly: let event_json = serde_json::to_string(&event).unwrap(); - yield event_json; + yield Ok::(Bytes::from(event_json)); // The client expects a newline after each event because we are using // a method to read the stream line-by-line: - yield "\n".to_string(); + yield Ok::(Bytes::from("\n")); }, // Case: we lagged behind and missed some events @@ -305,15 +306,17 @@ pub async fn get_event_stream(_token: APIToken) -> TextStream![String] { // Again, we have to serialize the event as a single line: let event_json = serde_json::to_string(&ping_event).unwrap(); - yield event_json; + yield Ok::(Bytes::from(event_json)); // The client expects a newline after each event because we are using // a method to read the stream line-by-line: - yield "\n".to_string(); + yield Ok::(Bytes::from("\n")); }, } } - } + }; + + ([(CONTENT_TYPE, "application/jsonl")], Body::from_stream(stream)).into_response() } /// Data structure representing a Tauri event for our event API. @@ -428,7 +431,6 @@ pub async fn change_location_to(url: &str) { } /// Checks for updates. -#[get("/updates/check")] pub async fn check_for_update(_token: APIToken) -> Json { if is_dev() { warn!(Source = "Updater"; "The app is running in development mode; skipping update check."); @@ -514,7 +516,6 @@ pub struct CheckUpdateResponse { } /// Installs the update. -#[get("/updates/install")] pub async fn install_update(_token: APIToken) { if is_dev() { warn!(Source = "Updater"; "The app is running in development mode; skipping update installation."); @@ -623,8 +624,7 @@ fn register_shortcut_with_callback( } /// Requests a controlled shutdown of the entire desktop application. -#[post("/app/exit")] -pub fn exit_app(_token: APIToken) -> Json { +pub async fn exit_app(_token: APIToken) -> Json { let app_handle = { let main_window_lock = MAIN_WINDOW.lock().unwrap(); match main_window_lock.as_ref() { @@ -653,8 +653,7 @@ pub fn exit_app(_token: APIToken) -> Json { /// Registers or updates a global shortcut. If the shortcut string is empty, /// the existing shortcut for that name will be unregistered. -#[post("/shortcuts/register", data = "")] -pub fn register_shortcut(_token: APIToken, payload: Json) -> Json { +pub async fn register_shortcut(_token: APIToken, payload: Json) -> Json { let id = payload.id; let new_shortcut = payload.shortcut.clone(); @@ -761,8 +760,7 @@ pub struct ShortcutValidationResponse { /// Validates a shortcut string without registering it. /// Checks if the shortcut syntax is valid and if it /// conflicts with existing shortcuts. -#[post("/shortcuts/validate", data = "")] -pub fn validate_shortcut(_token: APIToken, payload: Json) -> Json { +pub async fn validate_shortcut(_token: APIToken, payload: Json) -> Json { let shortcut = payload.shortcut.clone(); // Empty shortcuts are always valid (means "disabled"): @@ -816,8 +814,7 @@ pub fn validate_shortcut(_token: APIToken, payload: Json Json { +pub async fn suspend_shortcuts(_token: APIToken) -> Json { // Get the main window to access the global shortcut manager: let main_window_lock = MAIN_WINDOW.lock().unwrap(); let main_window = match main_window_lock.as_ref() { @@ -853,8 +850,7 @@ pub fn suspend_shortcuts(_token: APIToken) -> Json { } /// Resumes shortcut processing by re-registering all shortcuts with the OS. -#[post("/shortcuts/resume")] -pub fn resume_shortcuts(_token: APIToken) -> Json { +pub async fn resume_shortcuts(_token: APIToken) -> Json { // Get the main window to access the global shortcut manager: let main_window_lock = MAIN_WINDOW.lock().unwrap(); let main_window = match main_window_lock.as_ref() { @@ -954,36 +950,6 @@ fn validate_shortcut_syntax(shortcut: &str) -> bool { has_key } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn tauri_localhost_is_tauri_asset_url() { - let https_url = tauri::Url::parse("https://tauri.localhost/index.html").unwrap(); - let http_url = tauri::Url::parse("http://tauri.localhost/index.html").unwrap(); - - assert!(is_tauri_asset_url(&https_url)); - assert!(is_tauri_asset_url(&http_url)); - } - - #[test] - fn localhost_app_url_is_not_tauri_asset_url() { - let url = tauri::Url::parse("http://localhost:12345/").unwrap(); - - assert!(!is_tauri_asset_url(&url)); - assert!(is_local_http_url(&url)); - } - - #[test] - fn external_url_is_not_internal_url() { - let url = tauri::Url::parse("https://example.com/").unwrap(); - - assert!(!is_tauri_asset_url(&url)); - assert!(!is_local_http_url(&url)); - } -} - fn set_pdfium_path(path_resolver: &PathResolver) { let resource_dir = match path_resolver.resource_dir() { Ok(path) => path, @@ -1012,3 +978,33 @@ fn set_pdfium_path(path_resolver: &PathResolver) { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn tauri_localhost_is_tauri_asset_url() { + let https_url = tauri::Url::parse("https://tauri.localhost/index.html").unwrap(); + let http_url = tauri::Url::parse("http://tauri.localhost/index.html").unwrap(); + + assert!(is_tauri_asset_url(&https_url)); + assert!(is_tauri_asset_url(&http_url)); + } + + #[test] + fn localhost_app_url_is_not_tauri_asset_url() { + let url = tauri::Url::parse("http://localhost:12345/").unwrap(); + + assert!(!is_tauri_asset_url(&url)); + assert!(is_local_http_url(&url)); + } + + #[test] + fn external_url_is_not_internal_url() { + let url = tauri::Url::parse("https://example.com/").unwrap(); + + assert!(!is_tauri_asset_url(&url)); + assert!(!is_local_http_url(&url)); + } +} \ No newline at end of file diff --git a/runtime/src/clipboard.rs b/runtime/src/clipboard.rs index b00617f2..bdb612ff 100644 --- a/runtime/src/clipboard.rs +++ b/runtime/src/clipboard.rs @@ -1,14 +1,13 @@ use arboard::Clipboard; use log::{debug, error}; -use rocket::post; -use rocket::serde::json::Json; +use axum::Json; use serde::Serialize; use crate::api_token::APIToken; use crate::encryption::{EncryptedText, ENCRYPTION}; /// Sets the clipboard text to the provided encrypted text. -#[post("/clipboard/set", data = "")] -pub fn set_clipboard(_token: APIToken, encrypted_text: EncryptedText) -> Json { +pub async fn set_clipboard(_token: APIToken, encrypted_text: String) -> Json { + let encrypted_text = EncryptedText::new(encrypted_text); // Decrypt this text first: let decrypted_text = match ENCRYPTION.decrypt(&encrypted_text) { diff --git a/runtime/src/dotnet.rs b/runtime/src/dotnet.rs index 7cca4599..c5158e13 100644 --- a/runtime/src/dotnet.rs +++ b/runtime/src/dotnet.rs @@ -5,7 +5,6 @@ use base64::Engine; use base64::prelude::BASE64_STANDARD; use log::{error, info, warn}; use once_cell::sync::Lazy; -use rocket::get; use tauri::Url; use tauri_plugin_shell::process::{CommandChild, CommandEvent}; use tauri_plugin_shell::ShellExt; @@ -89,8 +88,7 @@ fn sanitize_stdout_line(line: &str) -> String { /// Returns the desired port of the .NET server. Our .NET app calls this endpoint to get /// the port where the .NET server should listen to. -#[get("/system/dotnet/port")] -pub fn dotnet_port(_token: APIToken) -> String { +pub async fn dotnet_port(_token: APIToken) -> String { let dotnet_server_port = *DOTNET_SERVER_PORT; format!("{dotnet_server_port}") } @@ -179,7 +177,6 @@ pub fn start_dotnet_server(app_handle: tauri::AppHandle) { } /// This endpoint is called by the .NET server to signal that the server is ready. -#[get("/system/dotnet/ready")] pub async fn dotnet_ready(_token: APIToken) { // We create a manual scope for the lock to be released as soon as possible. diff --git a/runtime/src/encryption.rs b/runtime/src/encryption.rs index 41506855..2c7828b3 100644 --- a/runtime/src/encryption.rs +++ b/runtime/src/encryption.rs @@ -9,19 +9,13 @@ use once_cell::sync::Lazy; use pbkdf2::pbkdf2; use rand::rngs::SysRng; use rand::{Rng, SeedableRng}; -use rocket::{data, Data, Request}; -use rocket::data::ToByteUnit; -use rocket::http::Status; -use rocket::serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use sha2::Sha512; -use tokio::io::AsyncReadExt; type Aes256CbcEnc = cbc::Encryptor; type Aes256CbcDec = cbc::Decryptor; -type DataOutcome<'r, T> = data::Outcome<'r, T>; - /// The encryption instance used for the IPC channel. pub static ENCRYPTION: Lazy = Lazy::new(|| { // @@ -170,27 +164,4 @@ impl fmt::Display for EncryptedText { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "**********") } -} - -/// Use Case: When we receive encrypted text from the client as body (e.g., in a POST request). -/// We must interpret the body as EncryptedText. -#[rocket::async_trait] -impl<'r> data::FromData<'r> for EncryptedText { - type Error = String; - - /// Parses the data as EncryptedText. - async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> DataOutcome<'r, Self> { - let content_type = req.content_type(); - if content_type.map_or(true, |ct| !ct.is_text()) { - return DataOutcome::Forward((data, Status::Ok)); - } - - let mut stream = data.open(2.mebibytes()); - let mut body = String::new(); - if let Err(e) = stream.read_to_string(&mut body).await { - return DataOutcome::Error((Status::InternalServerError, format!("Failed to read data: {}", e))); - } - - DataOutcome::Success(EncryptedText(body)) - } } \ No newline at end of file diff --git a/runtime/src/environment.rs b/runtime/src/environment.rs index 593ac2d7..68198fbd 100644 --- a/runtime/src/environment.rs +++ b/runtime/src/environment.rs @@ -1,7 +1,6 @@ use crate::api_token::APIToken; +use axum::Json; use log::{debug, info, warn}; -use rocket::get; -use rocket::serde::json::Json; use serde::Serialize; use std::collections::{HashMap, HashSet}; use std::env; @@ -29,8 +28,7 @@ pub static CONFIG_DIRECTORY: OnceLock = OnceLock::new(); static USER_LANGUAGE: OnceLock = OnceLock::new(); /// Returns the config directory. -#[get("/system/directories/config")] -pub fn get_config_directory(_token: APIToken) -> String { +pub async fn get_config_directory(_token: APIToken) -> String { match CONFIG_DIRECTORY.get() { Some(config_directory) => config_directory.clone(), None => String::from(""), @@ -38,8 +36,7 @@ pub fn get_config_directory(_token: APIToken) -> String { } /// Returns the data directory. -#[get("/system/directories/data")] -pub fn get_data_directory(_token: APIToken) -> String { +pub async fn get_data_directory(_token: APIToken) -> String { match DATA_DIRECTORY.get() { Some(data_directory) => data_directory.clone(), None => String::from(""), @@ -150,8 +147,7 @@ fn detect_user_language() -> (String, LanguageDetectionSource) { ) } -#[get("/system/language")] -pub fn read_user_language(_token: APIToken) -> String { +pub async fn read_user_language(_token: APIToken) -> String { USER_LANGUAGE .get_or_init(|| { let (user_language, source) = detect_user_language(); @@ -194,8 +190,7 @@ struct EnterpriseSourceData { encryption_secret: String, } -#[get("/system/enterprise/config/id")] -pub fn read_enterprise_env_config_id(_token: APIToken) -> String { +pub async fn read_enterprise_env_config_id(_token: APIToken) -> String { debug!("Trying to read the effective enterprise configuration ID."); resolve_effective_enterprise_config_source() .configs @@ -205,8 +200,7 @@ pub fn read_enterprise_env_config_id(_token: APIToken) -> String { .unwrap_or_default() } -#[get("/system/enterprise/config/server")] -pub fn read_enterprise_env_config_server_url(_token: APIToken) -> String { +pub async fn read_enterprise_env_config_server_url(_token: APIToken) -> String { debug!("Trying to read the effective enterprise configuration server URL."); resolve_effective_enterprise_config_source() .configs @@ -216,15 +210,13 @@ pub fn read_enterprise_env_config_server_url(_token: APIToken) -> String { .unwrap_or_default() } -#[get("/system/enterprise/config/encryption_secret")] -pub fn read_enterprise_env_config_encryption_secret(_token: APIToken) -> String { +pub async fn read_enterprise_env_config_encryption_secret(_token: APIToken) -> String { debug!("Trying to read the effective enterprise configuration encryption secret."); resolve_effective_enterprise_secret_source().encryption_secret } /// Returns all enterprise configurations from the effective source. -#[get("/system/enterprise/configs")] -pub fn read_enterprise_configs(_token: APIToken) -> Json> { +pub async fn read_enterprise_configs(_token: APIToken) -> Json> { info!("Trying to read the effective enterprise configurations."); Json(resolve_effective_enterprise_config_source().configs) } diff --git a/runtime/src/file_actions.rs b/runtime/src/file_actions.rs index 94eeb629..3ef7d81d 100644 --- a/runtime/src/file_actions.rs +++ b/runtime/src/file_actions.rs @@ -1,7 +1,7 @@ use log::{error, info}; -use rocket::post; -use rocket::serde::{Deserialize, Serialize}; -use rocket::serde::json::Json; +use axum::extract::Query; +use axum::Json; +use serde::{Deserialize, Serialize}; use tauri_plugin_dialog::{DialogExt, FileDialogBuilder}; use crate::api_token::APIToken; use crate::app_window::MAIN_WINDOW; @@ -11,6 +11,11 @@ pub struct PreviousDirectory { path: String, } +#[derive(Deserialize)] +pub struct SelectDirectoryQuery { + title: String, +} + #[derive(Clone, Deserialize)] pub struct FileTypeFilter { filter_name: String, @@ -61,10 +66,9 @@ pub struct PreviousFile { } /// Let the user select a directory. -#[post("/select/directory?", data = "<previous_directory>")] -pub fn select_directory( +pub async fn select_directory( _token: APIToken, - title: &str, + Query(query): Query<SelectDirectoryQuery>, previous_directory: Option<Json<PreviousDirectory>>, ) -> Json<DirectorySelectionResponse> { let main_window_lock = MAIN_WINDOW.lock().unwrap(); @@ -79,7 +83,7 @@ pub fn select_directory( } }; - let mut dialog = main_window.dialog().file().set_parent(main_window).set_title(title); + let mut dialog = main_window.dialog().file().set_parent(main_window).set_title(&query.title); if let Some(previous) = previous_directory { dialog = dialog.set_directory(previous.path.clone()); } @@ -118,8 +122,7 @@ pub fn select_directory( } /// Let the user select a file. -#[post("/select/file", data = "<payload>")] -pub fn select_file( +pub async fn select_file( _token: APIToken, payload: Json<SelectFileOptions>, ) -> Json<FileSelectionResponse> { @@ -178,8 +181,7 @@ pub fn select_file( } /// Let the user select some files. -#[post("/select/files", data = "<payload>")] -pub fn select_files( +pub async fn select_files( _token: APIToken, payload: Json<SelectFileOptions>, ) -> Json<FilesSelectionResponse> { @@ -229,8 +231,7 @@ pub fn select_files( } } -#[post("/save/file", data = "<payload>")] -pub fn save_file(_token: APIToken, payload: Json<SaveFileOptions>) -> Json<FileSaveResponse> { +pub async fn save_file(_token: APIToken, payload: Json<SaveFileOptions>) -> Json<FileSaveResponse> { // Create a new file dialog builder: let file_dialog = MAIN_WINDOW .lock() diff --git a/runtime/src/file_data.rs b/runtime/src/file_data.rs index b0ba1b24..43446f46 100644 --- a/runtime/src/file_data.rs +++ b/runtime/src/file_data.rs @@ -1,22 +1,24 @@ use std::cmp::min; +use std::convert::Infallible; use crate::api_token::APIToken; use crate::pandoc::PandocProcessBuilder; use crate::pdfium::PdfiumInit; use async_stream::stream; +use axum::extract::Query; +use axum::extract::rejection::QueryRejection; +use axum::response::sse::{Event, Sse}; use base64::{engine::general_purpose, Engine as _}; use calamine::{open_workbook_auto, Reader}; use file_format::{FileFormat, Kind}; use futures::{Stream, StreamExt}; use pdfium_render::prelude::Pdfium; use pptx_to_md::{ImageHandlingMode, ParserConfig, PptxContainer}; -use rocket::get; -use rocket::response::stream::{Event, EventStream}; -use rocket::serde::Serialize; -use rocket::tokio::select; -use rocket::Shutdown; +use serde::{Deserialize, Deserializer, Serialize}; +use serde::de::{Error as SerdeError, Visitor}; use std::path::Path; use std::pin::Pin; -use log::{debug, error}; +use std::fmt; +use log::{debug, error, warn}; use tokio::io::AsyncBufReadExt; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; @@ -82,39 +84,95 @@ const IMAGE_SEGMENT_SIZE_IN_CHARS: usize = 8_192; // equivalent to ~ 5500 token type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; type ChunkStream = Pin<Box<dyn Stream<Item = Result<Chunk>> + Send>>; -#[get("/retrieval/fs/extract?<path>&<stream_id>&<extract_images>")] -pub async fn extract_data(_token: APIToken, path: String, stream_id: String, extract_images: bool, mut end: Shutdown) -> EventStream![] { - EventStream! { - let stream_result = stream_data(&path, extract_images).await; - let id_ref = &stream_id; - - match stream_result { - Ok(mut stream) => { - loop { - let chunk = select! { - chunk = stream.next() => match chunk { - Some(Ok(mut chunk)) => { - chunk.set_stream_id(id_ref); - chunk - }, - Some(Err(e)) => { - yield Event::json(&format!("Error: {e}")); - break; - }, - None => break, - }, - _ = &mut end => break, - }; - - yield Event::json(&chunk); - } - }, +#[derive(Deserialize)] +pub struct ExtractDataQuery { + path: String, + stream_id: String, + #[serde(deserialize_with = "deserialize_bool_case_insensitive")] + extract_images: bool, +} - Err(e) => { - yield Event::json(&format!("Error starting stream: {e}")); +fn deserialize_bool_case_insensitive<'de, D>(deserializer: D) -> std::result::Result<bool, D::Error> +where + D: Deserializer<'de>, +{ + struct BoolVisitor; + + impl<'de> Visitor<'de> for BoolVisitor { + type Value = bool; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a boolean value") + } + + fn visit_bool<E>(self, value: bool) -> std::result::Result<Self::Value, E> { + Ok(value) + } + + fn visit_str<E>(self, value: &str) -> std::result::Result<Self::Value, E> + where + E: SerdeError, + { + match value.to_ascii_lowercase().as_str() { + "true" | "1" => Ok(true), + "false" | "0" => Ok(false), + _ => Err(E::invalid_value(serde::de::Unexpected::Str(value), &self)), } } } + + deserializer.deserialize_any(BoolVisitor) +} + +pub async fn extract_data( + _token: APIToken, + query: std::result::Result<Query<ExtractDataQuery>, QueryRejection>, +) -> Sse<impl Stream<Item = std::result::Result<Event, Infallible>>> { + let query = match query { + Ok(Query(query)) => Ok(query), + Err(e) => { + let message = format!("Invalid query for '/retrieval/fs/extract': {e}"); + warn!("{message}"); + Err(message) + }, + }; + + let stream = stream! { + match query { + Ok(query) => { + let stream_result = stream_data(&query.path, query.extract_images).await; + let id_ref = &query.stream_id; + + match stream_result { + Ok(mut stream) => { + while let Some(chunk) = stream.next().await { + match chunk { + Ok(mut chunk) => { + chunk.set_stream_id(id_ref); + yield Ok(Event::default().json_data(&chunk).unwrap_or_else(|e| Event::default().data(format!("Error: {e}")))); + }, + + Err(e) => { + yield Ok(Event::default().json_data(format!("Error: {e}")).unwrap_or_else(|_| Event::default().data(format!("Error: {e}")))); + break; + }, + } + } + }, + + Err(e) => { + yield Ok(Event::default().json_data(format!("Error starting stream: {e}")).unwrap_or_else(|_| Event::default().data(format!("Error starting stream: {e}")))); + } + }; + }, + + Err(e) => { + yield Ok(Event::default().json_data(format!("Error starting stream: {e}")).unwrap_or_else(|_| Event::default().data(format!("Error starting stream: {e}")))); + }, + } + }; + + Sse::new(stream) } async fn stream_data(file_path: &str, extract_images: bool) -> Result<ChunkStream> { diff --git a/runtime/src/log.rs b/runtime/src/log.rs index a38d942c..18f0921a 100644 --- a/runtime/src/log.rs +++ b/runtime/src/log.rs @@ -8,9 +8,8 @@ use flexi_logger::{DeferredNow, Duplicate, FileSpec, Logger, LoggerHandle}; use flexi_logger::writers::FileLogWriter; use log::{kv, Level}; use log::kv::{Key, Value, VisitSource}; -use rocket::{get, post}; -use rocket::serde::json::Json; -use rocket::serde::{Deserialize, Serialize}; +use axum::Json; +use serde::{Deserialize, Serialize}; use crate::api_token::APIToken; use crate::environment::is_dev; @@ -34,14 +33,17 @@ pub fn init_logging() { false => log_config.push_str("info, "), }; - // Set the log level for the Rocket library: - log_config.push_str("rocket=info, "); - - // Set the log level for the Rocket server: - log_config.push_str("rocket::server=warn, "); - - // Set the log level for the Reqwest library: - log_config.push_str("reqwest::async_impl::client=info"); + // Keep noisy HTTP/TLS internals at info level even in development builds: + log_config.push_str("h2=info, "); + log_config.push_str("hyper=info, "); + log_config.push_str("hyper_util=info, "); + log_config.push_str("axum=info, "); + log_config.push_str("axum_server=info, "); + log_config.push_str("tower=info, "); + log_config.push_str("tower_http=info, "); + log_config.push_str("rustls=info, "); + log_config.push_str("tokio_rustls=info, "); + log_config.push_str("reqwest=info"); // Configure the initial filename. On Unix systems, the file should start // with a dot to be hidden. @@ -224,7 +226,6 @@ fn file_logger_format( write!(w, "{}", &record.args()) } -#[get("/log/paths")] pub async fn get_log_paths(_token: APIToken) -> Json<LogPathsResponse> { Json(LogPathsResponse { log_startup_path: LOG_STARTUP_PATH.get().expect("No startup log path was set").clone(), @@ -269,9 +270,7 @@ fn log_with_level( } /// Logs an event from the .NET server. -#[post("/log/event", data = "<event>")] -pub fn log_event(_token: APIToken, event: Json<LogEvent>) -> Json<LogEventResponse> { - let event = event.into_inner(); +pub async fn log_event(_token: APIToken, Json(event): Json<LogEvent>) -> Json<LogEventResponse> { let level = parse_dotnet_log_level(&event.level); let message = event.message.as_str(); let category = event.category.as_str(); diff --git a/runtime/src/main.rs b/runtime/src/main.rs index 00a7ba90..84d280fe 100644 --- a/runtime/src/main.rs +++ b/runtime/src/main.rs @@ -1,7 +1,6 @@ // Prevents an additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -extern crate rocket; extern crate core; use log::{info, warn}; @@ -12,7 +11,6 @@ use mindwork_ai_studio::log::init_logging; use mindwork_ai_studio::metadata::MetaData; use mindwork_ai_studio::runtime_api::start_runtime_api; - #[tokio::main] async fn main() { let metadata = MetaData::init_from_string(include_str!("../../metadata.txt")); diff --git a/runtime/src/qdrant.rs b/runtime/src/qdrant.rs index 11e52005..c24b7d6d 100644 --- a/runtime/src/qdrant.rs +++ b/runtime/src/qdrant.rs @@ -7,9 +7,8 @@ use std::path::Path; use std::sync::{Arc, Mutex, OnceLock}; use log::{debug, error, info, warn}; use once_cell::sync::Lazy; -use rocket::get; -use rocket::serde::json::Json; -use rocket::serde::Serialize; +use axum::Json; +use serde::Serialize; use crate::api_token::{APIToken}; use crate::environment::{is_dev, DATA_DIRECTORY}; use crate::certificate_factory::generate_certificate; @@ -70,8 +69,7 @@ pub struct ProvideQdrantInfo { unavailable_reason: Option<String>, } -#[get("/system/qdrant/info")] -pub fn qdrant_port(_token: APIToken) -> Json<ProvideQdrantInfo> { +pub async fn qdrant_port(_token: APIToken) -> Json<ProvideQdrantInfo> { let status = QDRANT_STATUS.lock().unwrap(); let is_available = status.is_available; let unavailable_reason = status.unavailable_reason.clone(); diff --git a/runtime/src/runtime_api.rs b/runtime/src/runtime_api.rs index 4d881fe3..213c8a55 100644 --- a/runtime/src/runtime_api.rs +++ b/runtime/src/runtime_api.rs @@ -1,12 +1,16 @@ use log::info; use once_cell::sync::Lazy; -use rocket::config::Shutdown; -use rocket::figment::Figment; -use rocket::routes; +use axum::routing::{get, post}; +use axum::Router; +use axum_server::tls_rustls::RustlsConfig; +use std::net::SocketAddr; +use std::sync::Once; use crate::runtime_certificate::{CERTIFICATE, CERTIFICATE_PRIVATE_KEY}; use crate::environment::is_dev; use crate::network::get_available_port; +static RUSTLS_CRYPTO_PROVIDER_INIT: Once = Once::new(); + /// The port used for the runtime API server. In the development environment, we use a fixed /// port, in the production environment we use the next available port. This differentiation /// is necessary because we cannot communicate the port to the .NET server in the development @@ -24,109 +28,55 @@ pub static API_SERVER_PORT: Lazy<u16> = Lazy::new(|| { pub fn start_runtime_api() { let api_port = *API_SERVER_PORT; info!("Try to start the API server on 'http://localhost:{api_port}'..."); - - // Get the shutdown configuration: - let shutdown = create_shutdown(); - // Configure the runtime API server: - let figment = Figment::from(rocket::Config::release_default()) + let app = Router::new() + .route("/system/dotnet/port", get(crate::dotnet::dotnet_port)) + .route("/system/dotnet/ready", get(crate::dotnet::dotnet_ready)) + .route("/system/qdrant/info", get(crate::qdrant::qdrant_port)) + .route("/clipboard/set", post(crate::clipboard::set_clipboard)) + .route("/events", get(crate::app_window::get_event_stream)) + .route("/updates/check", get(crate::app_window::check_for_update)) + .route("/updates/install", get(crate::app_window::install_update)) + .route("/app/exit", post(crate::app_window::exit_app)) + .route("/select/directory", post(crate::file_actions::select_directory)) + .route("/select/file", post(crate::file_actions::select_file)) + .route("/select/files", post(crate::file_actions::select_files)) + .route("/save/file", post(crate::file_actions::save_file)) + .route("/secrets/get", post(crate::secret::get_secret)) + .route("/secrets/store", post(crate::secret::store_secret)) + .route("/secrets/delete", post(crate::secret::delete_secret)) + .route("/system/directories/config", get(crate::environment::get_config_directory)) + .route("/system/directories/data", get(crate::environment::get_data_directory)) + .route("/system/language", get(crate::environment::read_user_language)) + .route("/system/enterprise/config/id", get(crate::environment::read_enterprise_env_config_id)) + .route("/system/enterprise/config/server", get(crate::environment::read_enterprise_env_config_server_url)) + .route("/system/enterprise/config/encryption_secret", get(crate::environment::read_enterprise_env_config_encryption_secret)) + .route("/system/enterprise/configs", get(crate::environment::read_enterprise_configs)) + .route("/retrieval/fs/extract", get(crate::file_data::extract_data)) + .route("/log/paths", get(crate::log::get_log_paths)) + .route("/log/event", post(crate::log::log_event)) + .route("/shortcuts/register", post(crate::app_window::register_shortcut)) + .route("/shortcuts/validate", post(crate::app_window::validate_shortcut)) + .route("/shortcuts/suspend", post(crate::app_window::suspend_shortcuts)) + .route("/shortcuts/resume", post(crate::app_window::resume_shortcuts)); - // We use the next available port which was determined before: - .merge(("port", api_port)) - - // The runtime API server should be accessible only from the local machine: - .merge(("address", "127.0.0.1")) - - // We do not want to use the Ctrl+C signal to stop the server: - .merge(("ctrlc", false)) - - // Set a name for the server: - .merge(("ident", "AI Studio Runtime API")) - - // Set the maximum number of workers and blocking threads: - .merge(("workers", 3)) - .merge(("max_blocking", 12)) - - // No colors and emojis in the log output: - .merge(("cli_colors", false)) - - // Read the TLS certificate and key from the generated certificate data in-memory: - .merge(("tls.certs", CERTIFICATE.get().unwrap())) - .merge(("tls.key", CERTIFICATE_PRIVATE_KEY.get().unwrap())) - - // Set the shutdown configuration: - .merge(("shutdown", shutdown)); - - // - // Start the runtime API server in a separate thread. This is necessary - // because the server is blocking, and we need to run the Tauri app in - // parallel: - // tauri::async_runtime::spawn(async move { - rocket::custom(figment) - .mount("/", routes![ - crate::dotnet::dotnet_port, - crate::dotnet::dotnet_ready, - crate::qdrant::qdrant_port, - crate::clipboard::set_clipboard, - crate::app_window::get_event_stream, - crate::app_window::check_for_update, - crate::app_window::install_update, - crate::app_window::exit_app, - crate::file_actions::select_directory, - crate::file_actions::select_file, - crate::file_actions::select_files, - crate::file_actions::save_file, - crate::secret::get_secret, - crate::secret::store_secret, - crate::secret::delete_secret, - crate::environment::get_data_directory, - crate::environment::get_config_directory, - crate::environment::read_user_language, - crate::environment::read_enterprise_env_config_id, - crate::environment::read_enterprise_env_config_server_url, - crate::environment::read_enterprise_env_config_encryption_secret, - crate::environment::read_enterprise_configs, - crate::file_data::extract_data, - crate::log::get_log_paths, - crate::log::log_event, - crate::app_window::register_shortcut, - crate::app_window::validate_shortcut, - crate::app_window::suspend_shortcuts, - crate::app_window::resume_shortcuts, - ]) - .ignite().await.unwrap() - .launch().await.unwrap(); + install_rustls_crypto_provider(); + + let cert = CERTIFICATE.get().unwrap().clone(); + let key = CERTIFICATE_PRIVATE_KEY.get().unwrap().clone(); + let tls_config = RustlsConfig::from_pem(cert, key).await.unwrap(); + let addr = SocketAddr::from(([127, 0, 0, 1], api_port)); + + axum_server::bind_rustls(addr, tls_config) + .serve(app.into_make_service()) + .await + .unwrap(); }); } -fn create_shutdown() -> Shutdown { - // - // Create a shutdown configuration, depending on the operating system: - // - #[cfg(unix)] - { - use std::collections::HashSet; - let mut shutdown = Shutdown { - // We do not want to use the Ctrl+C signal to stop the server: - ctrlc: false, - - // Everything else is set to default for now: - ..Shutdown::default() - }; - - shutdown.signals = HashSet::new(); - shutdown - } - - #[cfg(windows)] - { - Shutdown { - // We do not want to use the Ctrl+C signal to stop the server: - ctrlc: false, - - // Everything else is set to default for now: - ..Shutdown::default() - } - } +fn install_rustls_crypto_provider() { + RUSTLS_CRYPTO_PROVIDER_INIT.call_once(|| { + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); + }); } \ No newline at end of file diff --git a/runtime/src/runtime_api_token.rs b/runtime/src/runtime_api_token.rs index f1e762c9..795f4936 100644 --- a/runtime/src/runtime_api_token.rs +++ b/runtime/src/runtime_api_token.rs @@ -1,33 +1,29 @@ use once_cell::sync::Lazy; -use rocket::http::Status; -use rocket::Request; -use rocket::request::FromRequest; +use axum::extract::FromRequestParts; +use axum::http::request::Parts; +use axum::http::StatusCode; use crate::api_token::{generate_api_token, APIToken}; -pub static API_TOKEN: Lazy<APIToken> = Lazy::new(|| generate_api_token()); +pub static API_TOKEN: Lazy<APIToken> = Lazy::new(generate_api_token); -/// The request outcome type used to handle API token requests. -type RequestOutcome<R, T> = rocket::request::Outcome<R, T>; +impl<S> FromRequestParts<S> for APIToken +where + S: Send + Sync, +{ + type Rejection = StatusCode; -/// The request outcome implementation for the API token. -#[rocket::async_trait] -impl<'r> FromRequest<'r> for APIToken { - type Error = APITokenError; - - /// Handles the API token requests. - async fn from_request(request: &'r Request<'_>) -> RequestOutcome<Self, Self::Error> { - let token = request.headers().get_one("token"); - match token { + async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> { + match parts.headers.get("token").and_then(|value| value.to_str().ok()) { Some(token) => { let received_token = APIToken::from_hex_text(token); if API_TOKEN.validate(&received_token) { - RequestOutcome::Success(received_token) + Ok(received_token) } else { - RequestOutcome::Error((Status::Unauthorized, APITokenError::Invalid)) + Err(StatusCode::UNAUTHORIZED) } } - None => RequestOutcome::Error((Status::Unauthorized, APITokenError::Missing)), + None => Err(StatusCode::UNAUTHORIZED), } } } diff --git a/runtime/src/secret.rs b/runtime/src/secret.rs index 5ae07c8b..2f074a62 100644 --- a/runtime/src/secret.rs +++ b/runtime/src/secret.rs @@ -1,15 +1,13 @@ use keyring::Entry; use log::{error, info, warn}; -use rocket::post; -use rocket::serde::json::Json; +use axum::Json; use serde::{Deserialize, Serialize}; use keyring::error::Error::NoEntry; use crate::api_token::APIToken; use crate::encryption::{EncryptedText, ENCRYPTION}; /// Stores a secret in the secret store using the operating system's keyring. -#[post("/secrets/store", data = "<request>")] -pub fn store_secret(_token: APIToken, request: Json<StoreSecret>) -> Json<StoreSecretResponse> { +pub async fn store_secret(_token: APIToken, request: Json<StoreSecret>) -> Json<StoreSecretResponse> { let user_name = request.user_name.as_str(); let decrypted_text = match ENCRYPTION.decrypt(&request.secret) { Ok(text) => text, @@ -60,8 +58,7 @@ pub struct StoreSecretResponse { } /// Retrieves a secret from the secret store using the operating system's keyring. -#[post("/secrets/get", data = "<request>")] -pub fn get_secret(_token: APIToken, request: Json<RequestSecret>) -> Json<RequestedSecret> { +pub async fn get_secret(_token: APIToken, request: Json<RequestSecret>) -> Json<RequestedSecret> { let user_name = request.user_name.as_str(); let service = format!("mindwork-ai-studio::{}", request.destination); let entry = Entry::new(service.as_str(), user_name).unwrap(); @@ -121,8 +118,7 @@ pub struct RequestedSecret { } /// Deletes a secret from the secret store using the operating system's keyring. -#[post("/secrets/delete", data = "<request>")] -pub fn delete_secret(_token: APIToken, request: Json<RequestSecret>) -> Json<DeleteSecretResponse> { +pub async fn delete_secret(_token: APIToken, request: Json<RequestSecret>) -> Json<DeleteSecretResponse> { let user_name = request.user_name.as_str(); let service = format!("mindwork-ai-studio::{}", request.destination); let entry = Entry::new(service.as_str(), user_name).unwrap();