From 4f144fa540a0c43363501f3a6fe8faba66dc2a3b Mon Sep 17 00:00:00 2001 From: YueMiyuki <76854136+YueMiyuki@users.noreply.github.com> Date: Sun, 21 Jun 2026 16:47:16 +0800 Subject: [PATCH 1/4] fix(deps): Fix rust dep issues --- package.json | 11 +- pnpm-lock.yaml | 261 ++- src-tauri/Cargo.lock | 1726 +++-------------- src-tauri/Cargo.toml | 13 +- src-tauri/capabilities/default.json | 29 - src-tauri/capabilities/desktop.json | 6 +- src-tauri/risuko-bt/Cargo.toml | 2 +- src-tauri/risuko-bt/src/dht.rs | 8 +- src-tauri/risuko-bt/src/lsd.rs | 4 +- src-tauri/risuko-bt/src/magnet.rs | 18 +- src-tauri/risuko-bt/src/peer/connection.rs | 8 +- src-tauri/risuko-bt/src/session.rs | 30 +- src-tauri/risuko-bt/src/torrent.rs | 46 +- src-tauri/risuko-bt/src/upnp.rs | 8 +- src-tauri/risuko-cli/Cargo.toml | 1 - src-tauri/risuko-cookies/Cargo.toml | 30 +- .../risuko-cookies/src/browser/chromium.rs | 365 ++++ .../risuko-cookies/src/browser/firefox.rs | 173 ++ src-tauri/risuko-cookies/src/browser/mod.rs | 5 + .../risuko-cookies/src/browser/safari.rs | 126 ++ src-tauri/risuko-cookies/src/lib.rs | 501 ++--- .../risuko-cookies/src/platform/linux.rs | 81 + .../risuko-cookies/src/platform/macos.rs | 48 + src-tauri/risuko-cookies/src/platform/mod.rs | 8 + .../risuko-cookies/src/platform/windows.rs | 104 + src-tauri/risuko-cookies/src/utils/mod.rs | 2 + src-tauri/risuko-cookies/src/utils/paths.rs | 83 + src-tauri/risuko-cookies/src/utils/time.rs | 19 + src-tauri/risuko-engine/Cargo.toml | 5 +- src-tauri/risuko-engine/src/config/mod.rs | 4 +- .../risuko-engine/src/engine/cookie_store.rs | 6 +- src-tauri/risuko-engine/src/engine/dns.rs | 8 +- .../risuko-engine/src/engine/ed2k/download.rs | 24 +- .../risuko-engine/src/engine/ed2k/server.rs | 4 +- src-tauri/risuko-engine/src/engine/manager.rs | 60 +- src-tauri/risuko-engine/src/engine/mod.rs | 18 +- src-tauri/risuko-engine/src/engine/options.rs | 2 +- src-tauri/risuko-engine/src/engine/rpc.rs | 2 +- src-tauri/risuko-engine/src/engine/rss/mod.rs | 36 +- src-tauri/risuko-engine/src/engine/session.rs | 2 +- .../src/engine/ssh_known_hosts.rs | 10 +- src-tauri/risuko-engine/src/engine/torrent.rs | 24 +- .../src/engine/upload/manager.rs | 6 +- .../risuko-engine/src/engine/upload/s3.rs | 2 +- .../risuko-engine/src/engine/upload/sftp.rs | 26 +- .../risuko-engine/src/engine/upload/webdav.rs | 2 +- src-tauri/risuko-engine/src/traits.rs | 2 +- src-tauri/risuko-napi/Cargo.toml | 2 +- src-tauri/risuko-napi/src/lib.rs | 2 +- src-tauri/src/bridge.rs | 2 +- src-tauri/src/cli/commands.rs | 22 +- src-tauri/src/cli/headless.rs | 8 +- src-tauri/src/cli/mod.rs | 24 + src-tauri/src/commands/android_intent.rs | 4 +- src-tauri/src/commands/app_cmds.rs | 4 +- .../src/commands/completion_script_cmds.rs | 4 +- src-tauri/src/commands/cookie_cmds.rs | 180 +- src-tauri/src/commands/engine_cmds.rs | 2 +- src-tauri/src/commands/event_cmds.rs | 24 +- src-tauri/src/commands/file_cmds.rs | 2 +- src-tauri/src/commands/rss_cmds.rs | 6 +- src-tauri/src/commands/upload_cmds.rs | 18 +- src-tauri/src/lib.rs | 34 +- src-tauri/src/managers/flyout.rs | 2 +- src-tauri/src/managers/vault.rs | 6 +- src-tauri/src/state.rs | 4 +- .../components/Native/EngineClient.vue | 48 +- 67 files changed, 2063 insertions(+), 2294 deletions(-) create mode 100644 src-tauri/risuko-cookies/src/browser/chromium.rs create mode 100644 src-tauri/risuko-cookies/src/browser/firefox.rs create mode 100644 src-tauri/risuko-cookies/src/browser/mod.rs create mode 100644 src-tauri/risuko-cookies/src/browser/safari.rs create mode 100644 src-tauri/risuko-cookies/src/platform/linux.rs create mode 100644 src-tauri/risuko-cookies/src/platform/macos.rs create mode 100644 src-tauri/risuko-cookies/src/platform/mod.rs create mode 100644 src-tauri/risuko-cookies/src/platform/windows.rs create mode 100644 src-tauri/risuko-cookies/src/utils/mod.rs create mode 100644 src-tauri/risuko-cookies/src/utils/paths.rs create mode 100644 src-tauri/risuko-cookies/src/utils/time.rs diff --git a/package.json b/package.json index 71e0fa21..7cd4e310 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,8 @@ "node": ">=22.0.0" }, "dependencies": { - "@lucide/vue": "^1.18.0", - "@tauri-apps/api": "^2.11.0", + "@lucide/vue": "^1.21.0", + "@tauri-apps/api": "^2.11.1", "@tauri-apps/plugin-clipboard-manager": "^2.3.2", "@tauri-apps/plugin-dialog": "^2.7.1", "@vueuse/core": "^14.3.0", @@ -44,9 +44,8 @@ "clsx": "^2.1.1", "eventemitter3": "^5.0.4", "motion-v": "^2.3.0", - "reka-ui": "^2.9.10", + "reka-ui": "^2.10.0", "tailwind-merge": "^3.6.0", - "tauri-plugin-nosleep-api": "^0.1.1", "tw-animate-css": "^1.4.0", "vaul-vue": "^0.4.1", "vee-validate": "^4.15.1", @@ -55,8 +54,8 @@ "devDependencies": { "@bany/curl-to-json": "^1.2.10", "@tailwindcss/vite": "^4.3.1", - "@tauri-apps/cli": "^2.11.2", - "@types/node": "^25.9.3", + "@tauri-apps/cli": "^2.11.3", + "@types/node": "^26.0.0", "@vitejs/plugin-vue": "^6.0.7", "@vue/compiler-sfc": "^3.5.38", "axios": "^1.18.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 59c65a43..76415e71 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,11 +9,11 @@ importers: .: dependencies: '@lucide/vue': - specifier: ^1.18.0 - version: 1.18.0(vue@3.5.38(typescript@6.0.3)) + specifier: ^1.21.0 + version: 1.21.0(vue@3.5.38(typescript@6.0.3)) '@tauri-apps/api': - specifier: ^2.11.0 - version: 2.11.0 + specifier: ^2.11.1 + version: 2.11.1 '@tauri-apps/plugin-clipboard-manager': specifier: ^2.3.2 version: 2.3.2 @@ -36,20 +36,17 @@ importers: specifier: ^2.3.0 version: 2.3.0(@vueuse/core@14.3.0(vue@3.5.38(typescript@6.0.3)))(vue@3.5.38(typescript@6.0.3)) reka-ui: - specifier: ^2.9.10 - version: 2.9.10(vue@3.5.38(typescript@6.0.3)) + specifier: ^2.10.0 + version: 2.10.0(vue@3.5.38(typescript@6.0.3)) tailwind-merge: specifier: ^3.6.0 version: 3.6.0 - tauri-plugin-nosleep-api: - specifier: ^0.1.1 - version: 0.1.1 tw-animate-css: specifier: ^1.4.0 version: 1.4.0 vaul-vue: specifier: ^0.4.1 - version: 0.4.1(reka-ui@2.9.10(vue@3.5.38(typescript@6.0.3)))(vue@3.5.38(typescript@6.0.3)) + version: 0.4.1(reka-ui@2.10.0(vue@3.5.38(typescript@6.0.3)))(vue@3.5.38(typescript@6.0.3)) vee-validate: specifier: ^4.15.1 version: 4.15.1(vue@3.5.38(typescript@6.0.3)) @@ -62,16 +59,16 @@ importers: version: 1.2.10 '@tailwindcss/vite': specifier: ^4.3.1 - version: 4.3.1(vite@8.0.16(@types/node@25.9.3)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0)) + version: 4.3.1(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0)) '@tauri-apps/cli': - specifier: ^2.11.2 - version: 2.11.2 + specifier: ^2.11.3 + version: 2.11.3 '@types/node': - specifier: ^25.9.3 - version: 25.9.3 + specifier: ^26.0.0 + version: 26.0.0 '@vitejs/plugin-vue': specifier: ^6.0.7 - version: 6.0.7(vite@8.0.16(@types/node@25.9.3)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)) + version: 6.0.7(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)) '@vue/compiler-sfc': specifier: ^3.5.38 version: 3.5.38 @@ -113,16 +110,16 @@ importers: version: 6.0.3 vite: specifier: ^8.0.16 - version: 8.0.16(@types/node@25.9.3)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0) + version: 8.0.16(@types/node@26.0.0)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0) vite-plugin-static-copy: specifier: ^4.1.1 - version: 4.1.1(vite@8.0.16(@types/node@25.9.3)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0)) + version: 4.1.1(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0)) vue: specifier: ^3.5.38 version: 3.5.38(typescript@6.0.3) vue-router: specifier: ^5.1.0 - version: 5.1.0(@vue/compiler-sfc@3.5.38)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)))(vite@8.0.16(@types/node@25.9.3)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)) + version: 5.1.0(@vue/compiler-sfc@3.5.38)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)))(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)) vue-tsc: specifier: ^3.3.5 version: 3.3.5(typescript@6.0.3) @@ -176,24 +173,24 @@ importers: packages: - '@babel/generator@8.0.0-rc.6': - resolution: {integrity: sha512-6mIzgVK8DgEzvIapoQwhXTMnnkuE4STQmVv9H03i/tZ2ml8oev3TRvZJgTenK2Bsq0YWNtzOrFdTyNzCMFtjJQ==} + '@babel/generator@8.0.0': + resolution: {integrity: sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==} engines: {node: ^22.18.0 || >=24.11.0} '@babel/helper-string-parser@7.29.7': resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@8.0.0-rc.6': - resolution: {integrity: sha512-BCkFy+zN6kXQed3YOT7aJl93NfDSzQc3pBfsvTVPs9gU9X3V0aefEF5kwBT0E+mDWH9QgKaZstYUQN9VdQZT4g==} + '@babel/helper-string-parser@8.0.0': + resolution: {integrity: sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==} engines: {node: ^22.18.0 || >=24.11.0} '@babel/helper-validator-identifier@7.29.7': resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@8.0.0-rc.6': - resolution: {integrity: sha512-nVJ+1JcCgntv8d78rRo++o2wuODT0Irknx2BF8Np4Ft2CRgjLqIs4qzSZ8b66yGbBdMWGmZBO9WEZv1hhNiSpg==} + '@babel/helper-validator-identifier@8.0.2': + resolution: {integrity: sha512-9Fr9QeyCAyi1BR1jKZ6uYQ24EIhQUx5ReHfQU7drOE+TPOb+w11/dsqLkMOT2U29OdCT71XajrOT8xDc1C7orA==} engines: {node: ^22.18.0 || >=24.11.0} '@babel/parser@7.29.7': @@ -201,8 +198,8 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@8.0.0-rc.6': - resolution: {integrity: sha512-rOS8IpdO7mQELkTPlCsTgPejO0bFuZdEDCGQJouYbYf9e1FLTym7Fei2pEjq8q7MWbX0ravcd7QQYKs1TxOuog==} + '@babel/parser@8.0.0': + resolution: {integrity: sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ==} engines: {node: ^22.18.0 || >=24.11.0} hasBin: true @@ -210,8 +207,8 @@ packages: resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} - '@babel/types@8.0.0-rc.6': - resolution: {integrity: sha512-p7/ABylAYlexb31wtRdIfH9L9A0Z2T/9H6zAqzqndkY2PLkvNNc580wGhp/gGKN4Sp9sQvSkhc6Oga8/O+wTyw==} + '@babel/types@8.0.0': + resolution: {integrity: sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==} engines: {node: ^22.18.0 || >=24.11.0} '@bany/curl-to-json@1.2.10': @@ -263,8 +260,8 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@lucide/vue@1.18.0': - resolution: {integrity: sha512-DmnUpDB85PlMZ+ofjZLcKq3JoJnaD1bk7SIj9xwUvqerfNqA6hCLa0/m3gIybH6rdrErABbqvTD8yYJdNqiZ3Q==} + '@lucide/vue@1.21.0': + resolution: {integrity: sha512-eoFn3tppjKAc12ZqdnRSMFdtwQ1ZMRQFb6SV1Eub6Y8kU28ccnqKeSFdnur9hMg8gIbosU2Y3WFJr/J/xS/IlQ==} peerDependencies: vue: '>=3.0.1' @@ -630,86 +627,82 @@ packages: peerDependencies: vue: ^2.7.0 || ^3.0.0 - '@tauri-apps/api@1.0.0-rc.4': - resolution: {integrity: sha512-1HaUsx8+TzFHDoQ+Mmd6RWaMsPyZlurQHgACDt+il5e/ui6pDkumVXaS92SnY5XOcS4gBC0BkgjVfkWgcm/Oww==} - engines: {node: '>= 12.13.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} - - '@tauri-apps/api@2.11.0': - resolution: {integrity: sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==} + '@tauri-apps/api@2.11.1': + resolution: {integrity: sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==} - '@tauri-apps/cli-darwin-arm64@2.11.2': - resolution: {integrity: sha512-+4UZzLt+eOAEQCwgd+TqKgyUJMrvx+BgdXLLaqJYmPqzP+nE6YZr/hY6CWLYGQb8jFn99jEkmC6uA3tNvamA1w==} + '@tauri-apps/cli-darwin-arm64@2.11.3': + resolution: {integrity: sha512-BxpaM8bsCoXs3wd4WKYhas/G1gs7+r7B+e4WnyRk2GEoVOouJB1hoL6E6YLXZDXbYci6VFdrNnobQwd2uVL4ew==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@tauri-apps/cli-darwin-x64@2.11.2': - resolution: {integrity: sha512-VjYYtZUPqDMLutSfJEyxFE3Bz+DPi7c8wC3imckgvciLDZLq4qwKJxBicg0BXGhXjJsl8vKWgWRFNMPELQ+Xyg==} + '@tauri-apps/cli-darwin-x64@2.11.3': + resolution: {integrity: sha512-DbZYuPB1ZEzcAHYeyCvo3ltzM27+aXwPloCrtexPnmgPgulYJm3TOq6aC4S+wPhSXteddg8zImtNkvx/gQzmwg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@tauri-apps/cli-linux-arm-gnueabihf@2.11.2': - resolution: {integrity: sha512-yMemD6f4i95AQriS8EazyOFzbE34yjnP16i3IOzpHGQvBoy2DjypFMFBq0NtPuITURv/cOGguRtHR5d79/9CSA==} + '@tauri-apps/cli-linux-arm-gnueabihf@2.11.3': + resolution: {integrity: sha512-741NduqBmz1XkdU8yz3OI/kBZtqHbvxo9F9ytIeWYU69/Ba9dcZEbqOU++Dp0G/XU8vAI0TfTywEl+p+BbLvaA==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@tauri-apps/cli-linux-arm64-gnu@2.11.2': - resolution: {integrity: sha512-cgI91D2wL8GSgoWwZXDqt+DwnuZCP2/bz03QAE4TrhgAKIsrB4hX26W/H1EONPUUNkqrsgeCD0wU6pcNjV/5kw==} + '@tauri-apps/cli-linux-arm64-gnu@2.11.3': + resolution: {integrity: sha512-RWAXT8pTqIczXcoic+LXlo6uEbAXGB0cgh6Pg7Y9xVnEbzryQ1JHtRGj9SxzrKSemBIDBH6Qc24kK2G69i8ofA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@tauri-apps/cli-linux-arm64-musl@2.11.2': - resolution: {integrity: sha512-X1rm0BERqAAggtYTESSgXrS3sz4Sb/OiPiz54UqISlXW+GkR3vNIGnsy/lejNmoXGVqri3Q53BCfQiclOIyRPw==} + '@tauri-apps/cli-linux-arm64-musl@2.11.3': + resolution: {integrity: sha512-qomqYS+yAkd0gXMRmhguWXc7RfVN+XKKXaEwbf5QmKURwydLFOTldd6F8/WoZDSsBMrV8dpNxz0YneGLmobiSA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@tauri-apps/cli-linux-riscv64-gnu@2.11.2': - resolution: {integrity: sha512-usbMLJbT3KtkOrBMDVeGYNM35aTHXx38SJSzTMSqqjeUIOQ+iVPjb2yAGNAE+KqmBbAx4FOFIyMeKXx2M/JKGQ==} + '@tauri-apps/cli-linux-riscv64-gnu@2.11.3': + resolution: {integrity: sha512-jOCXbDqeDj5XcclsOBAaXjtTgwZCVg8zEZ+dbPUCoADOgljFgL0rOkYTc96vUYgOrYEfuHYihWMxIDGaD6GwJw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] libc: [glibc] - '@tauri-apps/cli-linux-x64-gnu@2.11.2': - resolution: {integrity: sha512-Ru4gwJKPG0ctVGchRGpRup4Y4lW2SSfFnrbQcyHhCliKy4g8Qz97TrUgCur4CbWyAgKxvGh3SjrkA0LDYzDGiw==} + '@tauri-apps/cli-linux-x64-gnu@2.11.3': + resolution: {integrity: sha512-+u3HO/F3gHwL48t9gWN/urqZvpaEJzBFmTaq5eSIhvy8TOvnhb+LgJr3Q3BG+5JxuBrCUjqtOEz6gMttdJFSBA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@tauri-apps/cli-linux-x64-musl@2.11.2': - resolution: {integrity: sha512-eUm7T6clN1MMmNSRQ9gaWsQdyehQx2Gmn5hht/QUlqZQI/qcP2OJK5dnaxqwFzCr2HdsEo9ydxaqcS1oJzMvUw==} + '@tauri-apps/cli-linux-x64-musl@2.11.3': + resolution: {integrity: sha512-spr5Jpr6KF/vehkLwJ0YmdGv8QwpWU+uw7J8bgijO0sox6ZCYsSNMbcsQjTqPi4xl+p0woIYpWXgChgHYpAc8g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@tauri-apps/cli-win32-arm64-msvc@2.11.2': - resolution: {integrity: sha512-HeeZW80jU+gVTOEX4X/hC6NVSAdDVXajwP5fxIZ/3z9WvUC7qrudX2GMTilYq6Dg0e0sk0XgsAJD1hZ5wPBXUA==} + '@tauri-apps/cli-win32-arm64-msvc@2.11.3': + resolution: {integrity: sha512-abkoRQih5xBa3vz2spWaex0kP/MzVzVPQHom2f8jnCq46R/luOD6Uy85EMU9/bfzf6ZzdorWJsgO+OMX90Fx2w==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@tauri-apps/cli-win32-ia32-msvc@2.11.2': - resolution: {integrity: sha512-YhjQNZcXfbkCLyazSv1nPnJ9iRFE1wm6kc51FDbU10/Dk09io+6PAGMLjkxnX2GdM0qMnDmTjstY8mTDVvtKeA==} + '@tauri-apps/cli-win32-ia32-msvc@2.11.3': + resolution: {integrity: sha512-Vy6AvzFm1G40hg3r+OYDB3jkuu7R4wnMzbQBKuun9v6Cgg8IierpLL7toMzrZKs/8NlG8Sg4x1iLFR52oknyHg==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@tauri-apps/cli-win32-x64-msvc@2.11.2': - resolution: {integrity: sha512-d2JchlFIpZevZVReyqhQOekJmb1UH3rhZ5VX6sH3ty9ETE0TKQavpihvoScUXfKKpW6HZC0MrFGRU0ZtD+w3gA==} + '@tauri-apps/cli-win32-x64-msvc@2.11.3': + resolution: {integrity: sha512-GlciF75GdbseajOyib2aCHwE3BXIqZ1liGKWLFRvCdN5wm8h8hFssEVKQ/6E+2jsMLg9v7LCTb983YFnn0QSww==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@tauri-apps/cli@2.11.2': - resolution: {integrity: sha512-bk3HemqvGRoy+5D/dVMUQHKMYLglD0jVnMm/0iGMH6ufZ+p8r14m6BpIixwij3PBvZdvORUp1YifTD8QxVZ1Nw==} + '@tauri-apps/cli@2.11.3': + resolution: {integrity: sha512-EElQe8z8uD7Pi5++tJ/UfEwWuK08rd3oCDYdeIbJAb6pZRrxlqmoF5gh5H5YvzmUPhS4IRCaLSsQhvWkrfK+GQ==} engines: {node: '>= 10'} hasBin: true @@ -725,8 +718,8 @@ packages: '@types/jsesc@2.5.1': resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} - '@types/node@25.9.3': - resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==} + '@types/node@26.0.0': + resolution: {integrity: sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==} '@types/web-bluetooth@0.0.20': resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} @@ -1287,8 +1280,8 @@ packages: muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + nanoid@3.3.13: + resolution: {integrity: sha512-sPdqC6ByMVVGvF1ynvvMo0/o+oD1VX7DaHhijt1bFgjvBkHBib4t49GoNDhf2NDta4oeUNlaGbSt5K7qjZ955Q==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -1370,8 +1363,8 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} - reka-ui@2.9.10: - resolution: {integrity: sha512-yuvZVTp4fWH2G3qk+ze/x6YYlyc2Xl1d+eMUlIYrKqzTowBKteoDoN17fitURmqSUck3mc7JbcYgp49DnGu2EQ==} + reka-ui@2.10.0: + resolution: {integrity: sha512-HIUVfSBM/AyGkcUI7aiOxxMc4N+0UD2ZEun8dcrT0H4fveotEoeDdvzyZu97eeEvEa1H9oGHoOpApkfxlgnC7g==} peerDependencies: vue: '>= 3.4.0' @@ -1424,9 +1417,6 @@ packages: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} - tauri-plugin-nosleep-api@0.1.1: - resolution: {integrity: sha512-LN/coT6i2MDDFCVLFfUD6g+qDyosjG8xVFED76NKZYzdAxgrYVCr4v6zrQaDoShP0P1J58WJ45Yv47Hv0Z3+1A==} - terser@5.48.0: resolution: {integrity: sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==} engines: {node: '>=10'} @@ -1446,10 +1436,6 @@ packages: tw-animate-css@1.4.0: resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} - type-fest@2.12.2: - resolution: {integrity: sha512-qt6ylCGpLjZ7AaODxbpyBZSs9fCI9SkL3Z9q2oxMBQhs/uyY+VD8jHA8ULCGmWQJlBgqvO3EJeAngOHD8zQCrQ==} - engines: {node: '>=12.20'} - type-fest@4.41.0: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} @@ -1462,8 +1448,8 @@ packages: ufo@1.6.4: resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} - undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} unplugin-utils@0.3.1: resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==} @@ -1613,10 +1599,10 @@ packages: snapshots: - '@babel/generator@8.0.0-rc.6': + '@babel/generator@8.0.0': dependencies: - '@babel/parser': 8.0.0-rc.6 - '@babel/types': 8.0.0-rc.6 + '@babel/parser': 8.0.0 + '@babel/types': 8.0.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 '@types/jsesc': 2.5.1 @@ -1624,29 +1610,29 @@ snapshots: '@babel/helper-string-parser@7.29.7': {} - '@babel/helper-string-parser@8.0.0-rc.6': {} + '@babel/helper-string-parser@8.0.0': {} '@babel/helper-validator-identifier@7.29.7': {} - '@babel/helper-validator-identifier@8.0.0-rc.6': {} + '@babel/helper-validator-identifier@8.0.2': {} '@babel/parser@7.29.7': dependencies: '@babel/types': 7.29.7 - '@babel/parser@8.0.0-rc.6': + '@babel/parser@8.0.0': dependencies: - '@babel/types': 8.0.0-rc.6 + '@babel/types': 8.0.0 '@babel/types@7.29.7': dependencies: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@babel/types@8.0.0-rc.6': + '@babel/types@8.0.0': dependencies: - '@babel/helper-string-parser': 8.0.0-rc.6 - '@babel/helper-validator-identifier': 8.0.0-rc.6 + '@babel/helper-string-parser': 8.0.0 + '@babel/helper-validator-identifier': 8.0.2 '@bany/curl-to-json@1.2.10': dependencies: @@ -1720,7 +1706,7 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@lucide/vue@1.18.0(vue@3.5.38(typescript@6.0.3))': + '@lucide/vue@1.21.0(vue@3.5.38(typescript@6.0.3))': dependencies: vue: 3.5.38(typescript@6.0.3) @@ -1946,12 +1932,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.1 '@tailwindcss/oxide-win32-x64-msvc': 4.3.1 - '@tailwindcss/vite@4.3.1(vite@8.0.16(@types/node@25.9.3)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0))': + '@tailwindcss/vite@4.3.1(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0))': dependencies: '@tailwindcss/node': 4.3.1 '@tailwindcss/oxide': 4.3.1 tailwindcss: 4.3.1 - vite: 8.0.16(@types/node@25.9.3)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0) + vite: 8.0.16(@types/node@26.0.0)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0) '@tanstack/virtual-core@3.17.1': {} @@ -1960,66 +1946,62 @@ snapshots: '@tanstack/virtual-core': 3.17.1 vue: 3.5.38(typescript@6.0.3) - '@tauri-apps/api@1.0.0-rc.4': - dependencies: - type-fest: 2.12.2 - - '@tauri-apps/api@2.11.0': {} + '@tauri-apps/api@2.11.1': {} - '@tauri-apps/cli-darwin-arm64@2.11.2': + '@tauri-apps/cli-darwin-arm64@2.11.3': optional: true - '@tauri-apps/cli-darwin-x64@2.11.2': + '@tauri-apps/cli-darwin-x64@2.11.3': optional: true - '@tauri-apps/cli-linux-arm-gnueabihf@2.11.2': + '@tauri-apps/cli-linux-arm-gnueabihf@2.11.3': optional: true - '@tauri-apps/cli-linux-arm64-gnu@2.11.2': + '@tauri-apps/cli-linux-arm64-gnu@2.11.3': optional: true - '@tauri-apps/cli-linux-arm64-musl@2.11.2': + '@tauri-apps/cli-linux-arm64-musl@2.11.3': optional: true - '@tauri-apps/cli-linux-riscv64-gnu@2.11.2': + '@tauri-apps/cli-linux-riscv64-gnu@2.11.3': optional: true - '@tauri-apps/cli-linux-x64-gnu@2.11.2': + '@tauri-apps/cli-linux-x64-gnu@2.11.3': optional: true - '@tauri-apps/cli-linux-x64-musl@2.11.2': + '@tauri-apps/cli-linux-x64-musl@2.11.3': optional: true - '@tauri-apps/cli-win32-arm64-msvc@2.11.2': + '@tauri-apps/cli-win32-arm64-msvc@2.11.3': optional: true - '@tauri-apps/cli-win32-ia32-msvc@2.11.2': + '@tauri-apps/cli-win32-ia32-msvc@2.11.3': optional: true - '@tauri-apps/cli-win32-x64-msvc@2.11.2': + '@tauri-apps/cli-win32-x64-msvc@2.11.3': optional: true - '@tauri-apps/cli@2.11.2': + '@tauri-apps/cli@2.11.3': optionalDependencies: - '@tauri-apps/cli-darwin-arm64': 2.11.2 - '@tauri-apps/cli-darwin-x64': 2.11.2 - '@tauri-apps/cli-linux-arm-gnueabihf': 2.11.2 - '@tauri-apps/cli-linux-arm64-gnu': 2.11.2 - '@tauri-apps/cli-linux-arm64-musl': 2.11.2 - '@tauri-apps/cli-linux-riscv64-gnu': 2.11.2 - '@tauri-apps/cli-linux-x64-gnu': 2.11.2 - '@tauri-apps/cli-linux-x64-musl': 2.11.2 - '@tauri-apps/cli-win32-arm64-msvc': 2.11.2 - '@tauri-apps/cli-win32-ia32-msvc': 2.11.2 - '@tauri-apps/cli-win32-x64-msvc': 2.11.2 + '@tauri-apps/cli-darwin-arm64': 2.11.3 + '@tauri-apps/cli-darwin-x64': 2.11.3 + '@tauri-apps/cli-linux-arm-gnueabihf': 2.11.3 + '@tauri-apps/cli-linux-arm64-gnu': 2.11.3 + '@tauri-apps/cli-linux-arm64-musl': 2.11.3 + '@tauri-apps/cli-linux-riscv64-gnu': 2.11.3 + '@tauri-apps/cli-linux-x64-gnu': 2.11.3 + '@tauri-apps/cli-linux-x64-musl': 2.11.3 + '@tauri-apps/cli-win32-arm64-msvc': 2.11.3 + '@tauri-apps/cli-win32-ia32-msvc': 2.11.3 + '@tauri-apps/cli-win32-x64-msvc': 2.11.3 '@tauri-apps/plugin-clipboard-manager@2.3.2': dependencies: - '@tauri-apps/api': 2.11.0 + '@tauri-apps/api': 2.11.1 '@tauri-apps/plugin-dialog@2.7.1': dependencies: - '@tauri-apps/api': 2.11.0 + '@tauri-apps/api': 2.11.1 '@tybys/wasm-util@0.10.2': dependencies: @@ -2028,18 +2010,18 @@ snapshots: '@types/jsesc@2.5.1': {} - '@types/node@25.9.3': + '@types/node@26.0.0': dependencies: - undici-types: 7.24.6 + undici-types: 8.3.0 '@types/web-bluetooth@0.0.20': {} '@types/web-bluetooth@0.0.21': {} - '@vitejs/plugin-vue@6.0.7(vite@8.0.16(@types/node@25.9.3)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3))': + '@vitejs/plugin-vue@6.0.7(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.0.16(@types/node@25.9.3)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0) + vite: 8.0.16(@types/node@26.0.0)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0) vue: 3.5.38(typescript@6.0.3) '@volar/language-core@2.4.28': @@ -2581,7 +2563,7 @@ snapshots: muggle-string@0.4.1: {} - nanoid@3.3.12: {} + nanoid@3.3.13: {} node-addon-api@7.1.1: optional: true @@ -2629,7 +2611,7 @@ snapshots: postcss@8.5.15: dependencies: - nanoid: 3.3.12 + nanoid: 3.3.13 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -2649,7 +2631,7 @@ snapshots: readdirp@5.0.0: {} - reka-ui@2.9.10(vue@3.5.38(typescript@6.0.3)): + reka-ui@2.10.0(vue@3.5.38(typescript@6.0.3)): dependencies: '@floating-ui/dom': 1.7.6 '@floating-ui/vue': 1.1.11(vue@3.5.38(typescript@6.0.3)) @@ -2723,11 +2705,6 @@ snapshots: tapable@2.3.3: {} - tauri-plugin-nosleep-api@0.1.1: - dependencies: - '@tauri-apps/api': 1.0.0-rc.4 - tslib: 2.8.1 - terser@5.48.0: dependencies: '@jridgewell/source-map': 0.3.11 @@ -2748,15 +2725,13 @@ snapshots: tw-animate-css@1.4.0: {} - type-fest@2.12.2: {} - type-fest@4.41.0: {} typescript@6.0.3: {} ufo@1.6.4: {} - undici-types@7.24.6: {} + undici-types@8.3.0: {} unplugin-utils@0.3.1: dependencies: @@ -2769,10 +2744,10 @@ snapshots: picomatch: 4.0.4 webpack-virtual-modules: 0.6.2 - vaul-vue@0.4.1(reka-ui@2.9.10(vue@3.5.38(typescript@6.0.3)))(vue@3.5.38(typescript@6.0.3)): + vaul-vue@0.4.1(reka-ui@2.10.0(vue@3.5.38(typescript@6.0.3)))(vue@3.5.38(typescript@6.0.3)): dependencies: '@vueuse/core': 10.11.1(vue@3.5.38(typescript@6.0.3)) - reka-ui: 2.9.10(vue@3.5.38(typescript@6.0.3)) + reka-ui: 2.10.0(vue@3.5.38(typescript@6.0.3)) vue: 3.5.38(typescript@6.0.3) transitivePeerDependencies: - '@vue/composition-api' @@ -2783,15 +2758,15 @@ snapshots: type-fest: 4.41.0 vue: 3.5.38(typescript@6.0.3) - vite-plugin-static-copy@4.1.1(vite@8.0.16(@types/node@25.9.3)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0)): + vite-plugin-static-copy@4.1.1(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0)): dependencies: chokidar: 3.6.0 p-map: 7.0.4 picocolors: 1.1.1 tinyglobby: 0.2.17 - vite: 8.0.16(@types/node@25.9.3)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0) + vite: 8.0.16(@types/node@26.0.0)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0) - vite@8.0.16(@types/node@25.9.3)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0): + vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -2799,7 +2774,7 @@ snapshots: rolldown: 1.0.3 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 25.9.3 + '@types/node': 26.0.0 fsevents: 2.3.3 jiti: 2.7.0 sass: 1.101.0 @@ -2812,9 +2787,9 @@ snapshots: dependencies: vue: 3.5.38(typescript@6.0.3) - vue-router@5.1.0(@vue/compiler-sfc@3.5.38)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)))(vite@8.0.16(@types/node@25.9.3)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)): + vue-router@5.1.0(@vue/compiler-sfc@3.5.38)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)))(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)): dependencies: - '@babel/generator': 8.0.0-rc.6 + '@babel/generator': 8.0.0 '@vue-macros/common': 3.1.2(vue@3.5.38(typescript@6.0.3)) '@vue/devtools-api': 8.1.3 ast-walker-scope: 0.9.0 @@ -2835,7 +2810,7 @@ snapshots: optionalDependencies: '@vue/compiler-sfc': 3.5.38 pinia: 3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)) - vite: 8.0.16(@types/node@25.9.3)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0) + vite: 8.0.16(@types/node@26.0.0)(jiti@2.7.0)(sass@1.101.0)(terser@5.48.0)(yaml@2.9.0) vue-sonner@2.0.9: {} diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 4cba5ebf..d9fe925c 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -10,19 +10,9 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common 0.1.7", - "generic-array 0.14.7", -] - -[[package]] -name = "aead" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef60ac202874e574ce7a7158cc8bca7313dd344322482e4fadee288bf4a306b8" +checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" dependencies = [ "crypto-common 0.2.2", "inout 0.2.2", @@ -51,47 +41,21 @@ dependencies = [ "zeroize", ] -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead 0.5.2", - "aes 0.8.4", - "cipher 0.4.4", - "ctr 0.9.2", - "ghash 0.5.1", - "subtle", -] - [[package]] name = "aes-gcm" version = "0.11.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da8c919c118108f144adecad74b425b804ad075580d605d9b33c2d6d1c62a2f8" dependencies = [ - "aead 0.6.0", + "aead", "aes 0.9.1", "cipher 0.5.2", - "ctr 0.10.1", - "ghash 0.6.0", + "ctr", + "ghash", "subtle", "zeroize", ] -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.4" @@ -234,35 +198,13 @@ dependencies = [ "password-hash", ] -[[package]] -name = "array-init" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "async-broadcast" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" -dependencies = [ - "event-listener 2.5.3", - "futures-core", -] - [[package]] name = "async-broadcast" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" dependencies = [ - "event-listener 5.4.1", + "event-listener", "event-listener-strategy", "futures-core", "pin-project-lite", @@ -300,44 +242,12 @@ checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.4.1", - "futures-lite 2.6.1", + "fastrand", + "futures-lite", "pin-project-lite", "slab", ] -[[package]] -name = "async-fs" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "blocking", - "futures-lite 1.13.0", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.28", - "slab", - "socket2 0.4.10", - "waker-fn", -] - [[package]] name = "async-io" version = "2.6.0" @@ -348,51 +258,25 @@ dependencies = [ "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.6.1", + "futures-lite", "parking", - "polling 3.11.0", - "rustix 1.1.4", + "polling", + "rustix", "slab", "windows-sys 0.61.2", ] -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - [[package]] name = "async-lock" version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ - "event-listener 5.4.1", + "event-listener", "event-listener-strategy", "pin-project-lite", ] -[[package]] -name = "async-process" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" -dependencies = [ - "async-io 1.13.0", - "async-lock 2.8.0", - "async-signal", - "blocking", - "cfg-if", - "event-listener 3.1.0", - "futures-lite 1.13.0", - "rustix 0.38.44", - "windows-sys 0.48.0", -] - [[package]] name = "async-process" version = "2.5.0" @@ -400,15 +284,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" dependencies = [ "async-channel", - "async-io 2.6.0", - "async-lock 3.4.2", + "async-io", + "async-lock", "async-signal", "async-task", "blocking", "cfg-if", - "event-listener 5.4.1", - "futures-lite 2.6.1", - "rustix 1.1.4", + "event-listener", + "futures-lite", + "rustix", ] [[package]] @@ -428,13 +312,13 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" dependencies = [ - "async-io 2.6.0", - "async-lock 3.4.2", + "async-io", + "async-lock", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 1.1.4", + "rustix", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -611,33 +495,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "144e573728da132683b9488acd528274c790e07fc06ff81ee29f9d8f8b1041e0" dependencies = [ "blowfish", - "pbkdf2 0.13.0", + "pbkdf2", "sha2 0.11.0", ] -[[package]] -name = "binrw" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003da50fae3878bd26cc710742decd3412b8cfb396bc9b306e5e710106d207fb" -dependencies = [ - "array-init", - "binrw_derive", - "bytemuck", -] - -[[package]] -name = "binrw_derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfdce8fda72e8cc5038fb9e739e665d1f3072f7e3e24c573277b8fdce595ef72" -dependencies = [ - "either", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "bit-set" version = "0.8.0" @@ -677,12 +538,6 @@ dependencies = [ "digest 0.11.3", ] -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - [[package]] name = "block-buffer" version = "0.10.4" @@ -738,7 +593,7 @@ dependencies = [ "async-channel", "async-task", "futures-io", - "futures-lite 2.6.1", + "futures-lite", "piper", ] @@ -798,12 +653,6 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" -[[package]] -name = "bytecount" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" - [[package]] name = "bytemuck" version = "1.25.0" @@ -824,9 +673,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" dependencies = [ "serde", ] @@ -858,9 +707,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +checksum = "b4ce8d3bd5823c7504d3f579f13e7b2f3da252fcb938c594d5680ee508bf846f" dependencies = [ "serde_core", ] @@ -918,13 +767,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.64" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", - "jobserver", - "libc", "shlex", ] @@ -1140,12 +987,6 @@ dependencies = [ "tiny-keccak", ] -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "convert_case" version = "0.11.0" @@ -1286,7 +1127,7 @@ checksum = "97bb4a855e3b10f84c4e7e895a7de01db7f9a7b7eb7f73ed9773fd52ac686451" dependencies = [ "cpubits", "ctutils", - "getrandom 0.4.2", + "getrandom 0.4.3", "hybrid-array", "num-traits", "rand_core 0.10.1", @@ -1302,7 +1143,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array 0.14.7", - "rand_core 0.6.4", "typenum", ] @@ -1312,7 +1152,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "hybrid-array", "rand_core 0.10.1", ] @@ -1362,15 +1202,6 @@ version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher 0.4.4", -] - [[package]] name = "ctr" version = "0.10.1" @@ -1492,7 +1323,7 @@ dependencies = [ "block-padding 0.3.3", "cbc 0.1.2", "dbus", - "fastrand 2.4.1", + "fastrand", "hkdf 0.12.4", "num", "once_cell", @@ -1510,6 +1341,38 @@ dependencies = [ "keyring-core", ] +[[package]] +name = "defmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "delegate" version = "0.13.5" @@ -1541,30 +1404,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case 0.4.0", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.118", -] - [[package]] name = "derive_more" version = "2.1.1" @@ -1714,12 +1553,6 @@ dependencies = [ "const-random", ] -[[package]] -name = "doc-comment" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" - [[package]] name = "document-features" version = "0.2.12" @@ -1939,17 +1772,6 @@ dependencies = [ "syn 2.0.118", ] -[[package]] -name = "enumn" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - [[package]] name = "env_filter" version = "1.0.1" @@ -2006,23 +1828,6 @@ version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - [[package]] name = "event-listener" version = "5.4.1" @@ -2040,7 +1845,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.1", + "event-listener", "pin-project-lite", ] @@ -2066,15 +1871,6 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "fastrand" version = "2.4.1" @@ -2135,7 +1931,7 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ - "memoffset 0.9.1", + "memoffset", "rustc_version", ] @@ -2231,7 +2027,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e72ed92b67c146290f88e9c89d60ca163ea417a446f61ffd7b72df3e7f1dfd5" dependencies = [ - "rustix 1.1.4", + "rustix", "windows-sys 0.61.2", ] @@ -2283,28 +2079,13 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - [[package]] name = "futures-lite" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.4.1", + "fastrand", "futures-core", "futures-io", "parking", @@ -2477,7 +2258,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "rustix 1.1.4", + "rustix", "windows-link 0.2.1", ] @@ -2506,37 +2287,25 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", - "wasip2", - "wasip3", "wasm-bindgen", ] -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval 0.6.2", -] - [[package]] name = "ghash" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" dependencies = [ - "polyval 0.7.1", + "polyval", ] [[package]] @@ -2764,9 +2533,6 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] [[package]] name = "hashbrown" @@ -2777,19 +2543,31 @@ dependencies = [ "foldhash 0.1.5", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", +] + [[package]] name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash 0.2.0", +] [[package]] name = "hashlink" -version = "0.9.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +checksum = "a5081f264ed7adee96ea4b4778b6bb9da0a7228b084587aa3bd3ff05da7c5a3b" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.17.1", ] [[package]] @@ -2804,12 +2582,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "hermit-abi" version = "0.5.2" @@ -2864,15 +2636,6 @@ dependencies = [ "digest 0.11.3", ] -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "html5ever" version = "0.38.0" @@ -2979,7 +2742,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.4", + "socket2", "tokio", "tower-service", "tracing", @@ -3101,12 +2864,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -3177,15 +2934,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "indoc" -version = "2.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" -dependencies = [ - "rustversion", -] - [[package]] name = "infer" version = "0.19.0" @@ -3215,15 +2963,6 @@ dependencies = [ "hybrid-array", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "internal-russh-num-bigint" version = "0.5.0" @@ -3236,17 +2975,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ipnet" version = "2.12.0" @@ -3309,10 +3037,11 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.28" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" +checksum = "34f877a98676d2fb664698d74cc6a51ce6c484ce8c770f05d0108ec9090aeb46" dependencies = [ + "defmt", "jiff-static", "log", "portable-atomic", @@ -3322,9 +3051,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.28" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" +checksum = "0666b5ab5ecaca213fc2a85b8c0083d9004e84ee2d5f9a7e0017aaf50986f25f" dependencies = [ "proc-macro2", "quote", @@ -3405,16 +3134,6 @@ dependencies = [ "syn 2.0.118", ] -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - [[package]] name = "js-sys" version = "0.3.102" @@ -3517,12 +3236,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libappindicator" version = "0.9.0" @@ -3562,27 +3275,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "libesedb" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c72b56d9764a22feaaed1de8f2fc2accc6745dfdf79caff845714fcb52a2f72" -dependencies = [ - "libesedb-sys", - "time", -] - -[[package]] -name = "libesedb-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "110afdc2f932325712a6353243313d3878fd44e811d898176a13a6204b1f0186" -dependencies = [ - "cc", - "patch-apply", - "walkdir", -] - [[package]] name = "libloading" version = "0.7.4" @@ -3614,27 +3306,15 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.28.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +checksum = "f6c19a05435c21ac299d71b6a9c13db3e3f47c520517d58990a462a1397a61db" dependencies = [ "cc", "pkg-config", "vcpkg", ] -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -3664,18 +3344,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" - -[[package]] -name = "lz4_flex" -version = "0.11.6" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" -dependencies = [ - "twox-hash", -] +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "m3u8-rs" @@ -3687,29 +3358,6 @@ dependencies = [ "nom 7.1.3", ] -[[package]] -name = "mac-notification-sys" -version = "0.6.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca" -dependencies = [ - "cc", - "log", - "objc2", - "objc2-foundation", - "time", - "uuid", -] - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - [[package]] name = "markup5ever" version = "0.38.0" @@ -3767,15 +3415,6 @@ version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.9.1" @@ -3855,9 +3494,9 @@ dependencies = [ [[package]] name = "muda" -version = "0.19.2" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a2e3dff89cd322c66647942668faee0a2b1f88ea6cbb4d374b4a8d7e92528c" +checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" dependencies = [ "crossbeam-channel", "dpi", @@ -3876,9 +3515,9 @@ dependencies = [ [[package]] name = "napi" -version = "3.9.2" +version = "3.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d3c7dd60231116a47854321c9ac8df6f13435d11aa3a59d8533a76e07a3730" +checksum = "fbd9f9295f3ff5921e78a71222c3361a8216f7760b1a99a6ad4e8441de18bbb9" dependencies = [ "bitflags 2.13.0", "ctor 1.0.7", @@ -3904,7 +3543,7 @@ version = "3.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b3f766e04667e6da0e181e2da4f85475d5a6513b7cf6a80bea184e224a5b42" dependencies = [ - "convert_case 0.11.0", + "convert_case", "ctor 1.0.7", "napi-derive-backend", "proc-macro2", @@ -3918,7 +3557,7 @@ version = "5.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d5af30503edf933ce7377cf6d4c877a62b0f1107ea05585f1b5e430e88d5baf" dependencies = [ - "convert_case 0.11.0", + "convert_case", "proc-macro2", "quote", "semver", @@ -3982,18 +3621,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.7.1", -] - [[package]] name = "nix" version = "0.31.3" @@ -4031,126 +3658,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "nom_locate" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e3c83c053b0713da60c5b8de47fe8e494fe3ece5267b2f23090a07a053ba8f3" -dependencies = [ - "bytecount", - "memchr", - "nom 7.1.3", -] - -[[package]] -name = "normpath" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9985ef7269fa99f3b12437bb698381da2428743ab90f20393f399fa14cab21a" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "nosleep" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cedad558d018cdc0abbca10a799e91ed63fa442d4a567f91dc9dce775a3ebba" -dependencies = [ - "nosleep-mac-sys", - "nosleep-nix", - "nosleep-types", - "nosleep-windows", -] - -[[package]] -name = "nosleep-mac-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52b2df065619dbb4fc6034967a7511b0bc4d2caa7688cd6ea0f5601edd7f3901" -dependencies = [ - "cc", - "nosleep-types", - "objc-foundation", - "objc_id", - "snafu", -] - -[[package]] -name = "nosleep-nix" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cb4d94c4c2a46b6756dd6f2401c143cd2cb6b8324b0b151cff5de9dad3e59bd" -dependencies = [ - "dbus", - "nosleep-types", - "snafu", -] - -[[package]] -name = "nosleep-types" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd2d4d904e07b1c33a67ca29e5ac82902e7bb867130d993e938f8c4402986449" -dependencies = [ - "serde", - "snafu", -] - -[[package]] -name = "nosleep-windows" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2570bd99af538bf408bfaec6f14a63d7d89ad8fe82a17c0c43a31395da4c8d1e" -dependencies = [ - "nosleep-types", - "snafu", - "windows 0.36.1", -] - -[[package]] -name = "notify-rust" -version = "4.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b4c1b4f2aa9f25f63a7a49d3dd0ed567b3670da15330a66b29434be899b891" -dependencies = [ - "futures-lite 2.6.1", - "log", - "mac-notification-sys", - "serde", - "tauri-winrt-notification", - "zbus 5.16.0", -] - -[[package]] -name = "nt-string" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97eac41de714f55e8daa14e27a51d8a9c16c17f52191f45c9abfaef38bc68459" -dependencies = [ - "displaydoc", - "widestring", -] - -[[package]] -name = "ntfs" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fe2140f3b6eef2648fa331ca7af7652da1bf3bb84f715878bf50f2f8f03c38" -dependencies = [ - "arrayvec", - "binrw", - "bitflags 2.13.0", - "byteorder", - "derive_more 0.99.20", - "displaydoc", - "enumn", - "memoffset 0.9.1", - "nt-string", - "strum_macros", - "time", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -4261,26 +3768,6 @@ dependencies = [ "syn 2.0.118", ] -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - [[package]] name = "objc2" version = "0.6.4" @@ -4478,15 +3965,6 @@ dependencies = [ "objc2-foundation", ] -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - [[package]] name = "once_cell" version = "1.21.4" @@ -4499,12 +3977,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "open" version = "5.3.5" @@ -4543,22 +4015,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "os_info" -version = "3.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b" -dependencies = [ - "android_system_properties", - "log", - "nix 0.31.3", - "objc2", - "objc2-foundation", - "objc2-ui-kit", - "serde", - "windows-sys 0.61.2", -] - [[package]] name = "os_pipe" version = "1.2.3" @@ -4693,33 +4149,12 @@ dependencies = [ "phc", ] -[[package]] -name = "patch-apply" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe95476ec50a4e9b95ed12a4677ff5996aba4329bf2535fb21c49afaad20809" -dependencies = [ - "chrono", - "nom 7.1.3", - "nom_locate", -] - [[package]] name = "pathdiff" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest 0.10.7", - "hmac 0.12.1", -] - [[package]] name = "pbkdf2" version = "0.13.0" @@ -4793,7 +4228,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [ - "fastrand 2.4.1", + "fastrand", "phf_shared", ] @@ -4852,7 +4287,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", - "fastrand 2.4.1", + "fastrand", "futures-io", ] @@ -4865,7 +4300,7 @@ dependencies = [ "aes 0.9.1", "cbc 0.2.1", "der", - "pbkdf2 0.13.0", + "pbkdf2", "rand_core 0.10.1", "scrypt", "sha2 0.11.0", @@ -4929,22 +4364,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - [[package]] name = "polling" version = "3.11.0" @@ -4953,9 +4372,9 @@ checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.5.2", + "hermit-abi", "pin-project-lite", - "rustix 1.1.4", + "rustix", "windows-sys 0.61.2", ] @@ -4966,22 +4385,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00baa632505d05512f48a963e16051c54fda9a95cc9acea1a4e3c90991c4a2e" dependencies = [ "cpufeatures 0.3.0", - "universal-hash 0.6.1", + "universal-hash", "zeroize", ] -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash 0.5.1", -] - [[package]] name = "polyval" version = "0.7.1" @@ -4990,7 +4397,7 @@ checksum = "7dfc63250416fea14f5749b90725916a6c903f599d51cb635aa7a52bfd03eede" dependencies = [ "cpubits", "cpufeatures 0.3.0", - "universal-hash 0.6.1", + "universal-hash", ] [[package]] @@ -5038,21 +4445,11 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.118", -] - [[package]] name = "primefield" -version = "0.14.0-rc.11" +version = "0.14.0-rc.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d7e42f46a29abc16fb621a3466ee453358ebaae48a9e515f287e0af052ed8f" +checksum = "8675564771a62f69a0af716b03e89b917b963c7b173b5855575e84fd4f605ca0" dependencies = [ "crypto-bigint", "crypto-common 0.2.2", @@ -5071,18 +4468,6 @@ dependencies = [ "elliptic-curve", ] -[[package]] -name = "privilege" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765ec92721e112ffe07f5c06fb0654da0b708990888981d05cf12a7c9909df30" -dependencies = [ - "libc", - "security-framework-sys", - "which", - "windows-sys 0.48.0", -] - [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -5137,20 +4522,42 @@ dependencies = [ ] [[package]] -name = "proc-macro2" -version = "1.0.106" +name = "proc-macro-error-attr2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "unicode-ident", + "proc-macro2", + "quote", ] [[package]] -name = "psl-types" -version = "2.0.11" +name = "proc-macro-error2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" - +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + [[package]] name = "publicsuffix" version = "2.3.0" @@ -5223,24 +4630,13 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ - "rand_chacha 0.9.0", + "rand_chacha", "rand_core 0.9.5", ] @@ -5251,20 +4647,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", - "getrandom 0.4.2", + "getrandom 0.4.3", "rand_core 0.10.1", ] -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - [[package]] name = "rand_chacha" version = "0.9.0" @@ -5275,15 +4661,6 @@ dependencies = [ "rand_core 0.9.5", ] -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - [[package]] name = "rand_core" version = "0.9.5" @@ -5305,18 +4682,6 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" -[[package]] -name = "rawcopy-rs-next" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ae516a90d6633521dd746eedb19018317daf519ef0c463c3fac4acb80bdec60" -dependencies = [ - "anyhow", - "log", - "normpath", - "ntfs", -] - [[package]] name = "rc4" version = "0.2.0" @@ -5500,7 +4865,6 @@ dependencies = [ "fs4", "jni 0.22.4", "keyring-core", - "log", "ndk-context", "open", "regex", @@ -5517,11 +4881,6 @@ dependencies = [ "tauri-plugin-clipboard-manager", "tauri-plugin-deep-link", "tauri-plugin-dialog", - "tauri-plugin-fs", - "tauri-plugin-nosleep", - "tauri-plugin-notification", - "tauri-plugin-os", - "tauri-plugin-process", "tauri-plugin-shell", "tauri-plugin-single-instance", "tauri-plugin-store", @@ -5533,6 +4892,7 @@ dependencies = [ "trash", "urlencoding", "windows-native-keyring-store", + "windows-sys 0.61.2", ] [[package]] @@ -5546,7 +4906,6 @@ dependencies = [ "env_logger", "hex", "httparse", - "log", "network-interface", "num-bigint", "once_cell", @@ -5559,10 +4918,11 @@ dependencies = [ "serde", "sha1 0.11.0", "sha2 0.11.0", - "socket2 0.6.4", + "socket2", "tempfile", "thiserror 2.0.18", "tokio", + "tracing", "url", ] @@ -5573,7 +4933,6 @@ dependencies = [ "base64 0.22.1", "clap", "dirs 6.0.0", - "log", "risuko-engine", "risuko-http", "serde", @@ -5587,12 +4946,27 @@ dependencies = [ name = "risuko-cookies" version = "0.4.2" dependencies = [ - "rookie", + "aes 0.9.1", + "aes-gcm", + "base64 0.22.1", + "cbc 0.2.1", + "cipher 0.5.2", + "eyre", + "glob", + "hmac 0.13.0", + "pbkdf2", + "regex", + "rusqlite", + "security-framework", "serde", "serde_json", + "sha1 0.11.0", + "tempfile", "tokio", "tracing", "url", + "windows-sys 0.61.2", + "zbus", ] [[package]] @@ -5620,10 +4994,9 @@ dependencies = [ "hyper", "hyper-util", "libc", - "log", "m3u8-rs", "md-5", - "nix 0.31.3", + "nix", "parking_lot", "percent-encoding", "rand 0.10.1", @@ -5638,7 +5011,7 @@ dependencies = [ "serde_json", "sha1 0.11.0", "sha2 0.11.0", - "socket2 0.6.4", + "socket2", "suppaftp", "tempfile", "thiserror 2.0.18", @@ -5676,7 +5049,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "serial_test", - "socket2 0.6.4", + "socket2", "thiserror 2.0.18", "tokio", "tokio-rustls", @@ -5694,7 +5067,6 @@ version = "0.4.2" dependencies = [ "base64 0.22.1", "dirs 6.0.0", - "log", "napi", "napi-build", "napi-derive", @@ -5702,47 +5074,24 @@ dependencies = [ "serde", "serde_json", "tokio", + "tracing", ] [[package]] -name = "rookie" -version = "0.5.6" +name = "rsqlite-vfs" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec13c4a0a48ef3a59770e8680eab740027ade9502c693320be12af29c025897e" +checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" dependencies = [ - "aes 0.8.4", - "aes-gcm 0.10.3", - "base64 0.22.1", - "byteorder", - "cbc 0.1.2", - "eyre", - "glob", - "indoc", - "libesedb", - "log", - "lz4_flex", - "once_cell", - "pbkdf2 0.12.2", - "privilege", - "rand 0.8.6", - "rawcopy-rs-next", - "regex", - "rusqlite", - "rust-ini", - "serde", - "serde_json", - "sha1 0.10.6", - "url", - "windows 0.51.1", - "zbus 3.15.2", - "zvariant 3.15.2", + "hashbrown 0.16.1", + "thiserror 2.0.18", ] [[package]] name = "rusqlite" -version = "0.31.0" +version = "0.40.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" +checksum = "11438310b19e3109b6446c33d1ed5e889428cf2e278407bc7896bc4aaea43323" dependencies = [ "bitflags 2.13.0", "fallible-iterator", @@ -5750,6 +5099,7 @@ dependencies = [ "hashlink", "libsqlite3-sys", "smallvec", + "sqlite-wasm-rs", ] [[package]] @@ -5766,7 +5116,7 @@ dependencies = [ "cbc 0.2.1", "cipher 0.5.2", "crypto-bigint", - "ctr 0.10.1", + "ctr", "curve25519-dalek", "data-encoding", "delegate", @@ -5779,8 +5129,8 @@ dependencies = [ "flate2", "futures", "generic-array 1.4.3", - "getrandom 0.4.2", - "ghash 0.6.0", + "getrandom 0.4.3", + "ghash", "hex-literal", "hmac 0.13.0", "inout 0.2.2", @@ -5795,10 +5145,10 @@ dependencies = [ "p384", "p521", "pageant", - "pbkdf2 0.13.0", + "pbkdf2", "pkcs5", "pkcs8", - "polyval 0.7.1", + "polyval", "rand 0.10.1", "rand_core 0.10.1", "ring", @@ -5818,7 +5168,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "typenum", - "universal-hash 0.6.1", + "universal-hash", "zeroize", ] @@ -5829,7 +5179,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443f6bbcfacb34a1aab2b12b99bf08e0c63abdc5a0db261901365df9d57fff51" dependencies = [ "log", - "nix 0.31.3", + "nix", "ssh-encoding", "windows-sys 0.61.2", ] @@ -5891,33 +5241,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.37.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.13.0", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - [[package]] name = "rustix" version = "1.1.4" @@ -5927,7 +5250,7 @@ dependencies = [ "bitflags 2.13.0", "errno", "libc", - "linux-raw-sys 0.12.1", + "linux-raw-sys", "windows-sys 0.61.2", ] @@ -6061,7 +5384,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87af57419b594aa23fa95f09f0e06d80d84ba01c26148c43844cad6ff4485f0" dependencies = [ "cfg-if", - "pbkdf2 0.13.0", + "pbkdf2", "salsa20", "sha2 0.11.0", ] @@ -6111,7 +5434,7 @@ checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" dependencies = [ "bitflags 2.13.0", "cssparser", - "derive_more 2.1.1", + "derive_more", "log", "new_debug_unreachable", "phf", @@ -6201,7 +5524,6 @@ version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ - "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -6520,38 +5842,6 @@ version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" -[[package]] -name = "snafu" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" -dependencies = [ - "doc-comment", - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.6.4" @@ -6620,19 +5910,31 @@ dependencies = [ "der", ] +[[package]] +name = "sqlite-wasm-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" +dependencies = [ + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", +] + [[package]] name = "ssh-cipher" version = "0.3.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10db6f219196a8528f9ec904d9d45cdad692d65b0e57e72be4dedd1c5fddce36" dependencies = [ - "aead 0.6.0", + "aead", "aes 0.9.1", - "aes-gcm 0.11.0-rc.4", + "aes-gcm", "cbc 0.2.1", "chacha20", "cipher 0.5.2", - "ctr 0.10.1", + "ctr", "ctutils", "des", "poly1305", @@ -6685,12 +5987,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "string_cache" version = "0.9.0" @@ -6721,19 +6017,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum_macros" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", -] - [[package]] name = "subtle" version = "2.6.1" @@ -6742,13 +6025,12 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "suppaftp" -version = "8.0.4" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf00e4d8418c477a8cb3c13ae5396a68d31658e760c74280bdbd34926e3b94b" +checksum = "b367d8e84825dc6e858d2044f8cfff5e80f63b2aa0634c6382fc9a94b6926b22" dependencies = [ "async-trait", "chrono", - "futures-lite 2.6.1", "lazy-regex", "log", "pin-project", @@ -6782,7 +6064,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", - "quote", "unicode-ident", ] @@ -6817,15 +6098,6 @@ dependencies = [ "syn 2.0.118", ] -[[package]] -name = "sys-locale" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" -dependencies = [ - "libc", -] - [[package]] name = "system-deps" version = "6.2.2" @@ -6898,9 +6170,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.11.2" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437404997acf375d85f1177afa7e11bb971f274ed6a7b83a2a3e339015f4cc28" +checksum = "c2616f96cb644bf2c5c456d9de4d5d5100e592d7424c74d8b55c5cb96e359e93" dependencies = [ "anyhow", "bytes", @@ -6950,9 +6222,9 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.6.2" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa1f9055fc23919a54e4e125052bed16ed04aef0487086e758fe01a67b451c7" +checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632" dependencies = [ "anyhow", "cargo_toml", @@ -6971,9 +6243,9 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.6.2" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a0319528a025a38c4078e7dae2c446f4e63620ddb0659a643ede1cb38f90e9" +checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5" dependencies = [ "base64 0.22.1", "brotli", @@ -6998,9 +6270,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.6.2" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6cb4e3896c21d2f6da5b31251d2faea0153bba56ed0e970f918115dbee4924" +checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -7012,9 +6284,9 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.6.2" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e126abc9e84e35cdfd01596140a73a1850cdb0df0a23acf0185776c30b469a6e" +checksum = "74be5dd4bed9afbd145e5716b5fa2ec28cbc29c34ffa61c258c9273d896c8020" dependencies = [ "anyhow", "glob", @@ -7118,65 +6390,6 @@ dependencies = [ "url", ] -[[package]] -name = "tauri-plugin-nosleep" -version = "2.0.0-beta.1" -source = "git+https://github.com/pevers/tauri-plugin-nosleep?rev=6f6ab76ec171d075476b585282290fcf2e1c40b5#6f6ab76ec171d075476b585282290fcf2e1c40b5" -dependencies = [ - "nosleep", - "serde", - "tauri", - "tauri-plugin", - "thiserror 1.0.69", -] - -[[package]] -name = "tauri-plugin-notification" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01fc2c5ff41105bd1f7242d8201fdf3efd70749b82fa013a17f2126357d194cc" -dependencies = [ - "log", - "notify-rust", - "rand 0.9.4", - "serde", - "serde_json", - "serde_repr", - "tauri", - "tauri-plugin", - "thiserror 2.0.18", - "time", - "url", -] - -[[package]] -name = "tauri-plugin-os" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f08346c8deb39e96f86973da0e2d76cbb933d7ac9b750f6dc4daf955a6f997" -dependencies = [ - "gethostname", - "log", - "os_info", - "serde", - "serde_json", - "serialize-to-javascript", - "sys-locale", - "tauri", - "tauri-plugin", - "thiserror 2.0.18", -] - -[[package]] -name = "tauri-plugin-process" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d55511a7bf6cd70c8767b02c97bf8134fa434daf3926cfc1be0a0f94132d165a" -dependencies = [ - "tauri", - "tauri-plugin", -] - [[package]] name = "tauri-plugin-shell" version = "2.3.5" @@ -7210,7 +6423,7 @@ dependencies = [ "thiserror 2.0.18", "tracing", "windows-sys 0.60.2", - "zbus 5.16.0", + "zbus", ] [[package]] @@ -7231,9 +6444,9 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.11.2" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48222d7116c8807eaa6fe2f372e023fae125084e61e6eca6d70b7961cdf129ef" +checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8" dependencies = [ "cookie", "dpi", @@ -7256,9 +6469,9 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.11.2" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b83849ee63ecb27a8e8d0fe51915ca215076914aca43f96db1179f0f415f6cd9" +checksum = "fe41e015bf8fc4d6477ff4926a0ef769dc64ff34c7b0038b6f7cacae892acb5c" dependencies = [ "gtk", "http", @@ -7282,9 +6495,9 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.9.2" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "092379df9a707631978e6c56b1bc2401d387f01e2d4a3c123360d167bbb9aa95" +checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" dependencies = [ "anyhow", "brotli", @@ -7329,28 +6542,16 @@ dependencies = [ "toml 1.1.2+spec-1.1.0", ] -[[package]] -name = "tauri-winrt-notification" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9" -dependencies = [ - "quick-xml 0.37.5", - "thiserror 2.0.18", - "windows 0.61.3", - "windows-version", -] - [[package]] name = "tempfile" version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "fastrand 2.4.1", - "getrandom 0.4.2", + "fastrand", + "getrandom 0.4.3", "once_cell", - "rustix 1.1.4", + "rustix", "windows-sys 0.61.2", ] @@ -7512,7 +6713,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.4", + "socket2", "tokio-macros", "windows-sys 0.61.2", ] @@ -7852,9 +7053,9 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.23.1" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15edbb0d80583e85ee8df283410038e17314df5cba30da2087a54a85216c0773" +checksum = "65ba1e5f6b9ef9fd87e21b9c6f351554dbd717960089168fcfdef854686961dc" dependencies = [ "crossbeam-channel", "dirs 6.0.0", @@ -7905,12 +7106,6 @@ dependencies = [ "thiserror 2.0.18", ] -[[package]] -name = "twox-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" - [[package]] name = "typeid" version = "1.0.3" @@ -7929,7 +7124,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ - "memoffset 0.9.1", + "memoffset", "tempfile", "windows-sys 0.61.2", ] @@ -7987,22 +7182,6 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common 0.1.7", - "subtle", -] - [[package]] name = "universal-hash" version = "0.6.1" @@ -8074,7 +7253,7 @@ version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "serde_core", "wasm-bindgen", @@ -8124,12 +7303,6 @@ dependencies = [ "libc", ] -[[package]] -name = "waker-fn" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" - [[package]] name = "walkdir" version = "2.5.0" @@ -8161,16 +7334,7 @@ version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] @@ -8228,28 +7392,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.14.0", - "wasm-encoder", - "wasmparser", -] - [[package]] name = "wasm-streams" version = "0.5.0" @@ -8263,18 +7405,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.13.0", - "hashbrown 0.15.5", - "indexmap 2.14.0", - "semver", -] - [[package]] name = "wayland-backend" version = "0.3.15" @@ -8283,7 +7413,7 @@ checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" dependencies = [ "cc", "downcast-rs", - "rustix 1.1.4", + "rustix", "smallvec", "wayland-sys", ] @@ -8295,16 +7425,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" dependencies = [ "bitflags 2.13.0", - "rustix 1.1.4", + "rustix", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-protocols" -version = "0.32.12" +version = "0.32.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" dependencies = [ "bitflags 2.13.0", "wayland-backend", @@ -8357,9 +7487,9 @@ dependencies = [ [[package]] name = "web_atoms" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7cff6eef815df1834fd250e3a2ff436044d82a9f1bc1980ca1dbdf07effc538" +checksum = "075474b12bcb3d2e3d4546580e9de478eeeead668a1761e2a8860c836b7ef297" dependencies = [ "phf", "phf_codegen", @@ -8413,9 +7543,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" dependencies = [ "rustls-pki-types", ] @@ -8462,24 +7592,6 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix 0.38.44", -] - -[[package]] -name = "widestring" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" - [[package]] name = "winapi" version = "0.3.9" @@ -8526,29 +7638,6 @@ dependencies = [ "windows-version", ] -[[package]] -name = "windows" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53b97a83176b369b0eb2fd8158d4ae215357d02df9d40c1e1bf1879c5482c80" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - -[[package]] -name = "windows" -version = "0.51.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" -dependencies = [ - "windows-core 0.51.1", - "windows-targets 0.48.5", -] - [[package]] name = "windows" version = "0.56.0" @@ -8602,15 +7691,6 @@ dependencies = [ "windows-core 0.62.2", ] -[[package]] -name = "windows-core" -version = "0.51.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-core" version = "0.56.0" @@ -8825,15 +7905,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -8885,21 +7956,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -8966,12 +8022,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -8984,24 +8034,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -9014,24 +8052,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -9056,24 +8082,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -9086,24 +8100,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -9122,12 +8124,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -9140,24 +8136,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -9213,100 +8197,12 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck 0.5.0", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap 2.14.0", - "prettyplease", - "syn 2.0.118", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.118", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.13.0", - "indexmap 2.14.0", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.14.0", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "wl-clipboard-rs" version = "0.9.3" @@ -9316,7 +8212,7 @@ dependencies = [ "libc", "log", "os_pipe", - "rustix 1.1.4", + "rustix", "thiserror 2.0.18", "tree_magic_mini", "wayland-backend", @@ -9403,7 +8299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ "gethostname", - "rustix 1.1.4", + "rustix", "x11rb-protocol", ] @@ -9413,16 +8309,6 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" -[[package]] -name = "xdg-home" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - [[package]] name = "yoke" version = "0.8.3" @@ -9446,70 +8332,29 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zbus" -version = "3.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" -dependencies = [ - "async-broadcast 0.5.1", - "async-executor", - "async-fs", - "async-io 1.13.0", - "async-lock 2.8.0", - "async-process 1.8.1", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "byteorder", - "derivative", - "enumflags2", - "event-listener 2.5.3", - "futures-core", - "futures-sink", - "futures-util", - "hex", - "nix 0.26.4", - "once_cell", - "ordered-stream", - "rand 0.8.6", - "serde", - "serde_repr", - "sha1 0.10.6", - "static_assertions", - "tracing", - "uds_windows", - "winapi", - "xdg-home", - "zbus_macros 3.15.2", - "zbus_names 2.6.1", - "zvariant 3.15.2", -] - [[package]] name = "zbus" version = "5.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285" dependencies = [ - "async-broadcast 0.7.2", + "async-broadcast", "async-executor", - "async-io 2.6.0", - "async-lock 3.4.2", - "async-process 2.5.0", + "async-io", + "async-lock", + "async-process", "async-recursion", "async-task", "async-trait", "blocking", "enumflags2", - "event-listener 5.4.1", + "event-listener", "futures-core", - "futures-lite 2.6.1", + "futures-lite", "hex", "libc", "ordered-stream", - "rustix 1.1.4", + "rustix", "serde", "serde_repr", "tracing", @@ -9517,23 +8362,9 @@ dependencies = [ "uuid", "windows-sys 0.61.2", "winnow 1.0.3", - "zbus_macros 5.16.0", - "zbus_names 4.3.2", - "zvariant 5.12.0", -] - -[[package]] -name = "zbus_macros" -version = "3.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "regex", - "syn 1.0.109", - "zvariant_utils 1.0.1", + "zbus_macros", + "zbus_names", + "zvariant", ] [[package]] @@ -9546,20 +8377,9 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.118", - "zbus_names 4.3.2", - "zvariant 5.12.0", - "zvariant_utils 3.4.0", -] - -[[package]] -name = "zbus_names" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" -dependencies = [ - "serde", - "static_assertions", - "zvariant 3.15.2", + "zbus_names", + "zvariant", + "zvariant_utils", ] [[package]] @@ -9570,7 +8390,7 @@ checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" dependencies = [ "serde", "winnow 1.0.3", - "zvariant 5.12.0", + "zvariant", ] [[package]] @@ -9688,20 +8508,6 @@ dependencies = [ "zune-core", ] -[[package]] -name = "zvariant" -version = "3.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" -dependencies = [ - "byteorder", - "enumflags2", - "libc", - "serde", - "static_assertions", - "zvariant_derive 3.15.2", -] - [[package]] name = "zvariant" version = "5.12.0" @@ -9712,21 +8518,8 @@ dependencies = [ "enumflags2", "serde", "winnow 1.0.3", - "zvariant_derive 5.12.0", - "zvariant_utils 3.4.0", -] - -[[package]] -name = "zvariant_derive" -version = "3.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", - "zvariant_utils 1.0.1", + "zvariant_derive", + "zvariant_utils", ] [[package]] @@ -9739,18 +8532,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.118", - "zvariant_utils 3.4.0", -] - -[[package]] -name = "zvariant_utils" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "zvariant_utils", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 4064c0de..3768c483 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -15,7 +15,6 @@ risuko-cookies = { path = "risuko-cookies" } serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["process", "sync", "time", "rt", "net", "macros", "signal", "io-util"] } -log = "0.4" dirs = "6" clap = { version = "4", features = ["derive"] } @@ -54,15 +53,10 @@ tauri-plugin-store = "2" tauri-plugin-deep-link = "2" tauri-plugin-dialog = "2" tauri-plugin-shell = "2" -tauri-plugin-process = "2" -tauri-plugin-fs = "2" -tauri-plugin-notification = "2" -tauri-plugin-os = "2" clap = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true } -log = { workspace = true } dirs = { workspace = true } open = "5" tauri-plugin-clipboard-manager = "2.3.2" @@ -85,13 +79,18 @@ apple-native-keyring-store = { version = "1", features = ["keychain"] } [target.'cfg(target_os = "windows")'.dependencies] windows-native-keyring-store = "1" +windows-sys = { version = "0.61", features = [ + "Win32_Foundation", + "Win32_System_Threading", + "Win32_UI_Shell", + "Win32_UI_WindowsAndMessaging", +] } [target.'cfg(all(unix, not(any(target_os = "macos", target_os = "ios", target_os = "android"))))'.dependencies] dbus-secret-service-keyring-store = { version = "1", features = ["crypto-rust"] } [target.'cfg(not(target_os = "android"))'.dependencies] trash = "5" -tauri-plugin-nosleep = { git = "https://github.com/pevers/tauri-plugin-nosleep", rev = "6f6ab76ec171d075476b585282290fcf2e1c40b5" } tauri-plugin-autostart = "2" tauri-plugin-single-instance = "2" diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index b72c1047..99ae2e39 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -36,35 +36,6 @@ ] }, "store:default", - "fs:default", - { - "identifier": "fs:allow-exists", - "allow": [ - { "path": "$DOWNLOAD/**" }, - { "path": "$DOCUMENT/**" }, - { "path": "$DESKTOP/**" }, - { "path": "$APPDATA/**" }, - { "path": "$APPLOCALDATA/**" }, - { "path": "$RESOURCE/**" } - ] - }, - { - "identifier": "fs:allow-read-file", - "allow": [ - { "path": "$DOWNLOAD/**" }, - { "path": "$DOCUMENT/**" }, - { "path": "$DESKTOP/**" }, - { "path": "$APPDATA/**" }, - { "path": "$APPLOCALDATA/**" }, - { "path": "$RESOURCE/**" } - ] - }, - "process:default", - "process:allow-exit", - "process:allow-restart", - "os:default", - "notification:default", - "notification:allow-notify", "deep-link:default", "clipboard-manager:default", "clipboard-manager:allow-read-text", diff --git a/src-tauri/capabilities/desktop.json b/src-tauri/capabilities/desktop.json index 5824ff77..e0531c12 100644 --- a/src-tauri/capabilities/desktop.json +++ b/src-tauri/capabilities/desktop.json @@ -3,9 +3,5 @@ "description": "Desktop-only capabilities for Risuko", "windows": ["main"], "platforms": ["macOS", "windows", "linux"], - "permissions": [ - "autostart:default", - "nosleep:allow-block", - "nosleep:allow-unblock" - ] + "permissions": ["autostart:default"] } diff --git a/src-tauri/risuko-bt/Cargo.toml b/src-tauri/risuko-bt/Cargo.toml index a2c52d98..9d6760c2 100644 --- a/src-tauri/risuko-bt/Cargo.toml +++ b/src-tauri/risuko-bt/Cargo.toml @@ -20,7 +20,7 @@ hex = "0.4" sha1 = "0.11" sha2 = "0.11" rand = "0.10" -log = "0.4" +tracing = "0.1" arc-swap = "1" once_cell = "1" async-trait = "0.1" diff --git a/src-tauri/risuko-bt/src/dht.rs b/src-tauri/risuko-bt/src/dht.rs index 007e2d3e..d43e15f0 100644 --- a/src-tauri/risuko-bt/src/dht.rs +++ b/src-tauri/risuko-bt/src/dht.rs @@ -155,7 +155,7 @@ impl Dht { let sock6 = match UdpSocket::bind("[::]:0").await { Ok(s) => Some(Arc::new(s)), Err(e) => { - log::debug!("dht: no ipv6 socket: {e}"); + tracing::debug!("dht: no ipv6 socket: {e}"); None } }; @@ -194,7 +194,7 @@ impl Dht { *this.reader6_handle.lock() = Some(reader6_handle); } - log::debug!( + tracing::debug!( "DHT started: id={}, bootstrap={} nodes, ipv6={}", hex::encode(our_id.as_bytes()), this.bootstrap.len(), @@ -271,7 +271,7 @@ impl Dht { } } if addrs.is_empty() { - log::debug!("dht: no bootstrap nodes resolved"); + tracing::debug!("dht: no bootstrap nodes resolved"); return; } @@ -387,7 +387,7 @@ impl Dht { } } - log::debug!( + tracing::debug!( "dht get_peers: peers={} nodes_learned={} nodes_queried={}", total_peers, total_nodes, diff --git a/src-tauri/risuko-bt/src/lsd.rs b/src-tauri/risuko-bt/src/lsd.rs index 387b321c..0a280a0f 100644 --- a/src-tauri/risuko-bt/src/lsd.rs +++ b/src-tauri/risuko-bt/src/lsd.rs @@ -85,14 +85,14 @@ impl LocalServiceDiscovery { let v4 = match bind_v4() { Ok(s) => Some(Arc::new(s)), Err(e) => { - log::warn!("lsd: failed to bind IPv4 socket: {e}"); + tracing::warn!("lsd: failed to bind IPv4 socket: {e}"); None } }; let v6 = match bind_v6() { Ok(s) => Some(Arc::new(s)), Err(e) => { - log::warn!("lsd: failed to bind IPv6 socket: {e}"); + tracing::warn!("lsd: failed to bind IPv6 socket: {e}"); None } }; diff --git a/src-tauri/risuko-bt/src/magnet.rs b/src-tauri/risuko-bt/src/magnet.rs index fe23f00a..d5480a9d 100644 --- a/src-tauri/risuko-bt/src/magnet.rs +++ b/src-tauri/risuko-bt/src/magnet.rs @@ -119,12 +119,12 @@ pub async fn resolve_with_peers( tracker_set.spawn(async move { match announce(&url, &req, per_tracker).await { Ok(r) => { - log::debug!("tracker {url} returned {} peers", r.peers.len()); + tracing::debug!("tracker {url} returned {} peers", r.peers.len()); for p in r.peers { let _ = tx.send(p); } } - Err(e) => log::debug!("tracker {url} failed: {e}"), + Err(e) => tracing::debug!("tracker {url} failed: {e}"), } }); } @@ -144,7 +144,7 @@ pub async fn resolve_with_peers( })) } None => { - log::debug!("dht unavailable for magnet resolution"); + tracing::debug!("dht unavailable for magnet resolution"); None } }; @@ -237,7 +237,7 @@ pub async fn resolve_with_peers( None => true, }; if !sha1_ok || !sha256_ok { - log::debug!( + tracing::debug!( "peer {addr}: info hash mismatch (sha1_ok={sha1_ok} sha256_ok={sha256_ok})" ); return; @@ -254,7 +254,7 @@ pub async fn resolve_with_peers( // not serve every required piece layer; let // another peer try layers_failed.store(true, std::sync::atomic::Ordering::Relaxed); - log::debug!("peer {addr}: piece layers incomplete; will try other peers"); + tracing::debug!("peer {addr}: piece layers incomplete; will try other peers"); return; } if let Some(tx) = result_tx.lock().take() { @@ -287,7 +287,7 @@ pub async fn resolve_with_peers( match winner { Some((info_bytes, piece_layers)) => { - log::info!("Resolved magnet in {:?}", started.elapsed()); + tracing::info!("Resolved magnet in {:?}", started.elapsed()); Ok(Resolved { info_hash, info_hash_v2: want_v2, @@ -498,7 +498,7 @@ async fn try_fetch_from_peer_inner( return Some((info_bytes, BTreeMap::new(), true)); }; if !peer_supports_v2 { - log::debug!("peer does not advertise v2; cannot serve piece layers"); + tracing::debug!("peer does not advertise v2; cannot serve piece layers"); return Some((info_bytes, BTreeMap::new(), false)); } let layers = fetch_piece_layers(handle, &mut rx, &v2).await; @@ -602,7 +602,7 @@ async fn fetch_piece_layers( } } Err(e) => { - log::debug!("piece-layer verify failed for {root:?}: {e}"); + tracing::debug!("piece-layer verify failed for {root:?}: {e}"); } } } @@ -611,7 +611,7 @@ async fn fetch_piece_layers( if wanted.contains_key(&root) && !out.contains_key(&root) { // Single rejection is terminal for this peer — the // caller will fall back to another seeder - log::debug!("peer rejected piece-layer request for {root:?}"); + tracing::debug!("peer rejected piece-layer request for {root:?}"); return Some(out); } } diff --git a/src-tauri/risuko-bt/src/peer/connection.rs b/src-tauri/risuko-bt/src/peer/connection.rs index 086e34f7..be7a3178 100644 --- a/src-tauri/risuko-bt/src/peer/connection.rs +++ b/src-tauri/risuko-bt/src/peer/connection.rs @@ -157,7 +157,7 @@ pub async fn connect_with_utp_fallback( Ok(v) => Ok(v), Err(tcp_err) => match utp.connect_timeout(addr, utp_timeout).await { Ok(stream) => { - log::debug!("tcp dial to {addr} failed ({tcp_err}); connected via µTP"); + tracing::debug!("tcp dial to {addr} failed ({tcp_err}); connected via µTP"); connect_utp_plaintext(stream, spawn).await } // Surface the TCP error — it's usually the more actionable one. @@ -416,7 +416,7 @@ async fn drive_handshake( std::io::Error::new(std::io::ErrorKind::TimedOut, "plaintext handshake timeout") } }; - log::debug!("plaintext handshake to {addr} failed: {fallback_err}; trying mse"); + tracing::debug!("plaintext handshake to {addr} failed: {fallback_err}; trying mse"); let stream = timeout(spawn.connect_timeout, TcpStream::connect(spawn.addr)) .await .map_err(|_| { @@ -430,11 +430,11 @@ async fn drive_handshake( // download is slow for some other reason match connect_mse(stream, addr, &spawn).await { Ok(v) => { - log::debug!("mse handshake to {addr} succeeded"); + tracing::debug!("mse handshake to {addr} succeeded"); Ok(v) } Err(e) => { - log::debug!("mse handshake to {addr} failed: {e}"); + tracing::debug!("mse handshake to {addr} failed: {e}"); Err(e) } } diff --git a/src-tauri/risuko-bt/src/session.rs b/src-tauri/risuko-bt/src/session.rs index 2870fff8..a4986317 100644 --- a/src-tauri/risuko-bt/src/session.rs +++ b/src-tauri/risuko-bt/src/session.rs @@ -186,7 +186,7 @@ impl Session { .unwrap_or_else(|| "0.0.0.0:0".parse().unwrap()); let listener = TcpListener::bind(listen_addr).await?; let local_port = listener.local_addr()?.port(); - log::info!("session listening on port {local_port}"); + tracing::info!("session listening on port {local_port}"); let mut upnp_handle: Option = None; if opts @@ -217,18 +217,18 @@ impl Session { let utp = match super::utp::UtpSocket::bind(SocketAddr::from(([0, 0, 0, 0], local_port))).await { Ok(s) => { - log::info!("µTP listening on udp/{}", s.local_addr().port()); + tracing::info!("µTP listening on udp/{}", s.local_addr().port()); Some(s) } Err(e) => { - log::warn!("µTP: bind udp/{local_port} failed ({e}); trying ephemeral"); + tracing::warn!("µTP: bind udp/{local_port} failed ({e}); trying ephemeral"); match super::utp::UtpSocket::bind(SocketAddr::from(([0, 0, 0, 0], 0))).await { Ok(s) => { - log::info!("µTP listening on udp/{}", s.local_addr().port()); + tracing::info!("µTP listening on udp/{}", s.local_addr().port()); Some(s) } Err(e2) => { - log::warn!("µTP: disabled (bind failed: {e2})"); + tracing::warn!("µTP: disabled (bind failed: {e2})"); None } } @@ -271,7 +271,7 @@ impl Session { *session.lsd.lock() = Some(svc); *session.lsd_router_handle.lock() = Some(router); } - Err(e) => log::warn!("lsd: not started: {e}"), + Err(e) => tracing::warn!("lsd: not started: {e}"), } } @@ -281,7 +281,7 @@ impl Session { // shared() spawns + bootstraps a single long-lived instance on first use match super::dht::Dht::shared().await { Some(dht) => *session.dht.lock() = Some(dht), - None => log::warn!("dht: not started"), + None => tracing::warn!("dht: not started"), } } @@ -315,14 +315,14 @@ impl Session { match bind_v6_listener(v6_addr) { Ok(std_listener) => match TcpListener::from_std(std_listener) { Ok(listener6) => { - log::info!("session v6 listening on port {local_port}"); + tracing::info!("session v6 listening on port {local_port}"); let weak6 = Arc::downgrade(&session); let accept6 = tokio::spawn(run_accept_loop(listener6, weak6)); *session.accept6_handle.lock() = Some(accept6); } - Err(e) => log::warn!("session v6 tokio convert failed: {e}"), + Err(e) => tracing::warn!("session v6 tokio convert failed: {e}"), }, - Err(e) => log::warn!("session v6 bind failed: {e}"), + Err(e) => tracing::warn!("session v6 bind failed: {e}"), } } @@ -704,9 +704,9 @@ impl Session { tokio::fs::remove_file(&p).await }; match res { - Ok(()) => log::info!("deleted torrent data: {}", p.display()), + Ok(()) => tracing::info!("deleted torrent data: {}", p.display()), Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} - Err(e) => log::warn!("failed to delete {}: {}", p.display(), e), + Err(e) => tracing::warn!("failed to delete {}: {}", p.display(), e), } } } @@ -777,13 +777,13 @@ async fn run_accept_loop(listener: TcpListener, weak: std::sync::Weak) s.route_inbound_peer(addr, handle.tx, rx).await; } Err(e) => { - log::debug!("inbound peer handshake failed: {e}") + tracing::debug!("inbound peer handshake failed: {e}") } } }); } Err(e) => { - log::debug!("accept failed: {e}"); + tracing::debug!("accept failed: {e}"); tokio::time::sleep(std::time::Duration::from_millis(200)).await; } } @@ -835,7 +835,7 @@ async fn run_utp_accept_loop(utp: Arc, weak: std::sync::W s.route_inbound_peer(addr, handle.tx, rx).await; } Err(e) => { - log::debug!("inbound µTP peer handshake failed: {e}"); + tracing::debug!("inbound µTP peer handshake failed: {e}"); } } }); diff --git a/src-tauri/risuko-bt/src/torrent.rs b/src-tauri/risuko-bt/src/torrent.rs index 56838baf..b281e4b9 100644 --- a/src-tauri/risuko-bt/src/torrent.rs +++ b/src-tauri/risuko-bt/src/torrent.rs @@ -227,7 +227,7 @@ pub async fn spawn( // `only_files` reaches TorrentInit, but the scheduler still fetches every piece // Warn so callers do not think a subset-only download is active if init.only_files.is_some() { - log::warn!( + tracing::warn!( "torrent {id}: selective download (only_files) is not yet supported; \ downloading all files" ); @@ -429,7 +429,7 @@ async fn torrent_loop( ) { Ok(t) => tbls.push(t), Err(e) => { - log::warn!("hybrid torrent {info_hash}: could not build Merkle table for serving: {e}"); + tracing::warn!("hybrid torrent {info_hash}: could not build Merkle table for serving: {e}"); ok = false; break; } @@ -472,14 +472,14 @@ async fn torrent_loop( ), }; let max_peers = init.max_peers.unwrap_or(DEFAULT_MAX_PEERS).max(1); - log::info!( + tracing::info!( target: "diag", "torrent pipeline config: max_outstanding_per_peer={:?} -> floor={} cap={} max_peers={}", init.max_outstanding_per_peer, pipeline_floor, pipeline_cap, max_peers ); let storage = Arc::new(FilesystemStorage::new(&info, &init.root_dir)); if let Err(e) = storage.preallocate().await { - log::warn!("preallocate failed for {info_hash}: {e}"); + tracing::warn!("preallocate failed for {info_hash}: {e}"); } let mut piece_tracker = PieceTracker::new(lengths); let mut chunk_tracker = ChunkTracker::new(lengths); @@ -631,12 +631,12 @@ async fn torrent_loop( // Wait for in-flight piece write tasks before closing handles while let Some(result) = write_tasks.join_next().await { if let Err(e) = result { - log::warn!("write task failed during pause: {e}"); + tracing::warn!("write task failed during pause: {e}"); } } // Release the cached file descriptors if let Err(e) = storage.close_handles().await { - log::warn!("failed to close storage handles on pause: {e}"); + tracing::warn!("failed to close storage handles on pause: {e}"); } let _ = ack.send(()); } @@ -652,7 +652,7 @@ async fn torrent_loop( while let Some(result) = tracker_tasks.join_next().await { if let Err(e) = result { if !e.is_cancelled() { - log::warn!("tracker task failed during stop: {e}"); + tracing::warn!("tracker task failed during stop: {e}"); } } } @@ -664,12 +664,12 @@ async fn torrent_loop( // Wait for in-flight write/verify tasks before Stop acknowledges disk completion while let Some(result) = write_tasks.join_next().await { if let Err(e) = result { - log::warn!("write task failed during stop: {e}"); + tracing::warn!("write task failed during stop: {e}"); } } // Flush and release cached file descriptors if let Err(e) = storage.close_handles().await { - log::warn!("failed to close storage handles on stop: {e}"); + tracing::warn!("failed to close storage handles on stop: {e}"); } let _ = ack.send(()); break; @@ -735,7 +735,7 @@ async fn torrent_loop( result = outbound_tasks.join_next(), if !outbound_tasks.is_empty() => { if let Some(Err(e)) = result { if !e.is_cancelled() { - log::warn!("outbound peer task failed: {e}"); + tracing::warn!("outbound peer task failed: {e}"); } } } @@ -888,7 +888,7 @@ async fn torrent_loop( s.peers = peer_snaps; } } - log::debug!( + tracing::debug!( target: "diag", "TICK summary peers={} pending_dials={} known={} endgame={} dl_bytes_tick={} ul_bytes_tick={} pending_chunks={} dt_ms={:.0}", peers.len(), @@ -1156,7 +1156,7 @@ async fn process_peer_event( let _ = cmd_tx.send(PeerCommand::Disconnect).await; return false; } - log::debug!( + tracing::debug!( "peer connected: {addr} (encrypted={encrypted}, peers={}/{max_peers})", peers.len() + 1 ); @@ -1206,7 +1206,7 @@ async fn process_peer_event( // and recycle their slot to a fresh dial \u2014 see the eviction // sweep in the `tick.tick()` arm peer.last_recv = Instant::now(); - if log::log_enabled!(target: "diag", log::Level::Debug) { + if tracing::enabled!(target: "diag", tracing::Level::DEBUG) { let kind = match &msg { Message::KeepAlive => "KeepAlive".to_string(), Message::Choke => "Choke".to_string(), @@ -1226,7 +1226,7 @@ async fn process_peer_event( } other => format!("{other:?}"), }; - log::debug!( + tracing::debug!( target: "diag", "RX {} {kind} am_interested={} peer_choking={} am_choking={}", peer.addr, peer.am_interested, peer.peer_choking, peer.am_choking @@ -1351,7 +1351,7 @@ async fn process_peer_event( { peer.max_outstanding = (peer.max_outstanding * 2).min(pipeline_cap); peer.received_since_grow = 0; - log::info!( + tracing::info!( target: "diag", "pipeline GROW pid={pid} addr={} {}->{} cap={pipeline_cap}", peer.addr, @@ -1581,7 +1581,7 @@ async fn process_peer_event( // encryption-only peers — is visible to operators // troubleshooting "0 KB/s" reports without us having to // re-deduce it from "trying mse" lines alone - log::debug!("peer {a} disconnected: {reason}"); + tracing::debug!("peer {a} disconnected: {reason}"); known_addrs.remove(&a); pex_source.retain(|_, relay| *relay != pid); if was_pending_dial { @@ -1644,7 +1644,7 @@ async fn process_verify_result( // failed verification can reallocate a fresh assembly on retry piece_assemblies.remove(&vr.piece_index); if vr.write_failed { - log::warn!( + tracing::warn!( "piece {} disk write failed; will re-request", vr.piece_index ); @@ -1678,7 +1678,7 @@ async fn process_verify_result( } s.finished = piece_tracker.is_complete(); } else { - log::debug!("piece {} verify failed", vr.piece_index); + tracing::debug!("piece {} verify failed", vr.piece_index); // Same reasoning as write_failed: stale chunks from the prior // attempt would interleave with the re-request cancel_piece_outstanding(peers, vr.piece_index); @@ -1758,7 +1758,7 @@ fn handle_holepunch( } } holepunch_type::ERROR => { - log::debug!( + tracing::debug!( target: "diag", "holepunch ERROR from {from_addr} target={} code={}", hp.addr, hp.err_code @@ -1799,7 +1799,7 @@ fn try_initiate_holepunch( .is_ok() { holepunch_attempted.insert(target); - log::debug!( + tracing::debug!( target: "diag", "holepunch RENDEZVOUS initiate target={target} via relay pid={relay_pid}" ); @@ -1809,7 +1809,7 @@ fn try_initiate_holepunch( async fn send_interested_if_useful(peer: &mut Peer, piece_tracker: &mut PieceTracker) { let useful = piece_tracker.choose_piece(&peer.bitfield).is_some(); let set_bits: u32 = peer.bitfield.iter().map(|x| x.count_ones()).sum(); - log::debug!( + tracing::debug!( target: "diag", "send_interested_if_useful {} am_interested={} useful={} my_bitfield_set={}", peer.addr, peer.am_interested, useful, set_bits @@ -2252,7 +2252,7 @@ fn spawn_tracker_pollers( Ok(resp) => { // DIAG: per-URL peer yield — quantifies whether Risuko's // (default-empty) tracker set is starving the swarm vs BitComet's. - log::info!( + tracing::info!( target: "diag", "tracker ANNOUNCE ok url={url} event={event:?} peers={} interval_s={}", resp.peers.len(), @@ -2269,7 +2269,7 @@ fn spawn_tracker_pollers( tokio::time::sleep(resp.interval).await; } Err(e) => { - log::info!(target: "diag", "tracker ANNOUNCE fail url={url} event={event:?} err={e}"); + tracing::info!(target: "diag", "tracker ANNOUNCE fail url={url} event={event:?} err={e}"); tokio::time::sleep(Duration::from_secs(120)).await; } } diff --git a/src-tauri/risuko-bt/src/upnp.rs b/src-tauri/risuko-bt/src/upnp.rs index bfacaa59..9b2a92cc 100644 --- a/src-tauri/risuko-bt/src/upnp.rs +++ b/src-tauri/risuko-bt/src/upnp.rs @@ -172,7 +172,7 @@ async fn run_forever( tokio::select! { _ = tick.tick() => { if let Err(e) = discover_and_map(&ports, &opts, &active).await { - log::debug!("upnp discover/map pass failed: {e}"); + tracing::debug!("upnp discover/map pass failed: {e}"); } attempts.fetch_add(1, Ordering::Relaxed); } @@ -203,7 +203,7 @@ async fn discover_and_map( let root = match fetch_root_desc(&ep.location).await { Ok(r) => r, Err(e) => { - log::debug!("upnp fetch_root_desc {}: {e}", ep.location); + tracing::debug!("upnp fetch_root_desc {}: {e}", ep.location); continue; } }; @@ -227,7 +227,7 @@ async fn discover_and_map( .await { Ok(()) => { - log::info!( + tracing::info!( "upnp mapped {} {} via {} (external {:?})", proto.as_str(), port, @@ -245,7 +245,7 @@ async fn discover_and_map( }); } Err(e) => { - log::warn!( + tracing::warn!( "upnp AddPortMapping {} {} failed via {}: {e}", proto.as_str(), port, diff --git a/src-tauri/risuko-cli/Cargo.toml b/src-tauri/risuko-cli/Cargo.toml index e60bc74e..44ed153c 100644 --- a/src-tauri/risuko-cli/Cargo.toml +++ b/src-tauri/risuko-cli/Cargo.toml @@ -16,7 +16,6 @@ clap = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true, features = ["full"] } -log = { workspace = true } dirs = { workspace = true } risuko-http = { workspace = true } base64 = "0.22" diff --git a/src-tauri/risuko-cookies/Cargo.toml b/src-tauri/risuko-cookies/Cargo.toml index e5228eef..fd74fb0c 100644 --- a/src-tauri/risuko-cookies/Cargo.toml +++ b/src-tauri/risuko-cookies/Cargo.toml @@ -13,8 +13,34 @@ name = "risuko_cookies" serde = { version = "1", features = ["derive"] } serde_json = "1" tracing = "0.1" -tokio = { version = "1", features = ["rt"] } +tokio = { version = "1", features = ["rt", "fs"] } url = "2" +eyre = "0.6" +glob = "0.3" +tempfile = "3" + +[target.'cfg(windows)'.dependencies] +regex = "1" [target.'cfg(not(target_os = "android"))'.dependencies] -rookie = "0.5" +rusqlite = { version = "0.40", features = ["bundled"] } +base64 = { version = "0.22", features = ["std"] } +aes = "0.9" +cbc = { version = "0.2", features = ["alloc"] } +cipher = { version = "0.5", features = ["alloc", "block-padding"] } +pbkdf2 = "0.13" +sha1 = "0.11" +hmac = "0.13" + +[target.'cfg(target_os = "windows")'.dependencies] +aes-gcm = { version = "0.11.0-rc.4", features = ["aes", "alloc"] } +windows-sys = { version = "0.61", features = [ + "Win32_Foundation", + "Win32_Security_Cryptography", +] } + +[target.'cfg(target_os = "macos")'.dependencies] +security-framework = "3" + +[target.'cfg(target_os = "linux")'.dependencies] +zbus = { version = "5", default-features = false, features = ["blocking"] } diff --git a/src-tauri/risuko-cookies/src/browser/chromium.rs b/src-tauri/risuko-cookies/src/browser/chromium.rs new file mode 100644 index 00000000..3da2de1b --- /dev/null +++ b/src-tauri/risuko-cookies/src/browser/chromium.rs @@ -0,0 +1,365 @@ +// Chromium-family cookie extraction + +use crate::utils::{paths, time}; +use eyre::Result; +use rusqlite::Connection; +use std::path::PathBuf; + +#[derive(Clone)] +pub struct Cookie { + pub name: String, + pub value: String, + pub domain: String, + pub path: String, + pub secure: bool, + pub http_only: bool, + pub expires: Option, +} + +pub struct BrowserConfig { + pub cookie_paths: Vec<&'static str>, +} + +impl BrowserConfig { + pub fn chrome() -> Self { + Self { + cookie_paths: if cfg!(windows) { + vec![ + "%LOCALAPPDATA%/Google/Chrome/User Data/*/Cookies", + "%LOCALAPPDATA%/Google/Chrome/User Data/*/Network/Cookies", + ] + } else if cfg!(target_os = "macos") { + vec![ + "~/Library/Application Support/Google/Chrome/*/Cookies", + "~/Library/Application Support/Google/Chrome/*/Network/Cookies", + ] + } else { + vec!["~/.config/google-chrome/*/Cookies"] + }, + } + } + + pub fn edge() -> Self { + Self { + cookie_paths: if cfg!(windows) { + vec![ + "%LOCALAPPDATA%/Microsoft/Edge/User Data/*/Cookies", + "%LOCALAPPDATA%/Microsoft/Edge/User Data/*/Network/Cookies", + ] + } else if cfg!(target_os = "macos") { + vec![ + "~/Library/Application Support/Microsoft Edge/*/Cookies", + "~/Library/Application Support/Microsoft Edge/*/Network/Cookies", + ] + } else { + vec!["~/.config/microsoft-edge/*/Cookies"] + }, + } + } + + pub fn brave() -> Self { + Self { + cookie_paths: if cfg!(windows) { + vec!["%LOCALAPPDATA%/BraveSoftware/Brave-Browser/User Data/*/Cookies"] + } else if cfg!(target_os = "macos") { + vec![ + "~/Library/Application Support/BraveSoftware/Brave-Browser/*/Cookies", + "~/Library/Application Support/BraveSoftware/Brave-Browser/*/Network/Cookies", + ] + } else { + vec!["~/.config/BraveSoftware/Brave-Browser/*/Cookies"] + }, + } + } + + pub fn chromium() -> Self { + Self { + cookie_paths: if cfg!(windows) { + vec!["%LOCALAPPDATA%/Chromium/User Data/*/Cookies"] + } else if cfg!(target_os = "macos") { + vec![ + "~/Library/Application Support/Chromium/*/Cookies", + "~/Library/Application Support/Chromium/*/Network/Cookies", + ] + } else { + vec!["~/.config/chromium/*/Cookies"] + }, + } + } + + pub fn vivaldi() -> Self { + Self { + cookie_paths: if cfg!(windows) { + vec!["%LOCALAPPDATA%/Vivaldi/User Data/*/Cookies"] + } else if cfg!(target_os = "macos") { + vec![ + "~/Library/Application Support/Vivaldi/*/Cookies", + "~/Library/Application Support/Vivaldi/*/Network/Cookies", + ] + } else { + vec!["~/.config/vivaldi/*/Cookies"] + }, + } + } + + pub fn opera() -> Self { + Self { + cookie_paths: if cfg!(windows) { + vec!["%APPDATA%/Opera Software/Opera Stable/Cookies"] + } else if cfg!(target_os = "macos") { + vec![ + "~/Library/Application Support/com.operasoftware.Opera/Cookies", + "~/Library/Application Support/com.operasoftware.Opera/Network/Cookies", + ] + } else { + vec!["~/.config/opera/Cookies"] + }, + } + } + + pub fn arc() -> Self { + Self { + cookie_paths: if cfg!(target_os = "macos") { + vec![ + "~/Library/Application Support/Arc/User Data/*/Cookies", + "~/Library/Application Support/Arc/User Data/*/Network/Cookies", + ] + } else { + vec![] + }, + } + } +} + +pub fn extract_cookies(config: &BrowserConfig, host: Option<&str>) -> Result> { + // Chromium browsers can have multiple profiles (Default, Profile 1, etc.) + // Read all of them and merge cookies — the active profile isn't always "Default" + let mut all_dbs = Vec::new(); + for pattern in &config.cookie_paths { + if let Ok(paths) = paths::find_matching(pattern) { + for p in paths { + if p.exists() { + all_dbs.push(p); + } + } + } + } + if all_dbs.is_empty() { + return Err(eyre::eyre!("cookie database not found")); + } + + let mut all_cookies = Vec::new(); + for cookie_db in &all_dbs { + tracing::debug!(target: "risuko_cookies", "chromium: using db path {}", cookie_db.display()); + + // "Local State" lives next to the profile directory and holds the encrypted master key + let local_state = paths::find_local_state(cookie_db); + tracing::debug!(target: "risuko_cookies", "chromium: local_state path = {:?}", local_state.as_ref().map(|p| p.display().to_string())); + + let master_key = if let Some(ls) = local_state { + #[cfg(target_os = "windows")] + { + let key = crate::platform::windows::extract_master_key(&ls).ok(); + tracing::debug!(target: "risuko_cookies", "chromium: windows master_key len = {:?}", key.as_ref().map(|k| k.len())); + key + } + #[cfg(target_os = "macos")] + { + let key = crate::platform::macos::extract_master_key(&ls).ok(); + tracing::debug!(target: "risuko_cookies", "chromium: macos master_key len = {:?}", key.as_ref().map(|k| k.len())); + key + } + #[cfg(target_os = "linux")] + { + let key = crate::platform::linux::extract_master_key(&ls).ok(); + tracing::debug!(target: "risuko_cookies", "chromium: linux master_key len = {:?}", key.as_ref().map(|k| k.len())); + key + } + #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] + { + tracing::debug!(target: "risuko_cookies", "chromium: unsupported platform, no master key"); + None + } + } else { + tracing::debug!(target: "risuko_cookies", "chromium: no local_state found, cookies will be raw"); + None + }; + + let mut cookies = read_cookies_from_db(cookie_db, master_key.as_deref(), host)?; + all_cookies.append(&mut cookies); + } + + tracing::debug!(target: "risuko_cookies", + "chromium: scanned {} db(s), host_filter={:?} -> total kept={}", + all_dbs.len(), host, all_cookies.len() + ); + + Ok(all_cookies) +} + +fn read_cookies_from_db( + db_path: &PathBuf, + master_key: Option<&[u8]>, + host: Option<&str>, +) -> Result> { + // Copy to a temp file because the browser keeps the real DB locked while running + let temp = tempfile::NamedTempFile::new()?; + std::fs::copy(db_path, temp.path())?; + + let conn = Connection::open(temp.path())?; + + // Count total rows first for debugging + let total_count: i64 = conn.query_row("SELECT COUNT(*) FROM cookies", [], |row| row.get(0))?; + tracing::debug!(target: "risuko_cookies", "chromium: total rows in db = {}", total_count); + + // Read all cookies + let mut stmt = conn.prepare( + "SELECT name, encrypted_value, host_key, path, is_secure, is_httponly, expires_utc FROM cookies" + )?; + let cookie_iter = stmt.query_map([], parse_row)?; + + let mut cookies = Vec::new(); + let mut skipped_empty = 0usize; + let mut skipped_domain = 0usize; + let mut decrypted_ok = 0usize; + let mut decrypted_fail = 0usize; + let mut no_key = 0usize; + + for cookie_result in cookie_iter { + if let Ok(raw) = cookie_result { + if raw.raw_value.is_empty() { + skipped_empty += 1; + continue; + } + + // Skip cookies that don't cover the requested host + if let Some(request_host) = host { + if !cookie_covers_host(request_host, &raw.domain) { + tracing::trace!(target: "risuko_cookies", "chromium: skip cookie '{}' host_key={} (does not cover {})", raw.name, raw.domain, request_host); + skipped_domain += 1; + continue; + } + } + + // Decrypt the raw BLOB bytes, then convert to string + let value = if let Some(key) = master_key { + match decrypt_cookie_value(&raw.raw_value, key) { + Ok(decrypted) => { + decrypted_ok += 1; + String::from_utf8_lossy(&decrypted).to_string() + } + Err(e) => { + tracing::trace!(target: "risuko_cookies", "chromium: decrypt failed for '{}' host_key={}: {}", raw.name, raw.domain, e); + decrypted_fail += 1; + String::from_utf8_lossy(&raw.raw_value).to_string() + } + } + } else { + no_key += 1; + String::from_utf8_lossy(&raw.raw_value).to_string() + }; + + cookies.push(Cookie { + name: raw.name, + value, + domain: raw.domain, + path: raw.path, + secure: raw.secure, + http_only: raw.http_only, + expires: raw.expires, + }); + } + } + + tracing::debug!(target: "risuko_cookies", + "chromium: host_filter={:?} -> kept={} (skipped_empty={} skipped_domain={} decrypted_ok={} decrypted_fail={} no_key={})", + host, cookies.len(), skipped_empty, skipped_domain, decrypted_ok, decrypted_fail, no_key + ); + + for c in cookies.iter() { + tracing::trace!(target: "risuko_cookies", "chromium: kept cookie name={} domain={} value_len={}", c.name, c.domain, c.value.len()); + } + + Ok(cookies) +} + +fn cookie_covers_host(request_host: &str, cookie_host_key: &str) -> bool { + let r = request_host.to_lowercase(); + let c = cookie_host_key.to_lowercase(); + + if c.starts_with('.') { + let domain = &c[1..]; + r == domain || r.ends_with(&format!(".{domain}")) + } else { + r == c + } +} + +struct RawCookie { + name: String, + raw_value: Vec, + domain: String, + path: String, + secure: bool, + http_only: bool, + expires: Option, +} + +fn parse_row(row: &rusqlite::Row) -> rusqlite::Result { + Ok(RawCookie { + name: row.get(0)?, + raw_value: row.get::<_, Vec>(1)?, + domain: row.get(2)?, + path: row.get(3)?, + secure: row.get::<_, i32>(4)? != 0, + http_only: row.get::<_, i32>(5)? != 0, + expires: time::webkit_to_unix(row.get::<_, i64>(6)? as u64), + }) +} + +fn decrypt_cookie_value(encrypted: &[u8], master_key: &[u8]) -> Result> { + #[cfg(target_os = "windows")] + { + crate::platform::windows::decrypt_value(encrypted, master_key) + } + + #[cfg(target_os = "macos")] + { + crate::platform::macos::decrypt_value(encrypted, master_key) + } + + #[cfg(target_os = "linux")] + { + crate::platform::linux::decrypt_value(encrypted, master_key) + } + + #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] + { + let _ = (encrypted, master_key); + eyre::bail!("unsupported platform") + } +} + +#[cfg(test)] +mod tests { + use super::cookie_covers_host; + + #[test] + fn domain_cookie_with_dot_covers_subdomain() { + assert!(cookie_covers_host("www.spigotmc.org", ".spigotmc.org")); + assert!(cookie_covers_host("dl.spigotmc.org", ".spigotmc.org")); + assert!(cookie_covers_host("spigotmc.org", ".spigotmc.org")); + } + + #[test] + fn host_only_cookie_exact_match() { + assert!(cookie_covers_host("www.spigotmc.org", "www.spigotmc.org")); + assert!(!cookie_covers_host("dl.spigotmc.org", "www.spigotmc.org")); + } + + #[test] + fn no_false_match_on_suffix() { + assert!(!cookie_covers_host("notspigotmc.org", ".spigotmc.org")); + assert!(!cookie_covers_host("evil.spigotmc.org", ".example.com")); + } +} diff --git a/src-tauri/risuko-cookies/src/browser/firefox.rs b/src-tauri/risuko-cookies/src/browser/firefox.rs new file mode 100644 index 00000000..4fa8cbe5 --- /dev/null +++ b/src-tauri/risuko-cookies/src/browser/firefox.rs @@ -0,0 +1,173 @@ +// Firefox-family cookie extraction + +use crate::browser::chromium::Cookie; +use crate::utils::paths; +use eyre::{bail, Result}; +use rusqlite::Connection; + +pub struct BrowserConfig { + pub cookie_paths: Vec<&'static str>, +} + +impl BrowserConfig { + pub fn firefox() -> Self { + Self { + cookie_paths: if cfg!(windows) { + vec!["%APPDATA%/Mozilla/Firefox/Profiles/*/cookies.sqlite"] + } else if cfg!(target_os = "macos") { + vec!["~/Library/Application Support/Firefox/Profiles/*/cookies.sqlite"] + } else { + vec!["~/.mozilla/firefox/*/cookies.sqlite"] + }, + } + } + + pub fn librewolf() -> Self { + Self { + cookie_paths: if cfg!(windows) { + vec!["%APPDATA%/LibreWolf/Profiles/*/cookies.sqlite"] + } else if cfg!(target_os = "macos") { + vec!["~/Library/Application Support/LibreWolf/Profiles/*/cookies.sqlite"] + } else { + vec!["~/.librewolf/*/cookies.sqlite"] + }, + } + } + + pub fn zen() -> Self { + Self { + cookie_paths: if cfg!(windows) { + vec!["%APPDATA%/Zen/Profiles/*/cookies.sqlite"] + } else if cfg!(target_os = "macos") { + vec!["~/Library/Application Support/Zen/Profiles/*/cookies.sqlite"] + } else { + vec!["~/.zen/*/cookies.sqlite"] + }, + } + } +} + +pub fn extract_cookies(config: &BrowserConfig, host: Option<&str>) -> Result> { + // Browsers can have multiple profiles + let mut all_dbs = Vec::new(); + for pattern in &config.cookie_paths { + if let Ok(paths) = paths::find_matching(pattern) { + for p in paths { + if p.exists() { + all_dbs.push(p); + } + } + } + } + if all_dbs.is_empty() { + bail!("firefox cookie database not found"); + } + + let mut cookies = Vec::new(); + let mut skipped_domain = 0usize; + + for cookie_db in &all_dbs { + tracing::debug!(target: "risuko_cookies", "firefox: using db path {}", cookie_db.display()); + + // Copy to a temp file because Firefox keeps the DB locked while running + let temp = tempfile::NamedTempFile::new()?; + std::fs::copy(cookie_db, temp.path())?; + + let conn = Connection::open(temp.path())?; + + let total_count: i64 = + conn.query_row("SELECT COUNT(*) FROM moz_cookies", [], |row| row.get(0))?; + tracing::debug!(target: "risuko_cookies", "firefox: total rows in moz_cookies = {}", total_count); + + let mut stmt = conn.prepare( + "SELECT name, value, host, path, isSecure, isHttpOnly, expiry FROM moz_cookies", + )?; + let cookie_iter = stmt.query_map([], parse_row)?; + + for cookie_result in cookie_iter { + if let Ok(cookie) = cookie_result { + if let Some(request_host) = host { + if !cookie_covers_host(request_host, &cookie.domain) { + tracing::trace!(target: "risuko_cookies", "firefox: skip cookie '{}' host={} (does not cover {})", cookie.name, cookie.domain, request_host); + skipped_domain += 1; + continue; + } + } + tracing::trace!(target: "risuko_cookies", "firefox: keep cookie '{}' host={} value_len={}", cookie.name, cookie.domain, cookie.value.len()); + cookies.push(cookie); + } + } + } + + tracing::debug!(target: "risuko_cookies", + "firefox: scanned {} db(s), host_filter={:?} -> kept={} skipped_domain={}", + all_dbs.len(), host, cookies.len(), skipped_domain + ); + + Ok(cookies) +} + +// Modern Firefox stores domain cookies without a leading dot +fn cookie_covers_host(request_host: &str, cookie_host: &str) -> bool { + let r = request_host.to_lowercase(); + let c = cookie_host.to_lowercase(); + + if c.starts_with('.') { + // Older Firefox: domain cookie with explicit leading dot + let domain = &c[1..]; + r == domain || r.ends_with(&format!(".{domain}")) + } else { + // Modern Firefox: bare domain is a domain cookie (covers subdomains) + r == c || r.ends_with(&format!(".{c}")) + } +} + +fn parse_row(row: &rusqlite::Row) -> rusqlite::Result { + let expiry: i64 = row.get(6)?; + let expires = if expiry > 0 { + Some(expiry as u64) + } else { + None + }; + + Ok(Cookie { + name: row.get(0)?, + value: row.get(1)?, + domain: row.get(2)?, + path: row.get(3)?, + secure: row.get::<_, i32>(4)? != 0, + http_only: row.get::<_, i32>(5)? != 0, + expires, + }) +} + +#[cfg(test)] +mod tests { + use super::cookie_covers_host; + + #[test] + fn domain_cookie_without_dot_covers_subdomain() { + // Modern Firefox: domain cookie stored as "spigotmc.org" (no dot) + assert!(cookie_covers_host("www.spigotmc.org", "spigotmc.org")); + assert!(cookie_covers_host("dl.spigotmc.org", "spigotmc.org")); + assert!(cookie_covers_host("spigotmc.org", "spigotmc.org")); + } + + #[test] + fn old_domain_cookie_with_dot_covers_subdomain() { + // Older Firefox: domain cookie stored as ".spigotmc.org" + assert!(cookie_covers_host("www.spigotmc.org", ".spigotmc.org")); + assert!(cookie_covers_host("dl.spigotmc.org", ".spigotmc.org")); + } + + #[test] + fn host_only_cookie_exact_match() { + assert!(cookie_covers_host("www.spigotmc.org", "www.spigotmc.org")); + } + + #[test] + fn no_false_match_on_suffix() { + assert!(!cookie_covers_host("notspigotmc.org", "spigotmc.org")); + assert!(!cookie_covers_host("www.spigotmc.org", "example.com")); + } +} diff --git a/src-tauri/risuko-cookies/src/browser/mod.rs b/src-tauri/risuko-cookies/src/browser/mod.rs new file mode 100644 index 00000000..e4e27dde --- /dev/null +++ b/src-tauri/risuko-cookies/src/browser/mod.rs @@ -0,0 +1,5 @@ +pub mod chromium; +pub mod firefox; + +#[cfg(target_os = "macos")] +pub mod safari; diff --git a/src-tauri/risuko-cookies/src/browser/safari.rs b/src-tauri/risuko-cookies/src/browser/safari.rs new file mode 100644 index 00000000..c193cf8e --- /dev/null +++ b/src-tauri/risuko-cookies/src/browser/safari.rs @@ -0,0 +1,126 @@ +// Safari cookie extraction + +#[cfg(target_os = "macos")] +use crate::browser::chromium::Cookie; +#[cfg(target_os = "macos")] +use crate::utils::{paths, time}; +#[cfg(target_os = "macos")] +use eyre::{bail, Result}; +#[cfg(target_os = "macos")] +use rusqlite::Connection; + +#[cfg(target_os = "macos")] +pub fn extract_cookies(host: Option<&str>) -> Result> { + let cookie_db = paths::find_first_existing(&[ + "~/Library/Cookies/Cookies.binarycookies", + "~/Library/Containers/com.apple.Safari/Data/Library/Cookies/Cookies.binarycookies", + ]); + let cookie_db = match cookie_db { + Some(p) => p, + None => bail!("safari cookie file not found"), + }; + tracing::debug!(target: "risuko_cookies", "safari: using db path {}", cookie_db.display()); + + // Export the binary cookie store to a temporary plist we can read + let temp_db = tempfile::NamedTempFile::new()?; + std::process::Command::new("defaults") + .arg("export") + .arg(&cookie_db) + .arg(temp_db.path()) + .output()?; + + let conn = Connection::open(temp_db.path())?; + + // Count total rows first for debugging + let total_count: i64 = conn.query_row("SELECT COUNT(*) FROM cookies", [], |row| row.get(0))?; + tracing::debug!(target: "risuko_cookies", "safari: total rows in cookies = {}", total_count); + + // Read all cookies and filter by domain + let mut stmt = + conn.prepare("SELECT name, value, domain, path, secure, httpOnly, expires FROM cookies")?; + let cookie_iter = stmt.query_map([], parse_row)?; + + let mut cookies = Vec::new(); + let mut skipped_domain = 0usize; + + for cookie_result in cookie_iter { + if let Ok(cookie) = cookie_result { + if let Some(request_host) = host { + if !cookie_covers_host(request_host, &cookie.domain) { + tracing::trace!(target: "risuko_cookies", "safari: skip cookie '{}' domain={} (does not cover {})", cookie.name, cookie.domain, request_host); + skipped_domain += 1; + continue; + } + } + tracing::trace!(target: "risuko_cookies", "safari: keep cookie '{}' domain={} value_len={}", cookie.name, cookie.domain, cookie.value.len()); + cookies.push(cookie); + } + } + + tracing::debug!(target: "risuko_cookies", + "safari: host_filter={:?} -> kept={} skipped_domain={}", + host, cookies.len(), skipped_domain + ); + + Ok(cookies) +} + +#[cfg(target_os = "macos")] +fn cookie_covers_host(request_host: &str, cookie_domain: &str) -> bool { + let r = request_host.to_lowercase(); + let c = cookie_domain.to_lowercase(); + + if c.starts_with('.') { + // Older Safari: domain cookie with explicit leading dot + let domain = &c[1..]; + r == domain || r.ends_with(&format!(".{domain}")) + } else { + // Modern Safari: bare domain is a domain cookie (covers subdomains) + r == c || r.ends_with(&format!(".{c}")) + } +} + +#[cfg(target_os = "macos")] +fn parse_row(row: &rusqlite::Row) -> rusqlite::Result { + Ok(Cookie { + name: row.get(0)?, + value: row.get(1)?, + domain: row.get(2)?, + path: row.get(3)?, + secure: row.get::<_, i32>(4)? != 0, + http_only: row.get::<_, i32>(5)? != 0, + expires: time::safari_to_unix(row.get::<_, i64>(6)? as u64), + }) +} + +#[cfg(not(target_os = "macos"))] +pub fn extract_cookies(_host: Option<&str>) -> eyre::Result> { + eyre::bail!("safari only available on macos") +} + +#[cfg(test)] +mod tests { + #[cfg(target_os = "macos")] + use super::cookie_covers_host; + + #[test] + #[cfg(target_os = "macos")] + fn domain_cookie_covers_subdomain() { + assert!(cookie_covers_host("www.spigotmc.org", "spigotmc.org")); + assert!(cookie_covers_host("dl.spigotmc.org", "spigotmc.org")); + assert!(cookie_covers_host("spigotmc.org", "spigotmc.org")); + } + + #[test] + #[cfg(target_os = "macos")] + fn old_domain_cookie_with_dot() { + assert!(cookie_covers_host("www.spigotmc.org", ".spigotmc.org")); + } + + #[test] + #[cfg(target_os = "macos")] + fn no_false_match() { + assert!(!cookie_covers_host("notspigotmc.org", "spigotmc.org")); + assert!(!cookie_covers_host("www.spigotmc.org", "example.com")); + } +} diff --git a/src-tauri/risuko-cookies/src/lib.rs b/src-tauri/risuko-cookies/src/lib.rs index 105e8a41..74de5771 100644 --- a/src-tauri/risuko-cookies/src/lib.rs +++ b/src-tauri/risuko-cookies/src/lib.rs @@ -1,14 +1,23 @@ -//! Browser cookie extraction façade +//! Browser cookie extraction //! -//! Wraps the synchronous `rookie` crate behind async helpers and exposes a -//! flat list of supported browsers per host OS. Host-agnostic: both the -//! engine and the Tauri command layer pull from here +//! Reads cookies straight from the browser's own store: Chromium and its +//! forks, the Firefox family, and Safari on macOS + +mod browser; +mod platform; +mod utils; use serde::{Deserialize, Serialize}; use url::Url; -#[cfg(not(target_os = "android"))] -use tokio::task::spawn_blocking; +// Newer Chrome on Windows wraps the cookie key with app-bound encryption that +// only opens under an admin token. When we hit that we bail with this marker so +// the Tauri side can offer a UAC retry instead of just saying "failed" +#[cfg(target_os = "windows")] +pub const ELEVATION_REQUIRED: &str = platform::windows::ELEVATION_REQUIRED; + +#[cfg(not(target_os = "windows"))] +pub const ELEVATION_REQUIRED: &str = "risuko:elevation-required"; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Cookie { @@ -21,9 +30,8 @@ pub struct Cookie { pub expires: Option, } -#[cfg(not(target_os = "android"))] -impl From for Cookie { - fn from(c: rookie::enums::Cookie) -> Self { +impl From for Cookie { + fn from(c: browser::chromium::Cookie) -> Self { Self { name: c.name, value: c.value, @@ -45,8 +53,16 @@ pub struct BrowserInfo { pub user_agent: String, } -/// Format cookies into a single `Cookie:` header value +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HostCookies { + pub host: String, + pub user_agent: String, + pub cookies: Vec, +} + pub fn cookies_to_header(cookies: &[Cookie]) -> String { + // Join name=value pairs with semicolons — standard Cookie header format cookies .iter() .map(|c| format!("{}={}", c.name, c.value)) @@ -54,9 +70,6 @@ pub fn cookies_to_header(cookies: &[Cookie]) -> String { .join("; ") } -/// List browsers known to rookie on this OS. `available` reflects whether -/// a quick probe (no domain filter) returned without an error; sandbox -/// failures or missing profiles surface as `false` pub async fn list_browsers() -> Vec { #[cfg(target_os = "android")] { @@ -64,18 +77,31 @@ pub async fn list_browsers() -> Vec { } #[cfg(not(target_os = "android"))] { - spawn_blocking(list_browsers_sync).await.unwrap_or_default() + tokio::task::spawn_blocking(list_browsers_sync) + .await + .unwrap_or_default() } } #[cfg(not(target_os = "android"))] fn list_browsers_sync() -> Vec { - let mut out: Vec = Vec::new(); + let mut browsers = Vec::new(); + // Probe each browser by trying to read its cookie store — no actual cookies fetched, + // just a quick "can we open the database" check to set the available flag macro_rules! probe { - ($id:literal, $name:literal, $ua:expr, $f:expr) => {{ - let available = $f(None).is_ok(); - out.push(BrowserInfo { + ($id:literal, $name:literal, $ua:expr, $config:expr) => {{ + let available = browser::chromium::extract_cookies(&$config, None).is_ok(); + browsers.push(BrowserInfo { + id: $id.into(), + name: $name.into(), + available, + user_agent: $ua.into(), + }); + }}; + (firefox: $id:literal, $name:literal, $ua:expr, $config:expr) => {{ + let available = browser::firefox::extract_cookies(&$config, None).is_ok(); + browsers.push(BrowserInfo { id: $id.into(), name: $name.into(), available, @@ -88,361 +114,218 @@ fn list_browsers_sync() -> Vec { "chrome", "Google Chrome", default_ua("chrome"), - rookie::chrome + browser::chromium::BrowserConfig::chrome() + ); + probe!( + "edge", + "Microsoft Edge", + default_ua("edge"), + browser::chromium::BrowserConfig::edge() + ); + probe!( + "brave", + "Brave", + default_ua("brave"), + browser::chromium::BrowserConfig::brave() ); probe!( "chromium", "Chromium", - default_ua("chrome"), - rookie::chromium + default_ua("chromium"), + browser::chromium::BrowserConfig::chromium() + ); + probe!( + "vivaldi", + "Vivaldi", + default_ua("vivaldi"), + browser::chromium::BrowserConfig::vivaldi() ); - probe!("brave", "Brave", default_ua("chrome"), rookie::brave); - probe!("edge", "Microsoft Edge", default_ua("edge"), rookie::edge); - probe!("vivaldi", "Vivaldi", default_ua("chrome"), rookie::vivaldi); - probe!("opera", "Opera", default_ua("chrome"), rookie::opera); - probe!("arc", "Arc", default_ua("chrome"), rookie::arc); - probe!("firefox", "Firefox", default_ua("firefox"), rookie::firefox); probe!( - "librewolf", - "LibreWolf", - default_ua("firefox"), - rookie::librewolf + "opera", + "Opera", + default_ua("opera"), + browser::chromium::BrowserConfig::opera() ); - probe!("zen", "Zen", default_ua("firefox"), rookie::zen); - #[cfg(target_os = "windows")] - { - probe!( - "opera_gx", - "Opera GX", - default_ua("chrome"), - rookie::opera_gx - ); - } + #[cfg(target_os = "macos")] + probe!( + "arc", + "Arc", + default_ua("chrome"), + browser::chromium::BrowserConfig::arc() + ); + + probe!(firefox: "firefox", "Firefox", default_ua("firefox"), browser::firefox::BrowserConfig::firefox()); + probe!(firefox: "librewolf", "LibreWolf", default_ua("firefox"), browser::firefox::BrowserConfig::librewolf()); + probe!(firefox: "zen", "Zen Browser", default_ua("firefox"), browser::firefox::BrowserConfig::zen()); + #[cfg(target_os = "macos")] { - probe!( - "opera_gx", - "Opera GX", - default_ua("chrome"), - rookie::opera_gx - ); - probe!("safari", "Safari", default_ua("safari"), rookie::safari); - } - #[cfg(target_os = "linux")] - { - probe!( - "cachy", - "Cachy Browser", - default_ua("firefox"), - rookie::cachy - ); + let available = browser::safari::extract_cookies(None).is_ok(); + browsers.push(BrowserInfo { + id: "safari".into(), + name: "Safari".into(), + available, + user_agent: default_ua("safari"), + }); } - out + browsers } -/// Pull cookies for `host` (and parent domains) from the named browser. -/// Returns `Err(message)` on rookie failure so callers can surface it pub async fn cookies_for_host(browser: &str, host: &str) -> Result, String> { #[cfg(target_os = "android")] { let _ = (browser, host); - Err("Browser cookie import is not supported on Android".to_string()) + Err("not supported on android".into()) } #[cfg(not(target_os = "android"))] { let browser = browser.to_string(); let host = host.to_string(); - spawn_blocking(move || cookies_for_host_sync(&browser, &host)) + tokio::task::spawn_blocking(move || cookies_for_host_sync(&browser, &host)) .await - .map_err(|e| format!("join error: {e}"))? + .map_err(|e| e.to_string())? } } #[cfg(not(target_os = "android"))] fn cookies_for_host_sync(browser: &str, host: &str) -> Result, String> { - // rookie does a substring match on the cookie's domain field. Passing - // both the bare host and its eTLD+1 catches `example.com` and - // `.example.com` alike. The substring filter can also pull in - // unrelated cookies (e.g. for `co.uk` when the host is on a multi-part - // ccTLD); the post-filter below enforces a real RFC 6265 host-match - // before returning to callers - let mut domains: Vec = vec![host.to_string()]; - if let Some(parent) = registrable_domain(host) { - if parent != host { - domains.push(parent); + tracing::debug!(target: "risuko_cookies", "cookies_for_host_sync: browser={} host={}", browser, host); + + let result = match browser { + "chrome" => browser::chromium::extract_cookies( + &browser::chromium::BrowserConfig::chrome(), + Some(host), + ), + "edge" => browser::chromium::extract_cookies( + &browser::chromium::BrowserConfig::edge(), + Some(host), + ), + "brave" => browser::chromium::extract_cookies( + &browser::chromium::BrowserConfig::brave(), + Some(host), + ), + "chromium" => browser::chromium::extract_cookies( + &browser::chromium::BrowserConfig::chromium(), + Some(host), + ), + "vivaldi" => browser::chromium::extract_cookies( + &browser::chromium::BrowserConfig::vivaldi(), + Some(host), + ), + "opera" => browser::chromium::extract_cookies( + &browser::chromium::BrowserConfig::opera(), + Some(host), + ), + "arc" => { + #[cfg(target_os = "macos")] + { + browser::chromium::extract_cookies( + &browser::chromium::BrowserConfig::arc(), + Some(host), + ) + } + #[cfg(not(target_os = "macos"))] + { + Err(eyre::eyre!("arc only available on macos")) + } } + "firefox" => browser::firefox::extract_cookies( + &browser::firefox::BrowserConfig::firefox(), + Some(host), + ), + "librewolf" => browser::firefox::extract_cookies( + &browser::firefox::BrowserConfig::librewolf(), + Some(host), + ), + "zen" => { + browser::firefox::extract_cookies(&browser::firefox::BrowserConfig::zen(), Some(host)) + } + "safari" => { + #[cfg(target_os = "macos")] + { + browser::safari::extract_cookies(Some(host)) + } + #[cfg(not(target_os = "macos"))] + { + Err(eyre::eyre!("safari only available on macos")) + } + } + _ => Err(eyre::eyre!("unsupported browser: {}", browser)), } + .map_err(|e| e.to_string()); - tracing::info!("risuko-cookies: rookie fetch browser={browser} domains={domains:?}"); - let raw = call_rookie(browser, Some(domains.clone())).inspect_err(|e| { - tracing::warn!( - "risuko-cookies: rookie fetch failed browser={browser} domains={domains:?} err={e}" - ); - })?; - tracing::info!( - "risuko-cookies: rookie returned {} cookie(s) for browser={browser} host={host}", - raw.len() - ); - for c in raw.iter() { - tracing::debug!( - " cookie name={name} domain={domain} path={path} secure={secure} http_only={http_only} value_len={value_len}", - name = c.name, - domain = c.domain, - path = c.path, - secure = c.secure, - http_only = c.http_only, - value_len = c.value.len(), - ); - } - let total = raw.len(); - let filtered: Vec = raw - .into_iter() - .filter(|c| cookie_domain_matches_host(&c.domain, host)) - .map(Cookie::from) - .collect(); - if filtered.len() != total { - tracing::info!( - "risuko-cookies: filtered {} cookie(s) down to {} after host-match for {}", - total, - filtered.len(), - host - ); + match &result { + Ok(cookies) => { + tracing::debug!(target: "risuko_cookies", "cookies_for_host_sync: browser={} host={} -> {} cookies", browser, host, cookies.len()) + } + Err(e) => { + tracing::debug!(target: "risuko_cookies", "cookies_for_host_sync: browser={} host={} -> error: {}", browser, host, e) + } } - Ok(filtered) -} -/// RFC 6265 host-match: a cookie applies to `host` when the cookie's -/// domain attribute equals `host`, or when `host` is a subdomain of the -/// cookie domain. Leading dots on the cookie domain (legacy form) are -/// stripped before comparison. Hosts and domains are matched -/// case-insensitively -#[cfg(not(target_os = "android"))] -fn cookie_domain_matches_host(cookie_domain: &str, host: &str) -> bool { - let host_l = host.trim().to_ascii_lowercase(); - let domain_l = cookie_domain - .trim() - .trim_start_matches('.') - .to_ascii_lowercase(); - if domain_l.is_empty() || host_l.is_empty() { - return false; - } - if host_l == domain_l { - return true; - } - host_l.ends_with(&format!(".{domain_l}")) + Ok(result?.into_iter().map(Cookie::from).collect()) } -/// Resolve a URL or bare host to (host, cookies, ua) for the named browser pub async fn cookies_for_url(browser: &str, target: &str) -> Result { - let host = extract_host(target).ok_or_else(|| format!("invalid url: {target}"))?; - let cookies = cookies_for_host(browser, &host).await?; + // Accept bare hostnames or full URLs + let url = Url::parse(target) + .or_else(|_| Url::parse(&format!("https://{}", target))) + .map_err(|e| e.to_string())?; + let host = url.host_str().ok_or("invalid url")?; + tracing::debug!(target: "risuko_cookies", "cookies_for_url: browser={} target={} -> host={}", browser, target, host); + + let cookies = cookies_for_host(browser, host).await?; + let user_agent = default_ua(browser); + Ok(HostCookies { - host, - user_agent: default_ua_for(browser), + host: host.to_string(), + user_agent, cookies, }) } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct HostCookies { - pub host: String, - pub user_agent: String, - pub cookies: Vec, -} - -#[cfg(not(target_os = "android"))] -fn call_rookie( - browser: &str, - domains: Option>, -) -> Result, String> { - let r = match browser { - "chrome" => rookie::chrome(domains), - "chromium" => rookie::chromium(domains), - "brave" => rookie::brave(domains), - "edge" => rookie::edge(domains), - "vivaldi" => rookie::vivaldi(domains), - "opera" => rookie::opera(domains), - "arc" => rookie::arc(domains), - "firefox" => rookie::firefox(domains), - "librewolf" => rookie::librewolf(domains), - "zen" => rookie::zen(domains), - #[cfg(any(target_os = "windows", target_os = "macos"))] - "opera_gx" => rookie::opera_gx(domains), - #[cfg(target_os = "macos")] - "safari" => rookie::safari(domains), - #[cfg(target_os = "linux")] - "cachy" => rookie::cachy(domains), - other => return Err(format!("unknown browser: {other}")), - }; - r.map_err(|e| e.to_string()) -} - -fn default_ua_for(browser: &str) -> String { - let kind = match browser { - "firefox" | "librewolf" | "zen" | "cachy" => "firefox", - "edge" => "edge", - "safari" => "safari", - _ => "chrome", - }; - default_ua(kind).to_string() -} - -fn default_ua(kind: &str) -> &'static str { - // Fallback UAs used before the user captures a real one through the - // dialog's "Detect from browser" flow. Whatever they capture is what - // should reach Cloudflare; these strings only keep imports from - // shipping empty values - match kind { - "firefox" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0", - "edge" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0", - "safari" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15", - _ => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36", - } -} - -fn extract_host(target: &str) -> Option { - if let Ok(url) = Url::parse(target) { - return url.host_str().map(|s| s.to_string()); - } - let trimmed = target - .trim_start_matches("http://") - .trim_start_matches("https://"); - let host = trimmed.split('/').next()?; - let host = host.split(':').next()?; - if host.is_empty() { - None - } else { - Some(host.to_string()) - } -} - -/// Best-effort registrable domain. Returns the eTLD+1 for typical -/// hostnames, and falls back to eTLD+2 when the trailing two labels look -/// like a known multi-label public suffix (`co.uk`, `com.au`, `co.jp`, -/// etc). We avoid pulling in the full Public Suffix List — this only -/// needs to be good enough to widen rookie's substring filter without -/// over-broadening into a public suffix that would match unrelated -/// sites. The `cookie_domain_matches_host` post-filter in -/// `cookies_for_host_sync` enforces correctness for whatever rookie -/// returns -#[cfg(not(target_os = "android"))] -fn registrable_domain(host: &str) -> Option { - let labels: Vec<&str> = host.split('.').collect(); - if labels.len() < 2 { - return None; - } - let two = &labels[labels.len() - 2..]; - if labels.len() >= 3 && is_multi_label_public_suffix(two[0], two[1]) { - return Some(labels[labels.len() - 3..].join(".")); - } - Some(two.join(".")) -} - -/// True for the most common second-level public suffixes that are -/// shaped like `.` (`co.uk`, `com.au`, `ac.jp`, ...). Not -/// exhaustive; this is a conservative widening guard, not a security -/// boundary -#[cfg(not(target_os = "android"))] -fn is_multi_label_public_suffix(role: &str, cc: &str) -> bool { - if cc.len() != 2 { - return false; +fn default_ua(browser: &str) -> String { + match browser { + "firefox" | "librewolf" | "zen" => { + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0".into() + } + "safari" => { + "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Safari/605.1.15".into() + } + _ => { + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36".into() + } } - matches!( - role.to_ascii_lowercase().as_str(), - "co" | "com" | "net" | "org" | "edu" | "gov" | "mil" | "ac" | "or" | "ne" | "go" - ) } #[cfg(test)] mod tests { use super::*; - #[test] - fn extract_host_from_url() { - assert_eq!( - extract_host("https://example.com/path"), - Some("example.com".into()) - ); - assert_eq!( - extract_host("http://sub.example.com:8080"), - Some("sub.example.com".into()) - ); - assert_eq!(extract_host("example.com/foo"), Some("example.com".into())); - } - - #[test] - fn registrable_strips_one_label() { - assert_eq!( - registrable_domain("a.b.example.com"), - Some("example.com".into()) - ); - assert_eq!( - registrable_domain("example.com"), - Some("example.com".into()) - ); - assert_eq!(registrable_domain("localhost"), None); - } - - #[test] - fn registrable_widens_to_three_labels_for_known_multi_part_tlds() { - assert_eq!( - registrable_domain("foo.example.co.uk"), - Some("example.co.uk".into()) - ); - assert_eq!( - registrable_domain("a.b.bbc.co.uk"), - Some("bbc.co.uk".into()) - ); - assert_eq!( - registrable_domain("shop.example.com.au"), - Some("example.com.au".into()) - ); - // 2-label inputs cannot widen further; return as-is - assert_eq!(registrable_domain("co.uk"), Some("co.uk".into())); - } - - #[test] - fn cookie_domain_host_match_rules() { - // Exact match - assert!(cookie_domain_matches_host("example.com", "example.com")); - // Leading dot is treated like host-only (legacy compatibility) - assert!(cookie_domain_matches_host(".example.com", "dl.example.com")); - // Subdomain match - assert!(cookie_domain_matches_host("example.com", "dl.example.com")); - // Suffix-but-not-dot-bounded must NOT match - assert!(!cookie_domain_matches_host("example.com", "notexample.com")); - // Unrelated domain - assert!(!cookie_domain_matches_host("attacker.com", "example.com")); - // Public-suffix-style cookie should not match unrelated subdomains - assert!(!cookie_domain_matches_host(".co.uk", "example.com")); - // But still legitimately matches when host is on that domain - assert!(cookie_domain_matches_host(".co.uk", "foo.co.uk")); - } - #[test] fn cookies_to_header_format() { - let cs = vec![ + let cookies = vec![ Cookie { name: "a".into(), value: "1".into(), - domain: "example.com".into(), + domain: ".example.com".into(), path: "/".into(), - secure: true, + secure: false, http_only: false, expires: None, }, Cookie { name: "b".into(), - value: "two".into(), - domain: "example.com".into(), + value: "2".into(), + domain: ".example.com".into(), path: "/".into(), - secure: true, + secure: false, http_only: false, expires: None, }, ]; - assert_eq!(cookies_to_header(&cs), "a=1; b=two"); + assert_eq!(cookies_to_header(&cookies), "a=1; b=2"); } } diff --git a/src-tauri/risuko-cookies/src/platform/linux.rs b/src-tauri/risuko-cookies/src/platform/linux.rs new file mode 100644 index 00000000..faa2ba83 --- /dev/null +++ b/src-tauri/risuko-cookies/src/platform/linux.rs @@ -0,0 +1,81 @@ +// Linux Chromium cookie decryption + +use aes::Aes128; +use cipher::{block_padding::Pkcs7, BlockModeDecrypt, KeyIvInit}; +use eyre::{bail, Result}; +use pbkdf2::pbkdf2_hmac; +use sha1::Sha1; +use zbus::blocking::Connection; + +type Aes128CbcDec = cbc::Decryptor; + +pub fn decrypt_value(encrypted: &[u8], key: &[u8]) -> Result> { + if encrypted.len() < 3 { + bail!("encrypted data too short"); + } + + if &encrypted[..3] == b"v10" || &encrypted[..3] == b"v11" { + decrypt_v10(encrypted, key) + } else { + Ok(encrypted.to_vec()) + } +} + +fn decrypt_v10(data: &[u8], master_key: &[u8]) -> Result> { + if data.len() < 19 { + bail!("v10 data too short"); + } + + // v10 format: "v10" + 16-byte IV + ciphertext + let iv: [u8; 16] = data[3..19].try_into()?; + let ciphertext = &data[19..]; + + let mut key = [0u8; 16]; + pbkdf2_hmac::(master_key, b"saltysalt", 1, &mut key); + + let decrypted = Aes128CbcDec::new(&key.into(), &iv.into()) + .decrypt_padded_vec::(ciphertext) + .map_err(|e| eyre::eyre!("aes-cbc decrypt failed: {:?}", e))?; + + Ok(decrypted) +} + +pub fn extract_master_key(_local_state_path: &std::path::Path) -> Result> { + // Connect to the D-Bus Secret Service and look for a Chrome entry in the login collection + let conn = Connection::session()?; + + let proxy = zbus::blocking::Proxy::new( + &conn, + "org.freedesktop.secrets", + "/org/freedesktop/secrets/collection/login", + "org.freedesktop.Secret.Collection", + )?; + + let items: Vec = proxy.call( + "SearchItems", + &(std::collections::HashMap::<&str, &str>::from([( + "application", + "chrome", + )]),), + )?; + + if items.is_empty() { + bail!("no chrome password in secret service"); + } + + let item_path = &items[0]; + let item_proxy = zbus::blocking::Proxy::new( + &conn, + "org.freedesktop.secrets", + item_path, + "org.freedesktop.Secret.Item", + )?; + + let session_path: zbus::zvariant::OwnedObjectPath = + proxy.call("OpenSession", &("plain", zbus::zvariant::Value::from("")))?; + + let secret: (zbus::zvariant::OwnedObjectPath, Vec, Vec, String) = + item_proxy.call("GetSecret", &(session_path,))?; + + Ok(secret.2) +} diff --git a/src-tauri/risuko-cookies/src/platform/macos.rs b/src-tauri/risuko-cookies/src/platform/macos.rs new file mode 100644 index 00000000..8f5cd5dd --- /dev/null +++ b/src-tauri/risuko-cookies/src/platform/macos.rs @@ -0,0 +1,48 @@ +// macOS Chromium cookie decryption +use aes::Aes128; +use cipher::{block_padding::Pkcs7, BlockModeDecrypt, KeyIvInit}; +use eyre::{bail, Result}; +use pbkdf2::pbkdf2_hmac; +use security_framework::passwords::get_generic_password; +use sha1::Sha1; + +type Aes128CbcDec = cbc::Decryptor; + +pub fn decrypt_value(encrypted: &[u8], key: &[u8]) -> Result> { + if encrypted.len() < 3 { + bail!("encrypted data too short"); + } + + if &encrypted[..3] == b"v10" { + decrypt_v10(encrypted, key) + } else { + Ok(encrypted.to_vec()) + } +} + +fn decrypt_v10(data: &[u8], master_key: &[u8]) -> Result> { + if data.len() < 19 { + bail!("v10 data too short"); + } + + // v10 format: "v10" + 16-byte IV + ciphertext + let iv: [u8; 16] = data[3..19].try_into()?; + let ciphertext = &data[19..]; + + let mut key = [0u8; 16]; + pbkdf2_hmac::(master_key, b"saltysalt", 1003, &mut key); + + let decrypted = Aes128CbcDec::new(&key.into(), &iv.into()) + .decrypt_padded_vec::(ciphertext) + .map_err(|e| eyre::eyre!("aes-cbc decrypt failed: {:?}", e))?; + + Ok(decrypted) +} + +pub fn extract_master_key(_local_state_path: &std::path::Path) -> Result> { + // Try "Chrome" first, then "Chromium" as fallback + get_generic_password("Chrome Safe Storage", "Chrome") + .or_else(|_| get_generic_password("Chromium Safe Storage", "Chromium")) + .map(|pw| pw.to_vec()) + .map_err(|e| eyre::eyre!("keychain access failed: {}", e)) +} diff --git a/src-tauri/risuko-cookies/src/platform/mod.rs b/src-tauri/risuko-cookies/src/platform/mod.rs new file mode 100644 index 00000000..820240dd --- /dev/null +++ b/src-tauri/risuko-cookies/src/platform/mod.rs @@ -0,0 +1,8 @@ +#[cfg(target_os = "windows")] +pub mod windows; + +#[cfg(target_os = "macos")] +pub mod macos; + +#[cfg(target_os = "linux")] +pub mod linux; diff --git a/src-tauri/risuko-cookies/src/platform/windows.rs b/src-tauri/risuko-cookies/src/platform/windows.rs new file mode 100644 index 00000000..ca25065a --- /dev/null +++ b/src-tauri/risuko-cookies/src/platform/windows.rs @@ -0,0 +1,104 @@ +// Windows Chromium cookie decryption + +use aes_gcm::{aead::Aead, Aes256Gcm, Key, KeyInit, Nonce}; +use base64::Engine; +use eyre::{bail, Result}; +use windows_sys::Win32::{ + Foundation::LocalFree, + Security::Cryptography::{CryptUnprotectData, CRYPT_INTEGER_BLOB}, +}; + +pub const ELEVATION_REQUIRED: &str = "risuko:elevation-required"; + +pub fn decrypt_value(encrypted: &[u8], key: &[u8]) -> Result> { + if encrypted.len() < 3 { + bail!("encrypted data too short"); + } + + match &encrypted[..3] { + b"v10" => decrypt_v10(encrypted, key), + b"v20" => decrypt_v20(encrypted, key), + _ => decrypt_dpapi(encrypted), + } +} + +fn decrypt_v10(data: &[u8], master_key: &[u8]) -> Result> { + if data.len() < 15 { + bail!("v10 data too short"); + } + + let nonce = Nonce::from_slice(&data[3..15]); + let ciphertext = &data[15..]; + + let key = Key::::from_slice(master_key); + let cipher = Aes256Gcm::new(key); + + cipher + .decrypt(nonce, ciphertext) + .map_err(|e| eyre::eyre!("aes-gcm decrypt failed: {}", e)) +} + +fn decrypt_v20(data: &[u8], _master_key: &[u8]) -> Result> { + if data.len() < 35 { + bail!("v20 data too short"); + } + + // Chrome 130+ app-bound encryption needs an elevated token to access the key + bail!("{}", ELEVATION_REQUIRED); +} + +fn decrypt_dpapi(data: &[u8]) -> Result> { + // Use the Windows DPAPI to decrypt the master key — this runs under the current user's context + unsafe { + let mut blob_in = CRYPT_INTEGER_BLOB { + cbData: data.len() as u32, + pbData: data.as_ptr() as *mut u8, + }; + + let mut blob_out = CRYPT_INTEGER_BLOB { + cbData: 0, + pbData: std::ptr::null_mut(), + }; + + let result = CryptUnprotectData( + &mut blob_in, + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + 0, + &mut blob_out, + ); + + if result == 0 { + bail!("DPAPI decrypt failed"); + } + + let decrypted = + std::slice::from_raw_parts(blob_out.pbData, blob_out.cbData as usize).to_vec(); + + LocalFree(blob_out.pbData as isize); + + Ok(decrypted) + } +} + +pub fn extract_master_key(local_state_path: &std::path::Path) -> Result> { + // Read the "Local State" JSON, extract the base64-encoded encrypted key, strip the DPAPI prefix + let content = std::fs::read_to_string(local_state_path)?; + let json: serde_json::Value = serde_json::from_str(&content)?; + + let encrypted_key_b64 = json + .get("os_crypt") + .and_then(|v| v.get("encrypted_key")) + .and_then(|v| v.as_str()) + .ok_or_else(|| eyre::eyre!("missing os_crypt.encrypted_key"))?; + + let encrypted_key = base64::engine::general_purpose::STANDARD.decode(encrypted_key_b64)?; + + if encrypted_key.len() < 5 || &encrypted_key[..5] != b"DPAPI" { + bail!("invalid encrypted key format"); + } + + decrypt_dpapi(&encrypted_key[5..]) +} diff --git a/src-tauri/risuko-cookies/src/utils/mod.rs b/src-tauri/risuko-cookies/src/utils/mod.rs new file mode 100644 index 00000000..e17c85a5 --- /dev/null +++ b/src-tauri/risuko-cookies/src/utils/mod.rs @@ -0,0 +1,2 @@ +pub mod paths; +pub mod time; diff --git a/src-tauri/risuko-cookies/src/utils/paths.rs b/src-tauri/risuko-cookies/src/utils/paths.rs new file mode 100644 index 00000000..44b83ea2 --- /dev/null +++ b/src-tauri/risuko-cookies/src/utils/paths.rs @@ -0,0 +1,83 @@ +// Path helpers for finding browser cookie databases across platforms + +use std::path::{Path, PathBuf}; + +pub fn expand(path: &str) -> eyre::Result { + let expanded = if cfg!(windows) { + expand_windows(path) + } else { + expand_unix(path) + }; + Ok(PathBuf::from(expanded)) +} + +#[cfg(unix)] +fn expand_unix(path: &str) -> String { + let home = std::env::var("HOME").unwrap_or_default(); + path.replace('~', &home).replace("$HOME", &home) +} + +#[cfg(not(unix))] +fn expand_unix(_path: &str) -> String { + String::new() +} + +#[cfg(windows)] +fn expand_windows(path: &str) -> String { + use regex::Regex; + let re = Regex::new(r"%([^%]+)%").unwrap(); + let mut result = path.to_string(); + for cap in re.captures_iter(path) { + if let Ok(val) = std::env::var(&cap[1]) { + result = result.replace(&cap[0], &val); + } + } + result +} + +#[cfg(not(windows))] +fn expand_windows(_path: &str) -> String { + String::new() +} + +pub fn find_matching(pattern: &str) -> eyre::Result> { + let expanded = expand(pattern)?; + let path_str = expanded + .to_str() + .ok_or_else(|| eyre::eyre!("invalid path encoding"))?; + + let mut results = Vec::new(); + for entry in glob::glob(path_str)? { + if let Ok(p) = entry { + results.push(p); + } + } + Ok(results) +} + +pub fn find_first_existing(patterns: &[&str]) -> Option { + for pattern in patterns { + if let Ok(paths) = find_matching(pattern) { + for p in paths { + if p.exists() { + return Some(p); + } + } + } + } + None +} + +pub fn find_local_state(cookie_db: &Path) -> Option { + // "Local State" sits next to the profile directory or one level above it + let parent = cookie_db.parent()?; + + for rel in ["../../Local State", "../Local State", "Local State"] { + let candidate = parent.join(rel); + if candidate.exists() { + return candidate.canonicalize().ok(); + } + } + + None +} diff --git a/src-tauri/risuko-cookies/src/utils/time.rs b/src-tauri/risuko-cookies/src/utils/time.rs new file mode 100644 index 00000000..dee8d3c3 --- /dev/null +++ b/src-tauri/risuko-cookies/src/utils/time.rs @@ -0,0 +1,19 @@ +// WebKit and Safari timestamp conversion + +const WEBKIT_EPOCH_OFFSET: u64 = 11644473600; + +pub fn webkit_to_unix(micros: u64) -> Option { + if micros == 0 { + return None; + } + let secs = micros / 1_000_000; + secs.checked_sub(WEBKIT_EPOCH_OFFSET) +} + +#[cfg(target_os = "macos")] +pub fn safari_to_unix(timestamp: u64) -> Option { + if timestamp == 0 { + return None; + } + Some((timestamp + 978_307_200) / 1_000_000_000) +} diff --git a/src-tauri/risuko-engine/Cargo.toml b/src-tauri/risuko-engine/Cargo.toml index e41dcf12..9a6fd506 100644 --- a/src-tauri/risuko-engine/Cargo.toml +++ b/src-tauri/risuko-engine/Cargo.toml @@ -19,7 +19,7 @@ parking_lot = "0.12" thiserror = "2" socket2 = "0.6" percent-encoding = "2" -log = "0.4" +tracing = "0.1" dirs = "6" sha1 = "0.11" sha2 = "0.11" @@ -34,7 +34,6 @@ futures-util = "0.3" rand = "0.10" tiger = "0.3" tower-http = { version = "0.7", features = ["cors"] } -tracing = "0.1" risuko-http = { workspace = true } risuko-cookies = { workspace = true } tokio-util = { version = "0.7", features = ["io"] } @@ -52,7 +51,7 @@ globset = "0.4" urlencoding = "2.1.3" rustls = { version = "0.23", default-features = false, features = ["ring", "std"] } webpki-roots = "1" -suppaftp = { version = "8", features = ["tokio-rustls-ring", "deprecated"] } +suppaftp = { version = "9", features = ["tokio-rustls-ring", "deprecated"] } russh = { version = "0.61", default-features = false, features = ["ring", "flate2"] } russh-sftp = "2" diff --git a/src-tauri/risuko-engine/src/config/mod.rs b/src-tauri/risuko-engine/src/config/mod.rs index c36f6d6a..b3a79c70 100644 --- a/src-tauri/risuko-engine/src/config/mod.rs +++ b/src-tauri/risuko-engine/src/config/mod.rs @@ -29,7 +29,7 @@ impl ConfigManager { if manager.migrate_legacy_keep_seeding_defaults() { if let Err(err) = manager.save_system() { - log::warn!( + tracing::warn!( "Failed to persist legacy keep-seeding migration; continuing startup: {}", err ); @@ -55,7 +55,7 @@ impl ConfigManager { if manager.migrate_legacy_keep_seeding_defaults() { if let Err(err) = manager.save_system() { - log::warn!( + tracing::warn!( "Failed to persist legacy keep-seeding migration; continuing startup: {}", err ); diff --git a/src-tauri/risuko-engine/src/engine/cookie_store.rs b/src-tauri/risuko-engine/src/engine/cookie_store.rs index 01de4b79..9de5c792 100644 --- a/src-tauri/risuko-engine/src/engine/cookie_store.rs +++ b/src-tauri/risuko-engine/src/engine/cookie_store.rs @@ -65,14 +65,14 @@ impl CookieStore { // empty. Sidestep the file by renaming it with a // timestamped `.corrupt-N` suffix so the user can recover // it manually, and start with an empty in-memory store - log::warn!( + tracing::warn!( "cookie store load failed for {}: {err}. Backing up to .corrupt and starting empty", path.display() ); if path.exists() { let backup = corrupt_backup_path(&path); if let Err(rename_err) = std::fs::rename(&path, &backup) { - log::error!( + tracing::error!( "cookie store backup failed (rename {} -> {}): {rename_err}; refusing to overwrite", path.display(), backup.display() @@ -86,7 +86,7 @@ impl CookieStore { state: RwLock::new(StoreFile::default()), }; } - log::info!("cookie store backup created at {}", backup.display()); + tracing::info!("cookie store backup created at {}", backup.display()); } StoreFile::default() } diff --git a/src-tauri/risuko-engine/src/engine/dns.rs b/src-tauri/risuko-engine/src/engine/dns.rs index edd16d73..ac275e6f 100644 --- a/src-tauri/risuko-engine/src/engine/dns.rs +++ b/src-tauri/risuko-engine/src/engine/dns.rs @@ -115,7 +115,7 @@ pub fn apply_settings(settings: &DohSettings) { match risuko_http::DohResolver::new(cfg) { Ok(resolver) => { risuko_http::set_global_resolver(Some(std::sync::Arc::new(resolver))); - log::info!( + tracing::info!( "DoH enabled: endpoint={} bootstrap={} fallback={}", redacted_url_host(&settings.url), settings.bootstrap.len(), @@ -123,16 +123,16 @@ pub fn apply_settings(settings: &DohSettings) { ); } Err(e) => { - log::warn!("DoH config invalid ({}); leaving DNS resolver unchanged", e); + tracing::warn!("DoH config invalid ({}); leaving DNS resolver unchanged", e); return; } } } else { risuko_http::set_global_resolver(None); if settings.enabled { - log::warn!("DoH enabled but no endpoint URL set; using system DNS"); + tracing::warn!("DoH enabled but no endpoint URL set; using system DNS"); } else { - log::info!("DoH disabled: using system DNS"); + tracing::info!("DoH disabled: using system DNS"); } } diff --git a/src-tauri/risuko-engine/src/engine/ed2k/download.rs b/src-tauri/risuko-engine/src/engine/ed2k/download.rs index c086b388..2c9f268b 100644 --- a/src-tauri/risuko-engine/src/engine/ed2k/download.rs +++ b/src-tauri/risuko-engine/src/engine/ed2k/download.rs @@ -116,17 +116,17 @@ pub async fn run_ed2k_download( None => continue, }; - log::info!("[ed2k] Trying server {} ({})", entry.name, addr); + tracing::info!("[ed2k] Trying server {} ({})", entry.name, addr); let mut conn = ServerConnection::new(addr, client_hash, client_port); let (event_rx, _packet_tx) = match conn.connect().await { Ok(pair) => pair, Err(e) => { - log::warn!("[ed2k] Failed to connect to {}: {}", entry.name, e); + tracing::warn!("[ed2k] Failed to connect to {}: {}", entry.name, e); last_error = format!("Failed to connect to {}: {}", entry.name, e); continue; } }; - log::info!("[ed2k] Connected to server {}", entry.name); + tracing::info!("[ed2k] Connected to server {}", entry.name); let server_ip = u32::from_le_bytes(addr.ip().octets()); let server_port_val = addr.port(); @@ -157,7 +157,7 @@ pub async fn run_ed2k_download( Ok(path) => return Ok(path), Err(e) if e == "cancelled" => return Err(e), Err(e) => { - log::warn!("[ed2k] Server {} session ended: {}", entry.name, e); + tracing::warn!("[ed2k] Server {} session ended: {}", entry.name, e); last_error = e; // Continue to next server } @@ -165,7 +165,7 @@ pub async fn run_ed2k_download( } // All servers exhausted — wait for peers if any are active - log::info!("[ed2k] All servers tried, waiting for active peers to finish"); + tracing::info!("[ed2k] All servers tried, waiting for active peers to finish"); let deadline = tokio::time::Instant::now() + Duration::from_secs(30); loop { if cancel.load(Ordering::Relaxed) || cancel_token.is_cancelled() { @@ -240,7 +240,7 @@ async fn run_server_session( event = event_rx.recv() => { match event { Some(ServerEvent::Connected { client_id: cid }) => { - log::info!("[ed2k] Got client ID: {} ({})", + tracing::info!("[ed2k] Got client ID: {} ({})", cid, if is_high_id(cid) { "High" } else { "Low" } ); @@ -251,7 +251,7 @@ async fn run_server_session( } Some(ServerEvent::FoundSources { file_hash: fh, sources }) => { if fh == file_hash { - log::info!("[ed2k] Found {} sources", sources.len()); + tracing::info!("[ed2k] Found {} sources", sources.len()); for &(ip, port) in &sources { if !is_high_id(ip) { continue; @@ -276,14 +276,14 @@ async fn run_server_session( } } Some(ServerEvent::ServerMessage(msg)) => { - log::info!("[ed2k] Server message: {}", msg); + tracing::info!("[ed2k] Server message: {}", msg); } Some(ServerEvent::ServerStatus { users, files }) => { - log::info!("[ed2k] Server: {} users, {} files", users, files); + tracing::info!("[ed2k] Server: {} users, {} files", users, files); } Some(ServerEvent::ServerList) => {} Some(ServerEvent::Disconnected(reason)) => { - log::warn!("[ed2k] Server disconnected: {:?}", reason); + tracing::warn!("[ed2k] Server disconnected: {:?}", reason); return Err(format!("Server disconnected: {:?}", reason)); } None => { @@ -344,7 +344,7 @@ fn spawn_peer_task( peer_count.fetch_sub(1, Ordering::Relaxed); if let Err(e) = result { - log::debug!("[ed2k] Peer {} finished: {}", addr, e); + tracing::debug!("[ed2k] Peer {} finished: {}", addr, e); } }); } @@ -454,7 +454,7 @@ async fn run_peer_download( peer.request_slot(file_hash).await?; } Some(PeerEvent::QueueRanking(rank)) => { - log::debug!("[ed2k] Peer {} queue rank: {}", addr, rank); + tracing::debug!("[ed2k] Peer {} queue rank: {}", addr, rank); } Some(PeerEvent::Disconnected(reason)) => { return Err(format!("disconnected: {:?}", reason)); diff --git a/src-tauri/risuko-engine/src/engine/ed2k/server.rs b/src-tauri/risuko-engine/src/engine/ed2k/server.rs index b82b3255..68ca4047 100644 --- a/src-tauri/risuko-engine/src/engine/ed2k/server.rs +++ b/src-tauri/risuko-engine/src/engine/ed2k/server.rs @@ -43,7 +43,7 @@ enum ServerPacketError { impl ServerConnection { fn parse_packet_error(opcode: u8, err: String) -> ServerPacketError { let msg = format!("opcode 0x{opcode:02x}: {err}"); - log::warn!("[ed2k] Server packet parse error: {}", msg); + tracing::warn!("[ed2k] Server packet parse error: {}", msg); ServerPacketError::Parse(msg) } @@ -111,7 +111,7 @@ impl ServerConnection { match Self::handle_server_packet(&event_tx_clone, &packet).await { Ok(()) => {} Err(ServerPacketError::Parse(message)) => { - log::debug!( + tracing::debug!( "[ed2k] Ignoring malformed server packet: {}", message ); diff --git a/src-tauri/risuko-engine/src/engine/manager.rs b/src-tauri/risuko-engine/src/engine/manager.rs index 5b9f9841..fcc58d67 100644 --- a/src-tauri/risuko-engine/src/engine/manager.rs +++ b/src-tauri/risuko-engine/src/engine/manager.rs @@ -163,7 +163,7 @@ impl TaskManager { let torrent_engine = TorrentEngine::new_with_tuning(Path::new(&output_dir), tuning) .await .map_err(|e| { - log::warn!("Torrent engine init failed (non-fatal): {}", e); + tracing::warn!("Torrent engine init failed (non-fatal): {}", e); e }) .ok(); @@ -228,7 +228,7 @@ impl TaskManager { if task.status == TaskStatus::Paused { task.status = TaskStatus::Active; } - log::info!( + tracing::info!( "Restored torrent mapping: gid={} -> torrent_id={} ({})", task.gid, torrent_id, @@ -243,7 +243,7 @@ impl TaskManager { } } - log::info!( + tracing::info!( "Restored {} torrent mappings out of {} persisted torrents ({} orphan)", ids.len(), managed.len(), @@ -260,13 +260,13 @@ impl TaskManager { let removal_result = te.remove(torrent_id, delete_files).await; let removal_failed = removal_result.is_err(); match removal_result { - Ok(()) => log::info!( + Ok(()) => tracing::info!( "Purged orphan persisted torrent: torrent_id={} ({}) [delete_files={}]", torrent_id, info_hash, delete_files ), - Err(e) => log::warn!( + Err(e) => tracing::warn!( "Failed to purge orphan torrent torrent_id={} ({}): {}", torrent_id, info_hash, @@ -309,7 +309,7 @@ impl TaskManager { options: Map, ) -> Result { let gid = generate_gid(); - log::info!("[task:{}] Adding HTTP task, uris={:?}", gid, uris); + tracing::info!("[task:{}] Adding HTTP task, uris={:?}", gid, uris); let out = options .get("out") .and_then(|v| v.as_str()) @@ -352,7 +352,7 @@ impl TaskManager { options: Map, ) -> Result { let gid = generate_gid(); - log::info!("[task:{}] Adding media task, uri={}", gid, uri); + tracing::info!("[task:{}] Adding media task, uri={}", gid, uri); let out = options .get("out") .and_then(|v| v.as_str()) @@ -419,7 +419,7 @@ impl TaskManager { if let Some(ref te) = *te_guard { match te.add_torrent_bytes(&torrent_data, &merged).await { Ok(handle) => { - log::info!( + tracing::info!( "Torrent task {} added: id={}, info_hash={:?}", gid, handle.id, @@ -435,14 +435,14 @@ impl TaskManager { task.status = TaskStatus::Active; } Err(e) => { - log::error!("Torrent task {} failed to add: {}", gid, e); + tracing::error!("Torrent task {} failed to add: {}", gid, e); task.status = TaskStatus::Error; task.error_code = Some(classify_error(&e, "torrent").to_string()); task.error_message = Some(e); } } } else { - log::error!("Torrent task {} failed: engine not available", gid); + tracing::error!("Torrent task {} failed: engine not available", gid); task.status = TaskStatus::Error; task.error_code = Some(super::error_code::ErrorCode::ENGINE_NOT_RUNNING.to_string()); task.error_message = Some("Torrent engine not available".to_string()); @@ -1026,7 +1026,7 @@ impl TaskManager { match download_result { Ok(path) => { - log::info!( + tracing::info!( "[task:{}] {} download complete: {}", gid_clone, proto_label, @@ -1161,14 +1161,14 @@ impl TaskManager { return; }; let Some(entry) = self.cookie_store.find_for_url(uri) else { - log::debug!("apply_stored_cookies: no entry for uri={uri}"); + tracing::debug!("apply_stored_cookies: no entry for uri={uri}"); return; }; // Cookie names and the destination host together leak more than // we want at info level (recognizable session keys, plus what // the user is downloading from). Stick to debug and elide names - log::debug!( + tracing::debug!( "apply_stored_cookies: matched stored entry (browser={}, {} cookie(s))", entry.browser_id, entry.cookies.len(), @@ -1185,14 +1185,14 @@ impl TaskManager { if !has_cookie { let cookie_header = super::cookie_store::cookies_to_header(&entry.cookies); if !cookie_header.is_empty() { - log::debug!( + tracing::debug!( "apply_stored_cookies: injecting cookie header ({} bytes)", cookie_header.len(), ); merged.insert("cookie".to_string(), Value::String(cookie_header)); } } else { - log::debug!( + tracing::debug!( "apply_stored_cookies: cookie already set on task, leaving stored entry untouched" ); } @@ -1353,7 +1353,7 @@ impl TaskManager { match download_result { Ok(path) => { - log::info!( + tracing::info!( "[task:{}] HTTP download complete: {}", gid_clone, path.display() @@ -1412,7 +1412,7 @@ impl TaskManager { }); if let Some(entry) = cookie_store.find_for_url(&lookup_url) { if let Err(err) = cookie_store.remove(&entry.host) { - log::warn!( + tracing::warn!( "[task:{gid_clone}] cookie store remove({}) failed: {err}", entry.host ); @@ -1524,7 +1524,7 @@ impl TaskManager { match download_result { Ok(path) => { - log::info!( + tracing::info!( "[task:{}] ED2K download complete: {}", gid_clone, path.display() @@ -1690,7 +1690,7 @@ impl TaskManager { task.total_length = file_size; } } - log::info!( + tracing::info!( "[task:{}] Media download complete: {}", gid_clone, path.display() @@ -1827,7 +1827,7 @@ impl TaskManager { match download_result { Ok(path) => { - log::info!( + tracing::info!( "[task:{}] M3U8 download complete: {}", gid_clone, path.display() @@ -1964,7 +1964,7 @@ impl TaskManager { match download_result { Ok(path) => { - log::info!( + tracing::info!( "[task:{}] FTP download complete: {}", gid_clone, path.display() @@ -2408,7 +2408,7 @@ impl TaskManager { let mut tasks = self.tasks.write().await; if let Some(task) = tasks.iter_mut().find(|t| t.gid == gid) { if task.status == TaskStatus::Active || task.status == TaskStatus::Waiting { - log::info!("[task:{}] Paused (was {:?})", gid, task.status); + tracing::info!("[task:{}] Paused (was {:?})", gid, task.status); task.status = TaskStatus::Paused; task.download_speed = 0; task.upload_speed = 0; @@ -2422,7 +2422,7 @@ impl TaskManager { } pub async fn unpause(&self, gid: &str) -> Result<(), String> { - log::info!("[task:{}] Resuming", gid); + tracing::info!("[task:{}] Resuming", gid); let is_torrent; { let mut tasks = self.tasks.write().await; @@ -2477,7 +2477,7 @@ impl TaskManager { cookie: Option, user_agent: Option, ) -> Result<(), String> { - log::info!("[task:{}] Retrying with imported cookies", gid); + tracing::info!("[task:{}] Retrying with imported cookies", gid); let was_active; { let mut tasks = self.tasks.write().await; @@ -2522,7 +2522,7 @@ impl TaskManager { } pub async fn remove(&self, gid: &str) -> Result<(), String> { - log::info!("[task:{}] Removing", gid); + tracing::info!("[task:{}] Removing", gid); // Cancel any active download { let active = self.active_downloads.read().await; @@ -2940,7 +2940,7 @@ impl TaskManager { let size: u64 = match f.length.parse::() { Ok(n) => n, Err(e) => { - log::warn!( + tracing::warn!( "Skipping upload entry with unparseable size: path={} length={:?} err={}", f.path, f.length, @@ -2967,7 +2967,7 @@ impl TaskManager { .components() .any(|c| matches!(c, std::path::Component::ParentDir)) { - log::warn!( + tracing::warn!( "Skipping upload entry with parent-directory segment: path={}", f.path ); @@ -3130,7 +3130,7 @@ impl TaskManager { let te_guard = self.torrent_engine.read().await; if let Some(ref te) = *te_guard { if let Err(e) = te.remove(tid, false).await { - log::warn!( + tracing::warn!( "[task:{}] failed to drop torrent engine entry (id={}): {}", gid, tid, @@ -3245,7 +3245,7 @@ impl TaskManager { } pub async fn shutdown(&self) { - log::info!("Engine shutting down"); + tracing::info!("Engine shutting down"); // Cancel all active downloads let active = self.active_downloads.read().await; for (_, ad) in active.iter() { @@ -3256,7 +3256,7 @@ impl TaskManager { // Save session if let Err(e) = self.save_session().await { - log::error!("Failed to save session on shutdown: {}", e); + tracing::error!("Failed to save session on shutdown: {}", e); } // Shut down torrent engine diff --git a/src-tauri/risuko-engine/src/engine/mod.rs b/src-tauri/risuko-engine/src/engine/mod.rs index 02ade41a..a8d0bbf2 100644 --- a/src-tauri/risuko-engine/src/engine/mod.rs +++ b/src-tauri/risuko-engine/src/engine/mod.rs @@ -140,7 +140,7 @@ pub async fn start_engine( upload_sinks: Option>, ) -> Result<(), Box> { if !should_start_embedded_engine(config) { - log::info!("Embedded engine start skipped because external engine mode is enabled"); + tracing::info!("Embedded engine start skipped because external engine mode is enabled"); return Ok(()); } @@ -148,7 +148,7 @@ pub async fn start_engine( { let guard = ENGINE_INSTANCE.lock().await; if guard.is_some() { - log::info!("Engine already running"); + tracing::info!("Engine already running"); return Ok(()); } } @@ -171,7 +171,7 @@ pub async fn start_engine( let rpc_port = options.rpc_listen_port(); let rpc_secret = options.rpc_secret(); - log::info!("Starting Risuko engine (in-process)"); + tracing::info!("Starting Risuko engine (in-process)"); let manager = Arc::new( TaskManager::new(&config_dir, options, events.clone()) @@ -211,7 +211,7 @@ pub async fn start_engine( loop { interval.tick().await; if let Err(e) = mgr_for_save.save_session().await { - log::warn!("Auto-save session failed: {}", e); + tracing::warn!("Auto-save session failed: {}", e); } } }); @@ -283,7 +283,7 @@ pub async fn start_engine( } } Err(tokio::sync::broadcast::error::RecvError::Lagged(n)) => { - log::warn!("Event bridge lagged by {} events", n); + tracing::warn!("Event bridge lagged by {} events", n); } Err(tokio::sync::broadcast::error::RecvError::Closed) => { break; @@ -326,14 +326,14 @@ pub async fn start_engine( // Monitor for RPC-initiated shutdown requests tokio::spawn(async move { if rpc_shutdown_rx.recv().await.is_some() { - log::info!("Shutdown requested via RPC"); + tracing::info!("Shutdown requested via RPC"); if let Err(e) = stop_engine().await { - log::error!("Failed to stop engine via RPC shutdown: {}", e); + tracing::error!("Failed to stop engine via RPC shutdown: {}", e); } } }); - log::info!("Risuko engine started on port {}", rpc_port); + tracing::info!("Risuko engine started on port {}", rpc_port); Ok(()) } @@ -363,7 +363,7 @@ pub async fn stop_engine() -> Result<(), Box> { *g = None; } - log::info!("Risuko engine stopped"); + tracing::info!("Risuko engine stopped"); Ok(()) } diff --git a/src-tauri/risuko-engine/src/engine/options.rs b/src-tauri/risuko-engine/src/engine/options.rs index 88b3972a..aa54b10f 100644 --- a/src-tauri/risuko-engine/src/engine/options.rs +++ b/src-tauri/risuko-engine/src/engine/options.rs @@ -22,7 +22,7 @@ fn apply_engine_overrides(global: &mut Map, user: &Map(); self.shutdown_tx = Some(tx); - log::info!("RPC server listening on {}", addr); + tracing::info!("RPC server listening on {}", addr); tokio::spawn(async move { axum::serve(listener, app) diff --git a/src-tauri/risuko-engine/src/engine/rss/mod.rs b/src-tauri/risuko-engine/src/engine/rss/mod.rs index cd3853d4..e098c8ff 100644 --- a/src-tauri/risuko-engine/src/engine/rss/mod.rs +++ b/src-tauri/risuko-engine/src/engine/rss/mod.rs @@ -200,7 +200,7 @@ impl RssManager { Err(e) => { feed.error_count += 1; if feed.error_count >= MAX_CONSECUTIVE_ERRORS { - log::warn!( + tracing::warn!( "Feed '{}' disabled after {} consecutive errors", feed.title, feed.error_count @@ -234,7 +234,7 @@ impl RssManager { } Ok(_) => {} Err(e) => { - log::warn!("Failed to update feed {}: {}", feed_id, e); + tracing::warn!("Failed to update feed {}: {}", feed_id, e); } } } @@ -276,7 +276,7 @@ impl RssManager { } Ok(_) => {} Err(e) => { - log::warn!("Failed to update feed {}: {}", feed_id, e); + tracing::warn!("Failed to update feed {}: {}", feed_id, e); } } } @@ -404,7 +404,7 @@ impl RssManager { let p = std::path::Path::new(&path); if p.exists() { if let Err(e) = tokio::fs::remove_file(p).await { - log::warn!("Failed to delete downloaded file {}: {}", path, e); + tracing::warn!("Failed to delete downloaded file {}: {}", path, e); } } } @@ -440,7 +440,7 @@ impl RssManager { let p = std::path::Path::new(path); if p.exists() { if let Err(e) = tokio::fs::remove_file(p).await { - log::warn!("Failed to delete downloaded file {}: {}", path, e); + tracing::warn!("Failed to delete downloaded file {}: {}", path, e); } } } @@ -729,7 +729,7 @@ impl RssManager { let now = now_secs(); if let Some(ref sched) = rule.schedule { if !schedule_allows(sched, now) { - log::debug!( + tracing::debug!( "RSS rule '{}' matched '{}' but schedule denies", rule.name, item.title @@ -752,7 +752,7 @@ impl RssManager { match decision { DedupeDecision::Skip(reason) => { - log::debug!( + tracing::debug!( "RSS rule '{}' skipped '{}': {}", rule.name, item.title, @@ -769,7 +769,7 @@ impl RssManager { } let Some(manager) = super::get_manager().await else { - log::warn!("RSS auto-download: engine not available"); + tracing::warn!("RSS auto-download: engine not available"); return; }; @@ -793,13 +793,13 @@ impl RssManager { let html = build_article_html(item); let dir_path = std::path::Path::new(&dir); if let Err(e) = tokio::fs::create_dir_all(dir_path).await { - log::warn!("Auto-download: failed to create dir {}: {}", dir, e); + tracing::warn!("Auto-download: failed to create dir {}: {}", dir, e); return; } let file_path = dir_path.join(&filename); let path_str = file_path.to_string_lossy().to_string(); if let Err(e) = tokio::fs::write(&file_path, html.as_bytes()).await { - log::warn!("Auto-download: failed to write article html: {}", e); + tracing::warn!("Auto-download: failed to write article html: {}", e); return; } // Fan out inline media + the (thumbnail) enclosure as siblings @@ -819,7 +819,7 @@ impl RssManager { .add_http_task(vec![extra.clone()], media_opts.clone()) .await { - log::warn!("Auto-download extra media failed for '{}': {}", extra, e); + tracing::warn!("Auto-download extra media failed for '{}': {}", extra, e); } } Some(path_str) @@ -827,14 +827,14 @@ impl RssManager { let url = match self.get_item_download_url(&item.feed_id, &item.id).await { Ok(u) => u, Err(e) => { - log::warn!("RSS auto-download: no URL for '{}': {}", item.title, e); + tracing::warn!("RSS auto-download: no URL for '{}': {}", item.title, e); return; } }; let gid = match manager.add_http_task(vec![url.clone()], opts.clone()).await { Ok(g) => g, Err(e) => { - log::warn!("Auto-download failed for '{}': {}", item.title, e); + tracing::warn!("Auto-download failed for '{}': {}", item.title, e); return; } }; @@ -851,7 +851,7 @@ impl RssManager { .add_http_task(vec![extra.clone()], media_opts.clone()) .await { - log::warn!("Auto-download extra media failed for '{}': {}", extra, e); + tracing::warn!("Auto-download extra media failed for '{}': {}", extra, e); } } @@ -955,7 +955,7 @@ impl RssManager { let wrapper = serde_json::json!({ "data": *s }); drop(s); let _ = mon_storage.save(RSS_STORE_KEY, &wrapper); - log::info!( + tracing::info!( "Auto-downloaded '{}' via rule '{}'", mon_item_title, mon_rule_name @@ -963,7 +963,7 @@ impl RssManager { return; } "error" | "removed" => { - log::warn!( + tracing::warn!( "RSS auto-download task {} for '{}' ended with status '{}'", gid, mon_item_title, @@ -975,7 +975,7 @@ impl RssManager { } } - log::warn!( + tracing::warn!( "RSS auto-download monitor for '{}' (gid={}) timed out", mon_item_title, gid @@ -1033,7 +1033,7 @@ impl RssManager { } drop(s); let _ = self.save().await; - log::info!("Auto-downloaded '{}' via rule '{}'", item.title, rule.name); + tracing::info!("Auto-downloaded '{}' via rule '{}'", item.title, rule.name); } } diff --git a/src-tauri/risuko-engine/src/engine/session.rs b/src-tauri/risuko-engine/src/engine/session.rs index b26fcddf..e1185bca 100644 --- a/src-tauri/risuko-engine/src/engine/session.rs +++ b/src-tauri/risuko-engine/src/engine/session.rs @@ -56,7 +56,7 @@ impl SessionManager { .collect() } Err(e) => { - log::warn!("Failed to parse engine session: {}", e); + tracing::warn!("Failed to parse engine session: {}", e); Vec::new() } } diff --git a/src-tauri/risuko-engine/src/engine/ssh_known_hosts.rs b/src-tauri/risuko-engine/src/engine/ssh_known_hosts.rs index 51bcaf94..396055ea 100644 --- a/src-tauri/risuko-engine/src/engine/ssh_known_hosts.rs +++ b/src-tauri/risuko-engine/src/engine/ssh_known_hosts.rs @@ -99,7 +99,7 @@ impl client::Handler for TofuHandler { let fp = match fingerprint(key) { Ok(fp) => fp, Err(e) => { - log::warn!( + tracing::warn!( "SFTP host key fingerprint failed for {}: {} — refusing connection", self.host_key, e @@ -111,7 +111,7 @@ impl client::Handler for TofuHandler { if pin == &fp { return Ok(true); } - log::warn!( + tracing::warn!( "SFTP host key mismatch for {}: expected {} got {}", self.host_key, pin, @@ -123,7 +123,7 @@ impl client::Handler for TofuHandler { match store.map.get(&self.host_key) { Some(existing) if existing == &fp => Ok(true), Some(existing) => { - log::warn!( + tracing::warn!( "SFTP host key mismatch for {}: stored {} got {}", self.host_key, existing, @@ -136,14 +136,14 @@ impl client::Handler for TofuHandler { let mut next = store.map.clone(); next.insert(self.host_key.clone(), fp.clone()); if let Err(e) = KnownHosts::write_to_disk(&path, &next) { - log::error!( + tracing::error!( "SFTP TOFU: refusing to trust {} because known_hosts persist failed: {}", self.host_key, e ); return Ok(false); } - log::info!("SFTP TOFU: pinning {} -> {}", self.host_key, fp); + tracing::info!("SFTP TOFU: pinning {} -> {}", self.host_key, fp); store.map.insert(self.host_key.clone(), fp); Ok(true) } diff --git a/src-tauri/risuko-engine/src/engine/torrent.rs b/src-tauri/risuko-engine/src/engine/torrent.rs index 45773db2..d5fef390 100644 --- a/src-tauri/risuko-engine/src/engine/torrent.rs +++ b/src-tauri/risuko-engine/src/engine/torrent.rs @@ -75,7 +75,7 @@ impl TorrentEngine { .await .map_err(|e| format!("Failed to create torrent session: {}", e))?; - log::info!( + tracing::info!( "Torrent engine initialized, output_dir={}", output_dir.display() ); @@ -175,7 +175,7 @@ impl TorrentEngine { create_subfolder, }; - log::info!("Adding torrent bytes ({} bytes) to dir={}", data.len(), dir); + tracing::info!("Adding torrent bytes ({} bytes) to dir={}", data.len(), dir); let response = session .add_torrent( @@ -186,7 +186,7 @@ impl TorrentEngine { .map_err(|e| format!("Failed to add torrent: {}", e))?; let handle = extract_handle(response)?; - log::info!( + tracing::info!( "Torrent added: id={}, info_hash={:?}", handle.id, handle.info_hash @@ -226,7 +226,7 @@ impl TorrentEngine { create_subfolder, }; - log::info!("Adding magnet to dir={}: {}", dir, magnet_uri); + tracing::info!("Adding magnet to dir={}: {}", dir, magnet_uri); // When bt-save-metadata is enabled, resolve the magnet ourselves first // so we can write the synthesized .torrent file next to the payload. @@ -251,7 +251,7 @@ impl TorrentEngine { return self.add_torrent_bytes(&bytes, options).await; } Err(e) => { - log::warn!("bt-save-metadata resolve failed, falling back: {}", e); + tracing::warn!("bt-save-metadata resolve failed, falling back: {}", e); } } } @@ -262,7 +262,7 @@ impl TorrentEngine { .map_err(|e| format!("Failed to add magnet: {}", e))?; let handle = extract_handle(response)?; - log::info!( + tracing::info!( "Magnet added: id={}, info_hash={:?}", handle.id, handle.info_hash @@ -311,7 +311,7 @@ impl TorrentEngine { if let Ok(magnet) = bt::Magnet::parse(magnet_uri) { if let Some(handle) = session.get(bt::TorrentIdOrHash::Hash(magnet.info_hash())) { if let Ok(files) = handle.with_metadata(|meta| extract_file_details(&meta.info)) { - log::info!( + tracing::info!( "Magnet already managed, resolved from session ({} files)", files.len() ); @@ -321,7 +321,7 @@ impl TorrentEngine { } let trackers = Self::parse_trackers(options); - log::info!("Resolving magnet metadata: {}", magnet_uri); + tracing::info!("Resolving magnet metadata: {}", magnet_uri); let start = std::time::Instant::now(); let enc = encryption_policy_from_str( @@ -348,7 +348,7 @@ impl TorrentEngine { let meta = bt::parse_torrent(&torrent_bytes) .map_err(|e| format!("Failed to parse resolved metadata: {}", e))?; let files = extract_file_details(&meta.info); - log::info!( + tracing::info!( "Magnet metadata resolved in {:?} ({} files)", start.elapsed(), files.len() @@ -644,12 +644,12 @@ async fn save_torrent_metadata_if_enabled( let path = Path::new(dir).join(format!("{}.torrent", safe)); if let Some(parent) = path.parent() { if let Err(e) = tokio::fs::create_dir_all(parent).await { - log::warn!("Failed to create metadata dir {}: {}", parent.display(), e); + tracing::warn!("Failed to create metadata dir {}: {}", parent.display(), e); } } match tokio::fs::write(&path, bytes).await { - Ok(()) => log::info!("Saved torrent metadata to {}", path.display()), - Err(e) => log::warn!( + Ok(()) => tracing::info!("Saved torrent metadata to {}", path.display()), + Err(e) => tracing::warn!( "Failed to save torrent metadata to {}: {}", path.display(), e diff --git a/src-tauri/risuko-engine/src/engine/upload/manager.rs b/src-tauri/risuko-engine/src/engine/upload/manager.rs index 1918690c..7619a62f 100644 --- a/src-tauri/risuko-engine/src/engine/upload/manager.rs +++ b/src-tauri/risuko-engine/src/engine/upload/manager.rs @@ -420,7 +420,7 @@ impl UploadSinkManager { match s.sinks.iter().find(|x| x.id == sink_id).cloned() { Some(r) => r, None => { - log::warn!("upload: sink {sink_id} disappeared before enqueue"); + tracing::warn!("upload: sink {sink_id} disappeared before enqueue"); return; } } @@ -614,7 +614,7 @@ impl UploadSinkManager { ) .await { - log::warn!("upload {job_id}: post-action failed: {e}"); + tracing::warn!("upload {job_id}: post-action failed: {e}"); } self.complete_job(&job_id).await; self.touch_sink_used(&sink_record.id).await; @@ -696,7 +696,7 @@ impl UploadSinkManager { }; match serde_json::to_value(job) { Ok(v) => sink.emit(name, v), - Err(e) => log::warn!("upload event serialize failed: {e}"), + Err(e) => tracing::warn!("upload event serialize failed: {e}"), } } } diff --git a/src-tauri/risuko-engine/src/engine/upload/s3.rs b/src-tauri/risuko-engine/src/engine/upload/s3.rs index 0ed3a01c..52c44aea 100644 --- a/src-tauri/risuko-engine/src/engine/upload/s3.rs +++ b/src-tauri/risuko-engine/src/engine/upload/s3.rs @@ -237,7 +237,7 @@ impl S3Sink { if result.is_err() { // Best-effort abort; log errors without replacing the original failure if let Err(e) = self.abort_multipart(&url, &upload_id).await { - log::warn!("S3 abort multipart {upload_id}: {e}"); + tracing::warn!("S3 abort multipart {upload_id}: {e}"); } } diff --git a/src-tauri/risuko-engine/src/engine/upload/sftp.rs b/src-tauri/risuko-engine/src/engine/upload/sftp.rs index bed64a41..2b267fe8 100644 --- a/src-tauri/risuko-engine/src/engine/upload/sftp.rs +++ b/src-tauri/risuko-engine/src/engine/upload/sftp.rs @@ -62,11 +62,11 @@ impl SftpSink { .await { Ok(a) if a.success() => authed = true, - Ok(_) => log::warn!("SFTP key auth rejected"), - Err(e) => log::warn!("SFTP key auth error: {e}"), + Ok(_) => tracing::warn!("SFTP key auth rejected"), + Err(e) => tracing::warn!("SFTP key auth error: {e}"), } } - Err(e) => log::warn!("SFTP key decode error: {e}"), + Err(e) => tracing::warn!("SFTP key decode error: {e}"), } } @@ -124,7 +124,7 @@ impl SftpSink { Ok(false) => { if let Err(e) = sftp.create_dir(&accum).await { // Tolerate races where another upload created it - log::debug!("SFTP mkdir {accum} ignored: {e}"); + tracing::debug!("SFTP mkdir {accum} ignored: {e}"); } } Err(_) => { @@ -163,12 +163,12 @@ impl UploadSink for SftpSink { let (_ssh, sftp) = tokio::time::timeout(CONNECT_TIMEOUT, self.connect()) .await .map_err(|_| "SFTP connect timed out".to_string())? - .inspect_err(|e| log::error!("SFTP connect failed: {e}"))?; + .inspect_err(|e| tracing::error!("SFTP connect failed: {e}"))?; let remote = self.full_remote_path(&file.remote_relative); - log::debug!("SFTP upload starting: remote={remote}"); + tracing::debug!("SFTP upload starting: remote={remote}"); self.ensure_parent_dirs(&sftp, &remote) .await - .inspect_err(|e| log::debug!("SFTP ensure_parent_dirs({remote}) failed: {e}"))?; + .inspect_err(|e| tracing::debug!("SFTP ensure_parent_dirs({remote}) failed: {e}"))?; // Stage writes to a sibling `.part` file so an existing good upload // at the final path is never truncated or unlinked on failure. @@ -187,8 +187,8 @@ impl UploadSink for SftpSink { // the returned error or info logs. The full path is // still emitted at debug level for operators with // log access - log::debug!("SFTP open {remote_tmp}: {e}"); - log::error!("SFTP open failed: {e}"); + tracing::debug!("SFTP open {remote_tmp}: {e}"); + tracing::error!("SFTP open failed: {e}"); format!("SFTP open failed: {e}") })?; @@ -212,7 +212,7 @@ impl UploadSink for SftpSink { ) { let _ = remote_file.shutdown().await; if let Err(e) = sftp.remove_file(remote_tmp).await { - log::debug!("SFTP cleanup of partial {remote_tmp} ignored: {e}"); + tracing::debug!("SFTP cleanup of partial {remote_tmp} ignored: {e}"); } } @@ -245,7 +245,7 @@ impl UploadSink for SftpSink { // half-flushed file isn't left behind. Final `remote` is // untouched if let Err(re) = sftp.remove_file(&remote_tmp).await { - log::debug!("SFTP cleanup after close failure ignored: {re}"); + tracing::debug!("SFTP cleanup after close failure ignored: {re}"); } return Err(format!("SFTP close: {e}")); } @@ -255,7 +255,9 @@ impl UploadSink for SftpSink { // failure unlink the existing remote and retry once so users can // overwrite previous uploads if let Err(e) = sftp.rename(&remote_tmp, &remote).await { - log::debug!("SFTP rename {remote_tmp} -> {remote} failed ({e}); retrying after unlink"); + tracing::debug!( + "SFTP rename {remote_tmp} -> {remote} failed ({e}); retrying after unlink" + ); let _ = sftp.remove_file(&remote).await; if let Err(e2) = sftp.rename(&remote_tmp, &remote).await { let _ = sftp.remove_file(&remote_tmp).await; diff --git a/src-tauri/risuko-engine/src/engine/upload/webdav.rs b/src-tauri/risuko-engine/src/engine/upload/webdav.rs index fd462562..c8149266 100644 --- a/src-tauri/risuko-engine/src/engine/upload/webdav.rs +++ b/src-tauri/risuko-engine/src/engine/upload/webdav.rs @@ -234,7 +234,7 @@ impl UploadSink for WebdavSink { } // RFC 4918: any DAV-compliant server must include `DAV:` header if !resp.headers().contains_key("dav") { - log::warn!("WebDAV test: server reachable but missing DAV header"); + tracing::warn!("WebDAV test: server reachable but missing DAV header"); } Ok(()) } diff --git a/src-tauri/risuko-engine/src/traits.rs b/src-tauri/risuko-engine/src/traits.rs index 99bf6621..cf342aa9 100644 --- a/src-tauri/risuko-engine/src/traits.rs +++ b/src-tauri/risuko-engine/src/traits.rs @@ -41,7 +41,7 @@ pub struct LogEventSink; impl EventSink for LogEventSink { fn emit(&self, event: &str, payload: Value) { - log::info!("Engine event: {} {}", event, payload); + tracing::info!("Engine event: {} {}", event, payload); } } diff --git a/src-tauri/risuko-napi/Cargo.toml b/src-tauri/risuko-napi/Cargo.toml index ce8c1e41..e8bb5610 100644 --- a/src-tauri/risuko-napi/Cargo.toml +++ b/src-tauri/risuko-napi/Cargo.toml @@ -14,7 +14,7 @@ risuko-engine = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true, features = ["full"] } -log = { workspace = true } +tracing = "0.1" dirs = { workspace = true } base64 = "0.22" napi = { version = "3", features = ["async", "serde-json", "tokio_rt"] } diff --git a/src-tauri/risuko-napi/src/lib.rs b/src-tauri/risuko-napi/src/lib.rs index 787b535f..7bd17e49 100644 --- a/src-tauri/risuko-napi/src/lib.rs +++ b/src-tauri/risuko-napi/src/lib.rs @@ -117,7 +117,7 @@ pub async fn start_engine(config: Option) -> Result<()> { loop { interval.tick().await; if let Err(e) = mgr.save_session().await { - log::warn!("Auto-save session failed: {}", e); + tracing::warn!("Auto-save session failed: {}", e); } } }); diff --git a/src-tauri/src/bridge.rs b/src-tauri/src/bridge.rs index 206dfe7d..8c9e13bc 100644 --- a/src-tauri/src/bridge.rs +++ b/src-tauri/src/bridge.rs @@ -43,7 +43,7 @@ impl TauriEventSink { impl EventSink for TauriEventSink { fn emit(&self, event: &str, payload: Value) { if let Err(e) = self.handle.emit(event, payload) { - log::warn!("Failed to emit Tauri event {}: {}", event, e); + tracing::warn!("Failed to emit Tauri event {}: {}", event, e); } } } diff --git a/src-tauri/src/cli/commands.rs b/src-tauri/src/cli/commands.rs index b1f6e771..bbce1421 100644 --- a/src-tauri/src/cli/commands.rs +++ b/src-tauri/src/cli/commands.rs @@ -3,7 +3,9 @@ use serde_json::{json, Map, Value}; use super::headless; use super::progress::{self, format_size, format_size_speed}; use super::rpc_client::RpcClient; -use super::{DownloadArgs, PauseArgs, RemoveArgs, ResumeArgs, ServeArgs, StatusArgs}; +use super::{ + DownloadArgs, ExtractCookiesArgs, PauseArgs, RemoveArgs, ResumeArgs, ServeArgs, StatusArgs, +}; use risuko_engine::config::defaults; use risuko_engine::engine::media::is_media_uri; use risuko_engine::engine::options::EngineOptions; @@ -478,3 +480,21 @@ pub async fn serve(args: ServeArgs) -> Result<(), Box> { engine.shutdown().await; Ok(()) } + +/// Extract browser cookies for `--url` from `--browser` and emit them as JSON +/// (to `--out` if given, else stdout). +/// +/// This is the worker side of the Windows UAC-elevation flow: when a Chrome +/// profile uses app-bound (v20) encryption, the GUI relaunches itself elevated +/// as `risuko extract-cookies --browser .. --url .. --out ` so the keys +/// can be decrypted as administrator, then reads the JSON back. Runs entirely +/// in `main`'s CLI branch and exits before Tauri/single-instance initializes. +pub async fn extract_cookies(args: ExtractCookiesArgs) -> Result<(), Box> { + let host_cookies = risuko_cookies::cookies_for_url(&args.browser, &args.url).await?; + let json = serde_json::to_string(&host_cookies)?; + match args.out { + Some(path) => std::fs::write(&path, json)?, + None => println!("{json}"), + } + Ok(()) +} diff --git a/src-tauri/src/cli/headless.rs b/src-tauri/src/cli/headless.rs index 67a9e5ae..744d3166 100644 --- a/src-tauri/src/cli/headless.rs +++ b/src-tauri/src/cli/headless.rs @@ -35,7 +35,7 @@ pub async fn start_headless_engine( let rpc_secret = options.rpc_secret(); let rpc_secret_clone = rpc_secret.clone(); - log::info!("Starting headless engine on port {}", rpc_port); + tracing::info!("Starting headless engine on port {}", rpc_port); let manager = Arc::new( TaskManager::new(&config_dir, options, events.clone()) @@ -75,7 +75,7 @@ pub async fn start_headless_engine( loop { interval.tick().await; if let Err(e) = mgr_save.save_session().await { - log::warn!("Auto-save session failed: {}", e); + tracing::warn!("Auto-save session failed: {}", e); } } }); @@ -85,7 +85,7 @@ pub async fn start_headless_engine( let shutdown_notify_clone = shutdown_notify.clone(); tokio::spawn(async move { if rpc_shutdown_rx.recv().await.is_some() { - log::info!("Shutdown requested via RPC (headless)"); + tracing::info!("Shutdown requested via RPC (headless)"); shutdown_notify_clone.notify_one(); } }); @@ -129,7 +129,7 @@ impl HeadlessEngine { self.auto_save_task.abort(); self.rpc_server.stop(); self.manager.shutdown().await; - log::info!("Headless engine stopped"); + tracing::info!("Headless engine stopped"); } } diff --git a/src-tauri/src/cli/mod.rs b/src-tauri/src/cli/mod.rs index 50cd9722..b003aa0c 100644 --- a/src-tauri/src/cli/mod.rs +++ b/src-tauri/src/cli/mod.rs @@ -36,6 +36,12 @@ pub enum Command { /// Start headless engine (RPC server only, no GUI) Serve(ServeArgs), + + /// Internal: extract browser cookies and print them as JSON. Used by the + /// Windows elevated helper to decrypt app-bound (Chrome v20) cookies. + /// Hidden from `--help`. + #[command(hide = true)] + ExtractCookies(ExtractCookiesArgs), } #[derive(clap::Args)] @@ -182,6 +188,23 @@ pub struct ServeArgs { pub rpc_port: u16, } +#[derive(clap::Args)] +pub struct ExtractCookiesArgs { + /// Browser id (chrome, chromium, brave, edge, vivaldi, opera, arc, + /// firefox, librewolf, zen, ...) + #[arg(long)] + pub browser: String, + + /// Target URL or bare host to scope the cookies to + #[arg(long)] + pub url: String, + + /// Write the resulting HostCookies JSON to this file instead of stdout. + /// The GUI passes a temp path here when relaunching elevated. + #[arg(long)] + pub out: Option, +} + pub async fn run(command: Command) -> Result<(), Box> { match command { Command::Download(args) => commands::download(args).await, @@ -190,5 +213,6 @@ pub async fn run(command: Command) -> Result<(), Box> { Command::Resume(args) => commands::resume(args).await, Command::Remove(args) => commands::remove(args).await, Command::Serve(args) => commands::serve(args).await, + Command::ExtractCookies(args) => commands::extract_cookies(args).await, } } diff --git a/src-tauri/src/commands/android_intent.rs b/src-tauri/src/commands/android_intent.rs index 2f5dab20..8a95712d 100644 --- a/src-tauri/src/commands/android_intent.rs +++ b/src-tauri/src/commands/android_intent.rs @@ -250,7 +250,7 @@ pub fn reveal_folder(path: &str) -> Result<(), String> { if outcome == "ok" { Ok(()) } else { - log::warn!("[Risuko] revealFolder({path}) -> {outcome}"); + tracing::warn!("[Risuko] revealFolder({path}) -> {outcome}"); Err(outcome) } } @@ -283,7 +283,7 @@ pub fn open_file(path: &str, mime: &str) -> Result<(), String> { if outcome == "ok" { Ok(()) } else { - log::warn!("[Risuko] openFile({path}, {mime}) -> {outcome}"); + tracing::warn!("[Risuko] openFile({path}, {mime}) -> {outcome}"); Err(outcome) } } diff --git a/src-tauri/src/commands/app_cmds.rs b/src-tauri/src/commands/app_cmds.rs index fb84ed77..f920b3a5 100644 --- a/src-tauri/src/commands/app_cmds.rs +++ b/src-tauri/src/commands/app_cmds.rs @@ -101,7 +101,7 @@ pub async fn reset_session(handle: AppHandle) -> Result<(), String> { #[tauri::command] pub fn auto_hide_window(enabled: bool) -> Result<(), String> { - log::info!("auto_hide_window: {}", enabled); + tracing::info!("auto_hide_window: {}", enabled); Ok(()) } @@ -171,7 +171,7 @@ pub fn clear_android_download_notification() -> Result<(), String> { #[tauri::command] pub async fn shutdown_system(handle: AppHandle) -> Result<(), String> { - log::info!("[Risuko] shutdown_system: initiating OS shutdown"); + tracing::info!("[Risuko] shutdown_system: initiating OS shutdown"); #[cfg(target_os = "android")] { diff --git a/src-tauri/src/commands/completion_script_cmds.rs b/src-tauri/src/commands/completion_script_cmds.rs index 3ace7215..3c284594 100644 --- a/src-tauri/src/commands/completion_script_cmds.rs +++ b/src-tauri/src/commands/completion_script_cmds.rs @@ -235,14 +235,14 @@ pub async fn run_completion_script( tauri::async_runtime::spawn(async move { let result = execute(&handle_clone, &command, args, env, timeout_ms).await; if result.success { - log::info!( + tracing::info!( "[completion-script] ok exit={:?} dur={}ms path={}", result.exit_code, result.duration_ms, path ); } else { - log::warn!( + tracing::warn!( "[completion-script] failed exit={:?} timed_out={} dur={}ms msg={:?} stderr={}", result.exit_code, result.timed_out, diff --git a/src-tauri/src/commands/cookie_cmds.rs b/src-tauri/src/commands/cookie_cmds.rs index 41a50e4a..045c212d 100644 --- a/src-tauri/src/commands/cookie_cmds.rs +++ b/src-tauri/src/commands/cookie_cmds.rs @@ -81,19 +81,74 @@ pub async fn import_browser_cookies( persist: Option, user_agent: Option, ) -> Result { - log::info!("import_browser_cookies: browser={browser}"); + tracing::info!("import_browser_cookies: browser={browser}"); + let host_cookies = match cookies_for_url(&browser, &url).await { + Ok(hc) => hc, + Err(e) => { + // Windows Chrome v20 (app-bound) profiles can only be decrypted as + // administrator. Surface a stable code so the renderer can ask the + // user to approve elevation and retry via the elevated command. + if e.contains(risuko_cookies::ELEVATION_REQUIRED) { + tracing::info!( + "import_browser_cookies: app-bound cookies require elevation (browser={browser})" + ); + return Err("ELEVATION_REQUIRED".to_string()); + } + return Err(e); + } + }; + build_imported_cookies(host_cookies, &browser, persist, user_agent).await +} + +/// Run cookie extraction through a UAC-elevated helper process, then import the +/// result. Windows-only (app-bound / Chrome v20). The renderer calls this after +/// the user agrees to the elevation prompt that `import_browser_cookies` +/// requested via the `ELEVATION_REQUIRED` code. The elevated child is this same +/// binary invoked as `extract-cookies`, which decrypts the keys as admin and +/// writes the cookies back as JSON. +#[tauri::command] +pub async fn import_browser_cookies_elevated( + browser: String, + url: String, + persist: Option, + user_agent: Option, +) -> Result { + #[cfg(target_os = "windows")] + { + tracing::info!("import_browser_cookies_elevated: browser={browser}"); + let host_cookies = elevate::extract_cookies_elevated(&browser, &url).await?; + build_imported_cookies(host_cookies, &browser, persist, user_agent).await + } + #[cfg(not(target_os = "windows"))] + { + let _ = (&url, &persist, &user_agent); + Err(format!( + "elevated cookie import for {browser} is only supported on Windows" + )) + } +} + +/// Shared tail of both import paths: turn extracted `HostCookies` into the +/// renderer-facing `ImportedCookies`, optionally persisting them in the engine +/// cookie store. +async fn build_imported_cookies( + host_cookies: HostCookies, + browser: &str, + persist: Option, + user_agent: Option, +) -> Result { let HostCookies { host, user_agent: browser_user_agent, cookies, - } = cookies_for_url(&browser, &url).await?; + } = host_cookies; - log::debug!( - "import_browser_cookies: rookie returned {} cookie(s) for browser={browser}", + tracing::debug!( + "import_browser_cookies: extractor returned {} cookie(s) for browser={browser}", cookies.len() ); for c in cookies.iter() { - log::debug!( + tracing::debug!( " imported cookie name={} domain={} path={} secure={} http_only={} value_len={}", c.name, c.domain, @@ -126,14 +181,14 @@ pub async fn import_browser_cookies( .any(|c| c.name.eq_ignore_ascii_case("cf_clearance")); let cookie_names: Vec = stored.iter().map(|c| c.name.clone()).collect(); - log::debug!( + tracing::debug!( "import_browser_cookies: {} cookie(s) imported (cf_clearance present: {has_cf_clearance})", cookie_names.len() ); - // Caller-supplied UA wins (e.g. Cloudflare dialog passes the - // textarea's edited value). Falls back to whatever rookie reports - // for the browser so empty hand-offs still get a sensible default + // Caller-supplied UA wins (e.g. Cloudflare dialog passes the textarea's + // edited value). Falls back to the browser's built-in default so empty + // hand-offs still get a sensible default let effective_user_agent = user_agent .as_ref() .map(|s| s.trim().to_string()) @@ -146,7 +201,7 @@ pub async fn import_browser_cookies( .ok_or_else(|| "cookie persistence failed: engine manager unavailable".to_string())?; let entry = CookieEntry { host: host.clone(), - browser_id: browser.clone(), + browser_id: browser.to_string(), user_agent: effective_user_agent.clone(), cookies: stored.clone(), imported_at: 0, @@ -172,7 +227,7 @@ pub async fn import_browser_cookies( host, user_agent: effective_user_agent, cookie_header, - count: cookies.len(), + count: stored.len(), has_cf_clearance, cookie_names, cookies: cookies_view, @@ -228,7 +283,7 @@ pub struct CapturedUserAgent { /// then shuts down. Times out after 60s if nothing connects #[tauri::command] pub async fn capture_user_agent() -> Result { - log::info!("capture_user_agent: starting one-shot listener"); + tracing::info!("capture_user_agent: starting one-shot listener"); let listener = TcpListener::bind("127.0.0.1:0") .await .map_err(|e| format!("bind localhost listener failed: {e}"))?; @@ -241,7 +296,7 @@ pub async fn capture_user_agent() -> Result { // Open in the user's default browser. Best effort; the user can also // paste the URL into a different browser if they prefer if let Err(e) = open::that(&url) { - log::warn!("capture_user_agent: open::that({url}) failed: {e}"); + tracing::warn!("capture_user_agent: open::that({url}) failed: {e}"); // Keep the listener alive in case the user pastes the URL manually } @@ -253,11 +308,11 @@ pub async fn capture_user_agent() -> Result { let (mut stream, peer) = match listener.accept().await { Ok(v) => v, Err(e) => { - log::warn!("capture_user_agent: accept failed: {e}"); + tracing::warn!("capture_user_agent: accept failed: {e}"); continue; } }; - log::debug!("capture_user_agent: connection from {peer}"); + tracing::debug!("capture_user_agent: connection from {peer}"); let mut buf = vec![0u8; 4096]; let mut total = 0usize; @@ -304,7 +359,7 @@ pub async fn capture_user_agent() -> Result { .await .map_err(|_| "timed out waiting for browser to open the capture URL".to_string())?; - log::info!("capture_user_agent: captured ua={captured}"); + tracing::info!("capture_user_agent: captured ua={captured}"); Ok(CapturedUserAgent { user_agent: captured, }) @@ -336,3 +391,96 @@ fn parse_request(buf: &[u8]) -> Option<(String, String)> { } Some((path, user_agent)) } + +/// Windows UAC elevation: relaunch this binary elevated as `extract-cookies` to +/// decrypt app-bound (Chrome v20) cookies, then read the JSON it writes back. +#[cfg(target_os = "windows")] +mod elevate { + use risuko_cookies::HostCookies; + use std::ffi::OsStr; + use std::os::windows::ffi::OsStrExt; + + use windows_sys::Win32::Foundation::CloseHandle; + use windows_sys::Win32::System::Threading::{WaitForSingleObject, INFINITE}; + use windows_sys::Win32::UI::Shell::{ + ShellExecuteExW, SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW, + }; + use windows_sys::Win32::UI::WindowsAndMessaging::SW_HIDE; + + fn to_wide(s: &str) -> Vec { + OsStr::new(s) + .encode_wide() + .chain(std::iter::once(0)) + .collect() + } + + /// Relaunch this binary elevated, wait for it, and parse the cookies it + /// wrote. The OS shows the UAC consent dialog; if the user declines, + /// `ShellExecuteExW` fails and we return a friendly error rather than hang. + pub async fn extract_cookies_elevated(browser: &str, url: &str) -> Result { + let browser = browser.to_string(); + let url = url.to_string(); + tokio::task::spawn_blocking(move || run_elevated(&browser, &url)) + .await + .map_err(|e| format!("join error: {e}"))? + } + + fn run_elevated(browser: &str, url: &str) -> Result { + let exe = std::env::current_exe().map_err(|e| format!("current_exe failed: {e}"))?; + + // Temp file the elevated child writes the cookies JSON to. We create + // (and auto-remove) it; the elevated process only needs to write it. + let out_path = tempfile::Builder::new() + .prefix("risuko-cookies-") + .suffix(".json") + .tempfile() + .map_err(|e| format!("create temp file failed: {e}"))? + .into_temp_path(); + + // Quote values and strip embedded quotes so a value can't break out of + // its argument (browser comes from a fixed allowlist; url is user data). + let params = format!( + "extract-cookies --browser \"{}\" --url \"{}\" --out \"{}\"", + browser.replace('"', ""), + url.replace('"', ""), + out_path.to_string_lossy().replace('"', "") + ); + + let verb = to_wide("runas"); + let file = to_wide(&exe.to_string_lossy()); + let params_w = to_wide(¶ms); + + let mut info: SHELLEXECUTEINFOW = unsafe { std::mem::zeroed() }; + info.cbSize = std::mem::size_of::() as u32; + info.fMask = SEE_MASK_NOCLOSEPROCESS; + info.lpVerb = verb.as_ptr(); + info.lpFile = file.as_ptr(); + info.lpParameters = params_w.as_ptr(); + info.nShow = SW_HIDE; + + let ok = unsafe { ShellExecuteExW(&mut info) }; + if ok == 0 { + // Most commonly ERROR_CANCELLED (1223): the user dismissed UAC. + return Err( + "administrator approval was declined or the elevated helper failed to start" + .to_string(), + ); + } + if info.hProcess.is_null() { + return Err("elevated helper did not return a process handle".to_string()); + } + + unsafe { + WaitForSingleObject(info.hProcess, INFINITE); + CloseHandle(info.hProcess); + } + + let bytes = + std::fs::read(&out_path).map_err(|e| format!("read elevated output failed: {e}"))?; + if bytes.is_empty() { + return Err("the elevated helper produced no cookies".to_string()); + } + serde_json::from_slice::(&bytes) + .map_err(|e| format!("parse elevated output failed: {e}")) + } +} diff --git a/src-tauri/src/commands/engine_cmds.rs b/src-tauri/src/commands/engine_cmds.rs index 121d8f5a..bf6173a1 100644 --- a/src-tauri/src/commands/engine_cmds.rs +++ b/src-tauri/src/commands/engine_cmds.rs @@ -839,7 +839,7 @@ pub async fn add_uri( return Err(first_error_message.unwrap_or_else(|| "task.new-task-fail".to_string())); } - log::warn!( + tracing::warn!( "[Risuko] add_uri partially failed: {} succeeded, {} failed", success_count, failed_count diff --git a/src-tauri/src/commands/event_cmds.rs b/src-tauri/src/commands/event_cmds.rs index 271da66f..1e0f5acd 100644 --- a/src-tauri/src/commands/event_cmds.rs +++ b/src-tauri/src/commands/event_cmds.rs @@ -24,16 +24,6 @@ pub fn on_download_status_change( Ok(()) } -/// Reports the sleep-inhibit state from the renderer when the inhibit was -/// applied via the JS nosleep plugin (which doesn't go through -/// `apply_download_inhibit`). Keeps the health check's view in sync without -/// double-applying the platform inhibit -#[tauri::command] -pub fn set_sleep_inhibit_flag(active: bool) -> Result<(), String> { - SLEEP_INHIBIT_ACTIVE.store(active, std::sync::atomic::Ordering::Relaxed); - Ok(()) -} - #[tauri::command] pub fn on_speed_change( handle: AppHandle, @@ -277,7 +267,7 @@ fn macos_apply_download_inhibit(downloading: bool) { *child_guard = Some(child); } Err(err) => { - log::warn!("Failed to spawn caffeinate: {}", err); + tracing::warn!("Failed to spawn caffeinate: {}", err); } } } @@ -327,17 +317,17 @@ fn linux_apply_download_inhibit(downloading: bool) { if let Some(cookie) = parse_dbus_uint32(&output.stdout) { *cookie_guard = Some(cookie); } else { - log::warn!("Failed to parse DBus inhibit cookie from response"); + tracing::warn!("Failed to parse DBus inhibit cookie from response"); } } else { - log::warn!( + tracing::warn!( "DBus Inhibit call failed with status {:?}", output.status.code() ); } } Err(err) => { - log::warn!("Failed to invoke DBus Inhibit call: {}", err); + tracing::warn!("Failed to invoke DBus Inhibit call: {}", err); } } } @@ -358,7 +348,7 @@ fn linux_apply_download_inhibit(downloading: bool) { if output.status.success() { *cookie_guard = None; } else { - log::warn!( + tracing::warn!( "DBus UnInhibit call failed with status {:?}, keeping cookie {} for retry", output.status.code(), cookie @@ -366,7 +356,7 @@ fn linux_apply_download_inhibit(downloading: bool) { } } Err(err) => { - log::warn!( + tracing::warn!( "Failed to invoke DBus UnInhibit call, keeping cookie {} for retry: {}", cookie, err @@ -420,7 +410,7 @@ fn windows_inhibit_worker() -> &'static WindowsInhibitWorker { fn windows_apply_download_inhibit(downloading: bool) { let worker = windows_inhibit_worker(); if let Err(err) = worker.tx.send(downloading) { - log::warn!("Failed to update Windows sleep inhibit state: {}", err); + tracing::warn!("Failed to update Windows sleep inhibit state: {}", err); } } diff --git a/src-tauri/src/commands/file_cmds.rs b/src-tauri/src/commands/file_cmds.rs index 1f747e68..8dcb7a79 100644 --- a/src-tauri/src/commands/file_cmds.rs +++ b/src-tauri/src/commands/file_cmds.rs @@ -269,7 +269,7 @@ fn guess_android_mime(path: &str) -> String { pub fn trash_item(path: String) -> Result { let p = std::path::Path::new(&path); if !p.exists() { - log::debug!("trash_item: path does not exist, skipped: {}", path); + tracing::debug!("trash_item: path does not exist, skipped: {}", path); // Still try to clean up .chunks sidecar even if .part is gone if path.ends_with(TEMP_DOWNLOAD_SUFFIX) { let chunks_path = format!("{}{}", path, CHUNK_META_SUFFIX); diff --git a/src-tauri/src/commands/rss_cmds.rs b/src-tauri/src/commands/rss_cmds.rs index af85fcfb..23d2a72f 100644 --- a/src-tauri/src/commands/rss_cmds.rs +++ b/src-tauri/src/commands/rss_cmds.rs @@ -72,7 +72,7 @@ async fn write_article_and_fan_media( .await { Ok(g) => extra_gids.push(g), - Err(e) => log::warn!("Failed to queue inline media {}: {}", url, e), + Err(e) => tracing::warn!("Failed to queue inline media {}: {}", url, e), } } Ok((path_str, extra_gids)) @@ -224,7 +224,7 @@ pub async fn download_rss_item( .await { Ok(g) => extra_gids.push(g), - Err(e) => log::warn!("Failed to queue inline media {}: {}", url, e), + Err(e) => tracing::warn!("Failed to queue inline media {}: {}", url, e), } } @@ -393,7 +393,7 @@ pub async fn download_rss_item_tracked( .await { Ok(g) => extra_gids.push(g), - Err(e) => log::warn!("Failed to queue inline media {}: {}", url, e), + Err(e) => tracing::warn!("Failed to queue inline media {}: {}", url, e), } } diff --git a/src-tauri/src/commands/upload_cmds.rs b/src-tauri/src/commands/upload_cmds.rs index 476212df..8053b2c9 100644 --- a/src-tauri/src/commands/upload_cmds.rs +++ b/src-tauri/src/commands/upload_cmds.rs @@ -137,7 +137,7 @@ async fn fill_from_vault( } Ok(None) => {} Err(e) => { - log::warn!("Failed to load vault entry for sink {id}: {e}, trying fallback"); + tracing::warn!("Failed to load vault entry for sink {id}: {e}, trying fallback"); } } } @@ -164,29 +164,29 @@ async fn persist_sink_secrets( Ok(()) => { // Vault succeeded — clear any stale fallback entry if let Err(e) = mgr.remove_sink_secret_fallback(id).await { - log::warn!("Failed to clear stale fallback for sink {id}: {e}"); + tracing::warn!("Failed to clear stale fallback for sink {id}: {e}"); } return; } Err(e) => { - log::warn!( + tracing::warn!( "Failed to store sink secrets in vault for {id}: {e}, falling back" ); } } } if let Err(e) = mgr.put_sink_secret_fallback(id, &v).await { - log::warn!("Failed to store sink secrets in fallback for {id}: {e}"); + tracing::warn!("Failed to store sink secrets in fallback for {id}: {e}"); } } None => { if vault.enabled() { if let Err(e) = vault.remove_sink(id) { - log::warn!("Failed to clear vault entry for sink {id}: {e}"); + tracing::warn!("Failed to clear vault entry for sink {id}: {e}"); } } if let Err(e) = mgr.remove_sink_secret_fallback(id).await { - log::warn!("Failed to clear fallback secrets for sink {id}: {e}"); + tracing::warn!("Failed to clear fallback secrets for sink {id}: {e}"); } } } @@ -209,7 +209,7 @@ pub async fn rehydrate_upload_sinks(mgr: &UploadSinkManager, vault: &VaultManage Ok(Some(v)) => secrets = Some(v), Ok(None) => {} Err(e) => { - log::warn!( + tracing::warn!( "Failed to load vault entry for sink {}: {e}, trying fallback", record.id ); @@ -223,7 +223,7 @@ pub async fn rehydrate_upload_sinks(mgr: &UploadSinkManager, vault: &VaultManage let id = record.id.clone(); apply_sink_secrets(&mut record.config, &secrets); if let Err(e) = mgr.update_sink(record).await { - log::warn!("Failed to inject secrets into upload manager for sink {id}: {e}"); + tracing::warn!("Failed to inject secrets into upload manager for sink {id}: {e}"); } } } @@ -273,7 +273,7 @@ pub async fn remove_upload_sink(state: State<'_, AppState>, id: String) -> Resul let vault = state.vault.clone(); mgr.remove_sink(&id).await?; if let Err(e) = vault.remove_sink(&id) { - log::warn!("Failed to remove vault entry for sink {id}: {e}"); + tracing::warn!("Failed to remove vault entry for sink {id}: {e}"); } Ok(()) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 291dd77b..880b446b 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -21,16 +21,6 @@ use tauri_plugin_autostart::{MacosLauncher, ManagerExt}; use risuko_engine::engine::rss::RssManager; -#[cfg(not(target_os = "android"))] -fn with_nosleep_plugin(builder: tauri::Builder) -> tauri::Builder { - builder.plugin(tauri_plugin_nosleep::init()) -} - -#[cfg(target_os = "android")] -fn with_nosleep_plugin(builder: tauri::Builder) -> tauri::Builder { - builder -} - #[cfg(not(target_os = "android"))] fn with_desktop_plugins(builder: tauri::Builder) -> tauri::Builder { builder @@ -45,7 +35,7 @@ fn with_desktop_plugins(builder: tauri::Builder) -> tauri: if let Some(path) = args.get(1) { if std::path::Path::new(path).exists() { - log::info!("Single instance received file: {}", path); + tracing::info!("Single instance received file: {}", path); let _ = window.emit("open-file", path); } } @@ -172,16 +162,12 @@ pub fn run() { #[cfg(target_os = "linux")] apply_linux_webkit_workarounds(); - let app = with_desktop_plugins(with_nosleep_plugin( + let app = with_desktop_plugins( tauri::Builder::default() .plugin(tauri_plugin_store::Builder::default().build()) .plugin(tauri_plugin_dialog::init()) - .plugin(tauri_plugin_shell::init()) - .plugin(tauri_plugin_fs::init()) - .plugin(tauri_plugin_process::init()) - .plugin(tauri_plugin_os::init()) - .plugin(tauri_plugin_notification::init()), - )) + .plugin(tauri_plugin_shell::init()), + ) .plugin(tauri_plugin_clipboard_manager::init()) .plugin(tauri_plugin_deep_link::init()) .setup(|app| { @@ -220,13 +206,13 @@ pub fn run() { let log_guard = init_logging(&log_dir, &log_level); if log_dir != default_log_dir { - log::info!( + tracing::info!( "Log directory: {} (override of {})", log_dir.display(), default_log_dir.display() ); } else { - log::info!("Log directory: {}", log_dir.display()); + tracing::info!("Log directory: {}", log_dir.display()); } let app_state = state::AppState::new(config, storage.clone(), log_dir, log_guard)?; @@ -299,7 +285,7 @@ pub fn run() { let config = match risuko_engine::config::ConfigManager::with_dir(config_dir) { Ok(c) => c, Err(e) => { - log::error!("Failed to create ConfigManager: {}", e); + tracing::error!("Failed to create ConfigManager: {}", e); return; } }; @@ -311,7 +297,7 @@ pub fn run() { ) .await { - log::error!("Failed to start engine: {}", e); + tracing::error!("Failed to start engine: {}", e); } }); } @@ -460,6 +446,7 @@ pub fn run() { commands::engine_cmds::resolve_file_category, commands::cookie_cmds::list_browsers_cmd, commands::cookie_cmds::import_browser_cookies, + commands::cookie_cmds::import_browser_cookies_elevated, commands::cookie_cmds::list_cookie_entries, commands::cookie_cmds::delete_cookie_entry, commands::cookie_cmds::clear_cookie_entries, @@ -471,7 +458,6 @@ pub fn run() { commands::engine_cmds::remove_routing_rule, commands::engine_cmds::resolve_routing, commands::event_cmds::on_download_status_change, - commands::event_cmds::set_sleep_inhibit_flag, commands::event_cmds::on_speed_change, commands::event_cmds::on_progress_change, commands::event_cmds::on_task_download_complete, @@ -575,7 +561,7 @@ fn sync_open_at_login_setting(app: &tauri::App) { }; if let Err(err) = result { - log::warn!("Failed to sync open-at-login setting: {}", err); + tracing::warn!("Failed to sync open-at-login setting: {}", err); } } } diff --git a/src-tauri/src/managers/flyout.rs b/src-tauri/src/managers/flyout.rs index f462a3d8..84a52486 100644 --- a/src-tauri/src/managers/flyout.rs +++ b/src-tauri/src/managers/flyout.rs @@ -74,7 +74,7 @@ pub fn cache_tray_rect(app: &AppHandle, rect: &tauri::Rect, scale_factor: f64) { #[cfg(not(target_os = "android"))] pub fn toggle_flyout(app: &AppHandle) { let Some(window) = app.get_webview_window(FLYOUT_LABEL) else { - log::warn!("[Risuko] flyout window not found"); + tracing::warn!("[Risuko] flyout window not found"); return; }; diff --git a/src-tauri/src/managers/vault.rs b/src-tauri/src/managers/vault.rs index 2985b567..53dbf73f 100644 --- a/src-tauri/src/managers/vault.rs +++ b/src-tauri/src/managers/vault.rs @@ -37,7 +37,7 @@ fn ensure_default_store() { static INIT: Once = Once::new(); INIT.call_once(|| match build_default_store() { Ok(store) => keyring_core::set_default_store(store), - Err(e) => log::warn!("Credential vault: failed to init keystore: {e}"), + Err(e) => tracing::warn!("Credential vault: failed to init keystore: {e}"), }); } @@ -119,9 +119,9 @@ impl VaultManager { }; self.enabled.store(ok, Ordering::Relaxed); if ok { - log::info!("Credential vault: OS keychain available"); + tracing::info!("Credential vault: OS keychain available"); } else { - log::warn!("Credential vault: OS keychain unavailable, falling back to plaintext"); + tracing::warn!("Credential vault: OS keychain unavailable, falling back to plaintext"); } } diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs index ddf30efd..7121146c 100644 --- a/src-tauri/src/state.rs +++ b/src-tauri/src/state.rs @@ -31,11 +31,11 @@ impl AppState { let event_sink: Arc = Arc::new(risuko_engine::NoopEventSink); let rss_manager = RssManager::new(storage.clone(), event_sink.clone()); if let Err(e) = rss_manager.load() { - log::warn!("Failed to load RSS data: {}", e); + tracing::warn!("Failed to load RSS data: {}", e); } let upload_manager = UploadSinkManager::new(storage, event_sink); if let Err(e) = upload_manager.load() { - log::warn!("Failed to load upload sinks: {}", e); + tracing::warn!("Failed to load upload sinks: {}", e); } Ok(Self { config: Mutex::new(config), diff --git a/src/renderer/components/Native/EngineClient.vue b/src/renderer/components/Native/EngineClient.vue index e9b489f2..5f1c3db3 100644 --- a/src/renderer/components/Native/EngineClient.vue +++ b/src/renderer/components/Native/EngineClient.vue @@ -13,7 +13,6 @@ import { import logger from "@shared/utils/logger"; import { invoke } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; -import * as nosleep from "tauri-plugin-nosleep-api"; import { toast } from "vue-sonner"; import api from "@/api"; import { useAppStore } from "@/store/app"; @@ -55,7 +54,6 @@ export default { isDestroyed: false, noSleepDesired: false, noSleepApplied: false, - noSleepSource: null as null | "plugin" | "rust", noSleepSyncing: false, noSleepResyncNeeded: false, startupAutoResumeHandled: false, @@ -494,53 +492,11 @@ export default { }); }, async setNoSleepState(downloading: boolean) { - if (!downloading) { - if (this.noSleepSource === "plugin") { - try { - await nosleep.unblock(); - this.noSleepSource = null; - invoke("set_sleep_inhibit_flag", { active: false }).catch( - () => undefined, - ); - return true; - } catch { - return false; - } - } - - if (this.noSleepSource === "rust") { - try { - await invoke("on_download_status_change", { - downloading: false, - }); - this.noSleepSource = null; - return true; - } catch { - return false; - } - } - - return true; - } - try { - await nosleep.block( - nosleep.NoSleepType.PreventUserIdleSystemSleep as never, - ); - this.noSleepSource = "plugin"; - invoke("set_sleep_inhibit_flag", { active: true }).catch( - () => undefined, - ); + await invoke("on_download_status_change", { downloading }); return true; } catch { - // Keep Rust-side fallback for environments where the plugin command is unavailable. - try { - await invoke("on_download_status_change", { downloading }); - this.noSleepSource = "rust"; - return true; - } catch { - return false; - } + return false; } }, async syncNoSleepState() { From 6e8f59cf0713d276f7dc5125be8b1118a79fb944 Mon Sep 17 00:00:00 2001 From: YueMiyuki <76854136+YueMiyuki@users.noreply.github.com> Date: Sun, 21 Jun 2026 18:05:43 +0800 Subject: [PATCH 2/4] Accept suggestions from Cubic --- src-tauri/Cargo.lock | 109 +++++++++-- src-tauri/Cargo.toml | 2 +- .../risuko-cookies/src/browser/chromium.rs | 49 +++-- .../risuko-cookies/src/browser/safari.rs | 177 ++++++++++++++---- .../risuko-cookies/src/platform/linux.rs | 9 +- src-tauri/risuko-cookies/src/utils/time.rs | 11 +- 6 files changed, 277 insertions(+), 80 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index d9fe925c..df69d40c 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -8,6 +8,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array 0.14.7", +] + [[package]] name = "aead" version = "0.6.1" @@ -41,17 +51,31 @@ dependencies = [ "zeroize", ] +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead 0.5.2", + "aes 0.8.4", + "cipher 0.4.4", + "ctr 0.9.2", + "ghash 0.5.1", + "subtle", +] + [[package]] name = "aes-gcm" version = "0.11.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da8c919c118108f144adecad74b425b804ad075580d605d9b33c2d6d1c62a2f8" dependencies = [ - "aead", + "aead 0.6.1", "aes 0.9.1", "cipher 0.5.2", - "ctr", - "ghash", + "ctr 0.10.1", + "ghash 0.6.0", "subtle", "zeroize", ] @@ -1143,6 +1167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array 0.14.7", + "rand_core 0.6.4", "typenum", ] @@ -1202,6 +1227,15 @@ version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "ctr" version = "0.10.1" @@ -2299,13 +2333,23 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval 0.6.2", +] + [[package]] name = "ghash" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" dependencies = [ - "polyval", + "polyval 0.7.1", ] [[package]] @@ -3977,6 +4021,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "open" version = "5.3.5" @@ -4385,10 +4435,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00baa632505d05512f48a963e16051c54fda9a95cc9acea1a4e3c90991c4a2e" dependencies = [ "cpufeatures 0.3.0", - "universal-hash", + "universal-hash 0.6.1", "zeroize", ] +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash 0.5.1", +] + [[package]] name = "polyval" version = "0.7.1" @@ -4397,7 +4459,7 @@ checksum = "7dfc63250416fea14f5749b90725916a6c903f599d51cb635aa7a52bfd03eede" dependencies = [ "cpubits", "cpufeatures 0.3.0", - "universal-hash", + "universal-hash 0.6.1", ] [[package]] @@ -4661,6 +4723,15 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + [[package]] name = "rand_core" version = "0.9.5" @@ -4947,7 +5018,7 @@ name = "risuko-cookies" version = "0.4.2" dependencies = [ "aes 0.9.1", - "aes-gcm", + "aes-gcm 0.10.3", "base64 0.22.1", "cbc 0.2.1", "cipher 0.5.2", @@ -5116,7 +5187,7 @@ dependencies = [ "cbc 0.2.1", "cipher 0.5.2", "crypto-bigint", - "ctr", + "ctr 0.10.1", "curve25519-dalek", "data-encoding", "delegate", @@ -5130,7 +5201,7 @@ dependencies = [ "futures", "generic-array 1.4.3", "getrandom 0.4.3", - "ghash", + "ghash 0.6.0", "hex-literal", "hmac 0.13.0", "inout 0.2.2", @@ -5148,7 +5219,7 @@ dependencies = [ "pbkdf2", "pkcs5", "pkcs8", - "polyval", + "polyval 0.7.1", "rand 0.10.1", "rand_core 0.10.1", "ring", @@ -5168,7 +5239,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "typenum", - "universal-hash", + "universal-hash 0.6.1", "zeroize", ] @@ -5928,13 +5999,13 @@ version = "0.3.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10db6f219196a8528f9ec904d9d45cdad692d65b0e57e72be4dedd1c5fddce36" dependencies = [ - "aead", + "aead 0.6.1", "aes 0.9.1", - "aes-gcm", + "aes-gcm 0.11.0-rc.4", "cbc 0.2.1", "chacha20", "cipher 0.5.2", - "ctr", + "ctr 0.10.1", "ctutils", "des", "poly1305", @@ -7182,6 +7253,16 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + [[package]] name = "universal-hash" version = "0.6.1" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 3768c483..63f96425 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -5,7 +5,7 @@ members = ["risuko-engine", "risuko-bt", "risuko-cli", "risuko-napi", "risuko-ht version = "0.4.2" authors = ["YueMiyuki"] edition = "2021" -rust-version = "1.82" +rust-version = "1.85" [workspace.dependencies] risuko-engine = { path = "risuko-engine" } diff --git a/src-tauri/risuko-cookies/src/browser/chromium.rs b/src-tauri/risuko-cookies/src/browser/chromium.rs index 3da2de1b..8780f995 100644 --- a/src-tauri/risuko-cookies/src/browser/chromium.rs +++ b/src-tauri/risuko-cookies/src/browser/chromium.rs @@ -214,7 +214,7 @@ fn read_cookies_from_db( // Read all cookies let mut stmt = conn.prepare( - "SELECT name, encrypted_value, host_key, path, is_secure, is_httponly, expires_utc FROM cookies" + "SELECT name, encrypted_value, value, host_key, path, is_secure, is_httponly, expires_utc FROM cookies" )?; let cookie_iter = stmt.query_map([], parse_row)?; @@ -227,7 +227,8 @@ fn read_cookies_from_db( for cookie_result in cookie_iter { if let Ok(raw) = cookie_result { - if raw.raw_value.is_empty() { + // Skip only when both encrypted and plaintext values are empty + if raw.raw_value.is_empty() && raw.plaintext_value.is_empty() { skipped_empty += 1; continue; } @@ -241,22 +242,28 @@ fn read_cookies_from_db( } } - // Decrypt the raw BLOB bytes, then convert to string - let value = if let Some(key) = master_key { - match decrypt_cookie_value(&raw.raw_value, key) { - Ok(decrypted) => { - decrypted_ok += 1; - String::from_utf8_lossy(&decrypted).to_string() - } - Err(e) => { - tracing::trace!(target: "risuko_cookies", "chromium: decrypt failed for '{}' host_key={}: {}", raw.name, raw.domain, e); - decrypted_fail += 1; - String::from_utf8_lossy(&raw.raw_value).to_string() + // Decrypt the encrypted value if present, otherwise fall back to plaintext + let value = if !raw.raw_value.is_empty() { + if let Some(key) = master_key { + match decrypt_cookie_value(&raw.raw_value, key) { + Ok(decrypted) => { + decrypted_ok += 1; + String::from_utf8_lossy(&decrypted).to_string() + } + Err(e) => { + tracing::trace!(target: "risuko_cookies", "chromium: decrypt failed for '{}' host_key={}: {}", raw.name, raw.domain, e); + decrypted_fail += 1; + String::from_utf8_lossy(&raw.raw_value).to_string() + } } + } else { + no_key += 1; + String::from_utf8_lossy(&raw.raw_value).to_string() } } else { - no_key += 1; - String::from_utf8_lossy(&raw.raw_value).to_string() + // Plaintext cookie + decrypted_ok += 1; + raw.plaintext_value }; cookies.push(Cookie { @@ -298,6 +305,7 @@ fn cookie_covers_host(request_host: &str, cookie_host_key: &str) -> bool { struct RawCookie { name: String, raw_value: Vec, + plaintext_value: String, domain: String, path: String, secure: bool, @@ -309,11 +317,12 @@ fn parse_row(row: &rusqlite::Row) -> rusqlite::Result { Ok(RawCookie { name: row.get(0)?, raw_value: row.get::<_, Vec>(1)?, - domain: row.get(2)?, - path: row.get(3)?, - secure: row.get::<_, i32>(4)? != 0, - http_only: row.get::<_, i32>(5)? != 0, - expires: time::webkit_to_unix(row.get::<_, i64>(6)? as u64), + plaintext_value: row.get(2)?, + domain: row.get(3)?, + path: row.get(4)?, + secure: row.get::<_, i32>(5)? != 0, + http_only: row.get::<_, i32>(6)? != 0, + expires: time::webkit_to_unix(row.get::<_, i64>(7)? as u64), }) } diff --git a/src-tauri/risuko-cookies/src/browser/safari.rs b/src-tauri/risuko-cookies/src/browser/safari.rs index c193cf8e..ffab09b6 100644 --- a/src-tauri/risuko-cookies/src/browser/safari.rs +++ b/src-tauri/risuko-cookies/src/browser/safari.rs @@ -6,8 +6,6 @@ use crate::browser::chromium::Cookie; use crate::utils::{paths, time}; #[cfg(target_os = "macos")] use eyre::{bail, Result}; -#[cfg(target_os = "macos")] -use rusqlite::Connection; #[cfg(target_os = "macos")] pub fn extract_cookies(host: Option<&str>) -> Result> { @@ -21,40 +19,32 @@ pub fn extract_cookies(host: Option<&str>) -> Result> { }; tracing::debug!(target: "risuko_cookies", "safari: using db path {}", cookie_db.display()); - // Export the binary cookie store to a temporary plist we can read - let temp_db = tempfile::NamedTempFile::new()?; - std::process::Command::new("defaults") - .arg("export") - .arg(&cookie_db) - .arg(temp_db.path()) - .output()?; - - let conn = Connection::open(temp_db.path())?; - - // Count total rows first for debugging - let total_count: i64 = conn.query_row("SELECT COUNT(*) FROM cookies", [], |row| row.get(0))?; - tracing::debug!(target: "risuko_cookies", "safari: total rows in cookies = {}", total_count); + let data = std::fs::read(&cookie_db)?; + let raw_cookies = parse_binary_cookies(&data)?; - // Read all cookies and filter by domain - let mut stmt = - conn.prepare("SELECT name, value, domain, path, secure, httpOnly, expires FROM cookies")?; - let cookie_iter = stmt.query_map([], parse_row)?; + tracing::debug!(target: "risuko_cookies", "safari: total cookies parsed = {}", raw_cookies.len()); let mut cookies = Vec::new(); let mut skipped_domain = 0usize; - for cookie_result in cookie_iter { - if let Ok(cookie) = cookie_result { - if let Some(request_host) = host { - if !cookie_covers_host(request_host, &cookie.domain) { - tracing::trace!(target: "risuko_cookies", "safari: skip cookie '{}' domain={} (does not cover {})", cookie.name, cookie.domain, request_host); - skipped_domain += 1; - continue; - } + for raw in raw_cookies { + if let Some(request_host) = host { + if !cookie_covers_host(request_host, &raw.domain) { + tracing::trace!(target: "risuko_cookies", "safari: skip cookie '{}' domain={} (does not cover {})", raw.name, raw.domain, request_host); + skipped_domain += 1; + continue; } - tracing::trace!(target: "risuko_cookies", "safari: keep cookie '{}' domain={} value_len={}", cookie.name, cookie.domain, cookie.value.len()); - cookies.push(cookie); } + tracing::trace!(target: "risuko_cookies", "safari: keep cookie '{}' domain={} value_len={}", raw.name, raw.domain, raw.value.len()); + cookies.push(Cookie { + name: raw.name, + value: raw.value, + domain: raw.domain, + path: raw.path, + secure: raw.secure, + http_only: raw.http_only, + expires: raw.expires, + }); } tracing::debug!(target: "risuko_cookies", @@ -65,6 +55,123 @@ pub fn extract_cookies(host: Option<&str>) -> Result> { Ok(cookies) } +#[cfg(target_os = "macos")] +struct RawSafariCookie { + name: String, + value: String, + domain: String, + path: String, + secure: bool, + http_only: bool, + expires: Option, +} + +#[cfg(target_os = "macos")] +fn parse_binary_cookies(data: &[u8]) -> Result> { + if data.len() < 8 { + bail!("binary cookies file too short"); + } + + if &data[..4] != b"cook" { + bail!("invalid binary cookies magic"); + } + + let num_pages = u32::from_be_bytes(data[4..8].try_into().unwrap()) as usize; + if num_pages == 0 { + return Ok(Vec::new()); + } + + let header_size = 8 + num_pages * 4; + if data.len() < header_size { + bail!("binary cookies header truncated"); + } + + let mut cookies = Vec::new(); + for i in 0..num_pages { + let off = 8 + i * 4; + let page_offset = u32::from_be_bytes(data[off..off + 4].try_into().unwrap()) as usize; + if page_offset >= data.len() { + bail!("page offset out of bounds"); + } + parse_page(&data[page_offset..], &mut cookies)?; + } + + Ok(cookies) +} + +#[cfg(target_os = "macos")] +fn parse_page(page: &[u8], cookies: &mut Vec) -> Result<()> { + if page.len() < 8 { + bail!("page too short"); + } + + let num_cookies = u32::from_be_bytes(page[4..8].try_into().unwrap()) as usize; + if num_cookies == 0 { + return Ok(()); + } + + let header_size = 8 + num_cookies * 4; + if page.len() < header_size { + bail!("page header truncated"); + } + + for i in 0..num_cookies { + let off = 8 + i * 4; + let cookie_offset = u32::from_be_bytes(page[off..off + 4].try_into().unwrap()) as usize; + if cookie_offset >= page.len() { + bail!("cookie offset out of bounds"); + } + parse_cookie(&page[cookie_offset..], cookies)?; + } + + Ok(()) +} + +#[cfg(target_os = "macos")] +fn parse_cookie(cookie: &[u8], cookies: &mut Vec) -> Result<()> { + if cookie.len() < 44 { + bail!("cookie record too short"); + } + + let flags = u32::from_be_bytes(cookie[4..8].try_into().unwrap()); + let url_offset = u32::from_be_bytes(cookie[12..16].try_into().unwrap()) as usize; + let name_offset = u32::from_be_bytes(cookie[16..20].try_into().unwrap()) as usize; + let path_offset = u32::from_be_bytes(cookie[20..24].try_into().unwrap()) as usize; + let value_offset = u32::from_be_bytes(cookie[24..28].try_into().unwrap()) as usize; + + let expiry_bytes: [u8; 8] = cookie[28..36].try_into().unwrap(); + let expiry = f64::from_be_bytes(expiry_bytes); + + let domain = read_cstr(cookie, url_offset)?; + let name = read_cstr(cookie, name_offset)?; + let path = read_cstr(cookie, path_offset)?; + let value = read_cstr(cookie, value_offset)?; + + cookies.push(RawSafariCookie { + name, + value, + domain, + path, + secure: (flags & 0x01) != 0, + http_only: (flags & 0x04) != 0, + expires: time::safari_to_unix(expiry), + }); + + Ok(()) +} + +#[cfg(target_os = "macos")] +fn read_cstr(data: &[u8], offset: usize) -> Result { + if offset >= data.len() { + bail!("string offset out of bounds"); + } + let end = data[offset..] + .iter() + .position(|&b| b == 0) + .unwrap_or(data.len() - offset); + Ok(String::from_utf8_lossy(&data[offset..offset + end]).to_string()) +} + #[cfg(target_os = "macos")] fn cookie_covers_host(request_host: &str, cookie_domain: &str) -> bool { let r = request_host.to_lowercase(); @@ -80,18 +187,6 @@ fn cookie_covers_host(request_host: &str, cookie_domain: &str) -> bool { } } -#[cfg(target_os = "macos")] -fn parse_row(row: &rusqlite::Row) -> rusqlite::Result { - Ok(Cookie { - name: row.get(0)?, - value: row.get(1)?, - domain: row.get(2)?, - path: row.get(3)?, - secure: row.get::<_, i32>(4)? != 0, - http_only: row.get::<_, i32>(5)? != 0, - expires: time::safari_to_unix(row.get::<_, i64>(6)? as u64), - }) -} #[cfg(not(target_os = "macos"))] pub fn extract_cookies(_host: Option<&str>) -> eyre::Result> { diff --git a/src-tauri/risuko-cookies/src/platform/linux.rs b/src-tauri/risuko-cookies/src/platform/linux.rs index faa2ba83..dedad18e 100644 --- a/src-tauri/risuko-cookies/src/platform/linux.rs +++ b/src-tauri/risuko-cookies/src/platform/linux.rs @@ -71,8 +71,15 @@ pub fn extract_master_key(_local_state_path: &std::path::Path) -> Result "org.freedesktop.Secret.Item", )?; + let service_proxy = zbus::blocking::Proxy::new( + &conn, + "org.freedesktop.secrets", + "/org/freedesktop/secrets", + "org.freedesktop.Secret.Service", + )?; + let session_path: zbus::zvariant::OwnedObjectPath = - proxy.call("OpenSession", &("plain", zbus::zvariant::Value::from("")))?; + service_proxy.call("OpenSession", &("plain", zbus::zvariant::Value::from("")))?; let secret: (zbus::zvariant::OwnedObjectPath, Vec, Vec, String) = item_proxy.call("GetSecret", &(session_path,))?; diff --git a/src-tauri/risuko-cookies/src/utils/time.rs b/src-tauri/risuko-cookies/src/utils/time.rs index dee8d3c3..8f75bcfd 100644 --- a/src-tauri/risuko-cookies/src/utils/time.rs +++ b/src-tauri/risuko-cookies/src/utils/time.rs @@ -11,9 +11,14 @@ pub fn webkit_to_unix(micros: u64) -> Option { } #[cfg(target_os = "macos")] -pub fn safari_to_unix(timestamp: u64) -> Option { - if timestamp == 0 { +pub fn safari_to_unix(timestamp: f64) -> Option { + if timestamp <= 0.0 { return None; } - Some((timestamp + 978_307_200) / 1_000_000_000) + let unix = timestamp + 978_307_200.0; + if unix < 0.0 { + None + } else { + Some(unix as u64) + } } From ebb5785021f28ccc59119034ec8f2106d8a34876 Mon Sep 17 00:00:00 2001 From: YueMiyuki <76854136+YueMiyuki@users.noreply.github.com> Date: Sun, 21 Jun 2026 20:13:12 +0800 Subject: [PATCH 3/4] Accept suggestions from Cubic --- src-tauri/Cargo.lock | 109 +++--------------- .../risuko-cookies/src/browser/chromium.rs | 21 +++- .../risuko-cookies/src/browser/safari.rs | 1 - .../risuko-cookies/src/platform/linux.rs | 2 +- src-tauri/risuko-cookies/src/utils/time.rs | 6 +- 5 files changed, 34 insertions(+), 105 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index df69d40c..d9fe925c 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -8,16 +8,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common 0.1.7", - "generic-array 0.14.7", -] - [[package]] name = "aead" version = "0.6.1" @@ -51,31 +41,17 @@ dependencies = [ "zeroize", ] -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead 0.5.2", - "aes 0.8.4", - "cipher 0.4.4", - "ctr 0.9.2", - "ghash 0.5.1", - "subtle", -] - [[package]] name = "aes-gcm" version = "0.11.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da8c919c118108f144adecad74b425b804ad075580d605d9b33c2d6d1c62a2f8" dependencies = [ - "aead 0.6.1", + "aead", "aes 0.9.1", "cipher 0.5.2", - "ctr 0.10.1", - "ghash 0.6.0", + "ctr", + "ghash", "subtle", "zeroize", ] @@ -1167,7 +1143,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array 0.14.7", - "rand_core 0.6.4", "typenum", ] @@ -1227,15 +1202,6 @@ version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher 0.4.4", -] - [[package]] name = "ctr" version = "0.10.1" @@ -2333,23 +2299,13 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval 0.6.2", -] - [[package]] name = "ghash" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" dependencies = [ - "polyval 0.7.1", + "polyval", ] [[package]] @@ -4021,12 +3977,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "open" version = "5.3.5" @@ -4435,22 +4385,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00baa632505d05512f48a963e16051c54fda9a95cc9acea1a4e3c90991c4a2e" dependencies = [ "cpufeatures 0.3.0", - "universal-hash 0.6.1", + "universal-hash", "zeroize", ] -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash 0.5.1", -] - [[package]] name = "polyval" version = "0.7.1" @@ -4459,7 +4397,7 @@ checksum = "7dfc63250416fea14f5749b90725916a6c903f599d51cb635aa7a52bfd03eede" dependencies = [ "cpubits", "cpufeatures 0.3.0", - "universal-hash 0.6.1", + "universal-hash", ] [[package]] @@ -4723,15 +4661,6 @@ dependencies = [ "rand_core 0.9.5", ] -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - [[package]] name = "rand_core" version = "0.9.5" @@ -5018,7 +4947,7 @@ name = "risuko-cookies" version = "0.4.2" dependencies = [ "aes 0.9.1", - "aes-gcm 0.10.3", + "aes-gcm", "base64 0.22.1", "cbc 0.2.1", "cipher 0.5.2", @@ -5187,7 +5116,7 @@ dependencies = [ "cbc 0.2.1", "cipher 0.5.2", "crypto-bigint", - "ctr 0.10.1", + "ctr", "curve25519-dalek", "data-encoding", "delegate", @@ -5201,7 +5130,7 @@ dependencies = [ "futures", "generic-array 1.4.3", "getrandom 0.4.3", - "ghash 0.6.0", + "ghash", "hex-literal", "hmac 0.13.0", "inout 0.2.2", @@ -5219,7 +5148,7 @@ dependencies = [ "pbkdf2", "pkcs5", "pkcs8", - "polyval 0.7.1", + "polyval", "rand 0.10.1", "rand_core 0.10.1", "ring", @@ -5239,7 +5168,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "typenum", - "universal-hash 0.6.1", + "universal-hash", "zeroize", ] @@ -5999,13 +5928,13 @@ version = "0.3.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10db6f219196a8528f9ec904d9d45cdad692d65b0e57e72be4dedd1c5fddce36" dependencies = [ - "aead 0.6.1", + "aead", "aes 0.9.1", - "aes-gcm 0.11.0-rc.4", + "aes-gcm", "cbc 0.2.1", "chacha20", "cipher 0.5.2", - "ctr 0.10.1", + "ctr", "ctutils", "des", "poly1305", @@ -7253,16 +7182,6 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common 0.1.7", - "subtle", -] - [[package]] name = "universal-hash" version = "0.6.1" diff --git a/src-tauri/risuko-cookies/src/browser/chromium.rs b/src-tauri/risuko-cookies/src/browser/chromium.rs index 8780f995..ac3ffef4 100644 --- a/src-tauri/risuko-cookies/src/browser/chromium.rs +++ b/src-tauri/risuko-cookies/src/browser/chromium.rs @@ -253,12 +253,20 @@ fn read_cookies_from_db( Err(e) => { tracing::trace!(target: "risuko_cookies", "chromium: decrypt failed for '{}' host_key={}: {}", raw.name, raw.domain, e); decrypted_fail += 1; - String::from_utf8_lossy(&raw.raw_value).to_string() + if !raw.plaintext_value.is_empty() { + raw.plaintext_value + } else { + String::from_utf8_lossy(&raw.raw_value).to_string() + } } } } else { no_key += 1; - String::from_utf8_lossy(&raw.raw_value).to_string() + if !raw.plaintext_value.is_empty() { + raw.plaintext_value + } else { + String::from_utf8_lossy(&raw.raw_value).to_string() + } } } else { // Plaintext cookie @@ -322,7 +330,14 @@ fn parse_row(row: &rusqlite::Row) -> rusqlite::Result { path: row.get(4)?, secure: row.get::<_, i32>(5)? != 0, http_only: row.get::<_, i32>(6)? != 0, - expires: time::webkit_to_unix(row.get::<_, i64>(7)? as u64), + expires: { + let v: i64 = row.get(7)?; + if v <= 0 { + None + } else { + time::webkit_to_unix(v as u64) + } + }, }) } diff --git a/src-tauri/risuko-cookies/src/browser/safari.rs b/src-tauri/risuko-cookies/src/browser/safari.rs index ffab09b6..0d1c5700 100644 --- a/src-tauri/risuko-cookies/src/browser/safari.rs +++ b/src-tauri/risuko-cookies/src/browser/safari.rs @@ -187,7 +187,6 @@ fn cookie_covers_host(request_host: &str, cookie_domain: &str) -> bool { } } - #[cfg(not(target_os = "macos"))] pub fn extract_cookies(_host: Option<&str>) -> eyre::Result> { eyre::bail!("safari only available on macos") diff --git a/src-tauri/risuko-cookies/src/platform/linux.rs b/src-tauri/risuko-cookies/src/platform/linux.rs index dedad18e..593401e8 100644 --- a/src-tauri/risuko-cookies/src/platform/linux.rs +++ b/src-tauri/risuko-cookies/src/platform/linux.rs @@ -78,7 +78,7 @@ pub fn extract_master_key(_local_state_path: &std::path::Path) -> Result "org.freedesktop.Secret.Service", )?; - let session_path: zbus::zvariant::OwnedObjectPath = + let (_output, session_path): (zbus::zvariant::OwnedValue, zbus::zvariant::OwnedObjectPath) = service_proxy.call("OpenSession", &("plain", zbus::zvariant::Value::from("")))?; let secret: (zbus::zvariant::OwnedObjectPath, Vec, Vec, String) = diff --git a/src-tauri/risuko-cookies/src/utils/time.rs b/src-tauri/risuko-cookies/src/utils/time.rs index 8f75bcfd..68a4b4ce 100644 --- a/src-tauri/risuko-cookies/src/utils/time.rs +++ b/src-tauri/risuko-cookies/src/utils/time.rs @@ -16,9 +16,5 @@ pub fn safari_to_unix(timestamp: f64) -> Option { return None; } let unix = timestamp + 978_307_200.0; - if unix < 0.0 { - None - } else { - Some(unix as u64) - } + Some(unix as u64) } From ad9d09bbbdd1e920abb9a6eba3ff0f25f62c38a4 Mon Sep 17 00:00:00 2001 From: YueMiyuki <76854136+YueMiyuki@users.noreply.github.com> Date: Sun, 21 Jun 2026 20:23:44 +0800 Subject: [PATCH 4/4] Accept suggestions from Cubic --- src-tauri/risuko-cookies/src/browser/chromium.rs | 6 +----- src-tauri/risuko-cookies/src/utils/time.rs | 3 +++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src-tauri/risuko-cookies/src/browser/chromium.rs b/src-tauri/risuko-cookies/src/browser/chromium.rs index ac3ffef4..4dc4e0aa 100644 --- a/src-tauri/risuko-cookies/src/browser/chromium.rs +++ b/src-tauri/risuko-cookies/src/browser/chromium.rs @@ -253,11 +253,7 @@ fn read_cookies_from_db( Err(e) => { tracing::trace!(target: "risuko_cookies", "chromium: decrypt failed for '{}' host_key={}: {}", raw.name, raw.domain, e); decrypted_fail += 1; - if !raw.plaintext_value.is_empty() { - raw.plaintext_value - } else { - String::from_utf8_lossy(&raw.raw_value).to_string() - } + continue; } } } else { diff --git a/src-tauri/risuko-cookies/src/utils/time.rs b/src-tauri/risuko-cookies/src/utils/time.rs index 68a4b4ce..7c14e3b5 100644 --- a/src-tauri/risuko-cookies/src/utils/time.rs +++ b/src-tauri/risuko-cookies/src/utils/time.rs @@ -16,5 +16,8 @@ pub fn safari_to_unix(timestamp: f64) -> Option { return None; } let unix = timestamp + 978_307_200.0; + if !unix.is_finite() || unix < 0.0 { + return None; + } Some(unix as u64) }