diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8e19021..eb047ea 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Build run: cargo build --verbose diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2f508d0..247ad43 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,101 +1,123 @@ +# From https://github.com/BurntSushi/ripgrep/blob/master/.github/workflows/release.yml +# Which is also via https://eugene-babichenko.github.io/blog/2020/05/09/github-actions-cross-platform-auto-releases/ +# ...both of which are very good. +# +# I'm sure I don't need half the stuff I have in here (around cargo +# customization and whatnot) but. +# name: release on: - workflow_dispatch: push: tags: - "v[0-9]+.[0-9]+.[0-9]+" -permissions: - contents: write - jobs: create_release: name: Create release - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Get the release version - if: env.VERSION == '' - run: echo "VERSION=${{ github.ref_name }}" >> $GITHUB_ENV - - - name: Show the version - run: | - echo "version is: $VERSION" - - - name: Create GitHub release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh release create $VERSION --draft --verify-tag --title $VERSION + runs-on: ubuntu-22.04 outputs: - version: ${{ env.VERSION }} + upload_url: ${{ steps.create_release.outputs.upload_url }} - build_release: - name: Build all the stuff + steps: + - name: Create GitHub release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: true + + release_assets: + name: Release assets needs: ['create_release'] # We need to know the upload URL runs-on: ${{ matrix.os }} # We run many different builds env: + # For some builds, we use cross to test on 32-bit and big-endian + # systems. + CARGO: cargo + # When CARGO is set to CROSS, this is set to `--target matrix.target`. + TARGET_FLAGS: "" + # When CARGO is set to CROSS, TARGET_DIR includes matrix.target. + TARGET_DIR: ./target # Emit backtraces on panics. RUST_BACKTRACE: 1 + # Build static releases with PCRE2. + PCRE2_SYS_STATIC: 1 strategy: - fail-fast: false + # just an example matrix matrix: - build: ['linux', 'debian', 'macos', 'arm-macos', 'windows'] + build: ['linux', 'macos', 'windows'] include: - build: linux os: ubuntu-22.04 + rust: nightly target: x86_64-unknown-linux-musl - packages: apt - - - build: debian - os: ubuntu-22.04 - target: x86_64-unknown-linux-musl - packages: apt - build: macos - os: macos-latest + os: macos-12 + rust: nightly target: x86_64-apple-darwin - packages: brew - - - build: arm-macos - os: macos-latest - target: aarch64-apple-darwin - packages: brew - build: windows os: windows-2022 + rust: nightly target: x86_64-pc-windows-msvc - packages: none steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v3 - - name: Install packages (linux) - if: matrix.packages == 'apt' - run: | - sudo apt-get update - sudo apt-get install -y pandoc - - - name: Install packages (macos) - if: matrix.packages == 'brew' - run: | - brew update - brew install pandoc - - - name: Install rust - uses: dtolnay/rust-toolchain@stable + - name: Install Rust + uses: dtolnay/rust-toolchain@master with: + toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} - - name: Run the release automation + - name: Use Cross shell: bash + run: | + cargo install cross + echo "CARGO=cross" >> $GITHUB_ENV + echo "TARGET_FLAGS=--target ${{ matrix.target }}" >> $GITHUB_ENV + echo "TARGET_DIR=./target/${{ matrix.target }}" >> $GITHUB_ENV + + - name: Build release binary + run: ${{ env.CARGO }} build --verbose --release ${{ env.TARGET_FLAGS }} + + - name: Strip release binary (linux and macos) + if: matrix.build == 'linux' || matrix.build == 'macos' + run: | + strip "target/${{ matrix.target }}/release/fwd" + strip "target/${{ matrix.target }}/release/fwd-browse" + + - name: Build archive + shell: bash + run: | + staging="fwd-${{ matrix.target }}" + mkdir -p "$staging" + + if [ "${{ matrix.os }}" = "windows-2022" ]; then + cp "target/${{ matrix.target }}/release/fwd.exe" "$staging/" + 7z a "$staging.zip" "$staging" + echo "ASSET=$staging.zip" >> $GITHUB_ENV + else + cp "target/${{ matrix.target }}/release/fwd" "$staging/" + cp "target/${{ matrix.target }}/release/fwd-browse" "$staging/" + tar czf "$staging.tar.gz" "$staging" + echo "ASSET=$staging.tar.gz" >> $GITHUB_ENV + fi + + - name: Upload release archive + uses: actions/upload-release-asset@v1.0.2 env: - RELEASE_TAG: ${{ needs.create_release.outputs.version }} - BUILD: ${{ matrix.build }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: python3 release.py + with: + upload_url: ${{ needs.create_release.outputs.upload_url }} + asset_name: ${{ env.ASSET }} + asset_path: ${{ env.ASSET }} + asset_content_type: application/octet-stream diff --git a/Cargo.lock b/Cargo.lock index 3499c83..1a5d73a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -17,24 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - [[package]] name = "android-tzdata" version = "0.1.1" @@ -52,9 +34,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "assert_matches" @@ -64,15 +46,15 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -89,23 +71,11 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" @@ -115,35 +85,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" - -[[package]] -name = "calloop" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" -dependencies = [ - "bitflags 2.6.0", - "log", - "polling", - "rustix 0.38.34", - "slab", - "thiserror", -] - -[[package]] -name = "calloop-wayland-source" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" -dependencies = [ - "calloop", - "rustix 0.38.34", - "wayland-backend", - "wayland-client", -] +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cassowary" @@ -151,20 +95,14 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" -[[package]] -name = "castaway" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" -dependencies = [ - "rustversion", -] - [[package]] name = "cc" -version = "1.1.10" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -174,96 +112,43 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets 0.52.6", -] - -[[package]] -name = "clipboard-win" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" -dependencies = [ - "lazy-bytes-cast", - "winapi", -] - -[[package]] -name = "compact_str" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "rustversion", - "ryu", - "static_assertions", -] - -[[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 = "copypasta" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb85422867ca93da58b7f95fb5c0c10f6183ed6e1ef8841568968a896d3a858" -dependencies = [ - "clipboard-win", - "objc", - "objc-foundation", - "objc_id", - "smithay-clipboard", - "x11-clipboard", + "windows-targets 0.48.5", ] [[package]] name = "core-foundation-sys" -version = "0.8.7" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - [[package]] name = "crossterm" -version = "0.28.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" dependencies = [ - "bitflags 2.6.0", + "bitflags", "crossterm_winapi", "futures-core", + "libc", "mio", "parking_lot", - "rustix 0.38.34", "signal-hook", "signal-hook-mio", "winapi", @@ -278,191 +163,72 @@ dependencies = [ "winapi", ] -[[package]] -name = "cursor-icon" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" - -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - -[[package]] -name = "directories-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - -[[package]] -name = "env_filter" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" -dependencies = [ - "log", -] - -[[package]] -name = "env_logger" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" -dependencies = [ - "env_filter", - "log", -] - [[package]] name = "errno" -version = "0.3.9" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] -[[package]] -name = "fastrand" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" - [[package]] name = "flate2" -version = "1.0.31" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] -name = "futures-core" -version = "0.3.30" +name = "fuchsia-cprng" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "fwd" -version = "0.9.2" +version = "0.7.0" dependencies = [ "anyhow", "assert_matches", "bytes", - "copypasta", "crossterm", - "directories-next", - "env_logger", + "home", "indoc", - "libc", "log", "open", - "pretty_assertions", "procfs", - "rand", - "ratatui", - "tempfile", + "tempdir", "thiserror", "tokio", "tokio-stream", "toml", -] - -[[package]] -name = "gethostname" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" -dependencies = [ - "libc", - "windows-targets 0.48.5", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", + "tui", + "users", + "xdg", ] [[package]] name = "gimli" -version = "0.29.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" @@ -471,10 +237,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "iana-time-zone" -version = "0.1.60" +name = "home" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -499,88 +274,37 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" -[[package]] -name = "instability" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "io-lifetimes" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", "windows-sys 0.48.0", ] -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy-bytes-cast" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" - [[package]] name = "lazy_static" -version = "1.5.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" - -[[package]] -name = "libloading" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" -dependencies = [ - "cfg-if", - "windows-targets 0.52.6", -] - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.6.0", - "libc", -] +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linux-raw-sys" @@ -588,17 +312,11 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -606,117 +324,70 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "lru" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" -dependencies = [ - "hashbrown", -] - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.7.4" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "memmap2" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" -dependencies = [ - "libc", -] +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "1.0.2" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ - "hermit-abi 0.3.9", "libc", "log", "wasi", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] name = "num-traits" -version = "0.2.19" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] -name = "objc" -version = "0.2.7" +name = "num_cpus" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "malloc_buf", -] - -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", + "hermit-abi", + "libc", ] [[package]] name = "object" -version = "0.36.3" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "open" @@ -730,9 +401,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", @@ -740,23 +411,17 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pathdiff" version = "0.2.1" @@ -765,55 +430,15 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" - -[[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - -[[package]] -name = "polling" -version = "3.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi 0.4.0", - "pin-project-lite", - "rustix 0.38.34", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "pretty_assertions" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" -dependencies = [ - "diff", - "yansi", -] +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -824,109 +449,84 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1de8dacb0873f77e6aefc6d71e044761fcc68060290f5b1089fcdf84626bb69" dependencies = [ - "bitflags 1.3.2", + "bitflags", "byteorder", "chrono", "flate2", "hex", "lazy_static", - "rustix 0.36.17", -] - -[[package]] -name = "quick-xml" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f24d770aeca0eacb81ac29dfbc55ebcc09312fdd1f8bbecdc7e4a84e000e3b4" -dependencies = [ - "memchr", + "rustix", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "rand" -version = "0.8.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" dependencies = [ + "fuchsia-cprng", "libc", - "rand_chacha", - "rand_core", -] - -[[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", + "rand_core 0.3.1", + "rdrand", + "winapi", ] [[package]] name = "rand_core" -version = "0.6.4" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" dependencies = [ - "getrandom", + "rand_core 0.4.2", ] [[package]] -name = "ratatui" -version = "0.28.0" +name = "rand_core" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ba6a365afbe5615999275bea2446b970b10a41102500e27ce7678d50d978303" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" dependencies = [ - "bitflags 2.6.0", - "cassowary", - "compact_str", - "crossterm", - "instability", - "itertools", - "lru", - "paste", - "strum", - "strum_macros", - "unicode-segmentation", - "unicode-truncate", - "unicode-width", + "rand_core 0.3.1", ] [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags 2.6.0", + "bitflags", ] [[package]] -name = "redox_users" -version = "0.4.5" +name = "remove_dir_all" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "getrandom", - "libredox", - "thiserror", + "winapi", ] [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" @@ -934,45 +534,14 @@ version = "0.36.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "305efbd14fde4139eb501df5f136994bb520b033fa9fbdce287507dc23b8c7ed" dependencies = [ - "bitflags 1.3.2", + "bitflags", "errno", "io-lifetimes", "libc", - "linux-raw-sys 0.1.4", + "linux-raw-sys", "windows-sys 0.45.0", ] -[[package]] -name = "rustix" -version = "0.38.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" -dependencies = [ - "bitflags 2.6.0", - "errno", - "libc", - "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustversion" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -981,18 +550,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.207" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.207" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", @@ -1011,9 +580,9 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" dependencies = [ "libc", "mio", @@ -1022,107 +591,34 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - [[package]] name = "smallvec" -version = "1.13.2" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "smithay-client-toolkit" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" -dependencies = [ - "bitflags 2.6.0", - "calloop", - "calloop-wayland-source", - "cursor-icon", - "libc", - "log", - "memmap2", - "rustix 0.38.34", - "thiserror", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-wlr", - "wayland-scanner", - "xkeysym", -] - -[[package]] -name = "smithay-clipboard" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" -dependencies = [ - "libc", - "smithay-client-toolkit", - "wayland-backend", -] +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", + "windows-sys 0.48.0", ] [[package]] name = "syn" -version = "2.0.74" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -1130,32 +626,29 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.12.0" +name = "tempdir" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" dependencies = [ - "cfg-if", - "fastrand", - "once_cell", - "rustix 0.38.34", - "windows-sys 0.59.0", + "rand", + "remove_dir_all", ] [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", @@ -1164,26 +657,27 @@ dependencies = [ [[package]] name = "tokio" -version = "1.39.2" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", "libc", "mio", + "num_cpus", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", @@ -1192,9 +686,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite", @@ -1211,21 +705,18 @@ dependencies = [ ] [[package]] -name = "tracing" -version = "0.1.40" +name = "tui" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1" dependencies = [ - "pin-project-lite", - "tracing-core", + "bitflags", + "cassowary", + "crossterm", + "unicode-segmentation", + "unicode-width", ] -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" - [[package]] name = "unicode-ident" version = "1.0.12" @@ -1234,32 +725,25 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" - -[[package]] -name = "unicode-truncate" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" -dependencies = [ - "itertools", - "unicode-segmentation", - "unicode-width", -] +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] -name = "version_check" -version = "0.9.5" +name = "users" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", + "log", +] [[package]] name = "wasi" @@ -1269,20 +753,19 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" dependencies = [ "cfg-if", - "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" dependencies = [ "bumpalo", "log", @@ -1295,9 +778,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1305,9 +788,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", @@ -1318,105 +801,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" - -[[package]] -name = "wayland-backend" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993" -dependencies = [ - "cc", - "downcast-rs", - "rustix 0.38.34", - "scoped-tls", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-client" -version = "0.31.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943" -dependencies = [ - "bitflags 2.6.0", - "rustix 0.38.34", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-csd-frame" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" -dependencies = [ - "bitflags 2.6.0", - "cursor-icon", - "wayland-backend", -] - -[[package]] -name = "wayland-cursor" -version = "0.31.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef9489a8df197ebf3a8ce8a7a7f0a2320035c3743f3c1bd0bdbccf07ce64f95" -dependencies = [ - "rustix 0.38.34", - "wayland-client", - "xcursor", -] - -[[package]] -name = "wayland-protocols" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62989625a776e827cc0f15d41444a3cea5205b963c3a25be48ae1b52d6b4daaa" -dependencies = [ - "bitflags 2.6.0", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-wlr" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953" -dependencies = [ - "bitflags 2.6.0", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.31.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6" -dependencies = [ - "proc-macro2", - "quick-xml", - "quote", -] - -[[package]] -name = "wayland-sys" -version = "0.31.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148" -dependencies = [ - "dlib", - "log", - "once_cell", - "pkg-config", -] +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "winapi" @@ -1442,11 +829,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.52.0" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -1482,24 +869,6 @@ dependencies = [ "windows-targets 0.48.5", ] -[[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-targets" version = "0.42.2" @@ -1530,22 +899,6 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] -[[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", - "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_aarch64_gnullvm" version = "0.42.2" @@ -1558,12 +911,6 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -1576,12 +923,6 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -1594,18 +935,6 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[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_msvc" version = "0.42.2" @@ -1618,12 +947,6 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -1636,12 +959,6 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -1654,12 +971,6 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -1673,73 +984,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" +name = "xdg" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "x11-clipboard" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98785a09322d7446e28a13203d2cae1059a0dd3dfb32cb06d0a225f023d8286" -dependencies = [ - "libc", - "x11rb", -] - -[[package]] -name = "x11rb" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" -dependencies = [ - "gethostname", - "rustix 0.38.34", - "x11rb-protocol", -] - -[[package]] -name = "x11rb-protocol" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" - -[[package]] -name = "xcursor" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" - -[[package]] -name = "xkeysym" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" - -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" diff --git a/Cargo.toml b/Cargo.toml index bed9327..3e5ff60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,12 @@ [package] name = "fwd" -version = "0.9.2" -authors = ["John Doty "] +version = "0.7.0" edition = "2021" license = "MIT" -description = "Automatically forward ports to a remote server" -readme = "README.md" -documentation = "https://github.com/DeCarabas/fwd" +description = "Automatically forward ports to a remote server over ssh" homepage = "https://github.com/DeCarabas/fwd" repository = "https://github.com/DeCarabas/fwd" +readme = "README.md" [[bin]] name = "fwd-browse" @@ -18,47 +16,24 @@ bench = false [dependencies] anyhow = "1.0" bytes = "1" -copypasta = "0.10.1" -crossterm = { version = "0.28.1", features = ["event-stream"] } -directories-next = "2" -env_logger = { version = "0.11.5", default-features = false } +crossterm = { version = "0.25", features = ["event-stream"] } +home = "0.5.4" indoc = "1" log = { version = "0.4", features = ["std"] } open = "3" -rand = "0.8.5" -ratatui = "0.28.0" thiserror = "1.0" -tokio = { version = "1", features = ["io-std", "io-util", "macros", "net", "process", "rt", "rt-multi-thread", "fs"] } +tokio = { version = "1", features = ["io-std", "io-util", "macros", "net", "process", "rt", "rt-multi-thread"] } tokio-stream = "0.1" toml = "0.5" - +tui = "0.19" +xdg = "2" [dev-dependencies] assert_matches = "1" -pretty_assertions = "1" -tempfile = "3" +tempdir = "0.3" [target.'cfg(target_os="linux")'.dependencies] procfs = "0.14.1" [target.'cfg(target_family="unix")'.dependencies] -libc = "0.2.155" - -[package.metadata.deb] -section = "net" -depends = [] # No auto deps? -assets = [ - ["target/release/fwd", "usr/bin/", "755"], - ["target/release/fwd-browse", "usr/bin/", "755"], - ["LICENSE", "usr/share/doc/fwd/", "644"], - ["README.md", "usr/share/doc/fwd/README", "644"], - # The man page is automatically generated by fwd's build process. See - # release.py for details. - ["target/release/fwd.1", "usr/share/man/man1/fwd.1", "644"], -] -extended-description = """\ -fwd enumerates the listening ports the remote server and automatically listens -for connections on the same ports on the local machine. When fwd receives a -connection on the local machine, it automatically forwards that connection to -the remote machine. -""" +users = "0.11" diff --git a/LICENSE b/LICENSE index 630f272..7857f35 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2024 John Doty +Copyright 2022 John Doty Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 94a4337..1e3c07d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A port-forwarding utility. Here's how it works: 1. Get the latest [release](https://github.com/DeCarabas/fwd/releases) of `fwd` -2. You install `fwd` on the server somewhere in your `$PATH` (like `/usr/bin/`, or `.local/bin`) +2. You install `fwd` on the server somewhere in your `$PATH` (like `/usr/bin/`) 3. You install `fwd` on the client (like your laptop) 4. You run `fwd` on the client to connect to the server, like so: @@ -13,9 +13,6 @@ doty@my.laptop$ fwd some.server ``` `fwd` will connect to `some.server` via ssh, and then show you a screen listing all of the ports that the server is listening on locally. - -A terminal displaying a list of ports and descriptions. Some are dimmed and one is highlighted. - Use the up and down arrow keys (or `j`/`k`) to select the port you're interested in and press `e` to toggle forwarding of that port. Now, connections to that port locally will be forwarded to the remote server. @@ -24,3 +21,9 @@ If the port is something that might be interesting to a web browser, you can pre If something is going wrong, pressing `l` will toggle logs that might explain it. Press `q` to quit. + +## Future Improvements: + +- Clipboard integration: send something from the remote end of the pipe to the host's clipboard. (Sometimes you *really* want to copy some big buffer from the remote side and your terminal just can't make that work.) + +- Client heartbeats: I frequently wind up in a situation where the pipe is stalled: not broken but nothing is getting through. (This happens with my coder.com pipes all the time.) diff --git a/build.rs b/build.rs deleted file mode 100644 index b0c87c2..0000000 --- a/build.rs +++ /dev/null @@ -1,148 +0,0 @@ -use std::io::Write; -use std::path::{absolute, Path, PathBuf}; - -/// Fetch the contents of the given file, and also tell cargo that we looked -/// in there. -fn file_contents>(path: P) -> String { - let path = - absolute(path.as_ref()).expect("Unable to make the path absolute"); - - let mut stdout = std::io::stdout(); - stdout - .write_all(b"cargo::rerun-if-changed=") - .expect("Unable to write stdout"); - stdout - .write_all(path.as_os_str().as_encoded_bytes()) - .expect("Unable to write path to stdout"); - stdout - .write_all(b"\n") - .expect("Unable to write newline to stdout"); - - std::fs::read_to_string(path).expect("Unable to read file") -} - -fn git_rel>(path: P) -> PathBuf { - let output = std::process::Command::new("git") - .arg("rev-parse") - .arg("--show-toplevel") - .output() - .expect("Error launching git rev-parse"); - if !output.status.success() { - let stderr = std::str::from_utf8(&output.stderr) - .expect("git failed and stderr was not utf8"); - eprintln!("`git rev-parse --show-toplevel` failed, stderr: {stderr}"); - panic!("`git rev-parse --show-toplevel` failed"); - } - - let mut root = PathBuf::from( - std::str::from_utf8(&output.stdout) - .expect("Output was not utf-8") - .trim(), - ); - root.push(path); - root -} - -/// Emit the current git commit. -fn emit_git_commit() { - // Fetch the current commit from the head. We do it this way instead of - // asking `git rev-parse` to do it for us because we want to reliably - // tell cargo which files it should monitor for changes. - let head = file_contents(git_rel(".git/HEAD")); - let rev = if let Some(r) = head.strip_prefix("ref: ") { - let mut ref_path = git_rel(".git/"); - ref_path.push(r.trim()); - file_contents(ref_path) - } else { - head - }; - - // But *now* we ask git rev-parse to make this into a short hash (a) to - // make sure we got it right and (b) because git knows how to quickly - // determine how much of a commit is required to be unique. We don't need - // to tell cargo anything here, no file that git consults will be - // mutable. - let output = std::process::Command::new("git") - .arg("rev-parse") - .arg("--short") - .arg(rev.trim()) - .output() - .expect("could not spawn `git` to get the hash"); - if !output.status.success() { - let stderr = std::str::from_utf8(&output.stderr) - .expect("git failed and stderr was not utf8"); - eprintln!("`git rev-parse --short HEAD` failed, stderr: {stderr}"); - panic!("`git rev-parse --short HEAD` failed"); - } - let rev = std::str::from_utf8(&output.stdout) - .expect("git did not output utf8") - .trim(); - - println!("cargo::rustc-env=REPO_REV={rev}"); -} - -fn emit_git_dirty() { - // Here is the way to see if anything is up with the repository: run `git - // status --porcelain=v1`. The status output in the v1 porcelain format - // has one line for every file that's modified in some way: staged, - // changed but unstaged, untracked, you name it. Files in the working - // tree that are up to date with the repository are not emitted. This is - // exactly what we want. - // - // (Yes, I want to track untracked files, because they can mess with the - // build too. The only good build is a clean build!) - let output = std::process::Command::new("git") - .arg("status") - .arg("-z") - .arg("--porcelain=v1") - .output() - .expect("could not spawn `git` to get repository status"); - if !output.status.success() { - let stderr = std::str::from_utf8(&output.stderr) - .expect("git failed and stderr was not utf8"); - eprintln!("`git status` failed, stderr: {stderr}"); - panic!("`git status` failed"); - } - let output = - std::str::from_utf8(&output.stdout).expect("git did not output utf8"); - - // Emit the repository status. - let dirty = if output.trim().is_empty() { - "" - } else { - " *dirty*" - }; - println!("cargo::rustc-env=REPO_DIRTY={dirty}"); - - // NOW: The output here has to do with *all* of the files in the git - // respository. (Because if nothing was modified, but then *becomes* - // modified, we need to rerun the script to notice the dirty bit.) - // `git-ls-files` is the way to do that. - let output = std::process::Command::new("git") - .arg("ls-files") - .arg("-z") - .arg("--cached") - .arg("--deleted") - .arg("--modified") - .arg("--others") - .arg("--exclude-standard") - .output() - .expect("could not spawn `git` to get repository status"); - if !output.status.success() { - let stderr = std::str::from_utf8(&output.stderr) - .expect("git failed and stderr was not utf-8"); - eprintln!("`git ls-files` failed, stderr: {stderr}"); - panic!("`git ls-files` failed"); - } - let output = - std::str::from_utf8(&output.stdout).expect("git did not output utf8"); - - for fname in output.split_terminator("\0") { - println!("cargo::rerun-if-changed={fname}"); - } -} - -fn main() { - emit_git_commit(); - emit_git_dirty(); -} diff --git a/doc/fwd.man.md b/doc/fwd.man.md deleted file mode 100644 index 5d5f819..0000000 --- a/doc/fwd.man.md +++ /dev/null @@ -1,156 +0,0 @@ -% fwd(1) -% John Doty -% August 2024 - -# NAME - -fwd - Automatically forward connections to remote machines - -# SYNOPSIS - -**fwd** [OPTIONS] SERVER - -**fwd** [OPTIONS] browse URL - -**fwd** [OPTIONS] clip FILE - -**fwd-browse** URL - -# DESCRIPTION - -**fwd** enumerates the listening ports the remote server and automatically listens for connections on the same ports on the local machine. -When **fwd** receives a connection on the local machine, it automatically forwards that connection to the remote machine. - -**-s**, **-\-sudo** -: Run the server side of fwd with `sudo`. -: This allows the client to forward ports that are open by processes being run under other accounts (e.g., docker containers being run as root), but requires sudo access on the server and *might* end up forwarding ports that you do not want forwarded (e.g., port 22 for sshd, or port 53 for systemd.) - -**-\-log-filter** **FILTER** -: Set remote server's log level. Default is `warn`. -: Supports all of Rust's env_logger filter syntax, e.g. `--log-filter=fwd::trace`. - -**-\-version** -: Print the version of fwd and exit. - -# INTERACTIVE COMMANDS - -Once **fwd** is connected, it displays an interactive list of the ports available on the remote server. - -- Ports that **fwd** is listening on are displayed in the default terminal color. -- Ports that **fwd** is aware of but which are disabled are displayed in dark gray. -- Ports that **fwd** has tried to listen on but which have failed are displayed in red. - Details on the error may be found in the log window. - Disabling and re-enabling the port will cause **fwd** to try again. - -The following commands are available while **fwd** is connected: - -**Esc, q, Ctrl-C** -: Exit **fwd**. - -**?, h** -: Display the help window. - -**Up, k** -: Select the previous port in the list. - -**Down, j** -: Select the next port in the list. - -**Enter** -: Attempt to browse to localhost on the specified port with the default browser. - -**a** -: Hide or show anonymous ports. -: (See "identifying ports" below for more information on anonymous ports.) - -**e** -: Enable or disable the selected port. - -**l** -: Show or hide the log window. - - -# IDENTIFYING PORTS - -**fwd** enumerates all of the ports that the remote server is listening on, and attempts to identify the process that is listening on each port. -It can identify ports in the following ways: - -*docker* -: **fwd** will attempt to find and connect to a docker engine on the remote machine. -: If successful, it will list all of the forwarded ports, and identify each port as belonging to that docker container. - -*procfs* -: On Linux, the listening ports are found by reading procfs and mapping them back to process command lines. -: **fwd** can only identify processes that the user it is connected as has permissions to read on the remote machine. - -(Earlier methods take precedence over later methods.) - -If **fwd** cannot identify the process that is listening on a given port, then the port is *anonymous*. -Anonymous ports are not enabled by default, but can be enabled manually, either with the UI or by configuration. - -# OPENING BROWSERS -**fwd** can be used to open URLs in the default browser on the local machine. -Run **fwd browse URL** on the remote server to open the `URL` in the default browser on the local machine. - -This only works if **fwd** is connected, and if the user running **fwd browse** is the same as the user that connected the **fwd** session. - -The **fwd-browse** program acts as a wrapper around **fwd browse**, to be used with configurations that can't handle a browser being a program with an argument. - -# CLIPBOARD -**fwd** can be used from the remote machine to place text on the clipboard of the local machine. -Run **fwd clip FILE** to copy the contents of the named file to the clipboard. -If **FILE** is **-**, this reads text from stdin instead. - -# CONFIGURATION -**fwd** can be configured with a configuration file. - -- On Windows, the config file will be in your roaming AppData folder. - (e.g., *c:\\Users\\Winifred\\AppData\\Roaming\\fwd\\config\\config.toml*) -- On MacOS, the config file will be in *$HOME/Library/Application Support/fwd/config.toml*. - (e.g., /Users/Margarie/Library/Application Support/fwd/config.toml) -- On XDG-ish systems (like Linux), the config file is in *~/.config/fwd/config.toml*. - (e.g., */home/lynette/.config/fwd/config.toml*) - -The following is an example of a *config.toml* file: - -``` -auto=true # should `fwd` should enable identified ports (default true) - -[servers.foo] # Server-specific settings for foo -auto=true # defaults to the global setting -ports=[1080, 1082] # ports that are always present - -[servers.bar.ports] # `ports` can also be a table with port numbers as keys -1080=true # the values can be booleans (for enabled)... -1081="My program" # or strings (for descriptions). - -[servers.bar.ports.1082] # port values can also be tables -enabled=true -description="A humble python" -``` - -Ports that are specified in the configuration file will always be present in the list of ports for a given server, even if no process is listening on that port. - -# TROUBLESHOOTING - -Connections are made via the **ssh** command. -Your **ssh** must: - -- Be on your path, so that **fwd** can find it to invoke it -- Be able to authenticate you to the remote server. - (Interactive authentication is fine.) -- Understand the **-D** command line option, to operate as a SOCKS5 server -- Be able to start the **fwd** command on the remote server - -A typical ssh invocation from **fwd** looks like: - -```bash -ssh -T -D XXXX me@server FWD_LOG=warning FWD_SEND_ANONYMOUS=1 fwd --server -``` - -**fwd** only enumerates ports that are listening on loopback addresses (e.g., 127.0.0.1) or on all addresses (e.g., 0.0.0.0). -If it cannot find a particular port, check to make sure that the process listening on that port is accessible via localhost. - -# SEE ALSO - -ssh(1) diff --git a/doc/screenshot-01.png b/doc/screenshot-01.png deleted file mode 100644 index 4f5315f..0000000 Binary files a/doc/screenshot-01.png and /dev/null differ diff --git a/fuzz/.gitignore b/fuzz/.gitignore deleted file mode 100644 index 1a45eee..0000000 --- a/fuzz/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -target -corpus -artifacts -coverage diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock deleted file mode 100644 index 855731d..0000000 --- a/fuzz/Cargo.lock +++ /dev/null @@ -1,1735 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[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.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" - -[[package]] -name = "arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" -dependencies = [ - "derive_arbitrary", -] - -[[package]] -name = "autocfg" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" - -[[package]] -name = "backtrace" -version = "0.3.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" - -[[package]] -name = "calloop" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" -dependencies = [ - "bitflags 2.6.0", - "log", - "polling", - "rustix 0.38.34", - "slab", - "thiserror", -] - -[[package]] -name = "calloop-wayland-source" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" -dependencies = [ - "calloop", - "rustix 0.38.34", - "wayland-backend", - "wayland-client", -] - -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - -[[package]] -name = "castaway" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" -dependencies = [ - "rustversion", -] - -[[package]] -name = "cc" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" -dependencies = [ - "jobserver", - "libc", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", - "windows-targets 0.52.6", -] - -[[package]] -name = "clipboard-win" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" -dependencies = [ - "lazy-bytes-cast", - "winapi", -] - -[[package]] -name = "compact_str" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "rustversion", - "ryu", - "static_assertions", -] - -[[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 = "copypasta" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb85422867ca93da58b7f95fb5c0c10f6183ed6e1ef8841568968a896d3a858" -dependencies = [ - "clipboard-win", - "objc", - "objc-foundation", - "objc_id", - "smithay-clipboard", - "x11-clipboard", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - -[[package]] -name = "crossterm" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" -dependencies = [ - "bitflags 2.6.0", - "crossterm_winapi", - "futures-core", - "mio", - "parking_lot", - "rustix 0.38.34", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi", -] - -[[package]] -name = "cursor-icon" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" - -[[package]] -name = "derive_arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - -[[package]] -name = "env_filter" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" -dependencies = [ - "log", -] - -[[package]] -name = "env_logger" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" -dependencies = [ - "env_filter", - "log", -] - -[[package]] -name = "errno" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "flate2" -version = "1.0.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "fwd" -version = "0.9.2" -dependencies = [ - "anyhow", - "bytes", - "copypasta", - "crossterm", - "env_logger", - "home", - "indoc", - "libc", - "log", - "open", - "procfs", - "rand", - "ratatui", - "thiserror", - "tokio", - "tokio-stream", - "toml", - "xdg", -] - -[[package]] -name = "fwd-fuzz" -version = "0.0.0" -dependencies = [ - "arbitrary", - "fwd", - "libfuzzer-sys", - "serde_json", -] - -[[package]] -name = "gethostname" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" -dependencies = [ - "libc", - "windows-targets 0.48.5", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gimli" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[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.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[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 = "indoc" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" - -[[package]] -name = "instability" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "jobserver" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lazy-bytes-cast" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.155" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" - -[[package]] -name = "libfuzzer-sys" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" -dependencies = [ - "arbitrary", - "cc", - "once_cell", -] - -[[package]] -name = "libloading" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" -dependencies = [ - "cfg-if", - "windows-targets 0.52.6", -] - -[[package]] -name = "linux-raw-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" - -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "lru" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" -dependencies = [ - "hashbrown", -] - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "memmap2" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" -dependencies = [ - "libc", -] - -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "log", - "wasi", - "windows-sys 0.52.0", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - -[[package]] -name = "object" -version = "0.36.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "open" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8" -dependencies = [ - "pathdiff", - "windows-sys 0.42.0", -] - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - -[[package]] -name = "pin-project-lite" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" - -[[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - -[[package]] -name = "polling" -version = "3.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi 0.4.0", - "pin-project-lite", - "rustix 0.38.34", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "proc-macro2" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "procfs" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de8dacb0873f77e6aefc6d71e044761fcc68060290f5b1089fcdf84626bb69" -dependencies = [ - "bitflags 1.3.2", - "byteorder", - "chrono", - "flate2", - "hex", - "lazy_static", - "rustix 0.36.17", -] - -[[package]] -name = "quick-xml" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f24d770aeca0eacb81ac29dfbc55ebcc09312fdd1f8bbecdc7e4a84e000e3b4" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[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", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "ratatui" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ba6a365afbe5615999275bea2446b970b10a41102500e27ce7678d50d978303" -dependencies = [ - "bitflags 2.6.0", - "cassowary", - "compact_str", - "crossterm", - "instability", - "itertools", - "lru", - "paste", - "strum", - "strum_macros", - "unicode-segmentation", - "unicode-truncate", - "unicode-width", -] - -[[package]] -name = "redox_syscall" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" -dependencies = [ - "bitflags 2.6.0", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustix" -version = "0.36.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305efbd14fde4139eb501df5f136994bb520b033fa9fbdce287507dc23b8c7ed" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.1.4", - "windows-sys 0.45.0", -] - -[[package]] -name = "rustix" -version = "0.38.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" -dependencies = [ - "bitflags 2.6.0", - "errno", - "libc", - "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustversion" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serde" -version = "1.0.207" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.207" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.124" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "signal-hook" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" -dependencies = [ - "libc", - "mio", - "signal-hook", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "smithay-client-toolkit" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" -dependencies = [ - "bitflags 2.6.0", - "calloop", - "calloop-wayland-source", - "cursor-icon", - "libc", - "log", - "memmap2", - "rustix 0.38.34", - "thiserror", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-wlr", - "wayland-scanner", - "xkeysym", -] - -[[package]] -name = "smithay-clipboard" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" -dependencies = [ - "libc", - "smithay-client-toolkit", - "wayland-backend", -] - -[[package]] -name = "socket2" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - -[[package]] -name = "syn" -version = "2.0.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "1.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio" -version = "1.39.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-stream" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-segmentation" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" - -[[package]] -name = "unicode-truncate" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" -dependencies = [ - "itertools", - "unicode-segmentation", - "unicode-width", -] - -[[package]] -name = "unicode-width" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" -dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" - -[[package]] -name = "wayland-backend" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993" -dependencies = [ - "cc", - "downcast-rs", - "rustix 0.38.34", - "scoped-tls", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-client" -version = "0.31.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943" -dependencies = [ - "bitflags 2.6.0", - "rustix 0.38.34", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-csd-frame" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" -dependencies = [ - "bitflags 2.6.0", - "cursor-icon", - "wayland-backend", -] - -[[package]] -name = "wayland-cursor" -version = "0.31.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef9489a8df197ebf3a8ce8a7a7f0a2320035c3743f3c1bd0bdbccf07ce64f95" -dependencies = [ - "rustix 0.38.34", - "wayland-client", - "xcursor", -] - -[[package]] -name = "wayland-protocols" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62989625a776e827cc0f15d41444a3cea5205b963c3a25be48ae1b52d6b4daaa" -dependencies = [ - "bitflags 2.6.0", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-wlr" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953" -dependencies = [ - "bitflags 2.6.0", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.31.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6" -dependencies = [ - "proc-macro2", - "quick-xml", - "quote", -] - -[[package]] -name = "wayland-sys" -version = "0.31.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148" -dependencies = [ - "dlib", - "log", - "once_cell", - "pkg-config", -] - -[[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-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -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-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.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -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-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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -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", - "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_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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[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_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[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_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[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_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "x11-clipboard" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98785a09322d7446e28a13203d2cae1059a0dd3dfb32cb06d0a225f023d8286" -dependencies = [ - "libc", - "x11rb", -] - -[[package]] -name = "x11rb" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" -dependencies = [ - "gethostname", - "rustix 0.38.34", - "x11rb-protocol", -] - -[[package]] -name = "x11rb-protocol" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" - -[[package]] -name = "xcursor" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" - -[[package]] -name = "xdg" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" - -[[package]] -name = "xkeysym" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml deleted file mode 100644 index bc252d7..0000000 --- a/fuzz/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "fwd-fuzz" -version = "0.0.0" -publish = false -edition = "2021" - -[package.metadata] -cargo-fuzz = true - -[dependencies] -arbitrary = { version = "1.3.2", features = ["derive"] } -libfuzzer-sys = "0.4" -serde_json = "1.0.124" - -[dependencies.fwd] -path = ".." - -[[bin]] -name = "json_raw_input" -path = "fuzz_targets/json_raw_input.rs" -test = false -doc = false -bench = false - -[[bin]] -name = "json_only_valid_serde" -path = "fuzz_targets/json_only_valid_serde.rs" -test = false -doc = false -bench = false diff --git a/fuzz/fuzz_targets/json_only_valid_serde.rs b/fuzz/fuzz_targets/json_only_valid_serde.rs deleted file mode 100644 index 567642f..0000000 --- a/fuzz/fuzz_targets/json_only_valid_serde.rs +++ /dev/null @@ -1,77 +0,0 @@ -#![no_main] - -use arbitrary::{Arbitrary, Error, Unstructured}; -use libfuzzer_sys::fuzz_target; -use std::collections::HashMap; - -extern crate fwd; -use fwd::server::refresh::docker::JsonValue; - -/// InputNumber is a JSON number, i.e., a finite 64-bit floating point value -/// that is not NaN. We need to define our own little wrapper here so that we -/// can convince Arbitrary to only make finite f64s. -/// -/// Ideally we would actually wrap serde_json::Number but there are rules -/// about mixing 3rd party traits with 3rd party types. -#[derive(Debug, PartialEq)] -struct InputNumber(f64); - -impl<'a> Arbitrary<'a> for InputNumber { - fn arbitrary(u: &mut Unstructured<'a>) -> Result { - let value = f64::arbitrary(u)?; - if value.is_finite() { - Ok(InputNumber(value)) - } else { - Err(Error::IncorrectFormat) // REJECT - } - } - - #[inline] - fn size_hint(depth: usize) -> (usize, Option) { - f64::size_hint(depth) - } -} - -/// TestInput is basically serde_json::Value, except (a) it has a HashMap and -/// not serde_json's special `Map` structure, and (b) it has `InputNumber` -/// instead of `json_serde::Number` for reasons described above. -#[derive(Debug, PartialEq, Arbitrary)] -enum TestInput { - Null, - Bool(bool), - Number(InputNumber), - String(String), - Object(HashMap), - Array(Vec), -} - -fn convert(value: &TestInput) -> serde_json::Value { - match value { - TestInput::Null => serde_json::Value::Null, - TestInput::Bool(b) => serde_json::Value::Bool(*b), - TestInput::Number(n) => serde_json::Value::Number( - serde_json::Number::from_f64(n.0).expect("Unable to make an f64"), - ), - TestInput::String(s) => serde_json::Value::String(s.clone()), - TestInput::Object(o) => { - let mut out = serde_json::map::Map::new(); - for (k, v) in o.into_iter() { - out.insert(k.clone(), convert(v)); - } - serde_json::Value::Object(out) - } - TestInput::Array(v) => { - serde_json::Value::Array(v.into_iter().map(convert).collect()) - } - } -} - -fuzz_target!(|data: TestInput| { - // Convert the arbitrary TestInput into an arbitrary serde_json::Value, - // then use serde_json to write out arbitrary JSON. - let converted = convert(&data).to_string(); - - // Parse the JSON that serde_json produced. This fuzz test should ensure - // that we can parse anything that serde_json can produce. - let _ = JsonValue::parse(converted.as_bytes()); -}); diff --git a/fuzz/fuzz_targets/json_raw_input.rs b/fuzz/fuzz_targets/json_raw_input.rs deleted file mode 100644 index 3178e8b..0000000 --- a/fuzz/fuzz_targets/json_raw_input.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![no_main] - -use libfuzzer_sys::fuzz_target; - -extern crate fwd; -use fwd::server::refresh::docker::JsonValue; - -fuzz_target!(|data: &[u8]| { - let _ = JsonValue::parse(data); -}); diff --git a/release.py b/release.py deleted file mode 100644 index 0fa912b..0000000 --- a/release.py +++ /dev/null @@ -1,161 +0,0 @@ -"""A script to automate building and uploading a release archive. - -This is in python instead of bash because I abhor bash. Even though it's a -little nicer for running commands, it's worse at everything else. -""" - -import dataclasses -import enum -import os -import os.path -import pathlib -import shutil -import subprocess - -RELEASE_TAG = os.getenv("RELEASE_TAG") - -BUILD = os.getenv("BUILD") -if BUILD is None: - raise Exception("you *must* set the BUILD environment variable") - - -class Archive(enum.Enum): - TARBALL = 1 - ZIP = 2 - DEB = 3 - - -@dataclasses.dataclass -class BuildSettings: - target: str # The rust target to build for - test: bool = True # Whether or not to run tests - man_page: bool = True # Whether or not to generate a man page - strip: bool = True # Whether or not to strip binaries - archive: Archive = Archive.TARBALL # Archive type - ext: str = "" # The file extension of the binary - - -print(f"doing release: {BUILD}") -build = { - "linux": BuildSettings( - target="x86_64-unknown-linux-musl", - ), - "debian": BuildSettings( - target="x86_64-unknown-linux-musl", - test=False, - archive=Archive.DEB, - ), - "macos": BuildSettings( - target="x86_64-apple-darwin", - ), - "arm-macos": BuildSettings( - target="aarch64-apple-darwin", - ), - "windows": BuildSettings( - target="x86_64-pc-windows-msvc", - strip=False, - man_page=False, - archive=Archive.ZIP, - ext=".exe", - ), -}[BUILD] - -print(f"settings: {build}") - - -target_dir = pathlib.Path("target") / build.target / "release" -bins = [(target_dir / bin).with_suffix(build.ext) for bin in ["fwd", "fwd-browse"]] - - -def build_and_test(staging: pathlib.Path): - # Tools - subprocess.run( - ["rustup", "target", "add", build.target], - check=True, - ) - - # Test...? - if build.test: - subprocess.run( - ["cargo", "test", "--verbose", "--release", "--target", build.target], - check=True, - ) - - # Build - subprocess.run( - ["cargo", "build", "--verbose", "--release", "--target", build.target], - check=True, - ) - - # Strip - if build.strip: - for bin in bins: - subprocess.run(["strip", bin], check=True) - - # Copy - for bin in bins: - shutil.copyfile(bin, os.path.join(staging, os.path.basename(bin))) - - -def build_docs(staging: pathlib.Path): - shutil.copyfile("README.md", staging / "README.md") - if build.man_page: - print("Creating man page...") - proc = subprocess.run( - ["pandoc", "-s", "-tman", os.path.join("doc", "fwd.man.md")], - check=True, - capture_output=True, - encoding="utf8", - ) - contents = proc.stdout - with open(staging / "fwd.1", "w", encoding="utf-8") as f: - f.write(contents) - - -def build_archive(staging: pathlib.Path) -> pathlib.Path: - print("Creating archive...") - if build.archive == Archive.ZIP: - archive = pathlib.Path(f"{staging}.zip") - subprocess.run(["7z", "a", archive, f"{staging}"], check=True) - - elif build.archive == Archive.DEB: - subprocess.run(["cargo", "install", "cargo-deb"], check=True) - shutil.copyfile(staging / "fwd.1", target_dir / "fwd.1") - subprocess.run(["cargo", "deb", "--target", build.target], check=True) - - # Knowing the deb path means knowing the target version but I don't - # actually have the version here. (Or, like, I have the release tag - # but not in testing.) So just find the hopefully singular .deb that - # we made. - deb_path = pathlib.Path("target") / build.target / "debian" - archives = list(deb_path.glob("*.deb")) - assert len(archives) == 1 - archive = archives[0] - - else: - assert build.archive == Archive.TARBALL - archive = pathlib.Path(f"{staging}.tar.gz") - subprocess.run(["tar", "czf", archive, f"{staging}"], check=True) - - return archive - - -staging = pathlib.Path(f"fwd-{build.target}") -os.makedirs(staging, exist_ok=True) - -build_and_test(staging) -build_docs(staging) -archive = build_archive(staging) - -shutil.rmtree(staging) - -assert archive.exists() -if RELEASE_TAG is None: - print(f"Not releasing {archive} to github, RELEASE_TAG is none.") -else: - print(f"Uploading {archive} to github release {RELEASE_TAG}...") - subprocess.run( - ["gh", "release", "upload", RELEASE_TAG, archive, "--clobber"], - check=True, - ) - os.unlink(archive) diff --git a/resources/json/README.md b/resources/json/README.md deleted file mode 100644 index 533092f..0000000 --- a/resources/json/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Test JSON - -This directory contains test JSON files from https://github.com/nst/JSONTestSuite as of commit 984defc. - -It only has the positive and questionable JSON inputs, as our JSON parser is extremely forgiving, by design. - -## Filtered tests - -Some of the questionable tests have been removed: - -- `i_structure_UTF-8_BOM_empty_object.json` removed because we don't handle BOMs. -- `i_string_utf16LE_no_BOM.json` removed because we don't speak UTF16. -- `i_string_utf16BE_no_BOM.json` removed because we don't speak UTF16. -- `i_string_UTF-16LE_with_BOM.json` removed because we don't speak UTF16. diff --git a/resources/json/i_number_double_huge_neg_exp.json b/resources/json/i_number_double_huge_neg_exp.json deleted file mode 100644 index ae4c7b7..0000000 --- a/resources/json/i_number_double_huge_neg_exp.json +++ /dev/null @@ -1 +0,0 @@ -[123.456e-789] \ No newline at end of file diff --git a/resources/json/i_number_huge_exp.json b/resources/json/i_number_huge_exp.json deleted file mode 100644 index 9b5efa2..0000000 --- a/resources/json/i_number_huge_exp.json +++ /dev/null @@ -1 +0,0 @@ -[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006] \ No newline at end of file diff --git a/resources/json/i_number_neg_int_huge_exp.json b/resources/json/i_number_neg_int_huge_exp.json deleted file mode 100755 index 3abd58a..0000000 --- a/resources/json/i_number_neg_int_huge_exp.json +++ /dev/null @@ -1 +0,0 @@ -[-1e+9999] \ No newline at end of file diff --git a/resources/json/i_number_pos_double_huge_exp.json b/resources/json/i_number_pos_double_huge_exp.json deleted file mode 100755 index e10a7eb..0000000 --- a/resources/json/i_number_pos_double_huge_exp.json +++ /dev/null @@ -1 +0,0 @@ -[1.5e+9999] \ No newline at end of file diff --git a/resources/json/i_number_real_neg_overflow.json b/resources/json/i_number_real_neg_overflow.json deleted file mode 100644 index 3d628a9..0000000 --- a/resources/json/i_number_real_neg_overflow.json +++ /dev/null @@ -1 +0,0 @@ -[-123123e100000] \ No newline at end of file diff --git a/resources/json/i_number_real_pos_overflow.json b/resources/json/i_number_real_pos_overflow.json deleted file mode 100644 index 54d7d3d..0000000 --- a/resources/json/i_number_real_pos_overflow.json +++ /dev/null @@ -1 +0,0 @@ -[123123e100000] \ No newline at end of file diff --git a/resources/json/i_number_real_underflow.json b/resources/json/i_number_real_underflow.json deleted file mode 100644 index c5236eb..0000000 --- a/resources/json/i_number_real_underflow.json +++ /dev/null @@ -1 +0,0 @@ -[123e-10000000] \ No newline at end of file diff --git a/resources/json/i_number_too_big_neg_int.json b/resources/json/i_number_too_big_neg_int.json deleted file mode 100644 index dfa3846..0000000 --- a/resources/json/i_number_too_big_neg_int.json +++ /dev/null @@ -1 +0,0 @@ -[-123123123123123123123123123123] \ No newline at end of file diff --git a/resources/json/i_number_too_big_pos_int.json b/resources/json/i_number_too_big_pos_int.json deleted file mode 100644 index 338a8c3..0000000 --- a/resources/json/i_number_too_big_pos_int.json +++ /dev/null @@ -1 +0,0 @@ -[100000000000000000000] \ No newline at end of file diff --git a/resources/json/i_number_very_big_negative_int.json b/resources/json/i_number_very_big_negative_int.json deleted file mode 100755 index e2d9738..0000000 --- a/resources/json/i_number_very_big_negative_int.json +++ /dev/null @@ -1 +0,0 @@ -[-237462374673276894279832749832423479823246327846] \ No newline at end of file diff --git a/resources/json/i_object_key_lone_2nd_surrogate.json b/resources/json/i_object_key_lone_2nd_surrogate.json deleted file mode 100644 index 5be7eba..0000000 --- a/resources/json/i_object_key_lone_2nd_surrogate.json +++ /dev/null @@ -1 +0,0 @@ -{"\uDFAA":0} \ No newline at end of file diff --git a/resources/json/i_string_1st_surrogate_but_2nd_missing.json b/resources/json/i_string_1st_surrogate_but_2nd_missing.json deleted file mode 100644 index 3b9e37c..0000000 --- a/resources/json/i_string_1st_surrogate_but_2nd_missing.json +++ /dev/null @@ -1 +0,0 @@ -["\uDADA"] \ No newline at end of file diff --git a/resources/json/i_string_1st_valid_surrogate_2nd_invalid.json b/resources/json/i_string_1st_valid_surrogate_2nd_invalid.json deleted file mode 100644 index 4875928..0000000 --- a/resources/json/i_string_1st_valid_surrogate_2nd_invalid.json +++ /dev/null @@ -1 +0,0 @@ -["\uD888\u1234"] \ No newline at end of file diff --git a/resources/json/i_string_UTF-8_invalid_sequence.json b/resources/json/i_string_UTF-8_invalid_sequence.json deleted file mode 100755 index e2a968a..0000000 --- a/resources/json/i_string_UTF-8_invalid_sequence.json +++ /dev/null @@ -1 +0,0 @@ -["日шú"] \ No newline at end of file diff --git a/resources/json/i_string_UTF8_surrogate_U+D800.json b/resources/json/i_string_UTF8_surrogate_U+D800.json deleted file mode 100644 index 916bff9..0000000 --- a/resources/json/i_string_UTF8_surrogate_U+D800.json +++ /dev/null @@ -1 +0,0 @@ -["í €"] \ No newline at end of file diff --git a/resources/json/i_string_incomplete_surrogate_and_escape_valid.json b/resources/json/i_string_incomplete_surrogate_and_escape_valid.json deleted file mode 100755 index 3cb11d2..0000000 --- a/resources/json/i_string_incomplete_surrogate_and_escape_valid.json +++ /dev/null @@ -1 +0,0 @@ -["\uD800\n"] \ No newline at end of file diff --git a/resources/json/i_string_incomplete_surrogate_pair.json b/resources/json/i_string_incomplete_surrogate_pair.json deleted file mode 100755 index 38ec23b..0000000 --- a/resources/json/i_string_incomplete_surrogate_pair.json +++ /dev/null @@ -1 +0,0 @@ -["\uDd1ea"] \ No newline at end of file diff --git a/resources/json/i_string_incomplete_surrogates_escape_valid.json b/resources/json/i_string_incomplete_surrogates_escape_valid.json deleted file mode 100755 index c9cd6f6..0000000 --- a/resources/json/i_string_incomplete_surrogates_escape_valid.json +++ /dev/null @@ -1 +0,0 @@ -["\uD800\uD800\n"] \ No newline at end of file diff --git a/resources/json/i_string_invalid_lonely_surrogate.json b/resources/json/i_string_invalid_lonely_surrogate.json deleted file mode 100755 index 3abbd8d..0000000 --- a/resources/json/i_string_invalid_lonely_surrogate.json +++ /dev/null @@ -1 +0,0 @@ -["\ud800"] \ No newline at end of file diff --git a/resources/json/i_string_invalid_surrogate.json b/resources/json/i_string_invalid_surrogate.json deleted file mode 100755 index ffddc04..0000000 --- a/resources/json/i_string_invalid_surrogate.json +++ /dev/null @@ -1 +0,0 @@ -["\ud800abc"] \ No newline at end of file diff --git a/resources/json/i_string_invalid_utf-8.json b/resources/json/i_string_invalid_utf-8.json deleted file mode 100644 index 8e45a7e..0000000 --- a/resources/json/i_string_invalid_utf-8.json +++ /dev/null @@ -1 +0,0 @@ -["ÿ"] \ No newline at end of file diff --git a/resources/json/i_string_inverted_surrogates_U+1D11E.json b/resources/json/i_string_inverted_surrogates_U+1D11E.json deleted file mode 100755 index 0d5456c..0000000 --- a/resources/json/i_string_inverted_surrogates_U+1D11E.json +++ /dev/null @@ -1 +0,0 @@ -["\uDd1e\uD834"] \ No newline at end of file diff --git a/resources/json/i_string_iso_latin_1.json b/resources/json/i_string_iso_latin_1.json deleted file mode 100644 index 9389c98..0000000 --- a/resources/json/i_string_iso_latin_1.json +++ /dev/null @@ -1 +0,0 @@ -["é"] \ No newline at end of file diff --git a/resources/json/i_string_lone_second_surrogate.json b/resources/json/i_string_lone_second_surrogate.json deleted file mode 100644 index 1dbd397..0000000 --- a/resources/json/i_string_lone_second_surrogate.json +++ /dev/null @@ -1 +0,0 @@ -["\uDFAA"] \ No newline at end of file diff --git a/resources/json/i_string_lone_utf8_continuation_byte.json b/resources/json/i_string_lone_utf8_continuation_byte.json deleted file mode 100644 index 729337c..0000000 --- a/resources/json/i_string_lone_utf8_continuation_byte.json +++ /dev/null @@ -1 +0,0 @@ -[""] \ No newline at end of file diff --git a/resources/json/i_string_not_in_unicode_range.json b/resources/json/i_string_not_in_unicode_range.json deleted file mode 100644 index df90a29..0000000 --- a/resources/json/i_string_not_in_unicode_range.json +++ /dev/null @@ -1 +0,0 @@ -["ô¿¿¿"] \ No newline at end of file diff --git a/resources/json/i_string_overlong_sequence_2_bytes.json b/resources/json/i_string_overlong_sequence_2_bytes.json deleted file mode 100644 index c8cee5e..0000000 --- a/resources/json/i_string_overlong_sequence_2_bytes.json +++ /dev/null @@ -1 +0,0 @@ -["À¯"] \ No newline at end of file diff --git a/resources/json/i_string_overlong_sequence_6_bytes.json b/resources/json/i_string_overlong_sequence_6_bytes.json deleted file mode 100755 index 9a91da7..0000000 --- a/resources/json/i_string_overlong_sequence_6_bytes.json +++ /dev/null @@ -1 +0,0 @@ -["üƒ¿¿¿¿"] \ No newline at end of file diff --git a/resources/json/i_string_overlong_sequence_6_bytes_null.json b/resources/json/i_string_overlong_sequence_6_bytes_null.json deleted file mode 100755 index d24fffd..0000000 --- a/resources/json/i_string_overlong_sequence_6_bytes_null.json +++ /dev/null @@ -1 +0,0 @@ -["ü€€€€€"] \ No newline at end of file diff --git a/resources/json/i_string_truncated-utf-8.json b/resources/json/i_string_truncated-utf-8.json deleted file mode 100644 index 63c7777..0000000 --- a/resources/json/i_string_truncated-utf-8.json +++ /dev/null @@ -1 +0,0 @@ -["àÿ"] \ No newline at end of file diff --git a/resources/json/i_structure_500_nested_arrays.json b/resources/json/i_structure_500_nested_arrays.json deleted file mode 100644 index 7118405..0000000 --- a/resources/json/i_structure_500_nested_arrays.json +++ /dev/null @@ -1 +0,0 @@ -[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/resources/json/y_array_arraysWithSpaces.json b/resources/json/y_array_arraysWithSpaces.json deleted file mode 100755 index 5822907..0000000 --- a/resources/json/y_array_arraysWithSpaces.json +++ /dev/null @@ -1 +0,0 @@ -[[] ] \ No newline at end of file diff --git a/resources/json/y_array_empty-string.json b/resources/json/y_array_empty-string.json deleted file mode 100644 index 93b6be2..0000000 --- a/resources/json/y_array_empty-string.json +++ /dev/null @@ -1 +0,0 @@ -[""] \ No newline at end of file diff --git a/resources/json/y_array_empty.json b/resources/json/y_array_empty.json deleted file mode 100755 index 0637a08..0000000 --- a/resources/json/y_array_empty.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/resources/json/y_array_ending_with_newline.json b/resources/json/y_array_ending_with_newline.json deleted file mode 100755 index eac5f7b..0000000 --- a/resources/json/y_array_ending_with_newline.json +++ /dev/null @@ -1 +0,0 @@ -["a"] \ No newline at end of file diff --git a/resources/json/y_array_false.json b/resources/json/y_array_false.json deleted file mode 100644 index 67b2f07..0000000 --- a/resources/json/y_array_false.json +++ /dev/null @@ -1 +0,0 @@ -[false] \ No newline at end of file diff --git a/resources/json/y_array_heterogeneous.json b/resources/json/y_array_heterogeneous.json deleted file mode 100755 index d3c1e26..0000000 --- a/resources/json/y_array_heterogeneous.json +++ /dev/null @@ -1 +0,0 @@ -[null, 1, "1", {}] \ No newline at end of file diff --git a/resources/json/y_array_null.json b/resources/json/y_array_null.json deleted file mode 100644 index 500db4a..0000000 --- a/resources/json/y_array_null.json +++ /dev/null @@ -1 +0,0 @@ -[null] \ No newline at end of file diff --git a/resources/json/y_array_with_1_and_newline.json b/resources/json/y_array_with_1_and_newline.json deleted file mode 100644 index 9948255..0000000 --- a/resources/json/y_array_with_1_and_newline.json +++ /dev/null @@ -1,2 +0,0 @@ -[1 -] \ No newline at end of file diff --git a/resources/json/y_array_with_leading_space.json b/resources/json/y_array_with_leading_space.json deleted file mode 100755 index 18bfe64..0000000 --- a/resources/json/y_array_with_leading_space.json +++ /dev/null @@ -1 +0,0 @@ - [1] \ No newline at end of file diff --git a/resources/json/y_array_with_several_null.json b/resources/json/y_array_with_several_null.json deleted file mode 100755 index 99f6c5d..0000000 --- a/resources/json/y_array_with_several_null.json +++ /dev/null @@ -1 +0,0 @@ -[1,null,null,null,2] \ No newline at end of file diff --git a/resources/json/y_array_with_trailing_space.json b/resources/json/y_array_with_trailing_space.json deleted file mode 100755 index de9e7a9..0000000 --- a/resources/json/y_array_with_trailing_space.json +++ /dev/null @@ -1 +0,0 @@ -[2] \ No newline at end of file diff --git a/resources/json/y_number.json b/resources/json/y_number.json deleted file mode 100644 index e5f5cc3..0000000 --- a/resources/json/y_number.json +++ /dev/null @@ -1 +0,0 @@ -[123e65] \ No newline at end of file diff --git a/resources/json/y_number_0e+1.json b/resources/json/y_number_0e+1.json deleted file mode 100755 index d1d3967..0000000 --- a/resources/json/y_number_0e+1.json +++ /dev/null @@ -1 +0,0 @@ -[0e+1] \ No newline at end of file diff --git a/resources/json/y_number_0e1.json b/resources/json/y_number_0e1.json deleted file mode 100755 index 3283a79..0000000 --- a/resources/json/y_number_0e1.json +++ /dev/null @@ -1 +0,0 @@ -[0e1] \ No newline at end of file diff --git a/resources/json/y_number_after_space.json b/resources/json/y_number_after_space.json deleted file mode 100644 index 623570d..0000000 --- a/resources/json/y_number_after_space.json +++ /dev/null @@ -1 +0,0 @@ -[ 4] \ No newline at end of file diff --git a/resources/json/y_number_double_close_to_zero.json b/resources/json/y_number_double_close_to_zero.json deleted file mode 100755 index 96555ff..0000000 --- a/resources/json/y_number_double_close_to_zero.json +++ /dev/null @@ -1 +0,0 @@ -[-0.000000000000000000000000000000000000000000000000000000000000000000000000000001] diff --git a/resources/json/y_number_int_with_exp.json b/resources/json/y_number_int_with_exp.json deleted file mode 100755 index a4ca9e7..0000000 --- a/resources/json/y_number_int_with_exp.json +++ /dev/null @@ -1 +0,0 @@ -[20e1] \ No newline at end of file diff --git a/resources/json/y_number_minus_zero.json b/resources/json/y_number_minus_zero.json deleted file mode 100755 index 37af131..0000000 --- a/resources/json/y_number_minus_zero.json +++ /dev/null @@ -1 +0,0 @@ -[-0] \ No newline at end of file diff --git a/resources/json/y_number_negative_int.json b/resources/json/y_number_negative_int.json deleted file mode 100644 index 8e30f8b..0000000 --- a/resources/json/y_number_negative_int.json +++ /dev/null @@ -1 +0,0 @@ -[-123] \ No newline at end of file diff --git a/resources/json/y_number_negative_one.json b/resources/json/y_number_negative_one.json deleted file mode 100644 index 99d21a2..0000000 --- a/resources/json/y_number_negative_one.json +++ /dev/null @@ -1 +0,0 @@ -[-1] \ No newline at end of file diff --git a/resources/json/y_number_negative_zero.json b/resources/json/y_number_negative_zero.json deleted file mode 100644 index 37af131..0000000 --- a/resources/json/y_number_negative_zero.json +++ /dev/null @@ -1 +0,0 @@ -[-0] \ No newline at end of file diff --git a/resources/json/y_number_real_capital_e.json b/resources/json/y_number_real_capital_e.json deleted file mode 100644 index 6edbdfc..0000000 --- a/resources/json/y_number_real_capital_e.json +++ /dev/null @@ -1 +0,0 @@ -[1E22] \ No newline at end of file diff --git a/resources/json/y_number_real_capital_e_neg_exp.json b/resources/json/y_number_real_capital_e_neg_exp.json deleted file mode 100644 index 0a01bd3..0000000 --- a/resources/json/y_number_real_capital_e_neg_exp.json +++ /dev/null @@ -1 +0,0 @@ -[1E-2] \ No newline at end of file diff --git a/resources/json/y_number_real_capital_e_pos_exp.json b/resources/json/y_number_real_capital_e_pos_exp.json deleted file mode 100644 index 5a8fc09..0000000 --- a/resources/json/y_number_real_capital_e_pos_exp.json +++ /dev/null @@ -1 +0,0 @@ -[1E+2] \ No newline at end of file diff --git a/resources/json/y_number_real_exponent.json b/resources/json/y_number_real_exponent.json deleted file mode 100644 index da2522d..0000000 --- a/resources/json/y_number_real_exponent.json +++ /dev/null @@ -1 +0,0 @@ -[123e45] \ No newline at end of file diff --git a/resources/json/y_number_real_fraction_exponent.json b/resources/json/y_number_real_fraction_exponent.json deleted file mode 100644 index 3944a7a..0000000 --- a/resources/json/y_number_real_fraction_exponent.json +++ /dev/null @@ -1 +0,0 @@ -[123.456e78] \ No newline at end of file diff --git a/resources/json/y_number_real_neg_exp.json b/resources/json/y_number_real_neg_exp.json deleted file mode 100644 index ca40d3c..0000000 --- a/resources/json/y_number_real_neg_exp.json +++ /dev/null @@ -1 +0,0 @@ -[1e-2] \ No newline at end of file diff --git a/resources/json/y_number_real_pos_exponent.json b/resources/json/y_number_real_pos_exponent.json deleted file mode 100644 index 343601d..0000000 --- a/resources/json/y_number_real_pos_exponent.json +++ /dev/null @@ -1 +0,0 @@ -[1e+2] \ No newline at end of file diff --git a/resources/json/y_number_simple_int.json b/resources/json/y_number_simple_int.json deleted file mode 100644 index e47f69a..0000000 --- a/resources/json/y_number_simple_int.json +++ /dev/null @@ -1 +0,0 @@ -[123] \ No newline at end of file diff --git a/resources/json/y_number_simple_real.json b/resources/json/y_number_simple_real.json deleted file mode 100644 index b02878e..0000000 --- a/resources/json/y_number_simple_real.json +++ /dev/null @@ -1 +0,0 @@ -[123.456789] \ No newline at end of file diff --git a/resources/json/y_object.json b/resources/json/y_object.json deleted file mode 100755 index 78262ed..0000000 --- a/resources/json/y_object.json +++ /dev/null @@ -1 +0,0 @@ -{"asd":"sdf", "dfg":"fgh"} \ No newline at end of file diff --git a/resources/json/y_object_basic.json b/resources/json/y_object_basic.json deleted file mode 100755 index 646bbe7..0000000 --- a/resources/json/y_object_basic.json +++ /dev/null @@ -1 +0,0 @@ -{"asd":"sdf"} \ No newline at end of file diff --git a/resources/json/y_object_duplicated_key.json b/resources/json/y_object_duplicated_key.json deleted file mode 100755 index bbc2e1c..0000000 --- a/resources/json/y_object_duplicated_key.json +++ /dev/null @@ -1 +0,0 @@ -{"a":"b","a":"c"} \ No newline at end of file diff --git a/resources/json/y_object_duplicated_key_and_value.json b/resources/json/y_object_duplicated_key_and_value.json deleted file mode 100755 index 211581c..0000000 --- a/resources/json/y_object_duplicated_key_and_value.json +++ /dev/null @@ -1 +0,0 @@ -{"a":"b","a":"b"} \ No newline at end of file diff --git a/resources/json/y_object_empty.json b/resources/json/y_object_empty.json deleted file mode 100644 index 9e26dfe..0000000 --- a/resources/json/y_object_empty.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/resources/json/y_object_empty_key.json b/resources/json/y_object_empty_key.json deleted file mode 100755 index c0013d3..0000000 --- a/resources/json/y_object_empty_key.json +++ /dev/null @@ -1 +0,0 @@ -{"":0} \ No newline at end of file diff --git a/resources/json/y_object_escaped_null_in_key.json b/resources/json/y_object_escaped_null_in_key.json deleted file mode 100644 index 593f0f6..0000000 --- a/resources/json/y_object_escaped_null_in_key.json +++ /dev/null @@ -1 +0,0 @@ -{"foo\u0000bar": 42} \ No newline at end of file diff --git a/resources/json/y_object_extreme_numbers.json b/resources/json/y_object_extreme_numbers.json deleted file mode 100644 index a0d3531..0000000 --- a/resources/json/y_object_extreme_numbers.json +++ /dev/null @@ -1 +0,0 @@ -{ "min": -1.0e+28, "max": 1.0e+28 } \ No newline at end of file diff --git a/resources/json/y_object_long_strings.json b/resources/json/y_object_long_strings.json deleted file mode 100644 index bdc4a08..0000000 --- a/resources/json/y_object_long_strings.json +++ /dev/null @@ -1 +0,0 @@ -{"x":[{"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}], "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"} \ No newline at end of file diff --git a/resources/json/y_object_simple.json b/resources/json/y_object_simple.json deleted file mode 100644 index dacac91..0000000 --- a/resources/json/y_object_simple.json +++ /dev/null @@ -1 +0,0 @@ -{"a":[]} \ No newline at end of file diff --git a/resources/json/y_object_string_unicode.json b/resources/json/y_object_string_unicode.json deleted file mode 100644 index 8effdb2..0000000 --- a/resources/json/y_object_string_unicode.json +++ /dev/null @@ -1 +0,0 @@ -{"title":"\u041f\u043e\u043b\u0442\u043e\u0440\u0430 \u0417\u0435\u043c\u043b\u0435\u043a\u043e\u043f\u0430" } \ No newline at end of file diff --git a/resources/json/y_object_with_newlines.json b/resources/json/y_object_with_newlines.json deleted file mode 100644 index 246ec6b..0000000 --- a/resources/json/y_object_with_newlines.json +++ /dev/null @@ -1,3 +0,0 @@ -{ -"a": "b" -} \ No newline at end of file diff --git a/resources/json/y_string_1_2_3_bytes_UTF-8_sequences.json b/resources/json/y_string_1_2_3_bytes_UTF-8_sequences.json deleted file mode 100755 index 9967dde..0000000 --- a/resources/json/y_string_1_2_3_bytes_UTF-8_sequences.json +++ /dev/null @@ -1 +0,0 @@ -["\u0060\u012a\u12AB"] \ No newline at end of file diff --git a/resources/json/y_string_accepted_surrogate_pair.json b/resources/json/y_string_accepted_surrogate_pair.json deleted file mode 100755 index 996875c..0000000 --- a/resources/json/y_string_accepted_surrogate_pair.json +++ /dev/null @@ -1 +0,0 @@ -["\uD801\udc37"] \ No newline at end of file diff --git a/resources/json/y_string_accepted_surrogate_pairs.json b/resources/json/y_string_accepted_surrogate_pairs.json deleted file mode 100755 index 3401021..0000000 --- a/resources/json/y_string_accepted_surrogate_pairs.json +++ /dev/null @@ -1 +0,0 @@ -["\ud83d\ude39\ud83d\udc8d"] \ No newline at end of file diff --git a/resources/json/y_string_allowed_escapes.json b/resources/json/y_string_allowed_escapes.json deleted file mode 100644 index 7f49553..0000000 --- a/resources/json/y_string_allowed_escapes.json +++ /dev/null @@ -1 +0,0 @@ -["\"\\\/\b\f\n\r\t"] \ No newline at end of file diff --git a/resources/json/y_string_backslash_and_u_escaped_zero.json b/resources/json/y_string_backslash_and_u_escaped_zero.json deleted file mode 100755 index d4439ed..0000000 --- a/resources/json/y_string_backslash_and_u_escaped_zero.json +++ /dev/null @@ -1 +0,0 @@ -["\\u0000"] \ No newline at end of file diff --git a/resources/json/y_string_backslash_doublequotes.json b/resources/json/y_string_backslash_doublequotes.json deleted file mode 100644 index ae03243..0000000 --- a/resources/json/y_string_backslash_doublequotes.json +++ /dev/null @@ -1 +0,0 @@ -["\""] \ No newline at end of file diff --git a/resources/json/y_string_comments.json b/resources/json/y_string_comments.json deleted file mode 100644 index 2260c20..0000000 --- a/resources/json/y_string_comments.json +++ /dev/null @@ -1 +0,0 @@ -["a/*b*/c/*d//e"] \ No newline at end of file diff --git a/resources/json/y_string_double_escape_a.json b/resources/json/y_string_double_escape_a.json deleted file mode 100644 index 6715d6f..0000000 --- a/resources/json/y_string_double_escape_a.json +++ /dev/null @@ -1 +0,0 @@ -["\\a"] \ No newline at end of file diff --git a/resources/json/y_string_double_escape_n.json b/resources/json/y_string_double_escape_n.json deleted file mode 100644 index 44ca56c..0000000 --- a/resources/json/y_string_double_escape_n.json +++ /dev/null @@ -1 +0,0 @@ -["\\n"] \ No newline at end of file diff --git a/resources/json/y_string_escaped_control_character.json b/resources/json/y_string_escaped_control_character.json deleted file mode 100644 index 5b014a9..0000000 --- a/resources/json/y_string_escaped_control_character.json +++ /dev/null @@ -1 +0,0 @@ -["\u0012"] \ No newline at end of file diff --git a/resources/json/y_string_escaped_noncharacter.json b/resources/json/y_string_escaped_noncharacter.json deleted file mode 100755 index 2ff52e2..0000000 --- a/resources/json/y_string_escaped_noncharacter.json +++ /dev/null @@ -1 +0,0 @@ -["\uFFFF"] \ No newline at end of file diff --git a/resources/json/y_string_in_array.json b/resources/json/y_string_in_array.json deleted file mode 100755 index 21d7ae4..0000000 --- a/resources/json/y_string_in_array.json +++ /dev/null @@ -1 +0,0 @@ -["asd"] \ No newline at end of file diff --git a/resources/json/y_string_in_array_with_leading_space.json b/resources/json/y_string_in_array_with_leading_space.json deleted file mode 100755 index 9e1887c..0000000 --- a/resources/json/y_string_in_array_with_leading_space.json +++ /dev/null @@ -1 +0,0 @@ -[ "asd"] \ No newline at end of file diff --git a/resources/json/y_string_last_surrogates_1_and_2.json b/resources/json/y_string_last_surrogates_1_and_2.json deleted file mode 100644 index 3919cef..0000000 --- a/resources/json/y_string_last_surrogates_1_and_2.json +++ /dev/null @@ -1 +0,0 @@ -["\uDBFF\uDFFF"] \ No newline at end of file diff --git a/resources/json/y_string_nbsp_uescaped.json b/resources/json/y_string_nbsp_uescaped.json deleted file mode 100644 index 2085ab1..0000000 --- a/resources/json/y_string_nbsp_uescaped.json +++ /dev/null @@ -1 +0,0 @@ -["new\u00A0line"] \ No newline at end of file diff --git a/resources/json/y_string_nonCharacterInUTF-8_U+10FFFF.json b/resources/json/y_string_nonCharacterInUTF-8_U+10FFFF.json deleted file mode 100755 index 059e4d9..0000000 --- a/resources/json/y_string_nonCharacterInUTF-8_U+10FFFF.json +++ /dev/null @@ -1 +0,0 @@ -["ô¿¿"] \ No newline at end of file diff --git a/resources/json/y_string_nonCharacterInUTF-8_U+FFFF.json b/resources/json/y_string_nonCharacterInUTF-8_U+FFFF.json deleted file mode 100755 index 4c913bd..0000000 --- a/resources/json/y_string_nonCharacterInUTF-8_U+FFFF.json +++ /dev/null @@ -1 +0,0 @@ -["ï¿¿"] \ No newline at end of file diff --git a/resources/json/y_string_null_escape.json b/resources/json/y_string_null_escape.json deleted file mode 100644 index c1ad844..0000000 --- a/resources/json/y_string_null_escape.json +++ /dev/null @@ -1 +0,0 @@ -["\u0000"] \ No newline at end of file diff --git a/resources/json/y_string_one-byte-utf-8.json b/resources/json/y_string_one-byte-utf-8.json deleted file mode 100644 index 1571859..0000000 --- a/resources/json/y_string_one-byte-utf-8.json +++ /dev/null @@ -1 +0,0 @@ -["\u002c"] \ No newline at end of file diff --git a/resources/json/y_string_pi.json b/resources/json/y_string_pi.json deleted file mode 100644 index 9df11ae..0000000 --- a/resources/json/y_string_pi.json +++ /dev/null @@ -1 +0,0 @@ -["Ï€"] \ No newline at end of file diff --git a/resources/json/y_string_reservedCharacterInUTF-8_U+1BFFF.json b/resources/json/y_string_reservedCharacterInUTF-8_U+1BFFF.json deleted file mode 100755 index 10a33a1..0000000 --- a/resources/json/y_string_reservedCharacterInUTF-8_U+1BFFF.json +++ /dev/null @@ -1 +0,0 @@ -["𛿿"] \ No newline at end of file diff --git a/resources/json/y_string_simple_ascii.json b/resources/json/y_string_simple_ascii.json deleted file mode 100644 index 8cadf7d..0000000 --- a/resources/json/y_string_simple_ascii.json +++ /dev/null @@ -1 +0,0 @@ -["asd "] \ No newline at end of file diff --git a/resources/json/y_string_space.json b/resources/json/y_string_space.json deleted file mode 100644 index efd782c..0000000 --- a/resources/json/y_string_space.json +++ /dev/null @@ -1 +0,0 @@ -" " \ No newline at end of file diff --git a/resources/json/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json b/resources/json/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json deleted file mode 100755 index 7620b66..0000000 --- a/resources/json/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json +++ /dev/null @@ -1 +0,0 @@ -["\uD834\uDd1e"] \ No newline at end of file diff --git a/resources/json/y_string_three-byte-utf-8.json b/resources/json/y_string_three-byte-utf-8.json deleted file mode 100644 index 108f1d6..0000000 --- a/resources/json/y_string_three-byte-utf-8.json +++ /dev/null @@ -1 +0,0 @@ -["\u0821"] \ No newline at end of file diff --git a/resources/json/y_string_two-byte-utf-8.json b/resources/json/y_string_two-byte-utf-8.json deleted file mode 100644 index 461503c..0000000 --- a/resources/json/y_string_two-byte-utf-8.json +++ /dev/null @@ -1 +0,0 @@ -["\u0123"] \ No newline at end of file diff --git a/resources/json/y_string_u+2028_line_sep.json b/resources/json/y_string_u+2028_line_sep.json deleted file mode 100755 index 897b602..0000000 --- a/resources/json/y_string_u+2028_line_sep.json +++ /dev/null @@ -1 +0,0 @@ -["
"] \ No newline at end of file diff --git a/resources/json/y_string_u+2029_par_sep.json b/resources/json/y_string_u+2029_par_sep.json deleted file mode 100755 index 8cd998c..0000000 --- a/resources/json/y_string_u+2029_par_sep.json +++ /dev/null @@ -1 +0,0 @@ -["
"] \ No newline at end of file diff --git a/resources/json/y_string_uEscape.json b/resources/json/y_string_uEscape.json deleted file mode 100755 index f7b41a0..0000000 --- a/resources/json/y_string_uEscape.json +++ /dev/null @@ -1 +0,0 @@ -["\u0061\u30af\u30EA\u30b9"] \ No newline at end of file diff --git a/resources/json/y_string_uescaped_newline.json b/resources/json/y_string_uescaped_newline.json deleted file mode 100644 index 3a5a220..0000000 --- a/resources/json/y_string_uescaped_newline.json +++ /dev/null @@ -1 +0,0 @@ -["new\u000Aline"] \ No newline at end of file diff --git a/resources/json/y_string_unescaped_char_delete.json b/resources/json/y_string_unescaped_char_delete.json deleted file mode 100755 index 7d064f4..0000000 --- a/resources/json/y_string_unescaped_char_delete.json +++ /dev/null @@ -1 +0,0 @@ -[""] \ No newline at end of file diff --git a/resources/json/y_string_unicode.json b/resources/json/y_string_unicode.json deleted file mode 100644 index 3598095..0000000 --- a/resources/json/y_string_unicode.json +++ /dev/null @@ -1 +0,0 @@ -["\uA66D"] \ No newline at end of file diff --git a/resources/json/y_string_unicodeEscapedBackslash.json b/resources/json/y_string_unicodeEscapedBackslash.json deleted file mode 100755 index 0bb3b51..0000000 --- a/resources/json/y_string_unicodeEscapedBackslash.json +++ /dev/null @@ -1 +0,0 @@ -["\u005C"] \ No newline at end of file diff --git a/resources/json/y_string_unicode_2.json b/resources/json/y_string_unicode_2.json deleted file mode 100644 index a7dcb97..0000000 --- a/resources/json/y_string_unicode_2.json +++ /dev/null @@ -1 +0,0 @@ -["â‚㈴â‚"] \ No newline at end of file diff --git a/resources/json/y_string_unicode_U+10FFFE_nonchar.json b/resources/json/y_string_unicode_U+10FFFE_nonchar.json deleted file mode 100644 index 9a8370b..0000000 --- a/resources/json/y_string_unicode_U+10FFFE_nonchar.json +++ /dev/null @@ -1 +0,0 @@ -["\uDBFF\uDFFE"] \ No newline at end of file diff --git a/resources/json/y_string_unicode_U+1FFFE_nonchar.json b/resources/json/y_string_unicode_U+1FFFE_nonchar.json deleted file mode 100644 index c51f8ae..0000000 --- a/resources/json/y_string_unicode_U+1FFFE_nonchar.json +++ /dev/null @@ -1 +0,0 @@ -["\uD83F\uDFFE"] \ No newline at end of file diff --git a/resources/json/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json b/resources/json/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json deleted file mode 100644 index 626d5f8..0000000 --- a/resources/json/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json +++ /dev/null @@ -1 +0,0 @@ -["\u200B"] \ No newline at end of file diff --git a/resources/json/y_string_unicode_U+2064_invisible_plus.json b/resources/json/y_string_unicode_U+2064_invisible_plus.json deleted file mode 100644 index 1e23972..0000000 --- a/resources/json/y_string_unicode_U+2064_invisible_plus.json +++ /dev/null @@ -1 +0,0 @@ -["\u2064"] \ No newline at end of file diff --git a/resources/json/y_string_unicode_U+FDD0_nonchar.json b/resources/json/y_string_unicode_U+FDD0_nonchar.json deleted file mode 100644 index 18ef151..0000000 --- a/resources/json/y_string_unicode_U+FDD0_nonchar.json +++ /dev/null @@ -1 +0,0 @@ -["\uFDD0"] \ No newline at end of file diff --git a/resources/json/y_string_unicode_U+FFFE_nonchar.json b/resources/json/y_string_unicode_U+FFFE_nonchar.json deleted file mode 100644 index 13d261f..0000000 --- a/resources/json/y_string_unicode_U+FFFE_nonchar.json +++ /dev/null @@ -1 +0,0 @@ -["\uFFFE"] \ No newline at end of file diff --git a/resources/json/y_string_unicode_escaped_double_quote.json b/resources/json/y_string_unicode_escaped_double_quote.json deleted file mode 100755 index 4e62578..0000000 --- a/resources/json/y_string_unicode_escaped_double_quote.json +++ /dev/null @@ -1 +0,0 @@ -["\u0022"] \ No newline at end of file diff --git a/resources/json/y_string_utf8.json b/resources/json/y_string_utf8.json deleted file mode 100644 index 4087843..0000000 --- a/resources/json/y_string_utf8.json +++ /dev/null @@ -1 +0,0 @@ -["€ð„ž"] \ No newline at end of file diff --git a/resources/json/y_string_with_del_character.json b/resources/json/y_string_with_del_character.json deleted file mode 100755 index 8bd2490..0000000 --- a/resources/json/y_string_with_del_character.json +++ /dev/null @@ -1 +0,0 @@ -["aa"] \ No newline at end of file diff --git a/resources/json/y_structure_lonely_false.json b/resources/json/y_structure_lonely_false.json deleted file mode 100644 index 02e4a84..0000000 --- a/resources/json/y_structure_lonely_false.json +++ /dev/null @@ -1 +0,0 @@ -false \ No newline at end of file diff --git a/resources/json/y_structure_lonely_int.json b/resources/json/y_structure_lonely_int.json deleted file mode 100755 index f70d7bb..0000000 --- a/resources/json/y_structure_lonely_int.json +++ /dev/null @@ -1 +0,0 @@ -42 \ No newline at end of file diff --git a/resources/json/y_structure_lonely_negative_real.json b/resources/json/y_structure_lonely_negative_real.json deleted file mode 100755 index b5135a2..0000000 --- a/resources/json/y_structure_lonely_negative_real.json +++ /dev/null @@ -1 +0,0 @@ --0.1 \ No newline at end of file diff --git a/resources/json/y_structure_lonely_null.json b/resources/json/y_structure_lonely_null.json deleted file mode 100644 index ec747fa..0000000 --- a/resources/json/y_structure_lonely_null.json +++ /dev/null @@ -1 +0,0 @@ -null \ No newline at end of file diff --git a/resources/json/y_structure_lonely_string.json b/resources/json/y_structure_lonely_string.json deleted file mode 100755 index b6e982c..0000000 --- a/resources/json/y_structure_lonely_string.json +++ /dev/null @@ -1 +0,0 @@ -"asd" \ No newline at end of file diff --git a/resources/json/y_structure_lonely_true.json b/resources/json/y_structure_lonely_true.json deleted file mode 100755 index f32a580..0000000 --- a/resources/json/y_structure_lonely_true.json +++ /dev/null @@ -1 +0,0 @@ -true \ No newline at end of file diff --git a/resources/json/y_structure_string_empty.json b/resources/json/y_structure_string_empty.json deleted file mode 100644 index 3cc762b..0000000 --- a/resources/json/y_structure_string_empty.json +++ /dev/null @@ -1 +0,0 @@ -"" \ No newline at end of file diff --git a/resources/json/y_structure_trailing_newline.json b/resources/json/y_structure_trailing_newline.json deleted file mode 100644 index 0c3426d..0000000 --- a/resources/json/y_structure_trailing_newline.json +++ /dev/null @@ -1 +0,0 @@ -["a"] diff --git a/resources/json/y_structure_true_in_array.json b/resources/json/y_structure_true_in_array.json deleted file mode 100644 index de601e3..0000000 --- a/resources/json/y_structure_true_in_array.json +++ /dev/null @@ -1 +0,0 @@ -[true] \ No newline at end of file diff --git a/resources/json/y_structure_whitespace_array.json b/resources/json/y_structure_whitespace_array.json deleted file mode 100644 index 2bedf7f..0000000 --- a/resources/json/y_structure_whitespace_array.json +++ /dev/null @@ -1 +0,0 @@ - [] \ No newline at end of file diff --git a/src/bin/fwd-browse.rs b/src/bin/fwd-browse.rs index 9899597..bbebcdf 100644 --- a/src/bin/fwd-browse.rs +++ b/src/bin/fwd-browse.rs @@ -9,10 +9,5 @@ async fn main() { std::process::exit(1); } - let url = &args[1]; - if let Err(e) = fwd::browse_url(url).await { - eprintln!("Unable to open {url}"); - eprintln!("{}", e); - std::process::exit(1); - } + fwd::browse_url(&args[1]).await; } diff --git a/src/reverse/unix.rs b/src/browse/browse_unix.rs similarity index 61% rename from src/reverse/unix.rs rename to src/browse/browse_unix.rs index a968edd..54cca9c 100644 --- a/src/reverse/unix.rs +++ b/src/browse/browse_unix.rs @@ -1,75 +1,37 @@ -// The reverse client connects to the server via a local connection to send -// commands back to the client. +use crate::message::{Message, MessageReader, MessageWriter}; use anyhow::{bail, Context, Result}; use log::warn; use std::os::unix::fs::DirBuilderExt; use std::path::PathBuf; use tokio::net::{UnixListener, UnixStream}; use tokio::sync::mpsc; +use users; +use xdg; -use crate::message::{Message, MessageReader, MessageWriter}; - -pub struct ReverseConnection { - writer: MessageWriter, +pub async fn browse_url_impl(url: &String) -> Result<()> { + let path = socket_path().context("Error getting socket path")?; + let stream = match UnixStream::connect(&path).await { + Ok(s) => s, + Err(e) => bail!( + "Error connecting to socket: {e} (is fwd actually connected here?)" + ), + }; + let mut writer = MessageWriter::new(stream); + writer + .write(Message::Browse(url.clone())) + .await + .context("Error sending browse message")?; + Ok(()) } -impl ReverseConnection { - pub async fn new() -> Result { - let path = socket_path().context("Error getting socket path")?; - let stream = match UnixStream::connect(&path).await { - Ok(s) => s, - Err(e) => bail!("Error connecting to socket: {e} (is fwd actually connected here?)"), - }; - - Ok(ReverseConnection { writer: MessageWriter::new(stream) }) - } - - pub async fn send(&mut self, message: Message) -> Result<()> { - self.writer - .write(message) - .await - .context("Error sending reverse message")?; - Ok(()) - } -} - -pub fn socket_path() -> Result { - let mut socket_path = socket_directory()?; - - std::fs::DirBuilder::new() - .recursive(true) - .mode(0o700) - .create(&socket_path) - .context("Error creating socket directory")?; - - // TODO: check mode of directory - - socket_path.push("browser"); - Ok(socket_path) -} - -fn socket_directory() -> Result { - match directories_next::ProjectDirs::from("", "", "fwd") - .and_then(|p| p.runtime_dir().map(|p| p.to_path_buf())) - { - Some(p) => Ok(p), - None => { - let mut path = std::env::temp_dir(); - let uid = unsafe { libc::getuid() }; - path.push(format!("fwd{}", uid)); - Ok(path) - } - } -} - -pub async fn handle_reverse_connections( +pub async fn handle_browser_open_impl( messages: mpsc::Sender, ) -> Result<()> { let path = socket_path().context("Error getting socket path")?; - handle_reverse_connections_with_path(messages, path).await + handle_browser_open_with_path(messages, path).await } -async fn handle_reverse_connections_with_path( +async fn handle_browser_open_with_path( messages: mpsc::Sender, path: PathBuf, ) -> Result<()> { @@ -91,23 +53,43 @@ async fn handle_reverse_connections_with_path( } } +pub fn socket_path() -> Result { + let mut socket_path = socket_directory()?; + + std::fs::DirBuilder::new() + .recursive(true) + .mode(0o700) + .create(&socket_path) + .context("Error creating socket directory")?; + + // TODO: check mode of directory + + socket_path.push("browser"); + Ok(socket_path) +} + +fn socket_directory() -> Result { + let base_directories = xdg::BaseDirectories::new() + .context("Error creating BaseDirectories")?; + match base_directories.place_runtime_file("fwd") { + Ok(path) => Ok(path), + Err(_) => { + let mut path = std::env::temp_dir(); + path.push(format!("fwd{}", users::get_current_uid())); + Ok(path) + } + } +} + async fn handle_connection( socket: UnixStream, sender: mpsc::Sender, ) -> Result<()> { let mut reader = MessageReader::new(socket); - while let Ok(message) = reader.read().await { - match message { - Message::Browse(url) => sender.send(Message::Browse(url)).await?, - Message::ClipStart(id) => { - sender.send(Message::ClipStart(id)).await? - } - Message::ClipData(id, data) => { - sender.send(Message::ClipData(id, data)).await? - } - Message::ClipEnd(id) => sender.send(Message::ClipEnd(id)).await?, - _ => bail!("Unsupported message: {:?}", message), - } + let message = reader.read().await.context("Error reading message")?; + match message { + Message::Browse(url) => sender.send(Message::Browse(url)).await?, + _ => bail!("Unsupported message: {:?}", message), } Ok(()) @@ -117,7 +99,7 @@ async fn handle_connection( mod tests { use super::*; use crate::message::MessageWriter; - use tempfile::TempDir; + use tempdir::TempDir; #[test] fn socket_path_repeats() { @@ -131,13 +113,13 @@ mod tests { async fn url_to_message() { let (sender, mut receiver) = mpsc::channel(64); - let tmp_dir = TempDir::with_prefix("url_to_message") - .expect("Error getting tmpdir"); + let tmp_dir = + TempDir::new("url_to_message").expect("Error getting tmpdir"); let path = tmp_dir.path().join("socket"); let path_override = path.clone(); tokio::spawn(async move { - handle_reverse_connections_with_path(sender, path_override) + handle_browser_open_with_path(sender, path_override) .await .expect("Error in server!"); }); diff --git a/src/browse/mod.rs b/src/browse/mod.rs new file mode 100644 index 0000000..cf83858 --- /dev/null +++ b/src/browse/mod.rs @@ -0,0 +1,40 @@ +use crate::message::Message; +use anyhow::Result; +use tokio::sync::mpsc; + +#[cfg(target_family = "unix")] +mod browse_unix; + +#[cfg(target_family = "unix")] +use browse_unix::{browse_url_impl, handle_browser_open_impl}; + +#[inline] +pub async fn browse_url(url: &String) { + if let Err(e) = browse_url_impl(url).await { + eprintln!("Unable to open {url}"); + eprintln!("{}", e); + std::process::exit(1); + } +} + +#[cfg(not(target_family = "unix"))] +pub async fn browse_url_impl(_url: &String) -> Result<()> { + use anyhow::anyhow; + Err(anyhow!( + "Opening a browser is not supported on this platform" + )) +} + +#[inline] +pub async fn handle_browser_open( + messages: mpsc::Sender, +) -> Result<()> { + handle_browser_open_impl(messages).await +} + +#[cfg(not(target_family = "unix"))] +async fn handle_browser_open_impl( + _messages: mpsc::Sender, +) -> Result<()> { + std::future::pending().await +} diff --git a/src/client/config.rs b/src/client/config.rs index e3a3cc0..c7f0bea 100644 --- a/src/client/config.rs +++ b/src/client/config.rs @@ -1,17 +1,14 @@ use anyhow::{bail, Result}; -use std::collections::hash_map; use std::collections::HashMap; -use toml::value::{Table, Value}; +use toml::Value; #[derive(Debug, Clone)] -#[cfg_attr(test, derive(PartialEq, Eq))] pub struct PortConfig { pub enabled: bool, pub description: Option, } #[derive(Debug, Clone)] -#[cfg_attr(test, derive(PartialEq, Eq))] pub struct ServerConfig { auto: bool, ports: HashMap, @@ -23,31 +20,19 @@ impl ServerConfig { ServerConfig { auto: true, ports: HashMap::new() } } - #[cfg(test)] - pub fn set_auto(&mut self, auto: bool) { - self.auto = auto; - } - - #[cfg(test)] - pub fn insert(&mut self, port: u16, config: PortConfig) { - self.ports.insert(port, config); - } - - pub fn auto(&self) -> bool { - self.auto - } - - pub fn iter(&self) -> hash_map::Iter { - self.ports.iter() - } - pub fn contains_key(&self, port: u16) -> bool { self.ports.contains_key(&port) } + + pub fn get(&self, port: u16) -> PortConfig { + match self.ports.get(&port) { + None => PortConfig { enabled: self.auto, description: None }, + Some(c) => c.clone(), + } + } } #[derive(Debug)] -#[cfg_attr(test, derive(PartialEq, Eq))] pub struct Config { auto: bool, servers: HashMap, @@ -65,15 +50,13 @@ impl Config { pub fn load_config() -> Result { use std::io::ErrorKind; - let Some(directories) = directories_next::ProjectDirs::from("", "", "fwd") - else { - return Ok(default()); + let mut home = match home::home_dir() { + Some(h) => h, + None => return Ok(default()), }; + home.push(".fwd"); - let mut config_path = directories.config_dir().to_path_buf(); - config_path.push("config.toml"); - - let contents = match std::fs::read_to_string(config_path) { + let contents = match std::fs::read_to_string(home) { Ok(contents) => contents, Err(e) => match e.kind() { ErrorKind::NotFound => return Ok(default()), @@ -81,108 +64,95 @@ pub fn load_config() -> Result { }, }; - parse_config(&contents.parse::()?) + Ok(parse_config(&contents.parse::()?)?) } fn default() -> Config { Config { auto: true, servers: HashMap::new() } } -fn get_bool(table: &Table, key: &str, default: bool) -> Result { - match table.get(key) { - None => Ok(default), - Some(Value::Boolean(v)) => Ok(*v), - Some(v) => bail!("expected a true or false, got {v:?}"), +fn parse_config(value: &Value) -> Result { + match value { + Value::Table(table) => Ok({ + let auto = match table.get("auto") { + None => true, + Some(Value::Boolean(v)) => *v, + Some(v) => bail!("expected a true or false, got {:?}", v), + }; + Config { + auto, + servers: get_servers(&table, auto)?, + } + }), + _ => bail!("top level must be a table"), } } -fn parse_config(value: &Value) -> Result { - let Value::Table(table) = value else { - bail!("top level must be a table") - }; - - let auto = get_bool(table, "auto", true)?; - let servers = match table.get("servers") { - None => &Table::new(), - Some(Value::Table(t)) => t, - Some(v) => bail!("Expected a table in the servers key, got {v:?}"), - }; - - Ok(Config { - auto, - servers: parse_servers(servers, auto)?, - }) -} - -fn parse_servers( - table: &Table, +fn get_servers( + table: &toml::value::Table, auto: bool, ) -> Result> { - let mut servers = HashMap::new(); - for (k, v) in table { - let Value::Table(table) = v else { - bail!("expected a table for server {k}, got {v:?}"); - }; - - servers.insert(k.clone(), parse_server(table, auto)?); - } - Ok(servers) -} - -fn parse_server(table: &Table, auto: bool) -> Result { - let auto = get_bool(table, "auto", auto)?; - let ports = match table.get("ports") { - None => HashMap::new(), - Some(v) => parse_ports(v)?, - }; - - Ok(ServerConfig { auto, ports }) -} - -fn parse_ports(value: &Value) -> Result> { - match value { - Value::Array(array) => { - let mut ports = HashMap::new(); - for v in array { - ports.insert( - get_port_number(v)?, - PortConfig { enabled: true, description: None }, - ); - } - Ok(ports) - } - - Value::Table(table) => { - let mut ports = HashMap::new(); + match table.get("servers") { + None => Ok(HashMap::new()), + Some(Value::Table(table)) => Ok({ + let mut servers = HashMap::new(); for (k, v) in table { - let port: u16 = k.parse()?; - let config = parse_port_config(v)?; + servers.insert(k.clone(), get_server(v, auto)?); + } + servers + }), + v => bail!("expected a table in the servers key, got {:?}", v), + } +} + +fn get_server(value: &Value, auto: bool) -> Result { + match value { + Value::Table(table) => Ok(ServerConfig { + auto: match table.get("auto") { + None => auto, // Default to global default + Some(Value::Boolean(v)) => *v, + Some(v) => bail!("expected true or false, got {:?}", v), + }, + ports: get_ports(table)?, + }), + value => bail!("expected a table, got {:?}", value), + } +} + +fn get_ports(table: &toml::value::Table) -> Result> { + match table.get("ports") { + None => Ok(HashMap::new()), + Some(Value::Table(table)) => Ok({ + let mut ports = HashMap::new(); + for (k,v) in table { + let port:u16 = k.parse()?; + let config = match v { + Value::Boolean(enabled) => PortConfig{enabled:*enabled, description:None}, + Value::Table(table) => PortConfig{ + enabled: match table.get("enabled") { + Some(Value::Boolean(enabled)) => *enabled, + _ => bail!("not implemented"), + }, + description: match table.get("description") { + Some(Value::String(desc)) => Some(desc.clone()), + Some(v) => bail!("expect a string description, got {:?}", v), + None => None, + }, + }, + _ => bail!("expected either a boolean (enabled) or a table for a port config, got {:?}", v), + }; ports.insert(port, config); } - Ok(ports) - } - - _ => bail!("ports must be either an array or a table, got {value:?}"), - } -} - -fn parse_port_config(value: &Value) -> Result { - match value { - Value::Boolean(enabled) => Ok(PortConfig{enabled:*enabled, description:None}), - Value::String(description) => Ok(PortConfig{ - enabled: true, - description: Some(description.clone()), + ports }), - Value::Table(table) => { - let enabled = get_bool(table, "enabled", true)?; - let description = match table.get("description") { - Some(Value::String(desc)) => Some(desc.clone()), - Some(v) => bail!("expect a string description, got {v:?}"), - None => None, - }; - Ok(PortConfig { enabled, description }) - }, - _ => bail!("expected either a boolean (enabled), a string (description), or a table for a port config, got {value:?}"), + Some(Value::Array(array)) => Ok({ + let mut ports = HashMap::new(); + for v in array { + ports.insert(get_port_number(v)?, PortConfig{enabled:true, description:None}); + } + ports + }), + Some(v) => bail!("ports must be either a table of ' = ...' or an array of ports, got {:?}", v), } } @@ -193,258 +163,3 @@ fn get_port_number(v: &Value) -> Result { }; Ok(port) } - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - fn config_test(config: &str, expected: Config) { - let config = config.parse::().expect("case not toml"); - let config = parse_config(&config).expect("unable to parse config"); - - assert_eq!(expected, config); - } - - fn config_error_test(config: &str) { - let config = config.parse::().expect("case not toml"); - assert!(parse_config(&config).is_err()); - } - - #[test] - fn empty() { - config_test("", Config { auto: true, servers: HashMap::new() }); - } - - #[test] - fn auto_false() { - config_test( - " -auto=false -", - Config { auto: false, servers: HashMap::new() }, - ); - } - - #[test] - fn auto_not_boolean() { - config_error_test( - " -auto='what is going on' -", - ); - } - - #[test] - fn servers_not_table() { - config_error_test("servers=1234"); - } - - #[test] - fn servers_default() { - config_test("servers.foo={}", { - let mut servers = HashMap::new(); - servers.insert( - "foo".to_string(), - ServerConfig { auto: true, ports: HashMap::new() }, - ); - Config { auto: true, servers } - }) - } - - #[test] - fn servers_auto_false() { - config_test( - " -[servers.foo] -auto=false -", - { - let mut servers = HashMap::new(); - servers.insert( - "foo".to_string(), - ServerConfig { auto: false, ports: HashMap::new() }, - ); - Config { auto: true, servers } - }, - ) - } - - #[test] - fn servers_auto_not_bool() { - config_error_test( - " -[servers.foo] -auto=1234 -", - ) - } - - #[test] - fn servers_ports_list() { - config_test( - " -[servers.foo] -ports=[1,2,3] -", - { - let mut servers = HashMap::new(); - servers.insert( - "foo".to_string(), - ServerConfig { - auto: true, - ports: { - let mut ports = HashMap::new(); - ports.insert( - 1, - PortConfig { enabled: true, description: None }, - ); - ports.insert( - 2, - PortConfig { enabled: true, description: None }, - ); - ports.insert( - 3, - PortConfig { enabled: true, description: None }, - ); - ports - }, - }, - ); - Config { auto: true, servers } - }, - ) - } - - #[test] - fn servers_ports_table_variations() { - config_test( - " -[servers.foo.ports] -1=true -2={enabled=false} -3=false -", - { - let mut servers = HashMap::new(); - servers.insert( - "foo".to_string(), - ServerConfig { - auto: true, - ports: { - let mut ports = HashMap::new(); - ports.insert( - 1, - PortConfig { enabled: true, description: None }, - ); - ports.insert( - 2, - PortConfig { - enabled: false, - description: None, - }, - ); - ports.insert( - 3, - PortConfig { - enabled: false, - description: None, - }, - ); - ports - }, - }, - ); - Config { auto: true, servers } - }, - ) - } - - #[test] - fn servers_ports_table_descriptions() { - config_test( - " -[servers.foo.ports] -1={enabled=false} -2={description='humble'} -", - { - let mut servers = HashMap::new(); - servers.insert( - "foo".to_string(), - ServerConfig { - auto: true, - ports: { - let mut ports = HashMap::new(); - ports.insert( - 1, - PortConfig { - enabled: false, - description: None, - }, - ); - ports.insert( - 2, - PortConfig { - enabled: true, - description: Some("humble".to_string()), - }, - ); - ports - }, - }, - ); - Config { auto: true, servers } - }, - ) - } - - #[test] - fn servers_ports_raw_desc() { - config_test( - " -[servers.foo.ports] -1='humble' -", - { - let mut servers = HashMap::new(); - servers.insert( - "foo".to_string(), - ServerConfig { - auto: true, - ports: { - let mut ports = HashMap::new(); - ports.insert( - 1, - PortConfig { - enabled: true, - description: Some("humble".to_string()), - }, - ); - ports - }, - }, - ); - Config { auto: true, servers } - }, - ) - } - - #[test] - fn servers_inherit_auto() { - config_test( - " -auto=false -servers.foo={} -", - { - let mut servers = HashMap::new(); - servers.insert( - "foo".to_string(), - ServerConfig { auto: false, ports: HashMap::new() }, - ); - Config { auto: false, servers } - }, - ) - } -} diff --git a/src/client/mod.rs b/src/client/mod.rs index 90ec7fb..567a186 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -3,7 +3,6 @@ use anyhow::{bail, Result}; use bytes::BytesMut; use log::LevelFilter; use log::{debug, error, info, warn}; -use std::collections::HashMap; use std::net::{Ipv4Addr, SocketAddrV4}; use tokio::io::{ AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, @@ -16,14 +15,6 @@ use tokio::sync::mpsc; mod config; mod ui; -// 256MB clipboard. Operating systems do not generally have a maximum size -// but I need to buffer it all in memory in order to use the `copypasta` -// crate and so I want to set a limit somewhere and this seems.... rational. -// You should use another transfer mechanism other than this if you need more -// than 256 MB of data. (Obviously future me will come along and shake his -// head at the limitations of my foresight, but oh well.) -const MAX_CLIPBOARD_SIZE: usize = 256 * 1024 * 1024; - /// Wait for the server to be ready; we know the server is there and /// listening when we see the special sync marker, which is 8 NUL bytes in a /// row. @@ -61,7 +52,7 @@ async fn client_sync( } => result, }; - if result.is_err() { + if let Err(_) = result { // Something went wrong, let's just make sure we flush the client's // stderr before we return. _ = stderr.write_all(&buf[..]).await; @@ -120,7 +111,7 @@ async fn client_handle_connection( 0, // ..ho.. 1, // ..st ((port & 0xFF00) >> 8).try_into().unwrap(), // port (high) - (port & 0x00FF).try_into().unwrap(), // port (low) + ((port & 0x00FF) >> 0).try_into().unwrap(), // port (low) ]; dest_socket.write_all(&packet[..]).await?; @@ -171,22 +162,25 @@ async fn client_handle_connection( /// Listen on a port that we are currently forwarding, and use the SOCKS5 /// proxy on the specified port to handle the connections. async fn client_listen(port: u16, socks_port: u16) -> Result<()> { - let listener = - TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port)).await?; loop { - // The second item contains the IP and port of the new - // connection, but we don't care. - let (socket, _) = listener.accept().await?; + let listener = + TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port)) + .await?; + loop { + // The second item contains the IP and port of the new + // connection, but we don't care. + let (socket, _) = listener.accept().await?; - tokio::spawn(async move { - if let Err(e) = - client_handle_connection(socks_port, port, socket).await - { - error!("Error handling connection: {:?}", e); - } else { - debug!("Done???"); - } - }); + tokio::spawn(async move { + if let Err(e) = + client_handle_connection(socks_port, port, socket).await + { + error!("Error handling connection: {:?}", e); + } else { + debug!("Done???"); + } + }); + } } } @@ -194,56 +188,20 @@ async fn client_handle_messages( mut reader: MessageReader, events: mpsc::Sender, ) -> Result<()> { - let mut clipboard_messages = HashMap::new(); loop { use Message::*; match reader.read().await? { Ping => (), - Ports(ports) => { - if let Err(e) = events.send(ui::UIEvent::Ports(ports)).await { - error!("Error sending ports request: {:?}", e); + if let Err(_) = events.send(ui::UIEvent::Ports(ports)).await { + // TODO: Log } } - Browse(url) => { + // TODO: Uh, security? info!("Browsing to {url}..."); _ = open::that(url); } - - ClipStart(id) => { - clipboard_messages.insert(id, Vec::new()); - } - - ClipData(id, mut data) => match clipboard_messages.get_mut(&id) { - Some(bytes) => { - if bytes.len() < MAX_CLIPBOARD_SIZE { - bytes.append(&mut data); - } - } - None => { - warn!("Received data for unknown clip op {id}"); - } - }, - - ClipEnd(id) => { - let Some(data) = clipboard_messages.remove(&id) else { - warn!("Received end for unknown clip op {id}"); - continue; - }; - - let Ok(data) = String::from_utf8(data) else { - warn!("Received invalid utf8 for clipboard on op {id}"); - continue; - }; - - if let Err(e) = - events.send(ui::UIEvent::SetClipboard(data)).await - { - error!("Error sending clipboard request: {:?}", e); - } - } - message => error!("Unsupported: {:?}", message), }; } @@ -301,14 +259,14 @@ async fn client_main( } } => { if let Err(e) = result { - println!("Error sending refreshes"); + print!("Error sending refreshes\n"); return Err(e.into()); } }, result = client_handle_messages(reader, events) => { if let Err(e) = result { - println!("Error handling messages"); - return Err(e); + print!("Error handling messages\n"); + return Err(e.into()); } }, } @@ -318,7 +276,6 @@ async fn client_main( async fn spawn_ssh( server: &str, sudo: bool, - log_filter: &str, ) -> Result<(tokio::process::Child, u16), std::io::Error> { let socks_port = { let listener = TcpListener::bind("127.0.0.1:0").await?; @@ -333,10 +290,7 @@ async fn spawn_ssh( if sudo { cmd.arg("sudo"); } - cmd.arg(format!("FWD_LOG={log_filter}")) - .arg("FWD_SEND_ANONYMOUS=1") - .arg("fwd") - .arg("--server"); + cmd.arg("fwd").arg("--server"); cmd.stdout(std::process::Stdio::piped()); cmd.stdin(std::process::Stdio::piped()); @@ -366,15 +320,13 @@ fn is_sigint(status: std::process::ExitStatus) -> bool { async fn client_connect_loop( remote: &str, sudo: bool, - log_filter: &str, events: mpsc::Sender, ) { loop { _ = events.send(ui::UIEvent::Disconnected).await; - let (mut child, socks_port) = spawn_ssh(remote, sudo, log_filter) - .await - .expect("failed to spawn"); + let (mut child, socks_port) = + spawn_ssh(remote, sudo).await.expect("failed to spawn"); let mut stderr = child .stderr @@ -395,14 +347,18 @@ async fn client_connect_loop( if let Err(e) = client_sync(&mut reader, &mut stderr).await { error!("Error synchronizing: {:?}", e); - if let Ok(status) = child.wait().await { - if is_sigint(status) { - return; - } else if let Some(127) = status.code() { - eprintln!( - "Cannot find `fwd` remotely, make sure it is installed" - ); + match child.wait().await { + Ok(status) => { + if is_sigint(status) { + return; + } else { + match status.code() { + Some(127) => eprintln!("Cannot find `fwd` remotely, make sure it is installed"), + _ => (), + }; + } } + Err(_) => (), }; tokio::time::sleep(tokio::time::Duration::from_secs(3)).await; @@ -428,18 +384,13 @@ async fn client_connect_loop( } } -pub async fn run_client(remote: &str, sudo: bool, log_filter: &str) { +pub async fn run_client(remote: &str, sudo: bool) { let (event_sender, event_receiver) = mpsc::channel(1024); _ = log::set_boxed_logger(ui::Logger::new(event_sender.clone())); log::set_max_level(LevelFilter::Info); - let server = if let Some((_user, server)) = remote.split_once("@") { - server - } else { - remote - }; let config = match config::load_config() { - Ok(config) => config.get(server), + Ok(config) => config.get(remote), Err(e) => { eprintln!("Error loading configuration: {:?}", e); return; @@ -451,7 +402,7 @@ pub async fn run_client(remote: &str, sudo: bool, log_filter: &str) { // Start the reconnect loop. tokio::select! { _ = ui.run() => (), - _ = client_connect_loop(remote, sudo, log_filter, event_sender) => () + _ = client_connect_loop(remote, sudo, event_sender) => () } } diff --git a/src/client/ui.rs b/src/client/ui.rs index c29f86a..efb0ca3 100644 --- a/src/client/ui.rs +++ b/src/client/ui.rs @@ -1,10 +1,6 @@ -use super::{ - client_listen, - config::{PortConfig, ServerConfig}, -}; +use super::{client_listen, config::ServerConfig}; use crate::message::PortDesc; use anyhow::Result; -use copypasta::{ClipboardContext, ClipboardProvider}; use crossterm::{ event::{Event, EventStream, KeyCode, KeyEvent, KeyModifiers}, execute, @@ -13,23 +9,23 @@ use crossterm::{ EnterAlternateScreen, LeaveAlternateScreen, }, }; -use log::{error, info, warn, Level, Metadata, Record}; -use ratatui::{ - backend::CrosstermBackend, +use log::{error, info, Level, Metadata, Record}; +use open; +use std::collections::vec_deque::VecDeque; +use std::collections::{HashMap, HashSet}; +use std::io::stdout; +use tokio::sync::mpsc; +use tokio::sync::oneshot; +use tokio_stream::StreamExt; +use tui::{ + backend::{Backend, CrosstermBackend}, layout::{Constraint, Direction, Layout, Margin, Rect}, - style::{Color, Modifier, Style}, + style::{Color, Style}, widgets::{ Block, Borders, List, ListItem, ListState, Row, Table, TableState, }, Frame, Terminal, }; -use std::collections::vec_deque::VecDeque; -use std::collections::{HashMap, HashSet}; -use std::io::stdout; -use std::sync::{Arc, Mutex}; -use tokio::sync::mpsc; -use tokio::sync::oneshot; -use tokio_stream::StreamExt; pub enum UIEvent { Connected(u16), @@ -37,7 +33,6 @@ pub enum UIEvent { ServerLine(String), LogLine(log::Level, String), Ports(Vec), - SetClipboard(String), } pub enum UIReturn { @@ -73,23 +68,9 @@ impl log::Log for Logger { fn flush(&self) {} } -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum State { - Enabled, - Broken, - Disabled, -} - -impl State { - fn boxed(self) -> Arc> { - Arc::new(Mutex::new(self)) - } -} - #[derive(Debug)] struct Listener { - state: std::sync::Arc>, - config: Option, + enabled: bool, stop: Option>, desc: Option, } @@ -100,89 +81,28 @@ impl Listener { desc: PortDesc, enabled: bool, ) -> Listener { - let mut listener = Listener { - state: if enabled { - State::Enabled.boxed() - } else { - State::Disabled.boxed() - }, - config: None, - stop: None, - desc: Some(desc), - }; - listener.start(socks_port); + let mut listener = Listener { enabled, stop: None, desc: Some(desc) }; + if enabled { + listener.start(socks_port); + } listener } - pub fn from_config(config: PortConfig) -> Self { - Listener { - state: if config.enabled { - State::Enabled.boxed() - } else { - State::Disabled.boxed() - }, - config: Some(config), - stop: None, - desc: None, - } - } - - #[cfg(test)] - pub fn state_ref(&self) -> std::sync::Arc> { - self.state.clone() - } - pub fn enabled(&self) -> bool { - self.state() == State::Enabled + self.enabled } - pub fn description(&self) -> &str { - if let Some(config) = self.config.as_ref() { - if let Some(description) = config.description.as_deref() { - return description; - } - } - - if let Some(port) = self.desc.as_ref() { - let desc = port.desc.as_str(); - return if desc.is_empty() { - "" - } else { - desc - }; - } - - "" - } - - pub fn is_anonymous(&self) -> bool { - // Anonynous ports are not configured and came from the server but - // had no description there. - self.config.is_none() - && match self.desc.as_ref() { - Some(desc) => desc.desc.is_empty(), - None => false, - } - } - - fn state(&self) -> State { - *self.state.lock().unwrap() - } - - pub fn toggle_enabled(&mut self, socks_port: Option) { - if self.state() == State::Disabled { - self.state = State::Enabled.boxed(); + pub fn set_enabled(&mut self, socks_port: Option, enabled: bool) { + if enabled { + self.enabled = true; self.start(socks_port); } else { - self.state = State::Disabled.boxed(); + self.enabled = false; self.stop = None; } } pub fn connect(&mut self, socks_port: Option, desc: PortDesc) { - // If we're just sitting idle and the port comes in from the remote - // server then we should become enabled. Otherwise we should become - // real, but disabled. self.desc = Some(desc); self.start(socks_port); } @@ -193,22 +113,19 @@ impl Listener { } pub fn start(&mut self, socks_port: Option) { - if self.enabled() { + if self.enabled { if let (Some(desc), Some(socks_port), None) = (&self.desc, socks_port, &self.stop) { info!("Starting port {port} to {socks_port}", port = desc.port); let (l, stop) = oneshot::channel(); let port = desc.port; - let state = self.state.clone(); tokio::spawn(async move { let result = tokio::select! { r = client_listen(port, socks_port) => r, _ = stop => Ok(()), }; if let Err(e) = result { - let mut sg = state.lock().unwrap(); - *sg = State::Broken; error!("Error listening on port {port}: {e:?}"); } else { info!("Stopped listening on port {port}"); @@ -221,6 +138,7 @@ impl Listener { } } +#[derive(Debug)] pub struct UI { events: mpsc::Receiver, ports: HashMap, @@ -233,22 +151,13 @@ pub struct UI { show_help: bool, alternate_screen: bool, raw_mode: bool, - show_anonymous: bool, - clipboard: Option, } impl UI { pub fn new(events: mpsc::Receiver, config: ServerConfig) -> UI { - let mut ports = HashMap::new(); - for (port, config) in config.iter() { - ports.insert(*port, Listener::from_config(config.clone())); - } - - let clipboard = ClipboardContext::new().ok(); - UI { events, - ports, + ports: HashMap::new(), socks_port: None, running: true, show_logs: false, @@ -258,8 +167,6 @@ impl UI { config, alternate_screen: false, raw_mode: false, - show_anonymous: true, - clipboard, } } @@ -313,7 +220,7 @@ impl UI { Ok(code) } - fn render_connected(&mut self, frame: &mut Frame) { + fn render_connected(&mut self, frame: &mut Frame) { let constraints = if self.show_logs { vec![Constraint::Percentage(50), Constraint::Percentage(50)] } else { @@ -323,7 +230,7 @@ impl UI { let chunks = Layout::default() .direction(Direction::Vertical) .constraints(constraints) - .split(frame.area()); + .split(frame.size()); self.render_ports(frame, chunks[0]); if self.show_logs { @@ -334,11 +241,9 @@ impl UI { } } - fn render_ports(&mut self, frame: &mut Frame, size: Rect) { - let enabled_port_style = Style::reset(); - let disabled_port_style = Style::reset().fg(Color::DarkGray); - let broken_port_style = - Style::reset().fg(Color::Red).add_modifier(Modifier::DIM); + fn render_ports(&mut self, frame: &mut Frame, size: Rect) { + let enabled_port_style = Style::default(); + let disabled_port_style = Style::default().fg(Color::DarkGray); let mut rows = Vec::new(); let ports = self.get_ui_ports(); @@ -346,22 +251,20 @@ impl UI { ports.iter().map(|p| format!("{p}")).collect(); for (index, port) in ports.into_iter().enumerate() { let listener = self.ports.get(&port).unwrap(); - if !self.should_render_listener(listener) { - continue; - } - - let (symbol, style) = match listener.state() { - State::Enabled => (" ✓ ", enabled_port_style), - State::Broken => (" ✗ ", broken_port_style), - State::Disabled => ("", disabled_port_style), - }; rows.push( Row::new(vec![ - symbol, - &*port_strings[index], - listener.description(), + if listener.enabled { " ✓ " } else { "" }, + &port_strings[index][..], + match &listener.desc { + Some(port_desc) => &port_desc.desc, + None => "", + }, ]) - .style(style), + .style(if listener.enabled { + enabled_port_style + } else { + disabled_port_style + }), ); } @@ -374,16 +277,17 @@ impl UI { Constraint::Length(size.width), ]; - let port_list = Table::new(rows, &widths) + let port_list = Table::new(rows) .header(Row::new(vec!["fwd", "Port", "Description"])) .block(Block::default().title("Ports").borders(Borders::ALL)) .column_spacing(1) + .widths(&widths) .highlight_symbol(">> "); frame.render_stateful_widget(port_list, size, &mut self.selection); } - fn render_help(&mut self, frame: &mut Frame) { + fn render_help(&mut self, frame: &mut Frame) { let keybindings = vec![ Row::new(vec!["↑ / k", "Move cursor up"]), Row::new(vec!["↓ / j", "Move cursor down"]), @@ -395,7 +299,6 @@ impl UI { Row::new(vec!["ESC / q", "Quit"]), Row::new(vec!["? / h", "Show this help text"]), Row::new(vec!["l", "Show fwd's logs"]), - Row::new(vec!["a", "Hide/show anonymous ports"]), ]; let border_lines = 3; @@ -403,10 +306,10 @@ impl UI { let help_popup_area = centered_rect( 65, keybindings.len() as u16 + border_lines, - frame.area(), + frame.size(), ); let inner_area = - help_popup_area.inner(Margin { vertical: 1, horizontal: 1 }); + help_popup_area.inner(&Margin { vertical: 1, horizontal: 1 }); let key_width = 7; let binding_width = inner_area.width.saturating_sub(key_width); @@ -414,16 +317,16 @@ impl UI { Constraint::Length(key_width), Constraint::Length(binding_width), ]; - let keybindings = Table::new(keybindings, keybindings_widths) + let keybindings = Table::new(keybindings) + .widths(keybindings_widths) .column_spacing(1) - .block(Block::default().title("Keys").borders(Borders::ALL)) - .style(Style::reset()); + .block(Block::default().title("Keys").borders(Borders::ALL)); // keybindings frame.render_widget(keybindings, inner_area); } - fn render_logs(&mut self, frame: &mut Frame, size: Rect) { + fn render_logs(&mut self, frame: &mut Frame, size: Rect) { let items: Vec<_> = self.lines.iter().map(|l| ListItem::new(&l[..])).collect(); @@ -431,7 +334,7 @@ impl UI { .block(Block::default().title("Log").borders(Borders::ALL)); let mut list_state = ListState::default(); - list_state.select(if !self.lines.is_empty() { + list_state.select(if self.lines.len() > 0 { Some(self.lines.len() - 1) } else { None @@ -441,7 +344,10 @@ impl UI { } fn connected(&self) -> bool { - self.socks_port.is_some() + match self.socks_port { + Some(_) => true, + None => false, + } } fn get_ui_ports(&self) -> Vec { @@ -456,7 +362,7 @@ impl UI { fn enable_disable_port(&mut self, port: u16) { if let Some(listener) = self.ports.get_mut(&port) { - listener.toggle_enabled(self.socks_port); + listener.set_enabled(self.socks_port, !listener.enabled()); } } @@ -479,7 +385,7 @@ impl UI { fn enter_alternate_screen(&mut self) -> Result<()> { if !self.alternate_screen { enable_raw_mode()?; - execute!(stdout(), EnterAlternateScreen, DisableLineWrap,)?; + execute!(stdout(), EnterAlternateScreen, DisableLineWrap)?; self.alternate_screen = true; } Ok(()) @@ -494,19 +400,6 @@ impl UI { Ok(()) } - fn toggle_show_anonymous(&mut self) { - self.show_anonymous = !self.show_anonymous; - } - - fn should_render_listener(&self, listener: &Listener) -> bool { - // Named/Configured ports are always rendered - !listener.is_anonymous() - // ...or we might be explicitly asked to render everything - || self.show_anonymous - // ...or the port might be enabled or errored - || listener.state() != State::Disabled - } - async fn handle_events(&mut self, console_events: &mut EventStream) { tokio::select! { ev = console_events.next() => self.handle_console_event(ev), @@ -530,14 +423,15 @@ impl UI { ev: Option>, ) { match ev { - Some(Ok(Event::Key( + Some(Ok(Event::Key(ev))) => match ev { KeyEvent { code: KeyCode::Esc, .. } | KeyEvent { code: KeyCode::Char('q'), .. } | KeyEvent { code: KeyCode::Char('?'), .. } - | KeyEvent { code: KeyCode::Char('h'), .. }, - ))) => { - self.show_help = false; - } + | KeyEvent { code: KeyCode::Char('h'), .. } => { + self.show_help = false; + } + _ => (), + }, Some(Ok(_)) => (), // Don't care about this event... Some(Err(_)) => (), // Hmmmmmm.....? None => (), // ....no events? what? @@ -575,7 +469,7 @@ impl UI { | KeyEvent { code: KeyCode::Char('k'), .. } => { let index = match self.selection.selected() { Some(i) => { - assert!(!self.ports.is_empty(), "We must have ports because we have a selection."); + assert!(self.ports.len() > 0, "We must have ports because we have a selection."); if i == 0 { Some(self.ports.len() - 1) } else { @@ -583,7 +477,7 @@ impl UI { } } None => { - if !self.ports.is_empty() { + if self.ports.len() > 0 { Some(0) } else { None @@ -596,11 +490,11 @@ impl UI { | KeyEvent { code: KeyCode::Char('j'), .. } => { let index = match self.selection.selected() { Some(i) => { - assert!(!self.ports.is_empty(), "We must have ports because we have a selection."); + assert!(self.ports.len() > 0, "We must have ports because we have a selection."); Some((i + 1) % self.ports.len()) } None => { - if !self.ports.is_empty() { + if self.ports.len() > 0 { Some(0) } else { None @@ -614,10 +508,6 @@ impl UI { _ = open::that(format!("http://127.0.0.1:{}/", p)); } } - KeyEvent { code: KeyCode::Char('a'), .. } => { - self.toggle_show_anonymous() - } - _ => (), }, Some(Ok(_)) => (), // Don't care about this event... @@ -654,22 +544,15 @@ impl UI { { listener.connect(self.socks_port, port_desc); } else { - assert!(!self.config.contains_key(port_desc.port)); - - // The server can send us these ports it knows nothing about. - // These might be dangerous to enable by default, so don't. - let enabled = if port_desc.desc.is_empty() { - false - } else { - self.config.auto() - }; + let config = self.config.get(port_desc.port); + info!("Port config {port_desc:?} -> {config:?}"); self.ports.insert( port_desc.port, Listener::from_desc( self.socks_port, port_desc, - enabled, + config.enabled, ), ); } @@ -709,18 +592,6 @@ impl UI { } self.lines.push_back(format!("[CLIENT] {line}")); } - Some(UIEvent::SetClipboard(contents)) => { - let length = contents.len(); - if let Some(clipboard) = self.clipboard.as_mut() { - if let Err(e) = clipboard.set_contents(contents) { - error!("Error setting clipboard contents: {e:#}"); - } else { - info!("Received clipboard contents ({length} bytes)"); - } - } else { - warn!("No clipboard available, contents discarded"); - } - } None => { self.running = false; } @@ -735,8 +606,7 @@ impl Drop for UI { } } -/// helper function to create a centered rect using up certain percentage of -/// the available rect `r` +/// helper function to create a centered rect using up certain percentage of the available rect `r` fn centered_rect(width_chars: u16, height_chars: u16, r: Rect) -> Rect { let left = r.left() + if width_chars > r.width { @@ -760,8 +630,6 @@ fn centered_rect(width_chars: u16, height_chars: u16, r: Rect) -> Rect { #[cfg(test)] mod tests { - use crate::client::config::PortConfig; - use super::*; use assert_matches::assert_matches; @@ -1026,376 +894,4 @@ mod tests { assert_eq!(centered.width, 10); assert_eq!(centered.height, 10); } - - #[test] - fn port_config_description_respected() { - let (sender, receiver) = mpsc::channel(64); - let mut config = ServerConfig::default(); - config.insert( - 8080, - PortConfig { - enabled: true, - description: Some("override".to_string()), - }, - ); - - let mut ui = UI::new(receiver, config); - - // There are ports... - ui.handle_internal_event(Some(UIEvent::Ports(vec![PortDesc { - port: 8080, - desc: "my-service".to_string(), - }]))); - - let listener = ui.ports.get(&8080).unwrap(); - assert_eq!(listener.description(), "override"); - - drop(sender); - } - - #[test] - fn port_config_enabled_respected() { - let (sender, receiver) = mpsc::channel(64); - let mut config = ServerConfig::default(); - config.insert( - 8080, - PortConfig { - enabled: false, - description: Some("override".to_string()), - }, - ); - - let mut ui = UI::new(receiver, config); - - // There are ports... - ui.handle_internal_event(Some(UIEvent::Ports(vec![PortDesc { - port: 8080, - desc: "my-service".to_string(), - }]))); - - let state = ui.ports.get(&8080).unwrap().state(); - assert_eq!(state, State::Disabled); - - drop(sender); - } - - #[test] - fn port_config_missing_but_still_there() { - let (sender, receiver) = mpsc::channel(64); - let mut config = ServerConfig::default(); - config.insert( - 8080, - PortConfig { - enabled: false, - description: Some("override".to_string()), - }, - ); - - let mut ui = UI::new(receiver, config); - - // There are no ports... - ui.handle_internal_event(Some(UIEvent::Ports(vec![]))); - - // But there should still be ports, man. - let listener = ui.ports.get(&8080).unwrap(); - assert_eq!(listener.state(), State::Disabled); - assert_eq!(listener.description(), "override"); - - drop(sender); - } - - #[test] - fn port_config_state_interactions() { - let (sender, receiver) = mpsc::channel(64); - let mut config = ServerConfig::default(); - config.insert(8080, PortConfig { enabled: false, description: None }); - config.insert(8081, PortConfig { enabled: true, description: None }); - - let mut ui = UI::new(receiver, config); - - // No ports have been received, make sure everything's in its default state. - assert_eq!(ui.ports.get(&8080).unwrap().state(), State::Disabled); - assert_eq!(ui.ports.get(&8081).unwrap().state(), State::Enabled); - - // 8080 shows up.... doesn't affect anything. - ui.handle_internal_event(Some(UIEvent::Ports(vec![PortDesc { - port: 8080, - desc: "python3".to_string(), - }]))); - assert_eq!(ui.ports.get(&8080).unwrap().state(), State::Disabled); - assert_eq!(ui.ports.get(&8081).unwrap().state(), State::Enabled); - - // 8081 shows up.... configured as enabled so it becomes "enabled" - ui.handle_internal_event(Some(UIEvent::Ports(vec![ - PortDesc { port: 8080, desc: "python3".to_string() }, - PortDesc { port: 8081, desc: "python3".to_string() }, - ]))); - assert_eq!(ui.ports.get(&8080).unwrap().state(), State::Disabled); - assert_eq!(ui.ports.get(&8081).unwrap().state(), State::Enabled); - - // 8082 shows up.... it should be enabled by default! - ui.handle_internal_event(Some(UIEvent::Ports(vec![ - PortDesc { port: 8080, desc: "python3".to_string() }, - PortDesc { port: 8081, desc: "python3".to_string() }, - PortDesc { port: 8082, desc: "python3".to_string() }, - ]))); - assert_eq!(ui.ports.get(&8080).unwrap().state(), State::Disabled); - assert_eq!(ui.ports.get(&8081).unwrap().state(), State::Enabled); - assert_eq!(ui.ports.get(&8082).unwrap().state(), State::Enabled); - - // 8081 goes away.... - ui.handle_internal_event(Some(UIEvent::Ports(vec![ - PortDesc { port: 8080, desc: "python3".to_string() }, - PortDesc { port: 8082, desc: "python3".to_string() }, - ]))); - assert_eq!(ui.ports.get(&8080).unwrap().state(), State::Disabled); - assert_eq!(ui.ports.get(&8081).unwrap().state(), State::Enabled); - assert_eq!(ui.ports.get(&8082).unwrap().state(), State::Enabled); - - // All gone, state resets itself. - ui.handle_internal_event(Some(UIEvent::Ports(vec![]))); - assert_eq!(ui.ports.get(&8080).unwrap().state(), State::Disabled); - assert_eq!(ui.ports.get(&8081).unwrap().state(), State::Enabled); - assert!(!ui.ports.contains_key(&8082)); - - drop(sender); - } - - #[test] - fn port_defaults_respected() { - let (sender, receiver) = mpsc::channel(64); - let config = ServerConfig::default(); - let mut ui = UI::new(receiver, config); - - ui.handle_internal_event(Some(UIEvent::Ports(vec![PortDesc { - port: 8080, - desc: "python3".to_string(), - }]))); - - let listener = ui.ports.get(&8080).unwrap(); - assert_eq!(listener.state(), State::Enabled); - assert_eq!(listener.description(), "python3"); - - drop(sender); - } - - #[test] - fn port_default_disabled_respected() { - let (sender, receiver) = mpsc::channel(64); - let mut config = ServerConfig::default(); - config.set_auto(false); - - let mut ui = UI::new(receiver, config); - - ui.handle_internal_event(Some(UIEvent::Ports(vec![PortDesc { - port: 8080, - desc: "python3".to_string(), - }]))); - - let listener = ui.ports.get(&8080).unwrap(); - assert_eq!(listener.state(), State::Disabled); - assert_eq!(listener.description(), "python3"); - - drop(sender); - } - - #[test] - fn empty_port_desc_disabled_by_default() { - let (sender, receiver) = mpsc::channel(64); - let config = ServerConfig::default(); - let mut ui = UI::new(receiver, config); - - ui.handle_internal_event(Some(UIEvent::Ports(vec![PortDesc { - port: 8080, - desc: "".to_string(), - }]))); - - let listener = ui.ports.get(&8080).unwrap(); - assert_eq!(listener.state(), State::Disabled); - assert_eq!(listener.description(), ""); - - drop(sender); - } - - #[test] - fn empty_port_desc_disabled_on_refresh() { - let (sender, receiver) = mpsc::channel(64); - let config = ServerConfig::default(); - let mut ui = UI::new(receiver, config); - - ui.handle_internal_event(Some(UIEvent::Ports(vec![PortDesc { - port: 8080, - desc: "".to_string(), - }]))); - - let listener = ui.ports.get(&8080).unwrap(); - assert_eq!(listener.state(), State::Disabled); - - // Just do it again, make sure we haven't broken the refresh path. - ui.handle_internal_event(Some(UIEvent::Ports(vec![PortDesc { - port: 8080, - desc: "".to_string(), - }]))); - - let listener = ui.ports.get(&8080).unwrap(); - assert_eq!(listener.state(), State::Disabled); - - drop(sender); - } - - #[test] - fn state_toggle_enable_disable() { - let (sender, receiver) = mpsc::channel(64); - let config = ServerConfig::default(); - let mut ui = UI::new(receiver, config); - - ui.handle_internal_event(Some(UIEvent::Ports(vec![PortDesc { - port: 8080, - desc: "rando".to_string(), - }]))); - - let listener = ui.ports.get_mut(&8080).unwrap(); - assert_eq!(listener.state(), State::Enabled); - - // Enabled -> Disabled - ui.enable_disable_port(8080); // FLIP! - let listener = ui.ports.get(&8080).unwrap(); - assert_eq!(listener.state(), State::Disabled); - - // Disabled -> Enabled - ui.enable_disable_port(8080); // FLIP! - let listener = ui.ports.get(&8080).unwrap(); - assert_eq!(listener.state(), State::Enabled); - - { - // Oh no it broke! - let state = listener.state_ref(); - let mut sg = state.lock().unwrap(); - *sg = State::Broken; - } - - let listener = ui.ports.get_mut(&8080).unwrap(); - assert_eq!(listener.state(), State::Broken); - - // Broken -> Disabled - ui.enable_disable_port(8080); - let listener = ui.ports.get_mut(&8080).unwrap(); - assert_eq!(listener.state(), State::Disabled); - - drop(sender); - } - - #[test] - fn listener_anonymous() { - let (sender, receiver) = mpsc::channel(64); - let mut config = ServerConfig::default(); - config.insert( - 8079, - PortConfig { - enabled: false, - description: Some("body once told me".to_string()), - }, - ); - - let mut ui = UI::new(receiver, config); - - ui.handle_internal_event(Some(UIEvent::Ports(vec![ - PortDesc { - port: 8080, - desc: "python3 blaster.py".to_string(), - }, - PortDesc { port: 8081, desc: "".to_string() }, - PortDesc { port: 8082, desc: "".to_string() }, - ]))); - - // (Pretend that 8082 broke.) - ui.ports.get_mut(&8082).unwrap().state = State::Broken.boxed(); - - let listener = ui.ports.get(&8079).unwrap(); - assert!( - !listener.is_anonymous(), - "Configured ports should not be anonymous" - ); - - let listener = ui.ports.get(&8080).unwrap(); - assert!( - !listener.is_anonymous(), - "Ports with descriptions should not be anonymous" - ); - - let listener = ui.ports.get(&8081).unwrap(); - assert!( - listener.is_anonymous(), - "Not configured, disabled, no description should be anonymous" - ); - - drop(sender); - } - - #[test] - fn render_anonymous() { - let (sender, receiver) = mpsc::channel(64); - let mut config = ServerConfig::default(); - config.insert( - 8079, - PortConfig { - enabled: false, - description: Some("body once told me".to_string()), - }, - ); - - let mut ui = UI::new(receiver, config); - - ui.handle_internal_event(Some(UIEvent::Ports(vec![ - PortDesc { - port: 8080, - desc: "python3 blaster.py".to_string(), - }, - PortDesc { port: 8081, desc: "".to_string() }, - PortDesc { port: 8082, desc: "".to_string() }, - PortDesc { port: 8083, desc: "".to_string() }, - ]))); - - // (Pretend that 8082 broke.) - ui.ports.get_mut(&8082).unwrap().state = State::Broken.boxed(); - - // No showing anonymous ports! - ui.show_anonymous = false; - - let listener = ui.ports.get(&8079).unwrap(); - assert!( - ui.should_render_listener(listener), - "Configured ports should always be rendered" - ); - - let listener = ui.ports.get(&8080).unwrap(); - assert!( - ui.should_render_listener(listener), - "Ports with descriptions should be rendered" - ); - - let listener = ui.ports.get(&8081).unwrap(); - assert!( - !ui.should_render_listener(listener), - "Not configured, disabled, no description should be hidden" - ); - - ui.enable_disable_port(8081); - - let listener = ui.ports.get(&8081).unwrap(); - assert_eq!(listener.state(), State::Enabled); - assert!( - ui.should_render_listener(listener), - "Enabled ports should be rendered" - ); - - let listener = ui.ports.get(&8082).unwrap(); - assert_eq!(listener.state(), State::Broken); - assert!( - ui.should_render_listener(listener), - "Broken ports should be rendered" - ); - - drop(sender); - } } diff --git a/src/lib.rs b/src/lib.rs index d90a75e..5eaba0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,8 @@ +mod browse; mod client; mod message; -mod reverse; -pub mod server; - -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); -pub const REV: &str = env!("REPO_REV"); -pub const DIRTY: &str = env!("REPO_DIRTY"); +mod server; +pub use browse::browse_url; pub use client::run_client; -pub use reverse::browse_url; -pub use reverse::clip_file; pub use server::run_server; diff --git a/src/main.rs b/src/main.rs index a6201e2..667ff1c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,11 @@ // TODO: An actual proper command line parsing use indoc::indoc; +const VERSION: &str = env!("CARGO_PKG_VERSION"); + fn usage() { println!(indoc! {" -usage: fwd [options] ( | browse | clip []) +usage: fwd [options] ( | browse ) To connect a client to a server that has an `fwd` installed in its path, run `fwd ` on the client, where is the name of the server to @@ -12,22 +14,14 @@ connect to. On a server that already has a client connected to it you can use `fwd browse ` to open `` in the default browser of the client. -On a server that already has a client connected to it you can use `fwd clip -` -to read stdin and send it to the clipboard of the client, or `fwd clip ` -to send the the contents of `file`. - Options: --version Print the version of fwd and exit --sudo, -s Run the server side of fwd with `sudo`. This allows the - client to identify the ports that are open by processes - being run under other accounts (e.g., docker containers - being run as root), but requires sudo access on the server - and *might* end up forwarding ports that you do not want + client to forward ports that are open by processes being + run under other accounts (e.g., docker containers being + run as root), but requires sudo access on the server and + *might* end up forwarding ports that you do not want forwarded (e.g., port 22 for sshd, or port 53 for systemd.) - --log-filter FILTER - Set remote server's log level. Default is `warn`. Supports - all of Rust's env_logger filter syntax, e.g. - `--log-filter=fwd::trace`. "}); } @@ -36,20 +30,17 @@ enum Args { Help, Version, Server, - Client(String, bool, String), + Client(String, bool), Browse(String), - Clip(String), Error, } fn parse_args(args: Vec) -> Args { let mut server = None; let mut sudo = None; - let mut log_filter = None; let mut rest = Vec::new(); - let mut arg_iter = args.into_iter().skip(1); - while let Some(arg) = arg_iter.next() { + for arg in args.into_iter().skip(1) { if arg == "--help" || arg == "-?" || arg == "-h" { return Args::Help; } else if arg == "--version" { @@ -58,78 +49,30 @@ fn parse_args(args: Vec) -> Args { server = Some(true) } else if arg == "--sudo" || arg == "-s" { sudo = Some(true) - } else if arg.starts_with("--log-filter") { - if arg.contains('=') { - log_filter = Some(arg.split('=').nth(1).unwrap().to_owned()); - } else if let Some(arg) = arg_iter.next() { - log_filter = Some(arg); - } else { - return Args::Error; - } } else { rest.push(arg) } } if server.unwrap_or(false) { - if rest.is_empty() && sudo.is_none() { + if rest.len() == 0 && sudo.is_none() { Args::Server } else { Args::Error } - } else if rest.is_empty() { - Args::Error - } else if rest[0] == "browse" { + } else if rest.len() > 1 && rest[0] == "browse" { if rest.len() == 2 { Args::Browse(rest[1].to_string()) - } else if rest.len() == 1 { - Args::Client( - rest[0].to_string(), - sudo.unwrap_or(false), - log_filter.unwrap_or("warn".to_owned()), - ) - } else { - Args::Error - } - } else if rest[0] == "clip" { - if rest.len() == 1 { - Args::Client( - rest[0].to_string(), - sudo.unwrap_or(false), - log_filter.unwrap_or("warn".to_owned()), - ) - } else if rest.len() == 2 { - Args::Clip(rest[1].to_string()) } else { Args::Error } } else if rest.len() == 1 { - Args::Client( - rest[0].to_string(), - sudo.unwrap_or(false), - log_filter.unwrap_or("warn".to_owned()), - ) + Args::Client(rest[0].to_string(), sudo.unwrap_or(false)) } else { Args::Error } } -async fn browse_url(url: &str) { - if let Err(e) = fwd::browse_url(url).await { - eprintln!("Unable to open {url}"); - eprintln!("{:#}", e); - std::process::exit(1); - } -} - -async fn clip_file(file: String) { - if let Err(e) = fwd::clip_file(&file).await { - eprintln!("Unable to copy to the clipboard"); - eprintln!("{:#}", e); - std::process::exit(1); - } -} - #[tokio::main] async fn main() { match parse_args(std::env::args().collect()) { @@ -137,19 +80,16 @@ async fn main() { usage(); } Args::Version => { - println!("fwd {} (rev {}{})", fwd::VERSION, fwd::REV, fwd::DIRTY); + println!("fwd {VERSION}"); } Args::Server => { fwd::run_server().await; } Args::Browse(url) => { - browse_url(&url).await; + fwd::browse_url(&url).await; } - Args::Clip(file) => { - clip_file(file).await; - } - Args::Client(server, sudo, log_filter) => { - fwd::run_client(&server, sudo, &log_filter).await; + Args::Client(server, sudo) => { + fwd::run_client(&server, sudo).await; } Args::Error => { usage(); @@ -165,7 +105,8 @@ mod tests { // Goldarn it. fn args(x: &[&str]) -> Vec { - let mut vec: Vec = x.iter().map(|a| a.to_string()).collect(); + let mut vec: Vec = + x.into_iter().map(|a| a.to_string()).collect(); vec.insert(0, "fwd".to_string()); vec } @@ -190,7 +131,6 @@ mod tests { fn errors() { assert_arg_parse!(&[], Args::Error); assert_arg_parse!(&["browse", "google.com", "what"], Args::Error); - assert_arg_parse!(&["clip", "a.txt", "b.txt"], Args::Error); assert_arg_parse!(&["a", "b"], Args::Error); assert_arg_parse!(&["--server", "something"], Args::Error); assert_arg_parse!(&["--server", "--sudo"], Args::Error); @@ -199,39 +139,12 @@ mod tests { #[test] fn client() { - assert_arg_parse!(&["foo.com"], Args::Client(_, false, _)); - assert_arg_parse!(&["a"], Args::Client(_, false, _)); - assert_arg_parse!(&["browse"], Args::Client(_, false, _)); - assert_arg_parse!(&["clip"], Args::Client(_, false, _)); - assert_arg_parse!(&["foo.com", "--sudo"], Args::Client(_, true, _)); - assert_arg_parse!(&["a", "-s"], Args::Client(_, true, _)); - assert_arg_parse!(&["-s", "browse"], Args::Client(_, true, _)); - assert_arg_parse!(&["-s", "clip"], Args::Client(_, true, _)); - - assert_client_parse(&["a"], "a", false, "warn"); - assert_client_parse(&["a", "--log-filter", "info"], "a", false, "info"); - assert_client_parse(&["a", "--log-filter=info"], "a", false, "info"); - assert_client_parse( - &["a", "--sudo", "--log-filter=info"], - "a", - true, - "info", - ); - } - - fn assert_client_parse( - x: &[&str], - server: &str, - sudo: bool, - log_filter: &str, - ) { - let args = parse_args(args(x)); - assert_matches!(args, Args::Client(_, _, _)); - if let Args::Client(s, sdo, lf) = args { - assert_eq!(s, server); - assert_eq!(sdo, sudo); - assert_eq!(lf, log_filter); - } + assert_arg_parse!(&["foo.com"], Args::Client(_, false)); + assert_arg_parse!(&["a"], Args::Client(_, false)); + assert_arg_parse!(&["browse"], Args::Client(_, false)); + assert_arg_parse!(&["foo.com", "--sudo"], Args::Client(_, true)); + assert_arg_parse!(&["a", "-s"], Args::Client(_, true)); + assert_arg_parse!(&["-s", "browse"], Args::Client(_, true)); } #[test] @@ -239,12 +152,6 @@ mod tests { assert_arg_parse!(&["--server"], Args::Server); } - #[test] - fn clip() { - assert_arg_parse!(&["clip", "garbage"], Args::Clip(_)); - assert_arg_parse!(&["clip", "-"], Args::Clip(_)); - } - #[test] fn browse() { assert_arg_parse!(&["browse", "google.com"], Args::Browse(_)); diff --git a/src/message.rs b/src/message.rs index f347727..eb5efd8 100644 --- a/src/message.rs +++ b/src/message.rs @@ -57,11 +57,6 @@ pub enum Message { // Browse a thing Browse(String), - - // Send data to the remote clipboard - ClipStart(u64), - ClipData(u64, Vec), - ClipEnd(u64), } impl Message { @@ -99,7 +94,8 @@ impl Message { result.put_u16(port.port); // Port descriptions can be long, let's make sure they're not. - let sliced = slice_up_to(&port.desc, u16::MAX.into()); + let sliced = + slice_up_to(&port.desc, u16::max_value().into()); put_string(result, sliced); } } @@ -107,19 +103,6 @@ impl Message { result.put_u8(0x07); put_string(result, url); } - ClipStart(id) => { - result.put_u8(0x08); - result.put_u64(*id); - } - ClipData(id, data) => { - result.put_u8(0x09); - result.put_u64(*id); - put_data(result, data); - } - ClipEnd(id) => { - result.put_u8(0x0A); - result.put_u64(*id); - } }; } @@ -149,20 +132,7 @@ impl Message { Ok(Ports(ports)) } 0x07 => Ok(Browse(get_string(cursor)?)), - 0x08 => { - let id = get_u64(cursor)?; - Ok(ClipStart(id)) - } - 0x09 => { - let id = get_u64(cursor)?; - let data = get_data(cursor)?; - Ok(Self::ClipData(id, data)) - } - 0x0A => { - let id = get_u64(cursor)?; - Ok(ClipEnd(id)) - } - b => Err(Error::Unknown(b)), + b => Err(Error::Unknown(b).into()), } } } @@ -181,13 +151,6 @@ fn get_u16(cursor: &mut Cursor<&[u8]>) -> Result { Ok(cursor.get_u16()) } -fn get_u64(cursor: &mut Cursor<&[u8]>) -> Result { - if cursor.remaining() < 8 { - return Err(Error::Incomplete); - } - Ok(cursor.get_u64()) -} - fn get_bytes(cursor: &mut Cursor<&[u8]>, length: usize) -> Result { if cursor.remaining() < length { return Err(Error::Incomplete); @@ -219,22 +182,6 @@ fn put_string(target: &mut T, str: &str) { target.put_slice(str.as_bytes()); } -fn put_data(target: &mut T, data: &[u8]) { - target.put_u16(data.len().try_into().expect("Buffer is too long")); - target.put_slice(data); -} - -fn get_data(cursor: &mut Cursor<&[u8]>) -> Result> { - let length = get_u16(cursor)?; - if cursor.remaining() < length.into() { - return Err(Error::Incomplete); - } - - let mut data: Vec = vec![0; length.into()]; - cursor.copy_to_slice(&mut data); - Ok(data) -} - // ---------------------------------------------------------------------------- // Message IO @@ -246,14 +193,14 @@ impl MessageWriter { pub fn new(writer: T) -> MessageWriter { MessageWriter { writer } } - pub async fn write(&mut self, msg: Message) -> Result<()> { + pub async fn write(self: &mut Self, msg: Message) -> Result<()> { // TODO: Optimize buffer usage please this is bad // eprintln!("? {:?}", msg); - let buffer = msg.encode(); + let mut buffer = msg.encode(); self.writer .write_u32(buffer.len().try_into().expect("Message too large")) .await?; - self.writer.write_all(&buffer).await?; + self.writer.write_all(&mut buffer).await?; self.writer.flush().await?; Ok(()) } @@ -267,8 +214,7 @@ impl MessageReader { pub fn new(reader: T) -> MessageReader { MessageReader { reader } } - - pub async fn read(&mut self) -> Result { + pub async fn read(self: &mut Self) -> Result { let frame_length = self.reader.read_u32().await?; let mut data = vec![0; frame_length.try_into().unwrap()]; self.reader.read_exact(&mut data).await?; @@ -337,12 +283,6 @@ mod message_tests { }, ])); assert_round_trip(Browse("https://google.com/".to_string())); - assert_round_trip(ClipStart(0x1234567890ABCDEF)); - assert_round_trip(ClipData( - 0x1234567890ABCDEF, - vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - )); - assert_round_trip(ClipEnd(0x1234567890ABCDEF)); } #[test] diff --git a/src/reverse.rs b/src/reverse.rs deleted file mode 100644 index 971d274..0000000 --- a/src/reverse.rs +++ /dev/null @@ -1,88 +0,0 @@ -use anyhow::Result; -use rand::random; -use tokio::io::{AsyncRead, AsyncReadExt}; - -#[cfg(target_family = "unix")] -mod unix; - -#[cfg(target_family = "unix")] -pub use unix::{handle_reverse_connections, ReverseConnection}; - -use crate::message::Message; - -#[cfg(not(target_family = "unix"))] -pub struct ReverseConnection {} - -#[cfg(not(target_family = "unix"))] -impl ReverseConnection { - pub async fn new() -> Result { - use anyhow::anyhow; - Err(anyhow!( - "Server-side operations are not supported on this platform" - )) - } - - pub async fn send(&mut self, _message: Message) -> Result<()> { - use anyhow::anyhow; - Err(anyhow!( - "Server-side operations are not supported on this platform" - )) - } -} - -#[cfg(not(target_family = "unix"))] -pub async fn handle_reverse_connections( - _messages: tokio::sync::mpsc::Sender, -) -> Result<()> { - std::future::pending().await -} - -#[inline] -pub async fn browse_url(url: &str) -> Result<()> { - ReverseConnection::new() - .await? - .send(Message::Browse(url.to_string())) - .await -} - -async fn clip_reader(reader: &mut T) -> Result<()> { - let mut connection = ReverseConnection::new().await?; - let clip_id: u64 = random(); - connection.send(Message::ClipStart(clip_id)).await?; - - let mut count = 0; - let mut buf = vec![0; 1024]; - loop { - let read = reader.read(&mut buf[count..]).await?; - if read == 0 { - break; - } - count += read; - if count == buf.len() { - connection.send(Message::ClipData(clip_id, buf)).await?; - buf = vec![0; 1024]; - count = 0; - } - } - - if count > 0 { - buf.resize(count, 0); - connection.send(Message::ClipData(clip_id, buf)).await?; - } - - connection.send(Message::ClipEnd(clip_id)).await?; - Ok(()) -} - -#[inline] -pub async fn clip_file(file: &str) -> Result<()> { - if file == "-" { - let mut stdin = tokio::io::stdin(); - clip_reader(&mut stdin).await?; - } else { - let mut file = tokio::fs::File::open(file).await?; - clip_reader(&mut file).await?; - } - - Ok(()) -} diff --git a/src/server/mod.rs b/src/server/mod.rs index db56ce2..1b1f1b2 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,11 +1,11 @@ +use crate::browse::handle_browser_open; use crate::message::{Message, MessageReader, MessageWriter}; -use crate::reverse::handle_reverse_connections; use anyhow::Result; use log::{error, warn}; use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, BufWriter}; use tokio::sync::mpsc; -pub mod refresh; +mod refresh; // We drive writes through an mpsc queue, because we not only handle requests // and responses from the client (refresh ports and the like) but also need @@ -13,7 +13,7 @@ pub mod refresh; async fn write_driver( messages: &mut mpsc::Receiver, writer: &mut MessageWriter, -) { +) -> () { while let Some(m) = messages.recv().await { writer.write(m).await.expect("Failed to write the message") } @@ -24,38 +24,15 @@ async fn server_loop( reader: &mut MessageReader, writer: &mut mpsc::Sender, ) -> Result<()> { - // NOTE: The client needs to opt in to getting anonymous ports because it - // does not feel safe to automatically enable port forwarding by default - // for random system ports. The way we keep it from being unsafe is that - // the client leaves anonymous ports disabled by default. Older clients - // did not do this, and so we cannot send older clients anonymous ports. - let send_anonymous = std::env::var("FWD_SEND_ANONYMOUS") - .map(|v| v == "1") - .unwrap_or(false); - // The first message we send must be an announcement. writer.send(Message::Hello(0, 2, vec![])).await?; - let mut version_reported = false; + loop { use Message::*; match reader.read().await? { Ping => (), Refresh => { - // Just log the version, if we haven't yet. We do this extra - // work to avoid spamming the log, but we wait until we - // receive the first message to be sure that the client is in - // a place to display our logging properly. - if !version_reported { - eprintln!( - "fwd server {} (rev {}{})", - crate::VERSION, - crate::REV, - crate::DIRTY - ); - version_reported = true; - } - - let ports = match refresh::get_entries(send_anonymous).await { + let ports = match refresh::get_entries() { Ok(ports) => ports, Err(e) => { error!("Error scanning: {:?}", e); @@ -100,15 +77,11 @@ async fn server_main< tokio::select! { _ = write_driver(&mut receiver, &mut writer) => Ok(()), r = server_loop(&mut reader, &mut sender) => r, - r = handle_reverse_connections(browse_sender) => r, + r = handle_browser_open(browse_sender) => r, } } pub async fn run_server() { - env_logger::Builder::from_env( - env_logger::Env::new().filter_or("FWD_LOG", "warn"), - ) - .init(); let stdin = tokio::io::stdin(); let stdout = tokio::io::stdout(); if let Err(e) = server_main(stdin, stdout).await { @@ -123,7 +96,7 @@ mod tests { use tokio::io::{AsyncReadExt, DuplexStream}; async fn sync(client_read: &mut DuplexStream) { - println!("[client] Waiting for server sync..."); + print!("[client] Waiting for server sync...\n"); for _ in 0..8 { let b = client_read .read_u8() @@ -133,7 +106,7 @@ mod tests { } let mut reader = MessageReader::new(client_read); - println!("[client] Reading first message..."); + print!("[client] Reading first message...\n"); let msg = reader.read().await.expect("Error reading first message"); assert_matches!(msg, Message::Hello(0, 2, _)); } diff --git a/src/server/refresh.rs b/src/server/refresh.rs index a1a3d05..e21cf6f 100644 --- a/src/server/refresh.rs +++ b/src/server/refresh.rs @@ -1,53 +1,65 @@ -use anyhow::Result; -#[cfg_attr(not(target_os = "linux"), allow(unused))] -use log::error; -use log::warn; -use std::collections::HashMap; - use crate::message::PortDesc; +use anyhow::Result; + +#[cfg(not(target_os = "linux"))] +pub fn get_entries() -> Result> { + use anyhow::bail; + bail!("Not supported on this operating system"); +} #[cfg(target_os = "linux")] -mod procfs; +pub fn get_entries() -> Result> { + use procfs::process::FDTarget; + use std::collections::HashMap; -#[cfg(unix)] -pub mod docker; + let all_procs = procfs::process::all_processes()?; -pub async fn get_entries(_send_anonymous: bool) -> Result> { - #[cfg_attr(not(target_os = "linux"), allow(unused_mut))] - let mut attempts = 0; + // build up a map between socket inodes and process stat info. Ignore any + // error we encounter as it probably means we have no access to that + // process or something. + let mut map: HashMap = HashMap::new(); + for p in all_procs { + if let Ok(process) = p { + if !process.is_alive() { + continue; // Ignore zombies. + } - #[cfg_attr(not(target_os = "linux"), allow(unused_mut))] - let mut result: HashMap = HashMap::new(); - - #[cfg(unix)] - { - attempts += 1; - match docker::get_entries().await { - Ok(m) => { - for (p, d) in m { - result.entry(p).or_insert(d); + if let (Ok(fds), Ok(cmd)) = (process.fd(), process.cmdline()) { + for fd in fds { + if let Ok(fd) = fd { + if let FDTarget::Socket(inode) = fd.target { + map.insert(inode, cmd.join(" ")); + } + } } } - Err(e) => error!("Error reading from docker: {e:?}"), } } - #[cfg(target_os = "linux")] - { - attempts += 1; - match procfs::get_entries(_send_anonymous) { - Ok(m) => { - for (p, d) in m { - result.entry(p).or_insert(d); - } + let mut h: HashMap = HashMap::new(); + + // Go through all the listening IPv4 and IPv6 sockets and take the first + // instance of listening on each port *if* the address is loopback or + // unspecified. (TODO: Do we want this restriction really?) + let tcp = procfs::net::tcp()?; + let tcp6 = procfs::net::tcp6()?; + for tcp_entry in tcp.into_iter().chain(tcp6) { + if tcp_entry.state == procfs::net::TcpState::Listen + && (tcp_entry.local_address.ip().is_loopback() + || tcp_entry.local_address.ip().is_unspecified()) + && !h.contains_key(&tcp_entry.local_address.port()) + { + if let Some(cmd) = map.get(&tcp_entry.inode) { + h.insert( + tcp_entry.local_address.port(), + PortDesc { + port: tcp_entry.local_address.port(), + desc: cmd.clone(), + }, + ); } - Err(e) => error!("Error reading procfs: {e:?}"), } } - if attempts == 0 { - warn!("Port scanning is not supported for this server"); - } - - Ok(result.into_values().collect()) + Ok(h.into_values().collect()) } diff --git a/src/server/refresh/docker.rs b/src/server/refresh/docker.rs deleted file mode 100644 index 06d4fff..0000000 --- a/src/server/refresh/docker.rs +++ /dev/null @@ -1,1029 +0,0 @@ -use anyhow::{bail, Context, Result}; -use log::trace; -use std::collections::HashMap; -use tokio::io::{ - AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, -}; - -use crate::message::PortDesc; - -pub const DEFAULT_DOCKER_HOST: &str = "unix:///var/run/docker.sock"; - -async fn list_containers_with_connection(stream: T) -> Result> -where - T: AsyncRead + AsyncWrite + Unpin, -{ - // Send this one exact request. (Who needs an HTTP library?) - const DOCKER_LIST_CONTAINERS: &[u8] = b"\ -GET /containers/json HTTP/1.1\r\n\ -Host: localhost\r\n\ -User-Agent: fwd/1.0\r\n\ -Accept: */*\r\n\ -\r\n"; - - let mut stream = tokio::io::BufStream::new(stream); - stream.write_all(DOCKER_LIST_CONTAINERS).await?; - stream.flush().await?; - - // Check the HTTP response. - let mut line = String::new(); - stream.read_line(&mut line).await?; - trace!("[docker] {}", &line.trim_end()); - let parts: Vec<&str> = line.split(" ").collect(); - if parts.len() < 2 || parts[1] != "200" { - bail!("Error response from docker: {line:?}"); - } - - // Process the headers; all we really care about is content-length or content-encoding. - let mut content_length: Option = None; - let mut chunked = false; - loop { - line.clear(); - stream.read_line(&mut line).await?; - trace!("[docker] {}", line.trim_end()); - if line.trim().is_empty() { - break; - } - line.make_ascii_lowercase(); - if let Some(rest) = line.strip_prefix("content-length: ") { - content_length = Some(rest.trim().parse()?); - } - if let Some(rest) = line.strip_prefix("transfer-encoding: ") { - chunked = rest.trim() == "chunked"; - } - } - - // Read the JSON response. - let mut response_buffer = vec![0; content_length.unwrap_or(0)]; - if content_length.is_some() { - stream.read_exact(&mut response_buffer).await?; - } else if chunked { - // Docker will send a chunked encoding if the response seems too big to do - // all at once. I don't know the heuristic it uses but we need to deal with - // it. Fortunately chunked encoding is not too bad? - loop { - line.clear(); - stream.read_line(&mut line).await?; - // This is the hex length of the thing. - let Some(chunk_length) = line.split(";").next() else { - bail!("Can't make sense of chunk length line: {line:?}"); - }; - let Ok(chunk_length) = - usize::from_str_radix(chunk_length.trim(), 16) - else { - bail!("Cannot interpret chunk length '{chunk_length}' as hex (Full line: {line:?})"); - }; - if chunk_length > 0 { - let old_length = response_buffer.len(); - let new_length = old_length + chunk_length; - response_buffer.resize(new_length, 0); - stream - .read_exact(&mut response_buffer[old_length..new_length]) - .await?; - } - - let mut eol: [u8; 2] = [0, 0]; - stream.read_exact(&mut eol).await?; - if eol[0] != b'\r' || eol[1] != b'\n' { - bail!("Mal-formed end-of-chunk marker from server"); - } - if chunk_length == 0 { - break; // All done. - } - } - } else { - trace!("Docker did not send a content_length, just reading to the end"); - stream.read_to_end(&mut response_buffer).await?; - } - - if log::log_enabled!(log::Level::Trace) { - match std::str::from_utf8(&response_buffer) { - Ok(s) => trace!("[docker][{}b] {}", s.len(), s), - Err(_) => trace!( - "[docker][{}b, raw] {:?}", - response_buffer.len(), - &response_buffer - ), - } - } - - // Done with the stream. - Ok(response_buffer) -} - -async fn list_containers() -> Result> { - let host = std::env::var("DOCKER_HOST") - .unwrap_or_else(|_| DEFAULT_DOCKER_HOST.to_string()); - trace!("[docker] Connecting to {host}"); - match host { - h if h.starts_with("unix://") => { - let socket_path = &h[7..]; - let socket = tokio::net::UnixStream::connect(socket_path).await?; - list_containers_with_connection(socket).await - } - h if h.starts_with("tcp://") => { - let host_port = &h[6..]; // TODO: Routing to sub-paths? - let socket = tokio::net::TcpStream::connect(host_port).await?; - list_containers_with_connection(socket).await - } - h if h.starts_with("http://") => { - let host_port = &h[7..]; // TODO: Routing to sub-paths? - let socket = tokio::net::TcpStream::connect(host_port).await?; - list_containers_with_connection(socket).await - } - _ => bail!("Unsupported docker host: {host}"), - } -} - -#[derive(Debug, PartialEq)] -pub enum JsonValue { - Null, - True, - False, - Number(f64), - String(String), - Object(HashMap), - Array(Vec), -} - -/// If the characters at `chars` match the characters in `prefix`, consume -/// those and return true. Otherwise, return false and do not advance the -/// iterator. -fn matches(chars: &mut std::str::Chars, prefix: &str) -> bool { - let backup = chars.clone(); - for c in prefix.chars() { - if chars.next() != Some(c) { - *chars = backup; - return false; - } - } - true -} - -impl JsonValue { - pub fn parse(blob: &[u8]) -> Result { - Self::parse_impl(blob).with_context(|| { - match std::str::from_utf8(blob) { - Ok(s) => format!("Failed to parse {} bytes: '{}'", s.len(), s), - Err(_) => { - format!( - "Failed to parse {} bytes (not utf-8): {:?}", - blob.len(), - blob - ) - } - } - }) - } - - fn parse_impl(blob: &[u8]) -> Result { - enum Tok { - Val(JsonValue), - StartObject, - StartArray, - } - - let mut stack = Vec::new(); - let mut i = 0; - while i < blob.len() { - match blob[i] { - b'n' => { - i += 4; - stack.push(Tok::Val(JsonValue::Null)); - } - b't' => { - i += 4; - stack.push(Tok::Val(JsonValue::True)); - } - b'f' => { - i += 5; - stack.push(Tok::Val(JsonValue::False)); - } - b'{' => { - i += 1; - stack.push(Tok::StartObject); - } - b'}' => { - i += 1; - let mut values = HashMap::new(); - loop { - match stack.pop() { - None => bail!("unexpected object terminator"), - Some(Tok::StartObject) => break, - Some(Tok::StartArray) => { - bail!("unterminated array") - } - Some(Tok::Val(v)) => match stack.pop() { - None => bail!( - "unexpected object terminator (mismatch)" - ), - Some(Tok::StartObject) => { - bail!("mismatch item count") - } - Some(Tok::StartArray) => { - bail!("unterminated array") - } - Some(Tok::Val(JsonValue::String(k))) => { - values.insert(k, v); - } - Some(Tok::Val(_)) => { - bail!("object keys must be strings") - } - }, - } - } - stack.push(Tok::Val(JsonValue::Object(values))); - } - b'[' => { - i += 1; - stack.push(Tok::StartArray); - } - b']' => { - i += 1; - let mut values = Vec::new(); - loop { - match stack.pop() { - None => bail!("unexpected array terminator"), - Some(Tok::StartObject) => { - bail!("unterminated object") - } - Some(Tok::StartArray) => break, - Some(Tok::Val(v)) => values.push(v), - } - } - values.reverse(); - stack.push(Tok::Val(JsonValue::Array(values))); - } - b'"' => { - i += 1; - let start = i; - while i < blob.len() { - if blob[i] == b'"' { - break; - } - if blob[i] == b'\\' { - i += 1; - } - i += 1; - } - if i >= blob.len() { - bail!("Unterminated string at {i}"); - } - assert_eq!(blob[i], b'"'); - - let source = String::from_utf8_lossy(&blob[start..i]); - let mut chars = source.chars(); - i += 1; // Consume the final quote. - - let mut value = String::new(); - while let Some(c) = chars.next() { - if c == '\\' { - match chars.next().expect("mismatched escape") { - '"' => value.push('"'), - '\\' => value.push('\\'), - '/' => value.push('/'), - 'b' => value.push('\x08'), - 'f' => value.push('\x0C'), - 'n' => value.push('\n'), - 'r' => value.push('\r'), - 't' => value.push('\t'), - 'u' => { - // 4 hex to a 16bit number. - // - // This is complicated because it might - // be the first part of a surrogate pair, - // which is a utf-16 thing that we should - // decode properly. (Hard to think of an - // acceptable way to cheat this.) So we - // buffer all codes we can find into a - // vector of u16s and then use - // std::char's `decode_utf16` to get the - // final string. We could do this with - // fewer allocations if we cared more. - let mut temp = String::with_capacity(4); - let mut utf16 = Vec::new(); - loop { - temp.clear(); - for _ in 0..4 { - let Some(c) = chars.next() else { - bail!("not enough chars in unicode escape") - }; - temp.push(c); - } - let code = - u16::from_str_radix(&temp, 16)?; - utf16.push(code); - - // `matches` only consumes on a match... - if !matches(&mut chars, "\\u") { - break; - } - } - - value.extend( - char::decode_utf16(utf16).map(|r| { - r.unwrap_or( - char::REPLACEMENT_CHARACTER, - ) - }), - ); - } - _ => bail!("Invalid json escape"), - } - } else { - value.push(c); - } - } - - stack.push(Tok::Val(JsonValue::String(value))); - } - b',' => i += 1, // Value separator in object or array - b':' => i += 1, // Key/Value separator - x if x.is_ascii_whitespace() => i += 1, - x if x == b'-' || x.is_ascii_digit() => { - let start = i; - while i < blob.len() { - match blob[i] { - b' ' | b'\t' | b'\r' | b'\n' | b'{' | b'}' - | b'[' | b']' | b',' | b':' => { - break; - } - _ => i += 1, - } - } - let number: f64 = - std::str::from_utf8(&blob[start..i])?.parse()?; - stack.push(Tok::Val(JsonValue::Number(number))); - } - - x => bail!("Invalid json value start byte {x}"), - } - } - - match stack.pop() { - Some(Tok::Val(v)) => Ok(v), - Some(Tok::StartObject) => bail!("unterminated object"), - Some(Tok::StartArray) => bail!("unterminated array"), - None => bail!("No JSON found in input"), - } - } - - pub fn as_array(&self) -> Option<&[JsonValue]> { - match self { - JsonValue::Array(v) => Some(v), - _ => None, - } - } - - pub fn as_object(&self) -> Option<&HashMap> { - match self { - JsonValue::Object(v) => Some(v), - _ => None, - } - } - - pub fn as_string(&self) -> Option<&str> { - match self { - JsonValue::String(v) => Some(v), - _ => None, - } - } - - pub fn as_number(&self) -> Option { - match self { - JsonValue::Number(f) => Some(*f), - _ => None, - } - } -} - -pub async fn get_entries() -> Result> { - let mut h: HashMap = HashMap::new(); - - let response = list_containers().await?; - let response = JsonValue::parse(&response)?; - let Some(containers) = response.as_array() else { - bail!("Expected an array of containers") - }; - for container in containers { - let Some(container) = container.as_object() else { - bail!("Expected containers to be objects"); - }; - - let name = container - .get("Names") - .and_then(|n| n.as_array()) - .and_then(|n| n.first()) - .and_then(|n| n.as_string()) - .unwrap_or(""); - - for port in container - .get("Ports") - .and_then(|n| n.as_array()) - .unwrap_or(&[]) - { - let Some(port) = port.as_object() else { - bail!("port records must be objects") - }; - if let Some(public_port) = - port.get("PublicPort").and_then(|pp| pp.as_number()) - { - // NOTE: If these are really ports then `as u16` will be - // right, otherwise what are we even doing here? - let public_port = public_port.trunc() as u16; - let private_port = port - .get("PrivatePort") - .and_then(|pp| pp.as_number()) - .unwrap_or(0.0) - .trunc() as u16; - - h.insert( - public_port, - PortDesc { - port: public_port, - desc: format!("{name} (docker->{private_port})"), - }, - ); - } - } - } - - Ok(h) -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - pub fn json_decode_basic() { - let cases: Vec<(&str, JsonValue)> = vec![ - ("12", JsonValue::Number(12.0)), - ("12.7", JsonValue::Number(12.7)), - ("-12", JsonValue::Number(-12.0)), - ("true", JsonValue::True), - ("false", JsonValue::False), - ("null", JsonValue::Null), - ("\"abcd\"", JsonValue::String("abcd".to_string())), - ( - "\" a \\\" \\r b \\n c \\f d \\b e \\t f \\\\ \"", - JsonValue::String( - " a \" \r b \n c \x0C d \x08 e \t f \\ ".to_string(), - ), - ), - ]; - for (blob, expected) in cases { - assert_eq!(JsonValue::parse(blob.as_bytes()).unwrap(), expected); - } - } - - #[test] - pub fn json_decode_array() { - let result = JsonValue::parse(b"[1, true, \"foo\", null]").unwrap(); - let JsonValue::Array(result) = result else { - panic!("Expected an array"); - }; - assert_eq!(result.len(), 4); - assert_eq!(result[0], JsonValue::Number(1.0)); - assert_eq!(result[1], JsonValue::True); - assert_eq!(result[2], JsonValue::String("foo".to_owned())); - assert_eq!(result[3], JsonValue::Null); - } - - #[test] - pub fn json_decode_array_empty() { - let result = JsonValue::parse(b"[]").unwrap(); - assert_eq!(result, JsonValue::Array(vec![])); - } - - #[test] - pub fn json_decode_array_nested() { - let result = JsonValue::parse(b"[1, [2, 3], 4]").unwrap(); - assert_eq!( - result, - JsonValue::Array(vec![ - JsonValue::Number(1.0), - JsonValue::Array(vec![ - JsonValue::Number(2.0), - JsonValue::Number(3.0), - ]), - JsonValue::Number(4.0) - ]) - ); - } - - #[test] - pub fn json_decode_object() { - let result = JsonValue::parse( - b"{\"a\": 1.0, \"b\": [2.0, 3.0], \"c\": {\"d\": 4.0}}", - ) - .unwrap(); - assert_eq!( - result, - JsonValue::Object(HashMap::from([ - ("a".to_owned(), JsonValue::Number(1.0)), - ( - "b".to_owned(), - JsonValue::Array(vec![ - JsonValue::Number(2.0), - JsonValue::Number(3.0), - ]) - ), - ( - "c".to_owned(), - JsonValue::Object(HashMap::from([( - "d".to_owned(), - JsonValue::Number(4.0) - )])) - ) - ])) - ) - } - - #[test] - pub fn json_decode_test_files() { - use std::path::{Path, PathBuf}; - fn is_json(p: &Path) -> bool { - p.is_file() && p.extension().map(|s| s == "json").unwrap_or(false) - } - - let manifest_dir: PathBuf = - [env!("CARGO_MANIFEST_DIR"), "resources", "json"] - .iter() - .collect(); - - for file in manifest_dir.read_dir().unwrap().flatten() { - let path = file.path(); - if !is_json(&path) { - continue; - } - - let json = std::fs::read(&path).expect("Unable to read input file"); - let path = path.display(); - if let Err(err) = JsonValue::parse(&json) { - panic!("Unable to parse {path}: {err:?}"); - } - eprintln!("Parsed {path} successfully"); - } - } - - #[test] - pub fn json_decode_empty() { - assert!(JsonValue::parse(b" ").is_err()); - } - - #[test] - pub fn json_decode_docker() { - use pretty_assertions::assert_eq; - - // This is the example container response from docker - let result = JsonValue::parse(b" -[ - { - \"Id\": \"8dfafdbc3a40\", - \"Names\": [ - \"/boring_feynman\" - ], - \"Image\": \"ubuntu:latest\", - \"ImageID\": \"d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82\", - \"Command\": \"echo 1\", - \"Created\": 1367854155, - \"State\": \"Exited\", - \"Status\": \"Exit 0\", - \"Ports\": [ - { - \"PrivatePort\": 2222, - \"PublicPort\": 3333, - \"Type\": \"tcp\" - } - ], - \"Labels\": { - \"com.example.vendor\": \"Acme\", - \"com.example.license\": \"GPL\", - \"com.example.version\": \"1.0\" - }, - \"SizeRw\": 12288, - \"SizeRootFs\": 0, - \"HostConfig\": { - \"NetworkMode\": \"default\", - \"Annotations\": { - \"io.kubernetes.docker.type\": \"container\" - } - }, - \"NetworkSettings\": { - \"Networks\": { - \"bridge\": { - \"NetworkID\": \"7ea29fc1412292a2d7bba362f9253545fecdfa8ce9a6e37dd10ba8bee7129812\", - \"EndpointID\": \"2cdc4edb1ded3631c81f57966563e5c8525b81121bb3706a9a9a3ae102711f3f\", - \"Gateway\": \"172.17.0.1\", - \"IPAddress\": \"172.17.0.2\", - \"IPPrefixLen\": 16, - \"IPv6Gateway\": \"\", - \"GlobalIPv6Address\": \"\", - \"GlobalIPv6PrefixLen\": 0, - \"MacAddress\": \"02:42:ac:11:00:02\" - } - } - }, - \"Mounts\": [ - { - \"Name\": \"fac362...80535\", - \"Source\": \"/data\", - \"Destination\": \"/data\", - \"Driver\": \"local\", - \"Mode\": \"ro,Z\", - \"RW\": false, - \"Propagation\": \"\" - } - ] - }, - { - \"Id\": \"9cd87474be90\", - \"Names\": [ - \"/coolName\" - ], - \"Image\": \"ubuntu:latest\", - \"ImageID\": \"d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82\", - \"Command\": \"echo 222222\", - \"Created\": 1367854155, - \"State\": \"Exited\", - \"Status\": \"Exit 0\", - \"Ports\": [], - \"Labels\": {}, - \"SizeRw\": 12288, - \"SizeRootFs\": 0, - \"HostConfig\": { - \"NetworkMode\": \"default\", - \"Annotations\": { - \"io.kubernetes.docker.type\": \"container\", - \"io.kubernetes.sandbox.id\": \"3befe639bed0fd6afdd65fd1fa84506756f59360ec4adc270b0fdac9be22b4d3\" - } - }, - \"NetworkSettings\": { - \"Networks\": { - \"bridge\": { - \"NetworkID\": \"7ea29fc1412292a2d7bba362f9253545fecdfa8ce9a6e37dd10ba8bee7129812\", - \"EndpointID\": \"88eaed7b37b38c2a3f0c4bc796494fdf51b270c2d22656412a2ca5d559a64d7a\", - \"Gateway\": \"172.17.0.1\", - \"IPAddress\": \"172.17.0.8\", - \"IPPrefixLen\": 16, - \"IPv6Gateway\": \"\", - \"GlobalIPv6Address\": \"\", - \"GlobalIPv6PrefixLen\": 0, - \"MacAddress\": \"02:42:ac:11:00:08\" - } - } - }, - \"Mounts\": [] - }, - { - \"Id\": \"3176a2479c92\", - \"Names\": [ - \"/sleepy_dog\" - ], - \"Image\": \"ubuntu:latest\", - \"ImageID\": \"d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82\", - \"Command\": \"echo 3333333333333333\", - \"Created\": 1367854154, - \"State\": \"Exited\", - \"Status\": \"Exit 0\", - \"Ports\": [], - \"Labels\": {}, - \"SizeRw\": 12288, - \"SizeRootFs\": 0, - \"HostConfig\": { - \"NetworkMode\": \"default\", - \"Annotations\": { - \"io.kubernetes.image.id\": \"d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82\", - \"io.kubernetes.image.name\": \"ubuntu:latest\" - } - }, - \"NetworkSettings\": { - \"Networks\": { - \"bridge\": { - \"NetworkID\": \"7ea29fc1412292a2d7bba362f9253545fecdfa8ce9a6e37dd10ba8bee7129812\", - \"EndpointID\": \"8b27c041c30326d59cd6e6f510d4f8d1d570a228466f956edf7815508f78e30d\", - \"Gateway\": \"172.17.0.1\", - \"IPAddress\": \"172.17.0.6\", - \"IPPrefixLen\": 16, - \"IPv6Gateway\": \"\", - \"GlobalIPv6Address\": \"\", - \"GlobalIPv6PrefixLen\": 0, - \"MacAddress\": \"02:42:ac:11:00:06\" - } - } - }, - \"Mounts\": [] - }, - { - \"Id\": \"4cb07b47f9fb\", - \"Names\": [ - \"/running_cat\" - ], - \"Image\": \"ubuntu:latest\", - \"ImageID\": \"d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82\", - \"Command\": \"echo 444444444444444444444444444444444\", - \"Created\": 1367854152, - \"State\": \"Exited\", - \"Status\": \"Exit 0\", - \"Ports\": [], - \"Labels\": {}, - \"SizeRw\": 12288, - \"SizeRootFs\": 0, - \"HostConfig\": { - \"NetworkMode\": \"default\", - \"Annotations\": { - \"io.kubernetes.config.source\": \"api\" - } - }, - \"NetworkSettings\": { - \"Networks\": { - \"bridge\": { - \"NetworkID\": \"7ea29fc1412292a2d7bba362f9253545fecdfa8ce9a6e37dd10ba8bee7129812\", - \"EndpointID\": \"d91c7b2f0644403d7ef3095985ea0e2370325cd2332ff3a3225c4247328e66e9\", - \"Gateway\": \"172.17.0.1\", - \"IPAddress\": \"172.17.0.5\", - \"IPPrefixLen\": 16, - \"IPv6Gateway\": \"\", - \"GlobalIPv6Address\": \"\", - \"GlobalIPv6PrefixLen\": 0, - \"MacAddress\": \"02:42:ac:11:00:05\" - } - } - }, - \"Mounts\": [] - } -] -").unwrap(); - let expected = JsonValue::Array(vec![ - JsonValue::Object(HashMap::from([ - ("Id".to_owned(), JsonValue::String("8dfafdbc3a40".to_owned())), - ("Names".to_owned(), JsonValue::Array(vec![ - JsonValue::String("/boring_feynman".to_owned()) - ])), - ("Image".to_owned(), JsonValue::String("ubuntu:latest".to_owned())), - ("ImageID".to_owned(), JsonValue::String("d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82".to_owned())), - ("Command".to_owned(), JsonValue::String("echo 1".to_owned())), - ("Created".to_owned(), JsonValue::Number(1367854155_f64)), - ("State".to_owned(), JsonValue::String("Exited".to_owned())), - ("Status".to_owned(), JsonValue::String("Exit 0".to_owned())), - ("Ports".to_owned(), JsonValue::Array(vec![ - JsonValue::Object(HashMap::from([ - ("PrivatePort".to_owned(), JsonValue::Number(2222_f64)), - ("PublicPort".to_owned(), JsonValue::Number(3333_f64)), - ("Type".to_owned(), JsonValue::String("tcp".to_owned())) - ])) - ])), - ("Labels".to_owned(), JsonValue::Object(HashMap::from([ - ("com.example.vendor".to_owned(), JsonValue::String("Acme".to_owned())), - ("com.example.license".to_owned(), JsonValue::String("GPL".to_owned())), - ("com.example.version".to_owned(), JsonValue::String("1.0".to_owned())) - ]))), - ("SizeRw".to_owned(), JsonValue::Number(12288_f64)), - ("SizeRootFs".to_owned(), JsonValue::Number(0_f64)), - ("HostConfig".to_owned(), JsonValue::Object(HashMap::from([ - ("NetworkMode".to_owned(), JsonValue::String("default".to_owned())), - ("Annotations".to_owned(), JsonValue::Object(HashMap::from([ - ("io.kubernetes.docker.type".to_owned(), JsonValue::String("container".to_owned())) - ]))) - ]))), - ("NetworkSettings".to_owned(), JsonValue::Object(HashMap::from([ - ("Networks".to_owned(), JsonValue::Object(HashMap::from([ - ("bridge".to_owned(), JsonValue::Object(HashMap::from([ - ("NetworkID".to_owned(), JsonValue::String("7ea29fc1412292a2d7bba362f9253545fecdfa8ce9a6e37dd10ba8bee7129812".to_owned())), - ("EndpointID".to_owned(), JsonValue::String("2cdc4edb1ded3631c81f57966563e5c8525b81121bb3706a9a9a3ae102711f3f".to_owned())), - ("Gateway".to_owned(), JsonValue::String("172.17.0.1".to_owned())), - ("IPAddress".to_owned(), JsonValue::String("172.17.0.2".to_owned())), - ("IPPrefixLen".to_owned(), JsonValue::Number(16_f64)), - ("IPv6Gateway".to_owned(), JsonValue::String("".to_owned())), - ("GlobalIPv6Address".to_owned(), JsonValue::String("".to_owned())), - ("GlobalIPv6PrefixLen".to_owned(), JsonValue::Number(0_f64)), - ("MacAddress".to_owned(), JsonValue::String("02:42:ac:11:00:02".to_owned())) - ]))) - ]))) - ]))), - ("Mounts".to_owned(), JsonValue::Array(vec![ - JsonValue::Object(HashMap::from([ - ("Name".to_owned(), JsonValue::String("fac362...80535".to_owned())), - ("Source".to_owned(), JsonValue::String("/data".to_owned())), - ("Destination".to_owned(), JsonValue::String("/data".to_owned())), - ("Driver".to_owned(), JsonValue::String("local".to_owned())), - ("Mode".to_owned(), JsonValue::String("ro,Z".to_owned())), - ("RW".to_owned(), JsonValue::False), - ("Propagation".to_owned(), JsonValue::String("".to_owned())) - ])) - ])) - ])), - JsonValue::Object(HashMap::from([ - ("Id".to_owned(), JsonValue::String("9cd87474be90".to_owned())), - ("Names".to_owned(), JsonValue::Array(vec![ - JsonValue::String("/coolName".to_owned()) - ])), - ("Image".to_owned(), JsonValue::String("ubuntu:latest".to_owned())), - ("ImageID".to_owned(), JsonValue::String("d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82".to_owned())), - ("Command".to_owned(), JsonValue::String("echo 222222".to_owned())), - ("Created".to_owned(), JsonValue::Number(1367854155_f64)), - ("State".to_owned(), JsonValue::String("Exited".to_owned())), - ("Status".to_owned(), JsonValue::String("Exit 0".to_owned())), - ("Ports".to_owned(), JsonValue::Array(vec![])), - ("Labels".to_owned(), JsonValue::Object(HashMap::from([]))), - ("SizeRw".to_owned(), JsonValue::Number(12288_f64)), - ("SizeRootFs".to_owned(), JsonValue::Number(0_f64)), - ("HostConfig".to_owned(), JsonValue::Object(HashMap::from([ - ("NetworkMode".to_owned(), JsonValue::String("default".to_owned())), - ("Annotations".to_owned(), JsonValue::Object(HashMap::from([ - ("io.kubernetes.docker.type".to_owned(), JsonValue::String("container".to_owned())), - ("io.kubernetes.sandbox.id".to_owned(), JsonValue::String("3befe639bed0fd6afdd65fd1fa84506756f59360ec4adc270b0fdac9be22b4d3".to_owned())) - ]))) - ]))), - ("NetworkSettings".to_owned(), JsonValue::Object(HashMap::from([ - ("Networks".to_owned(), JsonValue::Object(HashMap::from([ - ("bridge".to_owned(), JsonValue::Object(HashMap::from([ - ("NetworkID".to_owned(), JsonValue::String("7ea29fc1412292a2d7bba362f9253545fecdfa8ce9a6e37dd10ba8bee7129812".to_owned())), - ("EndpointID".to_owned(), JsonValue::String("88eaed7b37b38c2a3f0c4bc796494fdf51b270c2d22656412a2ca5d559a64d7a".to_owned())), - ("Gateway".to_owned(), JsonValue::String("172.17.0.1".to_owned())), - ("IPAddress".to_owned(), JsonValue::String("172.17.0.8".to_owned())), - ("IPPrefixLen".to_owned(), JsonValue::Number(16_f64)), - ("IPv6Gateway".to_owned(), JsonValue::String("".to_owned())), - ("GlobalIPv6Address".to_owned(), JsonValue::String("".to_owned())), - ("GlobalIPv6PrefixLen".to_owned(), JsonValue::Number(0_f64)), - ("MacAddress".to_owned(), JsonValue::String("02:42:ac:11:00:08".to_owned())) - ]))) - ]))) - ]))), - ("Mounts".to_owned(), JsonValue::Array(vec![])) - ])), - JsonValue::Object(HashMap::from([ - ("Id".to_owned(), JsonValue::String("3176a2479c92".to_owned())), - ("Names".to_owned(), JsonValue::Array(vec![ - JsonValue::String("/sleepy_dog".to_owned()) - ])), - ("Image".to_owned(), JsonValue::String("ubuntu:latest".to_owned())), - ("ImageID".to_owned(), JsonValue::String("d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82".to_owned())), - ("Command".to_owned(), JsonValue::String("echo 3333333333333333".to_owned())), - ("Created".to_owned(), JsonValue::Number(1367854154_f64)), - ("State".to_owned(), JsonValue::String("Exited".to_owned())), - ("Status".to_owned(), JsonValue::String("Exit 0".to_owned())), - ("Ports".to_owned(), JsonValue::Array(vec![])), - ("Labels".to_owned(), JsonValue::Object(HashMap::from([]))), - ("SizeRw".to_owned(), JsonValue::Number(12288_f64)), - ("SizeRootFs".to_owned(), JsonValue::Number(0_f64)), - ("HostConfig".to_owned(), JsonValue::Object(HashMap::from([ - ("NetworkMode".to_owned(), JsonValue::String("default".to_owned())), - ("Annotations".to_owned(), JsonValue::Object(HashMap::from([ - ("io.kubernetes.image.id".to_owned(), JsonValue::String("d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82".to_owned())), - ("io.kubernetes.image.name".to_owned(), JsonValue::String("ubuntu:latest".to_owned())) - ]))) - ]))), - ("NetworkSettings".to_owned(), JsonValue::Object(HashMap::from([ - ("Networks".to_owned(), JsonValue::Object(HashMap::from([ - ("bridge".to_owned(), JsonValue::Object(HashMap::from([ - ("NetworkID".to_owned(), JsonValue::String("7ea29fc1412292a2d7bba362f9253545fecdfa8ce9a6e37dd10ba8bee7129812".to_owned())), - ("EndpointID".to_owned(), JsonValue::String("8b27c041c30326d59cd6e6f510d4f8d1d570a228466f956edf7815508f78e30d".to_owned())), - ("Gateway".to_owned(), JsonValue::String("172.17.0.1".to_owned())), - ("IPAddress".to_owned(), JsonValue::String("172.17.0.6".to_owned())), - ("IPPrefixLen".to_owned(), JsonValue::Number(16_f64)), - ("IPv6Gateway".to_owned(), JsonValue::String("".to_owned())), - ("GlobalIPv6Address".to_owned(), JsonValue::String("".to_owned())), - ("GlobalIPv6PrefixLen".to_owned(), JsonValue::Number(0_f64)), - ("MacAddress".to_owned(), JsonValue::String("02:42:ac:11:00:06".to_owned())) - ]))) - ]))) - ]))), - ("Mounts".to_owned(), JsonValue::Array(vec![])) - ])), - JsonValue::Object(HashMap::from([ - ("Id".to_owned(), JsonValue::String("4cb07b47f9fb".to_owned())), - ("Names".to_owned(), JsonValue::Array(vec![ - JsonValue::String("/running_cat".to_owned()) - ])), - ("Image".to_owned(), JsonValue::String("ubuntu:latest".to_owned())), - ("ImageID".to_owned(), JsonValue::String("d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82".to_owned())), - ("Command".to_owned(), JsonValue::String("echo 444444444444444444444444444444444".to_owned())), - ("Created".to_owned(), JsonValue::Number(1367854152_f64)), - ("State".to_owned(), JsonValue::String("Exited".to_owned())), - ("Status".to_owned(), JsonValue::String("Exit 0".to_owned())), - ("Ports".to_owned(), JsonValue::Array(vec![])), - ("Labels".to_owned(), JsonValue::Object(HashMap::from([]))), - ("SizeRw".to_owned(), JsonValue::Number(12288_f64)), - ("SizeRootFs".to_owned(), JsonValue::Number(0_f64)), - ("HostConfig".to_owned(), JsonValue::Object(HashMap::from([ - ("NetworkMode".to_owned(), JsonValue::String("default".to_owned())), - ("Annotations".to_owned(), JsonValue::Object(HashMap::from([ - ("io.kubernetes.config.source".to_owned(), JsonValue::String("api".to_owned())) - ]))) - ]))), - ("NetworkSettings".to_owned(), JsonValue::Object(HashMap::from([ - ("Networks".to_owned(), JsonValue::Object(HashMap::from([ - ("bridge".to_owned(), JsonValue::Object(HashMap::from([ - ("NetworkID".to_owned(), JsonValue::String("7ea29fc1412292a2d7bba362f9253545fecdfa8ce9a6e37dd10ba8bee7129812".to_owned())), - ("EndpointID".to_owned(), JsonValue::String("d91c7b2f0644403d7ef3095985ea0e2370325cd2332ff3a3225c4247328e66e9".to_owned())), - ("Gateway".to_owned(), JsonValue::String("172.17.0.1".to_owned())), - ("IPAddress".to_owned(), JsonValue::String("172.17.0.5".to_owned())), - ("IPPrefixLen".to_owned(), JsonValue::Number(16_f64)), - ("IPv6Gateway".to_owned(), JsonValue::String("".to_owned())), - ("GlobalIPv6Address".to_owned(), JsonValue::String("".to_owned())), - ("GlobalIPv6PrefixLen".to_owned(), JsonValue::Number(0_f64)), - ("MacAddress".to_owned(), JsonValue::String("02:42:ac:11:00:05".to_owned())) - ]))) - ]))) - ]))), - ("Mounts".to_owned(), JsonValue::Array(vec![])) - ])) - ]); - assert_eq!(result, expected); - } - - #[test] - pub fn json_decode_unterminated_string_with_escape() { - let input = b"\"\\"; - let _ = JsonValue::parse(input); - } - - async fn accept_and_send_single_response( - listener: tokio::net::TcpListener, - response: &[u8], - ) { - println!("[server] Awaiting connection..."); - let (stream, _) = listener - .accept() - .await - .expect("Unable to accept connection"); - let mut stream = tokio::io::BufStream::new(stream); - - println!("[server] Reading request..."); - let mut line = String::new(); - loop { - line.clear(); - stream - .read_line(&mut line) - .await - .expect("Unable to read line in server"); - if line.trim().is_empty() { - break; - } - } - - println!("[server] Sending response..."); - stream - .write_all(response) - .await - .expect("Unable to write response"); - stream.flush().await.expect("Unable to flush"); - println!("[server] Done."); - } - - #[tokio::test] - pub async fn docker_chunked_transfer_encoding() { - let listener = tokio::net::TcpListener::bind("127.0.0.1:0") - .await - .expect("Unable to create listener on localhost"); - let port = listener.local_addr().unwrap().port(); - - let mut set = tokio::task::JoinSet::new(); - set.spawn(async move { - const RESPONSE: &[u8] = b"\ -HTTP/1.1 200 OK\r\n\ -Transfer-Encoding: chunked\r\n\ -\r\n\ -4\r\nWiki\r\n7\r\npedia i\r\nB\r\nn \r\nchunks.\r\n0\r\n\r\n"; - - accept_and_send_single_response(listener, RESPONSE).await; - }); - - let addr = format!("127.0.0.1:{port}"); - let stream = tokio::net::TcpStream::connect(&addr) - .await - .expect("Unable to connect"); - let response = list_containers_with_connection(stream) - .await - .expect("Unable to get response"); - assert_eq!(&response, b"Wikipedia in \r\nchunks."); - } - - #[tokio::test] - pub async fn docker_with_no_content_length() { - let listener = tokio::net::TcpListener::bind("127.0.0.1:0") - .await - .expect("Unable to create listener on localhost"); - let port = listener.local_addr().unwrap().port(); - - let mut set = tokio::task::JoinSet::new(); - set.spawn(async move { - const RESPONSE: &[u8] = b"\ -HTTP/1.1 200 OK\r\n\ -\r\n\ -[\"Booo this is some data\"]\r\n"; - accept_and_send_single_response(listener, RESPONSE).await; - }); - - let addr = format!("127.0.0.1:{port}"); - let stream = tokio::net::TcpStream::connect(&addr) - .await - .expect("Unable to connect"); - let response = list_containers_with_connection(stream) - .await - .expect("Unable to get response"); - assert_eq!(&response, b"[\"Booo this is some data\"]\r\n"); - } -} diff --git a/src/server/refresh/procfs.rs b/src/server/refresh/procfs.rs deleted file mode 100644 index 9749dde..0000000 --- a/src/server/refresh/procfs.rs +++ /dev/null @@ -1,63 +0,0 @@ -use anyhow::Result; -use procfs::process::FDTarget; -use std::collections::HashMap; - -use crate::message::PortDesc; - -pub fn get_entries(send_anonymous: bool) -> Result> { - let all_procs = procfs::process::all_processes()?; - - // build up a map between socket inodes and process stat info. Ignore any - // error we encounter as it probably means we have no access to that - // process or something. - let mut map: HashMap = HashMap::new(); - for process in all_procs.flatten() { - if !process.is_alive() { - continue; // Ignore zombies. - } - - if let (Ok(fds), Ok(cmd)) = (process.fd(), process.cmdline()) { - for fd in fds.flatten() { - if let FDTarget::Socket(inode) = fd.target { - map.insert(inode, cmd.join(" ")); - } - } - } - } - - let mut h: HashMap = HashMap::new(); - - // Go through all the listening IPv4 and IPv6 sockets and take the first - // instance of listening on each port *if* the address is loopback or - // unspecified. (TODO: Do we want this restriction really?) - let tcp = procfs::net::tcp()?; - let tcp6 = procfs::net::tcp6()?; - for tcp_entry in tcp.into_iter().chain(tcp6) { - if tcp_entry.state == procfs::net::TcpState::Listen - && (tcp_entry.local_address.ip().is_loopback() - || tcp_entry.local_address.ip().is_unspecified()) - && !h.contains_key(&tcp_entry.local_address.port()) - { - // If the process is not one that we can identify, then we return - // the port but leave the description empty so that it can be - // identified by the client as "anonymous". - let desc = if let Some(cmd) = map.get(&tcp_entry.inode) { - cmd.clone() - } else { - String::new() - }; - - if send_anonymous || !desc.is_empty() { - h.insert( - tcp_entry.local_address.port(), - PortDesc { - port: tcp_entry.local_address.port(), - desc, - }, - ); - } - } - } - - Ok(h) -}