webbrowser-0.8.10/.cargo_vcs_info.json0000644000000001360000000000100133030ustar { "git": { "sha1": "322bc6132072368cf0ef96bd9217762c64f06514" }, "path_in_vcs": "" }webbrowser-0.8.10/.github/workflows/android.yaml000064400000000000000000000070251046102023000200000ustar 00000000000000name: Android on: push: branches-ignore: [ '**win**', '**linux**', '**macos**', '**bsd**', '**haiku**', '**wasm**', '**ios**' ] pull_request: branches: [ main ] env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 RUST_LOG: webbrowser=TRACE,tests=TRACE CARGO_APK_VERSION: '0.9.7' ANDROID_ARCH: x86_64 ANDROID_TARGET: x86_64-linux-android jobs: build: name: Build runs-on: macos-latest if: ${{ !contains(github.event.head_commit.message, '#build-') || contains(github.event.head_commit.message, '#build-android') }} strategy: matrix: rust: [ stable ] api-level: [ 30 ] ndk-version: [ '25.1.8937393' ] cmake-version: [ '3.22.1' ] continue-on-error: ${{ matrix.rust != 'stable' && matrix.rust != 'beta' }} steps: - uses: actions/checkout@v3 name: Checkout - name: Install rust version run: | rustup install ${{ matrix.rust }} --profile minimal rustup component add --toolchain ${{ matrix.rust }} rustfmt clippy rustup target add ${{ env.ANDROID_TARGET }} # Install cargo-apk - uses: actions/cache@v3 name: Fetch cargo-apk from cache id: cargo-apk-cache with: path: ~/.cargo/bin/cargo-apk key: cargo-apk-${{ env.CARGO_APK_VERSION }} - name: Install cargo-apk if: steps.cargo-apk-cache.outputs.cache-hit != 'true' run: cargo install cargo-apk --version ${{ env.CARGO_APK_VERSION }} # Pre-build - name: Compile run: rustup run ${{ matrix.rust }} cargo apk build --target ${{ env.ANDROID_TARGET }} working-directory: tests/test-android-app - name: Compile tests run: rustup run ${{ matrix.rust }} cargo build --test test_android # Check if AVD is in cache, else create AVD - name: AVD cache uses: actions/cache@v3 id: avd-cache with: path: | ~/.android/avd/* ~/.android/adb* key: avd-${{ matrix.api-level }} - name: Create AVD if: steps.avd-cache.outputs.cache-hit != 'true' uses: reactivecircus/android-emulator-runner@v2 with: arch: ${{ env.ANDROID_ARCH }} api-level: ${{ matrix.api-level }} force-avd-creation: false emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: false script: echo "Generated AVD snapshot for caching." # Run tests on emulator - name: Run tests uses: reactivecircus/android-emulator-runner@v2 with: arch: ${{ env.ANDROID_ARCH }} api-level: ${{ matrix.api-level }} ndk: ${{ matrix.ndk-version }} cmake: ${{ matrix.cmake-version }} force-avd-creation: false emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true script: bash -c 'ANDROID_TARGET=${{ env.ANDROID_TARGET }} ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/${{ matrix.ndk-version }} rustup run ${{ matrix.rust }} cargo test --test test_android -- --ignored' # Code format, linting etc. - name: Check Code Formatting if: ${{ matrix.rust == 'stable' || matrix.rust == 'beta' }} run: cargo +${{ matrix.rust }} fmt --all -- --check - name: Lints if: ${{ matrix.rust == 'stable' || matrix.rust == 'beta' }} run: cargo +${{ matrix.rust }} clippy --target ${{ env.ANDROID_TARGET }} -- -D warnings webbrowser-0.8.10/.github/workflows/ios.yaml000064400000000000000000000037531046102023000171560ustar 00000000000000name: iOS on: push: branches-ignore: [ '**win**', '**android**', '**linux**', '**bsd**', '**haiku**', '**wasm**', '**macos**' ] pull_request: branches: [ main ] env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 RUST_LOG: webbrowser=TRACE IOS_TARGET: x86_64-apple-ios jobs: build: name: Build runs-on: macos-latest if: ${{ !contains(github.event.head_commit.message, '#build-') || contains(github.event.head_commit.message, '#build-ios') }} strategy: matrix: rust: [stable] continue-on-error: ${{ matrix.rust != 'stable' && matrix.rust != 'beta' }} steps: - uses: actions/checkout@v3 name: Checkout - name: Install rust version run: | rustup install ${{ matrix.rust }} --profile minimal rustup component add --toolchain ${{ matrix.rust }} rustfmt clippy rustup target add ${{ env.IOS_TARGET }} - name: Configure and start iOS Simulator run: | set -e open -a Simulator sleep 5 IOSRUNTIME=$(xcrun simctl list 2>&1 | egrep '^iOS' | head -n 1 | awk '{ print $NF }') IOSDEV=$(xcrun simctl list 2>&1 | grep com.apple.CoreSimulator.SimDeviceType.iPhone | grep -v ' SE ' | tail -n 1 | tr -d '()' | awk '{ print $NF }') DEVID=$(xcrun simctl create iphone-latest $IOSDEV $IOSRUNTIME) echo "==== using device $IOSDEV, $IOSRUNTIME ====" xcrun simctl boot $DEVID sleep 5 xcrun simctl list 2>&1 # Run tests - name: Run tests run: cargo +${{ matrix.rust }} test --verbose --test test_ios -- --include-ignored # Code format, linting etc. - name: Check Code Formatting if: ${{ matrix.rust == 'stable' || matrix.rust == 'beta' }} run: cargo +${{ matrix.rust }} fmt --all -- --check - name: Lints if: ${{ matrix.rust == 'stable' || matrix.rust == 'beta' }} run: cargo +${{ matrix.rust }} clippy --target ${{ env.IOS_TARGET }} -- -D warnings webbrowser-0.8.10/.github/workflows/linux.yaml000064400000000000000000000032351046102023000175160ustar 00000000000000name: Linux on: push: branches-ignore: [ '**win**', '**android**', '**macos**', '**bsd**', '**haiku**', '**wasm**', '**ios**' ] pull_request: branches: [ main ] env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 RUST_LOG: webbrowser=TRACE jobs: build: name: Build runs-on: ubuntu-latest if: ${{ !contains(github.event.head_commit.message, '#build-') || contains(github.event.head_commit.message, '#build-linux') }} strategy: max-parallel: 1 matrix: rust: [stable, beta, nightly] continue-on-error: ${{ matrix.rust != 'stable' && matrix.rust != 'beta' }} steps: - uses: actions/checkout@v3 name: Checkout - name: Install rust version run: | rustup install ${{ matrix.rust }} --profile minimal rustup component add --toolchain ${{ matrix.rust }} rustfmt clippy - name: Compile run: cargo +${{ matrix.rust }} build - name: Run Tests env: BROWSER: firefox --screenshot %s run: cargo +${{ matrix.rust }} test --all --locked --verbose - name: Run Tests (hardened) run: cargo +${{ matrix.rust }} test --features hardened --locked --verbose --test test_unix tests::test_hardened_mode - name: Check compilation with WSL disabled run: cargo +${{ matrix.rust }} build --features disable-wsl - name: Check Code Formatting if: ${{ matrix.rust == 'stable' || matrix.rust == 'beta' }} run: cargo +${{ matrix.rust }} fmt --all -- --check - name: Lints if: ${{ matrix.rust == 'stable' || matrix.rust == 'beta' }} run: cargo +${{ matrix.rust }} clippy --all-targets -- -D warnings webbrowser-0.8.10/.github/workflows/macos.yaml000064400000000000000000000027641046102023000174670ustar 00000000000000name: MacOS on: push: branches-ignore: [ '**win**', '**android**', '**linux**', '**bsd**', '**haiku**', '**wasm**', '**ios**' ] pull_request: branches: [ main ] env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 RUST_LOG: webbrowser=TRACE jobs: build: name: Build runs-on: macos-latest if: ${{ !contains(github.event.head_commit.message, '#build-') || contains(github.event.head_commit.message, '#build-macos') }} strategy: matrix: rust: [stable] continue-on-error: ${{ matrix.rust != 'stable' && matrix.rust != 'beta' }} steps: - uses: actions/checkout@v3 name: Checkout - name: Install specific rust version run: | rustup install ${{ matrix.rust }} --profile minimal rustup component add --toolchain ${{ matrix.rust }} rustfmt clippy - name: Compile run: cargo +${{ matrix.rust }} build - name: Run Tests run: cargo +${{ matrix.rust }} test --locked --verbose --test test_macos -- --include-ignored - name: Run Tests (hardened) run: cargo +${{ matrix.rust }} test --features hardened --locked --verbose --test test_macos tests::test_hardened_mode - name: Check Code Formatting if: ${{ matrix.rust == 'stable' || matrix.rust == 'beta' }} run: cargo +${{ matrix.rust }} fmt --all -- --check - name: Lints if: ${{ matrix.rust == 'stable' || matrix.rust == 'beta' }} run: cargo +${{ matrix.rust }} clippy --all-targets -- -D warnings webbrowser-0.8.10/.github/workflows/release.yaml000064400000000000000000000025371046102023000200030ustar 00000000000000name: Release on: release: types: [ created ] env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 RUST_LOG: webbrowser=TRACE jobs: crates: name: Crates Publish runs-on: ubuntu-latest strategy: matrix: rust: [stable] continue-on-error: ${{ matrix.rust != 'stable' && matrix.rust != 'beta' }} steps: - uses: actions/checkout@v3 name: Checkout - name: Install rust version run: | rustup install ${{ matrix.rust }} --profile minimal rustup component add --toolchain ${{ matrix.rust }} rustfmt clippy # Check compilation - name: Compile run: cargo +${{ matrix.rust }} build # Run tests - name: Run Tests env: BROWSER: firefox --screenshot %s run: cargo +${{ matrix.rust }} test --all --locked --verbose # Code format and lints - name: Check Code Formatting if: ${{ matrix.rust == 'stable' || matrix.rust == 'beta' }} run: cargo +${{ matrix.rust }} fmt --all -- --check - name: Lints if: ${{ matrix.rust == 'stable' || matrix.rust == 'beta' }} run: cargo +${{ matrix.rust }} clippy --all-targets -- -D warnings # Publish to crates.io - name: Publish env: CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_TOKEN }} run: cargo publish --no-verify webbrowser-0.8.10/.github/workflows/wasm.yaml000064400000000000000000000043201046102023000173220ustar 00000000000000name: WASM on: push: branches-ignore: [ '**win**', '**android**', '**linux**', '**bsd**', '**haiku**', '**macos**', '**ios**' ] pull_request: branches: [ main ] env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 RUST_LOG: webbrowser=TRACE WASM_PACK_VERSION: '0.10.3' WASM_TARGET: wasm32-unknown-unknown jobs: build: name: Build runs-on: ubuntu-latest if: ${{ !contains(github.event.head_commit.message, '#build-') || contains(github.event.head_commit.message, '#build-wasm') }} strategy: max-parallel: 1 matrix: rust: [stable, beta] continue-on-error: ${{ matrix.rust != 'stable' && matrix.rust != 'beta' }} steps: - uses: actions/checkout@v3 name: Checkout - name: Install rust version run: | rustup install ${{ matrix.rust }} --profile minimal rustup component add --toolchain ${{ matrix.rust }} rustfmt clippy rustup target add ${{ env.WASM_TARGET }} # Install wasm-pack - uses: actions/cache@v3 name: Fetch wasm-pack from cache id: wasm-pack-cache with: path: ~/.cargo/bin/wasm-pack key: wasm-pack-${{ env.WASM_PACK_VERSION }} - name: Install wasm-pack if: steps.wasm-pack-cache.outputs.cache-hit != 'true' run: cargo install wasm-pack --version ${{ env.WASM_PACK_VERSION }} # Compile using WEBBROWSER_WASM_TARGET=_self so that we don't encounter popup blockers - name: Compile env: RUST_LOG: info WEBBROWSER_WASM_TARGET: _self run: rustup run ${{ matrix.rust }} wasm-pack build --target web --dev working-directory: tests/test-wasm-app # Run tests - name: Run tests env: BROWSER: firefox --screenshot %s run: cargo +${{ matrix.rust }} test --test test_wasm -- --ignored # Code format, linting etc. - name: Check Code Formatting if: ${{ matrix.rust == 'stable' || matrix.rust == 'beta' }} run: cargo +${{ matrix.rust }} fmt --all -- --check - name: Lints if: ${{ matrix.rust == 'stable' || matrix.rust == 'beta' }} run: cargo +${{ matrix.rust }} clippy --target ${{ env.WASM_TARGET }} -- -D warnings webbrowser-0.8.10/.github/workflows/windows.yaml000064400000000000000000000027711046102023000200550ustar 00000000000000name: Windows on: push: branches-ignore: [ '**wasm**', '**android**', '**linux**', '**bsd**', '**haiku**', '**macos**', '**ios**' ] pull_request: branches: [ main ] env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 RUST_LOG: webbrowser=TRACE jobs: build: name: Build runs-on: windows-latest if: ${{ !contains(github.event.head_commit.message, '#build-') || contains(github.event.head_commit.message, '#build-windows') }} strategy: max-parallel: 1 matrix: rust: [stable, beta] continue-on-error: ${{ matrix.rust != 'stable' && matrix.rust != 'beta' }} steps: - uses: actions/checkout@v3 name: Checkout - name: Install specific rust version run: | rustup install ${{ matrix.rust }} --profile minimal rustup component add --toolchain ${{ matrix.rust }} rustfmt clippy - name: Compile run: cargo +${{ matrix.rust }} build - name: Run Tests run: cargo +${{ matrix.rust }} test --all --locked --verbose - name: Run Tests (hardened) run: cargo +${{ matrix.rust }} test --features hardened --locked --verbose --test test_windows tests::test_hardened_mode - name: Check Code Formatting if: ${{ matrix.rust == 'stable' || matrix.rust == 'beta' }} run: cargo +${{ matrix.rust }} fmt --all -- --check - name: Lints if: ${{ matrix.rust == 'stable' || matrix.rust == 'beta' }} run: cargo +${{ matrix.rust }} clippy --all-targets -- -D warnings webbrowser-0.8.10/.gitignore000064400000000000000000000001261046102023000140620ustar 00000000000000/target/ /Cargo.lock /.idea/ /*.code-workspace /screenshot.png **/.DS_Store /.vscode/ webbrowser-0.8.10/CHANGELOG.md000064400000000000000000000237721046102023000137170ustar 00000000000000# Changelog The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [0.8.10] - 2023-04-12 ### Changed - Linux: move to `home` as a dependency, instead of `dirs` ## [0.8.9] - 2023-04-12 ### Added - Linux: add support for running under Flatpak sandboxes. See issue #67 and PR #70 ### Fixed - Windows: fix a bug where browser command parsing failed. See issue #68 and PR #69 ## [0.8.8] - 2023-01-30 ### Changed - Android: bumped `jni` dependency version to `0.21` ## [0.8.7] - 2023-01-30 ### Fixed - Fixes a bug on WSL, when `xdg-settings` executes successfully but returns no default browser name. Thanks to [@krsh732](https://github.com/krsh732). See #64. ## [0.8.6] - 2023-01-26 ### Fixed - For Windows 32-bit, fix ABI to be used, which was broken in v0.8.5. Thanks to [@alula](https://github.com/alula). See #62 and #63. ## [0.8.5] - 2022-12-31 ### Fixed - For Windows platform, removes the `windows` crate dependency, relying on selective FFI bindings instead, thus avoiding the large dependency. See #62. Thanks to [@Jake-Shadle](https://github.com/Jake-Shadle). ## [0.8.4] - 2022-12-31 ### Fixed - Urgent bug fix for windows, where rendering broke on Firefox & Chrome. See #60 ## [0.8.3] - 2022-12-30 ### Added - Web browser is guaranteed to open for local files even if local file association was to a non-browser app (say an editor). This now is formally incorporated as part of this crate's [Consistent Behaviour](https://github.com/amodm/webbrowser-rs/blob/main/README.md#consistent-behaviour) - WSL support, thanks to [@Nachtalb](https://github.com/Nachtalb). This works even if `wslu` is not installed in WSL environments. - A new feature `hardened` now available for applications which require only http(s) urls to be opened. This acts as a security feature. ### Changed - On macOS, we now use `CoreFoundation` library instead of `open` command. - On Linux/*BSD, we now parse xdg configuration to execute the command directly, instead of using `xdg-open` command. This allows us to open the browser for local html files, even if the `.html` extension was associated with an edit (see #55) ### Fixed - The guarantee of web browser being opened (instead of local file association), now solves for the scenario where the URL is crafted to be an executable. This was reported privately by [@offalltn](https://github.com/offalltn). ## [0.8.2] - 2022-11-08 ### Fixed - Fix app crashes when running under termux on Android. See #53 and #54. ## [0.8.1] - 2022-11-01 ### Fixed - On Android, app crashes due to ndk-glue dependency. See #51 and #52. Thanks to [@rib](https://github.com/rib) for the fix. ## [0.8.0] - 2022-09-09 ### Added - Support for iOS is finally here. Thanks to [hakolao](https://github.com/hakolao) for this. See [PR #48](https://github.com/amodm/webbrowser-rs/pull/48) ### Changed - Updated all dependencies to current versions ## [0.7.1] - 2022-04-27 ### Added - Introduce `Browser::is_available()` and `Browser::exists(&self)` to check availability of browsers without opening a URL ### Changed - Modify `BrowserOptions` to be constructable only via the builder pattern, to prevent future API compatibility issues ## [0.7.0] - 2022-04-24 ### Added - Introduce way to provide a target hint to the browser via `BrowserOptions::target_hint` [PR #45](https://github.com/amodm/webbrowser-rs/pull/45) ### Changed - Breaking API change for users of `BrowserOptions`. We've now shifted to a non-consuming builder pattern to avoid future breakages, as more items get added to `BrowserOptions` ## [0.6.0] - 2022-02-19 ### Changed - Define consistent non-blocking behaviour on all UNIX platforms. Now, unless it's specifically a text browser (like lynx etc.), we make sure that the browser is launched in a non-blocking way. See #18 and https://github.com/amodm/webbrowser-rs/commit/614cacf4a67ae0a75323768a1d70c16d792a760d - Define default behaviour on all UNIX platforms to make sure that stdout/stderr are suppressed. See #20 and https://github.com/amodm/webbrowser-rs/commit/ecfbf66daa0cc139bd557bd7899a183bd6575990 - (Low probability) breaking change: All public functions now return a `Result<()>`. As almost all the uses of this library do a `.is_ok()` or equivalent, there should not be any breaks, but please report a bug if you do. See #42 and #43 - @VZout modified Android implementation to use JNI instead of `am start` because of permission issues in more recent Android. - Define consistent behaviour for non-ascii URLs, where they're now encoded automatically before being invoked. See #34 and https://github.com/amodm/webbrowser-rs/commit/11789ddfe36264bbbe7d596ab61e3fff855c3adb - Richer set of underlying commands used for UNIX to cater to different scenarios at runtime. See https://github.com/amodm/webbrowser-rs/commit/d09eeae4f2ab5664fc01f4dba4a409e1bc11f10e ### Fixed - On WASM, by default URLs are opened with a target of `_blank`. See #39. Thanks to @vbeffa for pointing out the issue. - @tokusumi fixed #41 where addition of `open` command (done for Haiku) was breaking things in some places. ## [0.5.5] - 2020-07-20 ### Added - Support for WASM [PR #26](https://github.com/amodm/webbrowser-rs/pull/26) ## [0.5.4] - 2020-06-09 ### Fixed - Fix README to reflect platform support for Android and Haiku ## [0.5.3] - 2020-06-09 ### Changed - Added support for Haiku (Untested right now!) [PR #21](https://github.com/amodm/webbrowser-rs/pull/21) - Added support for Android [PR #19](https://github.com/amodm/webbrowser-rs/pull/19) - Added support for kioclient and x-www-browser [PR #17](https://github.com/amodm/webbrowser-rs/pull/17) ## [0.5.2] - 2019-08-22 ### Fixed - Fix a COM leak bug on Windows [PR #15](https://github.com/amodm/webbrowser-rs/pull/15) ## [0.5.1] - 2019-04-01 ### Fixed - Fix the behaviour that open() was blocking on Linux and BSD family. [Issue #13](https://github.com/amodm/webbrowser-rs/issues/13) - Fix tests on macos ## [0.5.0] - 2019-03-31 ### Added - Add BSD family to supported platforms. [PR #12](https://github.com/amodm/webbrowser-rs/pull/12) ## [0.4.0] - 2018-12-18 ### Changed - Use `ShellExecuteW` on Windows as the earlier approach of using cmd.exe was breaking on special characters. [PR #11](https://github.com/amodm/webbrowser-rs/pull/11) ### Fixed - Fixed Apache Licensing format ## [0.3.1] - 2018-06-22 ### Fixed - Fix broken examples header. [PR #7](https://github.com/amodm/webbrowser-rs/pull/7) - Fix undeclared reference to `env` that breaks Linux. [PR #8](https://github.com/amodm/webbrowser-rs/pull/8) ## [0.3.0] - 2018-06-18 ### Changed - Change the OS test to use conditional complication and raise a compile error if the target OS is unsupported. [PR #6](https://github.com/amodm/webbrowser-rs/pull/6) - Implement useful trait from StdLib for Browser such as `Display`, `Default` and `FromStr`. [PR #6](https://github.com/amodm/webbrowser-rs/pull/6) ### Fixed - Fix the command in `open_on_windows` to use `cmd.exe` instead of `start`. [PR #5](https://github.com/amodm/webbrowser-rs/pull/5) ## [0.2.2] - 2017-01-23 ### Fixed - Honour the right syntax for `$BROWSER`. Closes [#3](https://github.com/amodm/webbrowser-rs/issues/3) - Include `gvfs-open` and `gnome-open` for [#2](https://github.com/amodm/webbrowser-rs/issues/2) ## [0.2.1] - 2017-01-22 ### Changed - Honour `$BROWSER` env var on Linux, before choosing to fallback to `xdg-open`. [Issue #2](https://github.com/amodm/webbrowser-rs/issues/2) ## [0.1.3] - 2016-01-11 ### Added - Add Apache license [Issue #1](https://github.com/amodm/webbrowser-rs/issues/1) ## [0.1.2] - 2015-12-09 ### Added - Initial release. [Unreleased]: https://github.com/amodm/webbrowser-rs/compare/v0.8.10...HEAD [0.8.10]: https://github.com/amodm/webbrowser-rs/compare/v0.8.9...v0.8.10 [0.8.9]: https://github.com/amodm/webbrowser-rs/compare/v0.8.8...v0.8.9 [0.8.8]: https://github.com/amodm/webbrowser-rs/compare/v0.8.7...v0.8.8 [0.8.7]: https://github.com/amodm/webbrowser-rs/compare/v0.8.6...v0.8.7 [0.8.6]: https://github.com/amodm/webbrowser-rs/compare/v0.8.5...v0.8.6 [0.8.5]: https://github.com/amodm/webbrowser-rs/compare/v0.8.4...v0.8.5 [0.8.4]: https://github.com/amodm/webbrowser-rs/compare/v0.8.3...v0.8.4 [0.8.3]: https://github.com/amodm/webbrowser-rs/compare/v0.8.2...v0.8.3 [0.8.2]: https://github.com/amodm/webbrowser-rs/compare/v0.8.1...v0.8.2 [0.8.1]: https://github.com/amodm/webbrowser-rs/compare/v0.8.0...v0.8.1 [0.8.0]: https://github.com/amodm/webbrowser-rs/compare/v0.7.1...v0.8.0 [0.7.1]: https://github.com/amodm/webbrowser-rs/compare/v0.7.0...v0.7.1 [0.7.0]: https://github.com/amodm/webbrowser-rs/compare/v0.6.0...v0.7.0 [0.6.0]: https://github.com/amodm/webbrowser-rs/compare/v0.5.5...v0.6.0 [0.5.5]: https://github.com/amodm/webbrowser-rs/compare/v0.5.4...v0.5.5 [0.5.4]: https://github.com/amodm/webbrowser-rs/compare/v0.5.3...v0.5.4 [0.5.3]: https://github.com/amodm/webbrowser-rs/compare/v0.5.2...v0.5.3 [0.5.2]: https://github.com/amodm/webbrowser-rs/compare/v0.5.1...v0.5.2 [0.5.1]: https://github.com/amodm/webbrowser-rs/compare/v0.5.0...v0.5.1 [0.5.0]: https://github.com/amodm/webbrowser-rs/compare/v0.4.0...v0.5.0 [0.4.0]: https://github.com/amodm/webbrowser-rs/compare/v0.3.1...v0.4.0 [0.3.1]: https://github.com/amodm/webbrowser-rs/compare/v0.3.0...v0.3.1 [0.3.0]: https://github.com/amodm/webbrowser-rs/compare/v0.2.2...v0.3.0 [0.2.2]: https://github.com/amodm/webbrowser-rs/compare/v0.2.1...v0.2.2 [0.2.1]: https://github.com/amodm/webbrowser-rs/compare/v0.1.3...v0.2.1 [0.1.3]: https://github.com/amodm/webbrowser-rs/compare/v0.1.2...v0.1.3 webbrowser-0.8.10/Cargo.toml0000644000000042220000000000100113010ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "webbrowser" version = "0.8.10" authors = ["Amod Malviya @amodm"] description = "Open URLs in web browsers available on a platform" homepage = "https://github.com/amodm/webbrowser-rs" documentation = "https://docs.rs/webbrowser" readme = "README.md" keywords = [ "webbrowser", "browser", ] license = "MIT OR Apache-2.0" repository = "https://github.com/amodm/webbrowser-rs" [dependencies.log] version = "0.4" [dependencies.url] version = "2" [dev-dependencies.actix-files] version = "0.6" [dev-dependencies.actix-web] version = "4" [dev-dependencies.crossbeam-channel] version = "0.5" [dev-dependencies.env_logger] version = "0.9.0" [dev-dependencies.rand] version = "0.8" [dev-dependencies.serial_test] version = "0.10" [dev-dependencies.tokio] version = "1" features = ["full"] [dev-dependencies.urlencoding] version = "2.1" [features] disable-wsl = [] hardened = [] wasm-console = ["web-sys/console"] [target."cfg(any(target_os = \"linux\", target_os = \"freebsd\", target_os = \"netbsd\", target_os = \"openbsd\", target_os = \"haiku\"))".dependencies.home] version = "0.5" [target."cfg(target_arch = \"wasm32\")".dependencies.web-sys] version = "0.3" features = ["Window"] [target."cfg(target_os = \"android\")".dependencies.jni] version = "0.21" [target."cfg(target_os = \"android\")".dependencies.ndk-context] version = "0.1" [target."cfg(target_os = \"android\")".dev-dependencies.ndk-glue] version = ">= 0.3, <= 0.7" [target."cfg(target_os = \"ios\")".dependencies.objc] version = "0.2.7" [target."cfg(target_os = \"ios\")".dependencies.raw-window-handle] version = "0.5.0" [target."cfg(target_os = \"macos\")".dependencies.core-foundation] version = "0.9" webbrowser-0.8.10/Cargo.toml.orig000064400000000000000000000024611046102023000147650ustar 00000000000000[package] name = "webbrowser" description = "Open URLs in web browsers available on a platform" version = "0.8.10" authors = ["Amod Malviya @amodm"] documentation = "https://docs.rs/webbrowser" homepage = "https://github.com/amodm/webbrowser-rs" repository = "https://github.com/amodm/webbrowser-rs" readme = "README.md" keywords = ["webbrowser", "browser"] license = "MIT OR Apache-2.0" edition = "2018" [dependencies] log = "0.4" url = "2" [target.'cfg(target_arch = "wasm32")'.dependencies.web-sys] version = "0.3" features = ['Window'] [features] hardened = [] disable-wsl = [] wasm-console = ["web-sys/console"] [target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "haiku"))'.dependencies] home = "0.5" [target.'cfg(target_os = "macos")'.dependencies] core-foundation = "0.9" [target.'cfg(target_os = "android")'.dependencies] jni = "0.21" ndk-context = "0.1" [target.'cfg(target_os = "ios")'.dependencies] raw-window-handle = "0.5.0" objc = "0.2.7" [dev-dependencies] actix-web = "4" actix-files = "0.6" crossbeam-channel = "0.5" env_logger = "0.9.0" rand = "0.8" serial_test = "0.10" tokio = { version = "1", features = ["full"] } urlencoding = "2.1" [target.'cfg(target_os = "android")'.dev-dependencies] ndk-glue = { version = ">= 0.3, <= 0.7" } webbrowser-0.8.10/LICENSE-APACHE000064400000000000000000000240531046102023000140230ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Copyright (c) 2015-2022 Amod Malviya Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. webbrowser-0.8.10/LICENSE-MIT000064400000000000000000000020741046102023000135320ustar 00000000000000The MIT License (MIT) Copyright (c) 2015-2022 Amod Malviya Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. webbrowser-0.8.10/README.md000064400000000000000000000076741046102023000133700ustar 00000000000000# webbrowser [![Current Crates.io Version](https://img.shields.io/crates/v/webbrowser.svg)](https://crates.io/crates/webbrowser) [![Crates.io Downloads](https://img.shields.io/crates/d/webbrowser.svg)](https://crates.io/crates/webbrowser) [![License](https://img.shields.io/crates/l/webbrowser.svg)](#license) ![Linux Build](https://github.com/amodm/webbrowser-rs/workflows/Linux/badge.svg?branch=main&x=1) ![Windows Build](https://github.com/amodm/webbrowser-rs/workflows/Windows/badge.svg?branch=main&x=1) ![MacOS Build](https://github.com/amodm/webbrowser-rs/workflows/MacOS/badge.svg?branch=main&x=1) ![iOS Build](https://github.com/amodm/webbrowser-rs/workflows/iOS/badge.svg?branch=main&x=1) ![Android Build](https://github.com/amodm/webbrowser-rs/workflows/Android/badge.svg?branch=main&x=1) ![WASM Build](https://github.com/amodm/webbrowser-rs/workflows/WASM/badge.svg?branch=main&x=1) Rust library to open URLs and local files in the web browsers available on a platform, with guarantees of [Consistent Behaviour](#consistent-behaviour). Inspired by the [webbrowser](https://docs.python.org/2/library/webbrowser.html) python library ## Documentation - [API Reference](https://docs.rs/webbrowser) - [Release Notes](CHANGELOG.md) ## Examples ```rust use webbrowser; if webbrowser::open("http://github.com").is_ok() { // ... } ``` ## Platform Support | Platform | Supported | Browsers | Test status | |----------|-----------|----------|-------------| | macos | ✅ | default + [others](https://docs.rs/webbrowser/latest/webbrowser/enum.Browser.html) | ✅ | | windows | ✅ | default only | ✅ | | linux/wsl/*bsd | ✅ | default only (respects $BROWSER env var, so can be used with other browsers) | ✅ | | android | ✅ | default only | ✅ | | ios | ✅ | default only | ✅ | | wasm | ✅ | default only | ✅ | | haiku | ✅ (experimental) | default only | ❌ | ## Consistent Behaviour `webbrowser` defines consistent behaviour on all platforms as follows: * **Browser guarantee** - This library guarantees that the browser is opened, even for local files - the only crate to make such guarantees at the time of this writing. Alternative libraries rely on existing system commands, which may lead to an editor being opened (instead of the browser) for local html files, leading to an inconsistent behaviour for users. * **Non-Blocking** for GUI based browsers (e.g. Firefox, Chrome etc.), while **Blocking** for text based browser (e.g. lynx etc.) * **Suppressed output** by default for GUI based browsers, so that their stdout/stderr don't pollute the main program's output. This can be overridden by `webbrowser::open_browser_with_options`. ## Crate Features `webbrowser` optionally allows the following features to be configured: * `hardened` - this disables handling of non-http(s) urls (e.g. `file:///`) as a hard security precaution * `disable-wsl` - this disables WSL `file` implementation (`http` still works) * `wasm-console` - this enables logging to wasm console (valid only on wasm platform) ## Looking to contribute? PRs invited for * Bugs * Supporting non-default browser invocation on any platform Important note (while testing): * This library requires availability of browsers and a graphical environment during runtime * `cargo test` will actually open the browser locally When contributing, please note that your work will be dual licensed as MIT + Apache-2.0 (see below). ## License `SPDX-License-Identifier: Apache-2.0 OR MIT` Licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. webbrowser-0.8.10/SECURITY.md000064400000000000000000000010501046102023000136600ustar 00000000000000# Security Policy If you're looking to report a security vulnerability, please go to the [Security -> Advisories](https://github.com/amodm/webbrowser-rs/security/advisories) section, and click on the `Report a vulnerability` button What to expect: * 48hr turnaround to acknowledge the issue * The closure ETA can be varied depending on the degree of severity and complexity of resolution * I have nothing to offer as a reward, except a virtual hug :hugs: as thanks for your selfless effort, and acknowledging your contribution on the release notes. webbrowser-0.8.10/clippy.toml000064400000000000000000000000161046102023000142650ustar 00000000000000msrv = "1.45" webbrowser-0.8.10/src/android.rs000064400000000000000000000105161046102023000146530ustar 00000000000000use crate::{Browser, BrowserOptions, Error, ErrorKind, Result, TargetType}; use jni::objects::{JObject, JValue}; use std::process::{Command, Stdio}; /// Deal with opening of browsers on Android. Only [Browser::Default] is supported, and /// in options, only [BrowserOptions::dry_run] is honoured. pub(super) fn open_browser_internal( browser: Browser, target: &TargetType, options: &BrowserOptions, ) -> Result<()> { // ensure we're opening only http/https urls, failing otherwise let url = target.get_http_url()?; match browser { Browser::Default => open_browser_default(url, options), _ => Err(Error::new( ErrorKind::NotFound, "only default browser supported", )), } } /// Open the default browser fn open_browser_default(url: &str, options: &BrowserOptions) -> Result<()> { // always return true for a dry run if options.dry_run { return Ok(()); } // first we try to see if we're in a termux env, because if we are, then // the android context may not have been initialized, and it'll panic if try_for_termux(url, options).is_ok() { return Ok(()); } // Create a VM for executing Java calls let ctx = ndk_context::android_context(); let vm = unsafe { jni::JavaVM::from_raw(ctx.vm() as _) }.map_err(|_| { Error::new( ErrorKind::NotFound, "Expected to find JVM via ndk_context crate", ) })?; let activity = unsafe { jni::objects::JObject::from_raw(ctx.context() as _) }; let mut env = vm .attach_current_thread() .map_err(|_| Error::new(ErrorKind::Other, "Failed to attach current thread"))?; // Create ACTION_VIEW object let intent_class = env .find_class("android/content/Intent") .map_err(|_| Error::new(ErrorKind::NotFound, "Failed to find Intent class"))?; let action_view = env .get_static_field(&intent_class, "ACTION_VIEW", "Ljava/lang/String;") .map_err(|_| Error::new(ErrorKind::NotFound, "Failed to get intent.ACTION_VIEW"))?; // Create Uri object let uri_class = env .find_class("android/net/Uri") .map_err(|_| Error::new(ErrorKind::NotFound, "Failed to find Uri class"))?; let url = env .new_string(url) .map_err(|_| Error::new(ErrorKind::Other, "Failed to create JNI string"))?; let uri = env .call_static_method( &uri_class, "parse", "(Ljava/lang/String;)Landroid/net/Uri;", &[JValue::Object(&JObject::from(url))], ) .map_err(|_| Error::new(ErrorKind::Other, "Failed to parse JNI Uri"))?; // Create new ACTION_VIEW intent with the uri let intent = env .alloc_object(&intent_class) .map_err(|_| Error::new(ErrorKind::Other, "Failed to allocate intent"))?; env.call_method( &intent, "", "(Ljava/lang/String;Landroid/net/Uri;)V", &[action_view.borrow(), uri.borrow()], ) .map_err(|_| Error::new(ErrorKind::Other, "Failed to initialize intent"))?; // Start the intent activity. env.call_method( &activity, "startActivity", "(Landroid/content/Intent;)V", &[JValue::Object(&intent)], ) .map_err(|_| Error::new(ErrorKind::Other, "Failed to start activity"))?; Ok(()) } /// Attemps to open a browser assuming a termux environment /// /// See [issue #53](https://github.com/amodm/webbrowser-rs/issues/53) fn try_for_termux(url: &str, options: &BrowserOptions) -> Result<()> { use std::env; if env::var("TERMUX_VERSION").is_ok() { // return true on dry-run given that termux-open command is guaranteed to be present if options.dry_run { return Ok(()); } let mut cmd = Command::new("termux-open"); cmd.arg(url); if options.suppress_output { cmd.stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()); } cmd.status().and_then(|status| { if status.success() { Ok(()) } else { Err(Error::new( ErrorKind::Other, "command present but exited unsuccessfully", )) } }) } else { Err(Error::new(ErrorKind::Other, "Not a termux environment")) } } webbrowser-0.8.10/src/common.rs000064400000000000000000000052171046102023000145250ustar 00000000000000use super::{BrowserOptions, Error, ErrorKind, Result}; use log::debug; use std::process::{Command, Stdio}; /// Parses `line` to find tokens (including quoted strings), and invokes `op` /// on each token pub(crate) fn for_each_token(line: &str, mut op: F) where F: FnMut(&str), { let mut start: Option = None; let mut in_quotes = false; let mut idx = 0; for ch in line.chars() { idx += 1; match ch { '"' => { if let Some(start_idx) = start { op(&line[start_idx..idx - 1]); start = None; in_quotes = false; } else { start = Some(idx); in_quotes = true; } } ' ' => { if !in_quotes { if let Some(start_idx) = start { op(&line[start_idx..idx - 1]); start = None; } } } _ => { if start.is_none() { start = Some(idx - 1); } } } } if let Some(start_idx) = start { op(&line[start_idx..idx]); } } /// Run the specified command in foreground/background pub(crate) fn run_command( cmd: &mut Command, background: bool, options: &BrowserOptions, ) -> Result<()> { // if dry_run, we return a true, as executable existence check has // already been done if options.dry_run { debug!("dry-run enabled, so not running: {:?}", &cmd); return Ok(()); } if background { debug!("background spawn: {:?}", &cmd); // if we're in background, set stdin/stdout to null and spawn a child, as we're // not supposed to have any interaction. if options.suppress_output { cmd.stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) } else { cmd } .spawn() .map(|_| ()) } else { debug!("foreground exec: {:?}", &cmd); // if we're in foreground, use status() instead of spawn(), as we'd like to wait // till completion. // We also specifically don't suppress anything here, because we're running here // most likely because of a text browser cmd.status().and_then(|status| { if status.success() { Ok(()) } else { Err(Error::new( ErrorKind::Other, "command present but exited unsuccessfully", )) } }) } } webbrowser-0.8.10/src/ios.rs000064400000000000000000000025251046102023000140260ustar 00000000000000use crate::{Browser, BrowserOptions, Error, ErrorKind, Result, TargetType}; use objc::{class, msg_send, runtime::Object, sel, sel_impl}; /// Deal with opening of browsers on iOS pub(super) fn open_browser_internal( _browser: Browser, target: &TargetType, options: &BrowserOptions, ) -> Result<()> { // ensure we're opening only http/https urls, failing otherwise let url = target.get_http_url()?; // always return true for a dry run if options.dry_run { return Ok(()); } unsafe { let app: *mut Object = msg_send![class!(UIApplication), sharedApplication]; if app.is_null() { return Err(Error::new( ErrorKind::Other, "UIApplication is null, can't open url", )); } let url_cstr = std::ffi::CString::new(url).unwrap(); // Create ns string class from our string let url_string: *mut Object = msg_send![class!(NSString), stringWithUTF8String: url_cstr]; // Create NSURL object with given string let url_object: *mut Object = msg_send![class!(NSURL), URLWithString: url_string]; // No completion handler let null_ptr = std::ptr::null_mut::(); // Open url let () = msg_send![app, openURL: url_object options: {} completionHandler: null_ptr]; Ok(()) } } webbrowser-0.8.10/src/lib.rs000064400000000000000000000316011046102023000137770ustar 00000000000000//! Rust library to open URLs and local files in the web browsers available on a platform, with guarantees of [Consistent Behaviour](#consistent-behaviour). //! //! Inspired by the [webbrowser](https://docs.python.org/2/library/webbrowser.html) python library. //! //! ## Examples //! //! ```no_run //! use webbrowser; //! //! if webbrowser::open("http://github.com").is_ok() { //! // ... //! } //! ``` //! //! ## Platform Support Status //! //! | Platform | Supported | Browsers | Test status | //! |----------|-----------|----------|-------------| //! | macos | ✅ | default + [others](https://docs.rs/webbrowser/latest/webbrowser/enum.Browser.html) | ✅ | //! | windows | ✅ | default only | ✅ | //! | linux/wsl/*bsd | ✅ | default only (respects $BROWSER env var, so can be used with other browsers) | ✅ | //! | android | ✅ | default only | ✅ | //! | ios | ✅ | default only | ✅ | //! | wasm | ✅ | default only | ✅ | //! | haiku | ✅ (experimental) | default only | ❌ | //! //! ## Consistent Behaviour //! `webbrowser` defines consistent behaviour on all platforms as follows: //! * **Browser guarantee** - This library guarantees that the browser is opened, even for local files - the only crate to make such guarantees //! at the time of this writing. Alternative libraries rely on existing system commands, which may lead to an editor being opened (instead //! of the browser) for local html files, leading to an inconsistent behaviour for users. //! * **Non-Blocking** for GUI based browsers (e.g. Firefox, Chrome etc.), while **Blocking** for text based browser (e.g. lynx etc.) //! * **Suppressed output** by default for GUI based browsers, so that their stdout/stderr don't pollute the main program's output. This can be //! overridden by `webbrowser::open_browser_with_options`. //! //! ## Crate Features //! `webbrowser` optionally allows the following features to be configured: //! * `hardened` - this disables handling of non-http(s) urls (e.g. `file:///`) as a hard security precaution //! * `disable-wsl` - this disables WSL `file` implementation (`http` still works) //! * `wasm-console` - this enables logging to wasm console (valid only on wasm platform) #[cfg_attr(target_os = "ios", path = "ios.rs")] #[cfg_attr(target_os = "macos", path = "macos.rs")] #[cfg_attr(target_os = "android", path = "android.rs")] #[cfg_attr(target_arch = "wasm32", path = "wasm.rs")] #[cfg_attr(windows, path = "windows.rs")] #[cfg_attr( any( target_os = "linux", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "haiku" ), path = "unix.rs" )] mod os; #[cfg(not(any( target_os = "android", target_os = "windows", target_os = "macos", target_os = "linux", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "haiku", target_os = "ios", target_arch = "wasm32" )))] compile_error!( "Only Windows, Mac OS, iOS, Linux, *BSD and Haiku and Wasm32 are currently supported" ); #[cfg(any( target_os = "linux", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "haiku", target_os = "windows" ))] pub(crate) mod common; use std::convert::TryFrom; use std::default::Default; use std::fmt::Display; use std::io::{Error, ErrorKind, Result}; use std::ops::Deref; use std::str::FromStr; use std::{error, fmt}; #[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] /// Browser types available pub enum Browser { ///Operating system's default browser Default, ///Mozilla Firefox Firefox, ///Microsoft's Internet Explorer InternetExplorer, ///Google Chrome Chrome, ///Opera Opera, ///Mac OS Safari Safari, ///Haiku's WebPositive WebPositive, } impl Browser { /// Returns true if there is likely a browser detected in the system pub fn is_available() -> bool { Browser::Default.exists() } /// Returns true if this specific browser is detected in the system pub fn exists(&self) -> bool { open_browser_with_options( *self, "https://rootnet.in", BrowserOptions::new().with_dry_run(true), ) .is_ok() } } ///The Error type for parsing a string into a Browser. #[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] pub struct ParseBrowserError; impl fmt::Display for ParseBrowserError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("Invalid browser given") } } impl error::Error for ParseBrowserError { fn description(&self) -> &str { "invalid browser" } } impl Default for Browser { fn default() -> Self { Browser::Default } } impl fmt::Display for Browser { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Browser::Default => f.write_str("Default"), Browser::Firefox => f.write_str("Firefox"), Browser::InternetExplorer => f.write_str("Internet Explorer"), Browser::Chrome => f.write_str("Chrome"), Browser::Opera => f.write_str("Opera"), Browser::Safari => f.write_str("Safari"), Browser::WebPositive => f.write_str("WebPositive"), } } } impl FromStr for Browser { type Err = ParseBrowserError; fn from_str(s: &str) -> ::std::result::Result { match s { "firefox" => Ok(Browser::Firefox), "default" => Ok(Browser::Default), "ie" | "internet explorer" | "internetexplorer" => Ok(Browser::InternetExplorer), "chrome" => Ok(Browser::Chrome), "opera" => Ok(Browser::Opera), "safari" => Ok(Browser::Safari), "webpositive" => Ok(Browser::WebPositive), _ => Err(ParseBrowserError), } } } #[derive(Debug, Eq, PartialEq, Clone, Hash)] /// BrowserOptions to override certain default behaviour. Any option named as a `hint` is /// not guaranteed to be honoured. Use [BrowserOptions::new()] to create. /// /// e.g. by default, we suppress stdout/stderr, but that behaviour can be overridden here pub struct BrowserOptions { suppress_output: bool, target_hint: String, dry_run: bool, } impl fmt::Display for BrowserOptions { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_fmt(format_args!( "BrowserOptions(supress_output={}, target_hint={}, dry_run={})", self.suppress_output, self.target_hint, self.dry_run )) } } impl std::default::Default for BrowserOptions { fn default() -> Self { let target_hint = String::from(option_env!("WEBBROWSER_WASM_TARGET").unwrap_or("_blank")); BrowserOptions { suppress_output: true, target_hint, dry_run: false, } } } impl BrowserOptions { /// Create a new instance. Configure it with one of the `with_` methods. pub fn new() -> Self { Self::default() } /// Determines whether stdout/stderr of the appropriate browser command is suppressed /// or not pub fn with_suppress_output(&mut self, suppress_output: bool) -> &mut Self { self.suppress_output = suppress_output; self } /// Hint to the browser to open the url in the corresponding /// [target](https://www.w3schools.com/tags/att_a_target.asp). Note that this is just /// a hint, it may or may not be honoured (currently guaranteed only in wasm). pub fn with_target_hint(&mut self, target_hint: &str) -> &mut Self { self.target_hint = target_hint.to_owned(); self } /// Do not do an actual execution, just return true if this would've likely /// succeeded. Note the "likely" here - it's still indicative than guaranteed. pub fn with_dry_run(&mut self, dry_run: bool) -> &mut Self { self.dry_run = dry_run; self } } /// Opens the URL on the default browser of this platform /// /// Returns Ok(..) so long as the browser invocation was successful. An Err(..) is returned in the /// following scenarios: /// * The requested browser was not found /// * There was an error in opening the browser /// * `hardened` feature is enabled, and the URL was not a valid http(s) url, say a `file:///` /// * On ios/android/wasm, if the url is not a valid http(s) url /// /// Equivalent to: /// ```no_run /// # use webbrowser::{Browser, open_browser}; /// # let url = "http://example.com"; /// open_browser(Browser::Default, url); /// ``` /// /// # Examples /// ```no_run /// use webbrowser; /// /// if webbrowser::open("http://github.com").is_ok() { /// // ... /// } /// ``` pub fn open(url: &str) -> Result<()> { open_browser(Browser::Default, url) } /// Opens the specified URL on the specific browser (if available) requested. Return semantics are /// the same as for [open](fn.open.html). /// /// # Examples /// ```no_run /// use webbrowser::{open_browser, Browser}; /// /// if open_browser(Browser::Firefox, "http://github.com").is_ok() { /// // ... /// } /// ``` pub fn open_browser(browser: Browser, url: &str) -> Result<()> { open_browser_with_options(browser, url, &BrowserOptions::default()) } /// Opens the specified URL on the specific browser (if available) requested, while overriding the /// default options. /// /// Return semantics are /// the same as for [open](fn.open.html). /// /// # Examples /// ```no_run /// use webbrowser::{open_browser_with_options, Browser, BrowserOptions}; /// /// if open_browser_with_options(Browser::Default, "http://github.com", BrowserOptions::new().with_suppress_output(false)).is_ok() { /// // ... /// } /// ``` pub fn open_browser_with_options( browser: Browser, url: &str, options: &BrowserOptions, ) -> Result<()> { let target = TargetType::try_from(url)?; // if feature:hardened is enabled, make sure we accept only HTTP(S) URLs #[cfg(feature = "hardened")] if !target.is_http() { return Err(Error::new( ErrorKind::InvalidInput, "only http/https urls allowed", )); } os::open_browser_internal(browser, &target, options) } /// The link we're trying to open, represented as a URL. Local files get represented /// via `file://...` URLs struct TargetType(url::Url); impl TargetType { /// Returns true if this target represents an HTTP url, false otherwise #[cfg(any( feature = "hardened", target_os = "android", target_os = "ios", target_family = "wasm" ))] fn is_http(&self) -> bool { matches!(self.0.scheme(), "http" | "https") } /// If `target` represents a valid http/https url, return the str corresponding to it /// else return `std::io::Error` of kind `std::io::ErrorKind::InvalidInput` #[cfg(any(target_os = "android", target_os = "ios", target_family = "wasm"))] fn get_http_url(&self) -> Result<&str> { if self.is_http() { Ok(self.0.as_str()) } else { Err(Error::new(ErrorKind::InvalidInput, "not an http url")) } } #[cfg(not(target_family = "wasm"))] fn from_file_path(value: &str) -> Result { let pb = std::path::PathBuf::from(value); let url = url::Url::from_file_path(if pb.is_relative() { std::env::current_dir()?.join(pb) } else { pb }) .map_err(|_| Error::new(ErrorKind::InvalidInput, "failed to convert path to url"))?; Ok(Self(url)) } } impl Deref for TargetType { type Target = str; fn deref(&self) -> &Self::Target { self.0.as_str() } } impl Display for TargetType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { (self as &str).fmt(f) } } impl TryFrom<&str> for TargetType { type Error = Error; #[cfg(target_family = "wasm")] fn try_from(value: &str) -> Result { url::Url::parse(value) .map(|u| Ok(Self(u))) .map_err(|_| Error::new(ErrorKind::InvalidInput, "invalid url for wasm"))? } #[cfg(not(target_family = "wasm"))] fn try_from(value: &str) -> Result { match url::Url::parse(value) { Ok(u) => { if u.scheme().len() == 1 && cfg!(windows) { // this can happen in windows that C:\abc.html gets parsed as scheme "C" Self::from_file_path(value) } else { Ok(Self(u)) } } Err(_) => Self::from_file_path(value), } } } #[test] #[ignore] fn test_open_firefox() { assert!(open_browser(Browser::Firefox, "http://github.com").is_ok()); } #[test] #[ignore] fn test_open_chrome() { assert!(open_browser(Browser::Chrome, "http://github.com").is_ok()); } #[test] #[ignore] fn test_open_safari() { assert!(open_browser(Browser::Safari, "http://github.com").is_ok()); } #[test] #[ignore] fn test_open_webpositive() { assert!(open_browser(Browser::WebPositive, "http://github.com").is_ok()); } webbrowser-0.8.10/src/macos.rs000064400000000000000000000174131046102023000143400ustar 00000000000000use crate::{Browser, BrowserOptions, Error, ErrorKind, Result, TargetType}; use core_foundation::array::{CFArray, CFArrayRef}; use core_foundation::base::TCFType; use core_foundation::error::{CFError, CFErrorRef}; use core_foundation::url::{CFURLRef, CFURL}; use std::os::raw::c_void; /// Deal with opening of browsers on Mac OS X using Core Foundation framework pub(super) fn open_browser_internal( browser: Browser, target: &TargetType, options: &BrowserOptions, ) -> Result<()> { // create the CFUrl for the browser let browser_cf_url = match browser { Browser::Firefox => create_cf_url("file:///Applications/Firefox.app/"), Browser::Chrome => create_cf_url("file:///Applications/Google Chrome.app/"), Browser::Opera => create_cf_url("file:///Applications/Opera.app/"), Browser::Safari => create_cf_url("file:///Applications/Safari.app/"), Browser::Default => { if let Some(dummy_url) = create_cf_url("https://") { let mut err: CFErrorRef = std::ptr::null_mut(); let result = unsafe { LSCopyDefaultApplicationURLForURL( dummy_url.as_concrete_TypeRef(), LSROLE_VIEWER, &mut err, ) }; if result.is_null() { log::error!("failed to get default browser: {}", unsafe { CFError::wrap_under_create_rule(err) }); create_cf_url(DEFAULT_BROWSER_URL) } else { let cf_url = unsafe { CFURL::wrap_under_create_rule(result) }; log::trace!("default browser is {:?}", &cf_url); Some(cf_url) } } else { create_cf_url(DEFAULT_BROWSER_URL) } } _ => { return Err(Error::new( ErrorKind::NotFound, "browser not supported on macos", )) } } .ok_or_else(|| Error::new(ErrorKind::Other, "failed to create CFURL"))?; let cf_url = create_cf_url(target.as_ref()) .ok_or_else(|| Error::new(ErrorKind::Other, "failed to create CFURL"))?; let urls_v = [cf_url]; let urls_arr = CFArray::::from_CFTypes(&urls_v); let spec = LSLaunchURLSpec { app_url: browser_cf_url.as_concrete_TypeRef(), item_urls: urls_arr.as_concrete_TypeRef(), pass_thru_params: std::ptr::null(), launch_flags: LS_LAUNCH_FLAG_DEFAULTS | LS_LAUNCH_FLAG_ASYNC, async_ref_con: std::ptr::null(), }; // handle dry-run scenario if options.dry_run { return if let Some(path) = browser_cf_url.to_path() { if path.is_dir() { log::debug!("dry-run: not actually opening the browser {}", &browser); Ok(()) } else { log::debug!("dry-run: browser {} not found", &browser); Err(Error::new(ErrorKind::NotFound, "browser not found")) } } else { Err(Error::new( ErrorKind::Other, "unable to convert app url to path", )) }; } // launch the browser log::trace!("about to start browser: {} for {}", &browser, &target); let mut launched_app: CFURLRef = std::ptr::null_mut(); let status = unsafe { LSOpenFromURLSpec(&spec, &mut launched_app) }; log::trace!("received status: {}", status); if status == 0 { Ok(()) } else { Err(Error::from(LSError::from(status))) } } /// Create a Core Foundation CFURL object given a rust-y `url` fn create_cf_url(url: &str) -> Option { let url_u8 = url.as_bytes(); let url_ref = unsafe { core_foundation::url::CFURLCreateWithBytes( std::ptr::null(), url_u8.as_ptr(), url_u8.len() as isize, core_foundation::string::kCFStringEncodingUTF8, std::ptr::null(), ) }; if url_ref.is_null() { None } else { Some(unsafe { CFURL::wrap_under_create_rule(url_ref) }) } } type OSStatus = i32; /// A subset of Launch Services error codes as picked from (`Result Codes` section) /// https://developer.apple.com/documentation/coreservices/launch_services?language=objc#1661359 enum LSError { Unknown(OSStatus), ApplicationNotFound, NoLaunchPermission, } impl From for LSError { fn from(status: OSStatus) -> Self { match status { // -43 is file not found, while -10814 is launch services err code -43 | -10814 => Self::ApplicationNotFound, -10826 => Self::NoLaunchPermission, _ => Self::Unknown(status), } } } impl std::fmt::Display for LSError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Unknown(code) => write!(f, "ls_error: code {}", code), Self::ApplicationNotFound => f.write_str("ls_error: application not found"), Self::NoLaunchPermission => f.write_str("ls_error: no launch permission"), } } } impl std::fmt::Debug for LSError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self, f) } } impl From for Error { fn from(err: LSError) -> Self { let kind = match err { LSError::Unknown(_) => ErrorKind::Other, LSError::ApplicationNotFound => ErrorKind::NotFound, LSError::NoLaunchPermission => ErrorKind::PermissionDenied, }; Error::new(kind, err.to_string()) } } type LSRolesMask = u32; // as per https://developer.apple.com/documentation/coreservices/lsrolesmask/klsrolesviewer?language=objc const LSROLE_VIEWER: LSRolesMask = 0x00000002; // as per https://developer.apple.com/documentation/coreservices/lslaunchflags/klslaunchdefaults?language=objc const LS_LAUNCH_FLAG_DEFAULTS: u32 = 0x00000001; const LS_LAUNCH_FLAG_ASYNC: u32 = 0x00010000; #[repr(C)] struct LSLaunchURLSpec { app_url: CFURLRef, item_urls: CFArrayRef, pass_thru_params: *const c_void, launch_flags: u32, async_ref_con: *const c_void, } // Define the functions in CoreServices that we'll be using to open the browser #[link(name = "CoreServices", kind = "framework")] extern "C" { /// Used to get the default browser configured for the user. See: /// https://developer.apple.com/documentation/coreservices/1448824-lscopydefaultapplicationurlforur?language=objc fn LSCopyDefaultApplicationURLForURL( inURL: CFURLRef, inRoleMask: LSRolesMask, outError: *mut CFErrorRef, ) -> CFURLRef; /// Used to launch the browser to open a url /// https://developer.apple.com/documentation/coreservices/1441986-lsopenfromurlspec?language=objc fn LSOpenFromURLSpec( inLaunchSpec: *const LSLaunchURLSpec, outLaunchedURL: *mut CFURLRef, ) -> OSStatus; } /// We assume Safari to be the default browser, if deductions fail for any reason const DEFAULT_BROWSER_URL: &str = "file:///Applications/Safari.app/"; #[cfg(test)] mod tests { use super::*; use std::convert::TryFrom; #[test] fn open_non_existing_browser() { let _ = env_logger::try_init(); if let Err(err) = open_browser_internal( Browser::Opera, &TargetType::try_from("https://github.com").expect("failed to parse url"), &BrowserOptions::default(), ) { assert_eq!(err.kind(), ErrorKind::NotFound); } else { panic!("expected opening non-existing browser to fail"); } } #[test] fn test_existence() { let _ = env_logger::try_init(); assert!(Browser::Safari.exists()); assert!(!Browser::Opera.exists()); } } webbrowser-0.8.10/src/unix.rs000064400000000000000000000736041046102023000142250ustar 00000000000000use crate::common::run_command; use crate::{Browser, BrowserOptions, Error, ErrorKind, Result, TargetType}; use log::trace; use std::io::{BufRead, BufReader}; use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf, MAIN_SEPARATOR}; use std::process::{Command, Stdio}; macro_rules! try_browser { ( $options: expr, $name:expr, $( $arg:expr ),+ ) => { for_matching_path($name, |pb| { let mut cmd = Command::new(pb); $( cmd.arg($arg); )+ run_command(&mut cmd, !is_text_browser(&pb), $options) }) } } /// Deal with opening of browsers on Linux and *BSD - currently supports only the default browser /// /// The mechanism of opening the default browser is as follows: /// 1. Attempt to use $BROWSER env var if available /// 2. Attempt to use xdg-open /// 3. Attempt to use window manager specific commands, like gnome-open, kde-open etc. incl. WSL /// 4. Fallback to x-www-browser pub(super) fn open_browser_internal( browser: Browser, target: &TargetType, options: &BrowserOptions, ) -> Result<()> { match browser { Browser::Default => open_browser_default(target, options), _ => Err(Error::new( ErrorKind::NotFound, "only default browser supported", )), } } /// Open the default browser. /// /// [BrowserOptions::dry_run] is handled inside [run_command], as all execution paths eventually /// rely on it to execute. fn open_browser_default(target: &TargetType, options: &BrowserOptions) -> Result<()> { let url: &str = target; // we first try with the $BROWSER env try_with_browser_env(url, options) // allow for haiku's open specifically .or_else(|_| try_haiku(options, url)) // then we try with xdg configuration .or_else(|_| try_xdg(options, url)) // else do desktop specific stuff .or_else(|r| match guess_desktop_env() { "kde" => try_browser!(options, "kde-open", url) .or_else(|_| try_browser!(options, "kde-open5", url)) .or_else(|_| try_browser!(options, "kfmclient", "newTab", url)), "gnome" => try_browser!(options, "gio", "open", url) .or_else(|_| try_browser!(options, "gvfs-open", url)) .or_else(|_| try_browser!(options, "gnome-open", url)), "mate" => try_browser!(options, "gio", "open", url) .or_else(|_| try_browser!(options, "gvfs-open", url)) .or_else(|_| try_browser!(options, "mate-open", url)), "xfce" => try_browser!(options, "exo-open", url) .or_else(|_| try_browser!(options, "gio", "open", url)) .or_else(|_| try_browser!(options, "gvfs-open", url)), "wsl" => try_wsl(options, target), "flatpak" => try_flatpak(options, target), _ => Err(r), }) // at the end, we'll try x-www-browser and return the result as is .or_else(|_| try_browser!(options, "x-www-browser", url)) // if all above failed, map error to not found .map_err(|_| { Error::new( ErrorKind::NotFound, "No valid browsers detected. You can specify one in BROWSERS environment variable", ) }) // and convert a successful result into a () .map(|_| ()) } fn try_with_browser_env(url: &str, options: &BrowserOptions) -> Result<()> { // $BROWSER can contain ':' delimited options, each representing a potential browser command line for browser in std::env::var("BROWSER") .unwrap_or_else(|_| String::from("")) .split(':') { if !browser.is_empty() { // each browser command can have %s to represent URL, while %c needs to be replaced // with ':' and %% with '%' let cmdline = browser .replace("%s", url) .replace("%c", ":") .replace("%%", "%"); let cmdarr: Vec<&str> = cmdline.split_ascii_whitespace().collect(); let browser_cmd = cmdarr[0]; let env_exit = for_matching_path(browser_cmd, |pb| { let mut cmd = Command::new(pb); for arg in cmdarr.iter().skip(1) { cmd.arg(arg); } if !browser.contains("%s") { // append the url as an argument only if it was not already set via %s cmd.arg(url); } run_command(&mut cmd, !is_text_browser(pb), options) }); if env_exit.is_ok() { return Ok(()); } } } Err(Error::new( ErrorKind::NotFound, "No valid browser configured in BROWSER environment variable", )) } /// Check if we are inside WSL on Windows, and interoperability with Windows tools is /// enabled. fn is_wsl() -> bool { // we should check in procfs only on linux, as for non-linux it will likely be // a disk hit, which we should avoid. if cfg!(target_os = "linux") { // we check if interop with windows tools is allowed, as if it isn't, we won't // be able to invoke windows commands anyways. // See: https://learn.microsoft.com/en-us/windows/wsl/filesystems#disable-interoperability if let Ok(s) = std::fs::read_to_string("/proc/sys/fs/binfmt_misc/WSLInterop") { s.contains("enabled") } else { false } } else { // we short-circuit and return false on non-linux false } } /// Check if we're running inside Flatpak #[inline] fn is_flatpak() -> bool { std::env::var("container") .map(|x| x.eq_ignore_ascii_case("flatpak")) .unwrap_or(false) } /// Detect the desktop environment fn guess_desktop_env() -> &'static str { let unknown = "unknown"; let xcd: String = std::env::var("XDG_CURRENT_DESKTOP") .unwrap_or_else(|_| unknown.into()) .to_ascii_lowercase(); let dsession: String = std::env::var("DESKTOP_SESSION") .unwrap_or_else(|_| unknown.into()) .to_ascii_lowercase(); if is_flatpak() { "flatpak" } else if xcd.contains("gnome") || xcd.contains("cinnamon") || dsession.contains("gnome") { // GNOME and its derivatives "gnome" } else if xcd.contains("kde") || std::env::var("KDE_FULL_SESSION").is_ok() || std::env::var("KDE_SESSION_VERSION").is_ok() { // KDE: https://userbase.kde.org/KDE_System_Administration/Environment_Variables#Automatically_Set_Variables "kde" } else if xcd.contains("mate") || dsession.contains("mate") { // We'll treat MATE as distinct from GNOME due to mate-open "mate" } else if xcd.contains("xfce") || dsession.contains("xfce") { // XFCE "xfce" } else if is_wsl() { // WSL "wsl" } else { // All others unknown } } /// Open browser in WSL environments fn try_wsl(options: &BrowserOptions, target: &TargetType) -> Result<()> { match target.0.scheme() { "http" | "https" => { let url: &str = target; try_browser!(options, "cmd.exe", "/c", "start", url) .or_else(|_| try_browser!(options, "powershell.exe", "Start", url)) .or_else(|_| try_browser!(options, "wsl-open", url)) } #[cfg(all( target_os = "linux", not(feature = "hardened"), not(feature = "disable-wsl") ))] "file" => { // we'll need to detect the default browser and then invoke it // with wsl translated path let wc = wsl::get_wsl_win_config()?; let mut cmd = if wc.powershell_path.is_some() { wsl::get_wsl_windows_browser_ps(&wc, target) } else { wsl::get_wsl_windows_browser_cmd(&wc, target) }?; run_command(&mut cmd, true, options) } _ => Err(Error::new(ErrorKind::NotFound, "invalid browser")), } } /// Open browser in Flatpak environments fn try_flatpak(options: &BrowserOptions, target: &TargetType) -> Result<()> { match target.0.scheme() { "http" | "https" => { let url: &str = target; // we assume xdg-open to be present, given that it's a part of standard // runtime & SDK of flatpak try_browser!(options, "xdg-open", url) } // we support only http urls under Flatpak to adhere to the defined // Consistent Behaviour, as effectively DBUS is used interally, and // there doesn't seem to be a way for us to determine actual browser _ => Err(Error::new(ErrorKind::NotFound, "only http urls supported")), } } /// Handle Haiku explicitly, as it uses an "open" command, similar to macos /// but on other Unixes, open ends up translating to shell open fd fn try_haiku(options: &BrowserOptions, url: &str) -> Result<()> { if cfg!(target_os = "haiku") { try_browser!(options, "open", url).map(|_| ()) } else { Err(Error::new(ErrorKind::NotFound, "Not on haiku")) } } /// Dig into XDG settings (if xdg is available) to force it to open the browser, instead of /// the default application fn try_xdg(options: &BrowserOptions, url: &str) -> Result<()> { // run: xdg-settings get default-web-browser let browser_name_os = for_matching_path("xdg-settings", |pb| { Command::new(pb) .args(["get", "default-web-browser"]) .stdin(Stdio::null()) .stderr(Stdio::null()) .output() }) .map_err(|_| Error::new(ErrorKind::NotFound, "unable to determine xdg browser"))? .stdout; // convert browser name to a utf-8 string and trim off the trailing newline let browser_name = String::from_utf8(browser_name_os) .map_err(|_| Error::new(ErrorKind::NotFound, "invalid default browser name"))? .trim() .to_owned(); if browser_name.is_empty() { return Err(Error::new(ErrorKind::NotFound, "no default xdg browser")); } trace!("found xdg browser: {:?}", &browser_name); // search for the config file corresponding to this browser name let mut config_found = false; let app_suffix = "applications"; for xdg_dir in get_xdg_dirs().iter_mut() { let mut config_path = xdg_dir.join(app_suffix).join(&browser_name); trace!("checking for xdg config at {:?}", config_path); let mut metadata = config_path.metadata(); if metadata.is_err() && browser_name.contains('-') { // as per the spec, we need to replace '-' with / let child_path = browser_name.replace('-', "/"); config_path = xdg_dir.join(app_suffix).join(child_path); metadata = config_path.metadata(); } if metadata.is_ok() { // we've found the config file, so we try running using that config_found = true; match open_using_xdg_config(&config_path, options, url) { Ok(x) => return Ok(x), // return if successful Err(err) => { // if we got an error other than NotFound, then we short // circuit, and do not try any more options, else we // continue to try more if err.kind() != ErrorKind::NotFound { return Err(err); } } } } } if config_found { Err(Error::new(ErrorKind::Other, "xdg-open failed")) } else { Err(Error::new(ErrorKind::NotFound, "no valid xdg config found")) } } /// Opens `url` using xdg configuration found in `config_path` /// /// See https://specifications.freedesktop.org/desktop-entry-spec/latest for details fn open_using_xdg_config(config_path: &PathBuf, options: &BrowserOptions, url: &str) -> Result<()> { let file = std::fs::File::open(config_path)?; let mut in_desktop_entry = false; let mut hidden = false; let mut cmdline: Option = None; let mut requires_terminal = false; // we capture important keys under the [Desktop Entry] section, as defined under: // https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s06.html for line in BufReader::new(file).lines().flatten() { if line == "[Desktop Entry]" { in_desktop_entry = true; } else if line.starts_with('[') { in_desktop_entry = false; } else if in_desktop_entry && !line.starts_with('#') { if let Some(idx) = line.find('=') { let key = &line[..idx]; let value = &line[idx + 1..]; match key { "Exec" => cmdline = Some(value.to_owned()), "Hidden" => hidden = value == "true", "Terminal" => requires_terminal = value == "true", _ => (), // ignore } } } } if hidden { // we ignore this config if it was marked hidden/deleted return Err(Error::new(ErrorKind::NotFound, "xdg config is hidden")); } if let Some(cmdline) = cmdline { // we have a valid configuration let cmdarr: Vec<&str> = cmdline.split_ascii_whitespace().collect(); let browser_cmd = cmdarr[0]; for_matching_path(browser_cmd, |pb| { let mut cmd = Command::new(pb); let mut url_added = false; for arg in cmdarr.iter().skip(1) { match *arg { "%u" | "%U" | "%f" | "%F" => { url_added = true; cmd.arg(url) } _ => cmd.arg(arg), }; } if !url_added { // append the url as an argument only if it was not already set cmd.arg(url); } run_command(&mut cmd, !requires_terminal, options) }) } else { // we don't have a valid config Err(Error::new(ErrorKind::NotFound, "not a valid xdg config")) } } /// Get the list of directories in which the desktop file needs to be searched fn get_xdg_dirs() -> Vec { let mut xdg_dirs: Vec = Vec::new(); let data_home = std::env::var("XDG_DATA_HOME") .ok() .map(PathBuf::from) .filter(|path| path.is_absolute()) .or_else(|| home::home_dir().map(|path| path.join(".local/share"))); if let Some(data_home) = data_home { xdg_dirs.push(data_home); } if let Ok(data_dirs) = std::env::var("XDG_DATA_DIRS") { for d in data_dirs.split(':') { xdg_dirs.push(PathBuf::from(d)); } } else { xdg_dirs.push(PathBuf::from("/usr/local/share")); xdg_dirs.push(PathBuf::from("/usr/share")); } xdg_dirs } /// Returns true if specified command refers to a known list of text browsers fn is_text_browser(pb: &Path) -> bool { for browser in TEXT_BROWSERS.iter() { if pb.ends_with(browser) { return true; } } false } fn for_matching_path(name: &str, op: F) -> Result where F: FnOnce(&PathBuf) -> Result, { let err = Err(Error::new(ErrorKind::NotFound, "command not found")); // if the name already includes path separator, we should not try to do a PATH search on it // as it's likely an absolutely or relative name, so we treat it as such. if name.contains(MAIN_SEPARATOR) { let pb = std::path::PathBuf::from(name); if let Ok(metadata) = pb.metadata() { if metadata.is_file() && metadata.permissions().mode() & 0o111 != 0 { return op(&pb); } } else { return err; } } else { // search for this name inside PATH if let Ok(path) = std::env::var("PATH") { for entry in path.split(':') { let mut pb = std::path::PathBuf::from(entry); pb.push(name); if let Ok(metadata) = pb.metadata() { if metadata.is_file() && metadata.permissions().mode() & 0o111 != 0 { return op(&pb); } } } } } // return the not found err, if we didn't find anything above err } static TEXT_BROWSERS: [&str; 9] = [ "lynx", "links", "links2", "elinks", "w3m", "eww", "netrik", "retawq", "curl", ]; #[cfg(test)] mod tests_xdg { use super::*; use std::fs::File; use std::io::Write; fn get_temp_path(name: &str, suffix: &str) -> String { let pid = std::process::id(); std::env::temp_dir() .join(format!("{}.{}.{}", name, pid, suffix)) .into_os_string() .into_string() .expect("failed to convert into string") } #[test] fn test_xdg_open_local_file() { let _ = env_logger::try_init(); // ensure flag file is not existing let flag_path = get_temp_path("test_xdg", "flag"); let _ = std::fs::remove_file(&flag_path); // create browser script let txt_path = get_temp_path("test_xdf", "txt"); let browser_path = get_temp_path("test_xdg", "browser"); { let mut browser_file = File::create(&browser_path).expect("failed to create browser file"); let _ = browser_file.write_fmt(format_args!( r#"#!/bin/bash if [ "$1" != "p1" ]; then echo "1st parameter should've been p1" >&2 exit 1 elif [ "$2" != "{}" ]; then echo "2nd parameter should've been {}" >&2 exit 1 elif [ "$3" != "p3" ]; then echo "3rd parameter should've been p3" >&2 exit 1 fi echo "$2" > "{}" "#, &txt_path, &txt_path, &flag_path )); let mut perms = browser_file .metadata() .expect("failed to get permissions") .permissions(); perms.set_mode(0o755); let _ = browser_file.set_permissions(perms); } // create xdg desktop config let config_path = get_temp_path("test_xdg", "desktop"); { let mut xdg_file = std::fs::File::create(&config_path).expect("failed to create xdg desktop file"); let _ = xdg_file.write_fmt(format_args!( r#"# this line should be ignored [Desktop Entry] Exec={} p1 %u p3 [Another Entry] Exec=/bin/ls # the above Exec line should be getting ignored "#, &browser_path )); } // now try opening browser using above desktop config let result = open_using_xdg_config( &PathBuf::from(&config_path), &BrowserOptions::default(), &txt_path, ); // we need to wait until the flag file shows up due to the async // nature of browser invocation for _ in 0..10 { if std::fs::read_to_string(&flag_path).is_ok() { break; } std::thread::sleep(std::time::Duration::from_millis(500)); } std::thread::sleep(std::time::Duration::from_millis(500)); // validate that the flag file contains the url we passed assert_eq!( std::fs::read_to_string(&flag_path) .expect("flag file not found") .trim(), &txt_path, ); assert!(result.is_ok()); // delete all temp files let _ = std::fs::remove_file(&txt_path); let _ = std::fs::remove_file(&flag_path); let _ = std::fs::remove_file(&browser_path); let _ = std::fs::remove_file(&config_path); assert!(result.is_ok()); } } /// WSL related browser functionality. /// /// We treat it as a separate submod, to allow for easy logical grouping /// and to enable/disable based on some feature easily in future. #[cfg(all( target_os = "linux", not(feature = "hardened"), not(feature = "disable-wsl") ))] mod wsl { use crate::common::for_each_token; use crate::{Result, TargetType}; use std::io::{Error, ErrorKind}; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; pub(super) struct WindowsConfig { root: PathBuf, cmd_path: PathBuf, pub(super) powershell_path: Option, } /// Returns a [WindowsConfig] by iterating over PATH entries. This seems to be /// the fastest way to determine this. pub(super) fn get_wsl_win_config() -> Result { let err_fn = || Error::new(ErrorKind::NotFound, "invalid windows config"); if let Some(path_env) = std::env::var_os("PATH") { let mut root: Option = None; let mut cmd_path: Option = None; let mut powershell_path: Option = None; for path in std::env::split_paths(&path_env) { let path_s = path.to_string_lossy().to_ascii_lowercase(); let path_s = path_s.trim_end_matches('/'); if path_s.ends_with("/windows/system32") { root = Some(std::fs::canonicalize(path.join("../.."))?); cmd_path = Some(path.join("cmd.exe")); break; } } if let Some(ref root) = root { for path in std::env::split_paths(&path_env) { if path.starts_with(root) { let pb = path.join("powershell.exe"); if pb.is_file() { powershell_path = Some(pb); } } } } if let Some(root) = root { let cmd_path = cmd_path.unwrap_or_else(|| (root).join("windows/system32/cmd.exe")); Ok(WindowsConfig { root, cmd_path, powershell_path, }) } else { Err(err_fn()) } } else { Err(err_fn()) } } /// Try to get default browser command from powershell.exe pub(super) fn get_wsl_windows_browser_ps( wc: &WindowsConfig, url: &TargetType, ) -> Result { let err_fn = || Error::new(ErrorKind::NotFound, "powershell.exe error"); let ps_exe = wc.powershell_path.as_ref().ok_or_else(err_fn)?; let mut cmd = Command::new(ps_exe); cmd.arg("-NoLogo") .arg("-NoProfile") .arg("-NonInteractive") .arg("-Command") .arg("-") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::null()); log::debug!("running command: ${:?}", &cmd); let mut child = cmd.spawn()?; let mut stdin = child.stdin.take().ok_or_else(err_fn)?; std::io::Write::write_all(&mut stdin, WSL_PS_SCRIPT.as_bytes())?; drop(stdin); // flush to stdin, and close let output_u8 = child.wait_with_output()?; let output = String::from_utf8_lossy(&output_u8.stdout); let output = output.trim(); if output.is_empty() { Err(err_fn()) } else { parse_wsl_cmdline(wc, output, url) } } /// Try to get default browser command from cmd.exe pub(super) fn get_wsl_windows_browser_cmd( wc: &WindowsConfig, url: &TargetType, ) -> Result { let err_fn = || Error::new(ErrorKind::NotFound, "cmd.exe error"); let mut cmd = Command::new(&wc.cmd_path); cmd.arg("/Q") .arg("/C") .arg("ftype http") .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::null()); log::debug!("running command: ${:?}", &cmd); let output_u8 = cmd.output()?; let output = String::from_utf8_lossy(&output_u8.stdout); let output = output.trim(); if output.is_empty() { Err(err_fn()) } else { parse_wsl_cmdline(wc, output, url) } } /// Given the configured command line `cmdline` in registry, and the given `url`, /// return the appropriate `Command` to invoke fn parse_wsl_cmdline(wc: &WindowsConfig, cmdline: &str, url: &TargetType) -> Result { let mut tokens: Vec = Vec::new(); let filepath = wsl_get_filepath_from_url(wc, url)?; let fp = &filepath; for_each_token(cmdline, |token: &str| { if matches!(token, "%0" | "%1") { tokens.push(fp.to_owned()); } else { tokens.push(token.to_string()); } }); if tokens.is_empty() { Err(Error::new(ErrorKind::NotFound, "invalid command")) } else { let progpath = wsl_path_win2lin(wc, &tokens[0])?; let mut cmd = Command::new(progpath); if tokens.len() > 1 { cmd.args(&tokens[1..]); } Ok(cmd) } } fn wsl_get_filepath_from_url(wc: &WindowsConfig, target: &TargetType) -> Result { let url = &target.0; if url.scheme() == "file" { if url.host().is_none() { let path = url .to_file_path() .map_err(|_| Error::new(ErrorKind::NotFound, "invalid path"))?; wsl_path_lin2win(wc, path) } else { Ok(format!("\\\\wsl${}", url.path().replace('/', "\\"))) } } else { Ok(url.as_str().to_string()) } } /// Converts a windows path to linux `PathBuf` fn wsl_path_win2lin(wc: &WindowsConfig, path: &str) -> Result { let err_fn = || Error::new(ErrorKind::NotFound, "invalid windows path"); if path.len() > 3 { let pfx = &path[..3]; if matches!(pfx, "C:\\" | "c:\\") { let win_path = path[3..].replace('\\', "/"); Ok(wc.root.join(win_path)) } else { Err(err_fn()) } } else { Err(err_fn()) } } /// Converts a linux path to windows. We using `String` instead of `OsString` as /// return type because the `OsString` will be different b/w Windows & Linux. fn wsl_path_lin2win(wc: &WindowsConfig, path: impl AsRef) -> Result { let path = path.as_ref(); if let Ok(path) = path.strip_prefix(&wc.root) { // windows can access this path directly Ok(format!("C:\\{}", path.as_os_str().to_string_lossy()).replace('/', "\\")) } else { // windows needs to access it via network let wsl_hostname = get_wsl_distro_name(wc)?; Ok(format!( "\\\\wsl$\\{}{}", &wsl_hostname, path.as_os_str().to_string_lossy() ) .replace('/', "\\")) } } /// Gets the WSL distro name fn get_wsl_distro_name(wc: &WindowsConfig) -> Result { let err_fn = || Error::new(ErrorKind::Other, "unable to determine wsl distro name"); // mostly we should be able to get it from the WSL_DISTRO_NAME env var if let Ok(wsl_hostname) = std::env::var("WSL_DISTRO_NAME") { Ok(wsl_hostname) } else { // but if not (e.g. if we were running as root), we can invoke // powershell.exe to determine pwd and from there infer the distro name let psexe = wc.powershell_path.as_ref().ok_or_else(err_fn)?; let mut cmd = Command::new(psexe); cmd.arg("-NoLogo") .arg("-NoProfile") .arg("-NonInteractive") .arg("-Command") .arg("$loc = Get-Location\nWrite-Output $loc.Path") .current_dir("/") .stdin(Stdio::null()) .stderr(Stdio::null()); log::debug!("running command: ${:?}", &cmd); let output_u8 = cmd.output()?.stdout; let output = String::from_utf8_lossy(&output_u8); let output = output.trim_end_matches('\\'); let idx = output.find("::\\\\").ok_or_else(err_fn)?; Ok((output[idx + 9..]).trim().to_string()) } } /// Powershell script to get the default browser command. /// /// Adapted from https://stackoverflow.com/a/60972216 const WSL_PS_SCRIPT: &str = r#" $Signature = @" using System; using System.Runtime.InteropServices; using System.Text; public static class Win32Api { [DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)] static extern uint AssocQueryString(AssocF flags, AssocStr str, string pszAssoc, string pszExtra,[Out] System.Text.StringBuilder pszOut, ref uint pcchOut); public static string GetDefaultBrowser() { AssocF assocF = AssocF.IsProtocol; AssocStr association = AssocStr.Command; string assocString = "http"; uint length = 1024; // we assume 1k is sufficient memory to hold the command var sb = new System.Text.StringBuilder((int) length); uint ret = ret = AssocQueryString(assocF, association, assocString, null, sb, ref length); return (ret != 0) ? null : sb.ToString(); } [Flags] internal enum AssocF : uint { IsProtocol = 0x1000, } internal enum AssocStr { Command = 1, Executable, } } "@ Add-Type -TypeDefinition $Signature Write-Output $([Win32Api]::GetDefaultBrowser()) "#; /*#[cfg(test)] mod tests { use crate::open; #[test] fn test_url() { let _ = env_logger::try_init(); assert!(open("https://github.com").is_ok()); } #[test] fn test_linux_file() { let _ = env_logger::try_init(); assert!(open("abc.html").is_ok()); } #[test] fn test_windows_file() { let _ = env_logger::try_init(); assert!(open("/mnt/c/T/abc.html").is_ok()); } }*/ } webbrowser-0.8.10/src/wasm.rs000064400000000000000000000031551046102023000142030ustar 00000000000000use crate::{Browser, BrowserOptions, Error, ErrorKind, Result, TargetType}; /// Deal with opening a URL in wasm32. This implementation ignores the browser attribute /// and always opens URLs in the same browser where wasm32 vm is running. pub(super) fn open_browser_internal( _: Browser, target: &TargetType, options: &BrowserOptions, ) -> Result<()> { // ensure we're opening only http/https urls, failing otherwise let url = target.get_http_url()?; // always return true for a dry run if options.dry_run { return Ok(()); } let window = web_sys::window(); match window { Some(w) => match w.open_with_url_and_target(url, &options.target_hint) { Ok(x) => match x { Some(_) => Ok(()), None => { wasm_console_log(POPUP_ERR_MSG, options); Err(Error::new(ErrorKind::Other, POPUP_ERR_MSG)) } }, Err(_) => { wasm_console_log("window error while opening url", options); Err(Error::new(ErrorKind::Other, "error opening url")) } }, None => Err(Error::new( ErrorKind::Other, "should have a window in this context", )), } } /// Print to browser console fn wasm_console_log(_msg: &str, _options: &BrowserOptions) { #[cfg(all(debug_assertions, feature = "wasm-console"))] if !_options.suppress_output { web_sys::console::log_1(&format!("[webbrowser] {}", &_msg).into()); } } const POPUP_ERR_MSG: &str = "popup blocked? window detected, but open_url failed"; webbrowser-0.8.10/src/windows.rs000064400000000000000000000105301046102023000147210ustar 00000000000000use crate::common::{for_each_token, run_command}; use crate::{Browser, BrowserOptions, Error, ErrorKind, Result, TargetType}; use log::trace; use std::path::Path; use std::process::Command; const ASSOCF_IS_PROTOCOL: u32 = 0x00001000; const ASSOCSTR_COMMAND: i32 = 1; #[link(name = "shlwapi")] extern "system" { fn AssocQueryStringW( flags: u32, string: i32, association: *const u16, extra: *const u16, out: *mut u16, out_len: *mut u32, ) -> i32; } /// Deal with opening of browsers on Windows. /// /// We first use [`AssocQueryStringW`](https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-assocquerystringw) /// function to determine the default browser, and then invoke it with appropriate parameters. /// /// We ignore BrowserOptions on Windows, except for honouring [BrowserOptions::dry_run] pub(super) fn open_browser_internal( browser: Browser, target: &TargetType, options: &BrowserOptions, ) -> Result<()> { match browser { Browser::Default => { // always return true for a dry run for default browser if options.dry_run { return Ok(()); } trace!("trying to figure out default browser command"); let cmdline = unsafe { const BUF_SIZE: usize = 512; let mut cmdline_u16 = [0_u16; BUF_SIZE]; let mut line_len = BUF_SIZE as u32; if AssocQueryStringW( ASSOCF_IS_PROTOCOL, ASSOCSTR_COMMAND, [0x68, 0x74, 0x74, 0x70, 0x0].as_ptr(), // http\0 std::ptr::null(), cmdline_u16.as_mut_ptr(), &mut line_len, ) != 0 { return Err(Error::new( ErrorKind::Other, "failed to get default browser", )); } use std::os::windows::ffi::OsStringExt; std::ffi::OsString::from_wide(&cmdline_u16[..(line_len - 1) as usize]) .into_string() .map_err(|_err| { Error::new( ErrorKind::Other, "The default web browser command contains invalid unicode characters", ) })? }; trace!("default browser command: {}", &cmdline); let cmdline = ensure_cmd_quotes(&cmdline); let mut cmd = get_browser_cmd(&cmdline, target)?; run_command(&mut cmd, true, options) } _ => Err(Error::new( ErrorKind::NotFound, "Only the default browser is supported on this platform right now", )), } } /// It seems that sometimes browser exe paths which have spaces are not quoted, so we keep going over /// each token, until we encounter what looks like a valid exe. /// /// See https://github.com/amodm/webbrowser-rs/issues/68 fn ensure_cmd_quotes(cmdline: &str) -> String { if !cmdline.starts_with('"') { let mut end = 0; for (idx, ch) in cmdline.char_indices() { if ch == ' ' { // does the path till now look like a valid exe? let potential_exe = Path::new(&cmdline[..idx]); if potential_exe.exists() { end = idx; break; } } } if end > 0 { return format!("\"{}\"{}", &cmdline[..end], &cmdline[end..]); } } // else we default to returning the original cmdline cmdline.to_string() } /// Given the configured command line `cmdline` in registry, and the given `url`, /// return the appropriate `Command` to invoke fn get_browser_cmd(cmdline: &str, url: &TargetType) -> Result { let mut tokens: Vec = Vec::new(); for_each_token(cmdline, |token: &str| { if matches!(token, "%0" | "%1") { tokens.push(url.to_string()); } else { tokens.push(token.to_string()); } }); if tokens.is_empty() { Err(Error::new(ErrorKind::NotFound, "invalid command")) } else { let mut cmd = Command::new(&tokens[0]); if tokens.len() > 1 { cmd.args(&tokens[1..]); } Ok(cmd) } } webbrowser-0.8.10/tests/common.rs000064400000000000000000000116071046102023000151000ustar 00000000000000use actix_files as fs; use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer, Responder}; use crossbeam_channel as cbc; use rand::RngCore; use std::{io::Write, path::PathBuf, sync::Arc}; use urlencoding::decode; use webbrowser::{open_browser, Browser}; #[derive(Clone)] struct AppState { tx: Arc>, } async fn log_handler(req: HttpRequest, data: web::Data) -> impl Responder { if data.tx.send(req.uri().to_string()).is_err() { panic!("channel send failed"); } let uri = req.uri(); if uri.path() == URI_PNG_1PX { HttpResponse::Ok() .content_type("image/png") .body(PNG_1PX.to_vec()) } else { HttpResponse::Ok() .content_type("text/html; charset=utf-8") .body(format!("

URI: {}

", req.uri())) } } async fn delayed_response(req: HttpRequest) -> impl Responder { let qs = req.query_string(); let ms: u64 = qs .replace("ms=", "") .parse() .expect("failed to parse millis"); tokio::time::sleep(tokio::time::Duration::from_millis(ms)).await; HttpResponse::Ok() .content_type("text/html; charset=utf-8") .body(format!( "

Delayed by {}ms

", qs )) } pub async fn check_request_received_using(uri: String, host: &str, op: F) where F: FnOnce(&str, u16), { // initialize env logger let _ = env_logger::try_init(); // start the server on a random port let bind_addr = format!("{}:0", host); let (tx, rx) = cbc::bounded(2); let data = AppState { tx: Arc::new(tx.clone()), }; let http_server = HttpServer::new(move || { let wasm_pkg_dir = "tests/test-wasm-app/pkg"; let _ = std::fs::create_dir_all(std::path::Path::new(wasm_pkg_dir)); App::new() .service(fs::Files::new("/static/wasm", wasm_pkg_dir)) .service(web::scope("/utils").route("/delay", web::get().to(delayed_response))) .app_data(web::Data::new(data.clone())) .default_service(web::to(log_handler)) }) .bind(&bind_addr) .unwrap_or_else(|_| panic!("Can not bind to {}", &bind_addr)); let port = http_server .addrs() .first() .expect("Failed to find bound address") .port(); let server = http_server.run(); let server_handle = server.handle(); tokio::spawn(server); // invoke the op op(&format!("http://{}:{}{}", host, port, &uri), port); // wait for the url to be hit let timeout = 90; match rx.recv_timeout(std::time::Duration::from_secs(timeout)) { Ok(msg) => assert_eq!(decode(&msg).unwrap(), uri), Err(_) => panic!("failed to receive uri data"), } // stop the server server_handle.stop(true).await; } #[allow(dead_code)] pub async fn check_request_received(browser: Browser, uri: String) { check_request_received_using(uri, "127.0.0.1", |url, _port| { open_browser(browser, url).expect("failed to open browser"); }) .await; } #[allow(dead_code)] pub async fn check_local_file(browser: Browser, html_dir: Option, url_op: F) where F: FnOnce(&PathBuf) -> String, { let cwd = std::env::current_dir().expect("unable to determine current dir"); let tmpdir = cwd.join("target").join("tmp"); let html_dir = html_dir.unwrap_or(tmpdir); let id = rand::thread_rng().next_u32(); let pb = html_dir.join(format!("test.{}.html", id)); let img_uri = format!("{}?r={}", URI_PNG_1PX, id); check_request_received_using(img_uri, "127.0.0.1", |uri, _port| { let url = url_op(&pb); let mut html_file = std::fs::File::create(&pb).expect("failed to create html file"); html_file .write_fmt(format_args!( "

html file: {}

url: {}

img: ", &pb.as_os_str().to_string_lossy(), url, uri )) .expect("failed to write html file"); drop(html_file); open_browser(browser, &url).expect("failed to open browser"); }) .await; let _ = std::fs::remove_file(&pb); } #[allow(dead_code)] pub async fn check_browser(browser: Browser, platform: &str) { check_request_received(browser, format!("/{}", platform)).await; check_request_received(browser, format!("/{}/😀😀😀", platform)).await; } const URI_PNG_1PX: &str = "/img/1px.png"; // generated from https://shoonia.github.io/1x1/#ff4563ff const PNG_1PX: [u8; 82] = [ 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1, 0, 0, 0, 1, 8, 2, 0, 0, 0, 144, 119, 83, 222, 0, 0, 0, 1, 115, 82, 71, 66, 0, 174, 206, 28, 233, 0, 0, 0, 12, 73, 68, 65, 84, 24, 87, 99, 248, 239, 154, 12, 0, 3, 238, 1, 168, 16, 134, 253, 64, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130, ]; webbrowser-0.8.10/tests/test-ios-app/.gitignore000064400000000000000000000001741046102023000175540ustar 00000000000000build/ testglue/Cargo.lock testglue/target/ test-ios-app.xcodeproj/xcuserdata/ test-ios-app.xcodeproj/project.xcworkspace/ webbrowser-0.8.10/tests/test-ios-app/test-ios-app/Assets.xcassets/AccentColor.colorset/Contents.json000064400000000000000000000001731046102023000317230ustar 00000000000000{ "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } webbrowser-0.8.10/tests/test-ios-app/test-ios-app/Assets.xcassets/AppIcon.appiconset/Contents.json000064400000000000000000000030671046102023000314000ustar 00000000000000{ "images" : [ { "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } webbrowser-0.8.10/tests/test-ios-app/test-ios-app/Assets.xcassets/Contents.json000064400000000000000000000000771046102023000257010ustar 00000000000000{ "info" : { "author" : "xcode", "version" : 1 } } webbrowser-0.8.10/tests/test-ios-app/test-ios-app/ContentView.swift000064400000000000000000000006711046102023000234360ustar 00000000000000import SwiftUI let SERVER_URL = "https://ip.rootnet.in" struct ContentView: View { var body: some View { Text("Hello World!") .padding() .onAppear(perform: openBrowser) } private func openBrowser() { let _ = TestGlueInterface.testOpenBrowser(url: SERVER_URL) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } ././@LongLink00006440000000000000000000000150000000000000007767Lustar webbrowser-0.8.10/tests/test-ios-app/test-ios-app/Preview Content/Preview Assets.xcassets/Contents.jsonwebbrowser-0.8.10/tests/test-ios-app/test-ios-app/Preview Content/Preview Assets.xcassets/Contents.j000064400000000000000000000000771046102023000315770ustar 00000000000000{ "info" : { "author" : "xcode", "version" : 1 } } webbrowser-0.8.10/tests/test-ios-app/test-ios-app/Test-Glue-Header.h000064400000000000000000000003641046102023000232620ustar 00000000000000// // Test-Glue-Header.h // test-ios-app // // Created by Amod Malviya on 05/09/22. // #ifndef Test_Glue_Header_h #define Test_Glue_Header_h #include void test_open_webbrowser(const char* url); #endif /* Test_Glue_Header_h */ webbrowser-0.8.10/tests/test-ios-app/test-ios-app/TestGlueInterface.swift000064400000000000000000000001731046102023000245430ustar 00000000000000class TestGlueInterface { static func testOpenBrowser(url: String) -> Void { test_open_webbrowser(url) } } webbrowser-0.8.10/tests/test-ios-app/test-ios-app/test_ios_appApp.swift000064400000000000000000000003571046102023000243240ustar 00000000000000// // test_ios_appApp.swift // test-ios-app // // Created by Amod Malviya on 05/09/22. // import SwiftUI @main struct test_ios_appApp: App { var body: some Scene { WindowGroup { ContentView() } } } webbrowser-0.8.10/tests/test-ios-app/test-ios-app.xcodeproj/project.pbxproj000064400000000000000000000556421046102023000251730ustar 00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 55; objects = { /* Begin PBXBuildFile section */ 38CAD42128C5BA2800150BA6 /* test_ios_appApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38CAD42028C5BA2800150BA6 /* test_ios_appApp.swift */; }; 38CAD42328C5BA2800150BA6 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38CAD42228C5BA2800150BA6 /* ContentView.swift */; }; 38CAD42528C5BA2B00150BA6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 38CAD42428C5BA2B00150BA6 /* Assets.xcassets */; }; 38CAD42828C5BA2B00150BA6 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 38CAD42728C5BA2B00150BA6 /* Preview Assets.xcassets */; }; 38CAD44E28C5CB6500150BA6 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 38CAD44D28C5CB5C00150BA6 /* libresolv.tbd */; }; 38CAD45128C5CCF800150BA6 /* TestGlueInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38CAD45028C5CCF800150BA6 /* TestGlueInterface.swift */; }; 38CAD45328C5CF6400150BA6 /* libtestglue.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 38CAD45228C5CF5500150BA6 /* libtestglue.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 38CAD42E28C5BA2B00150BA6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 38CAD41528C5BA2800150BA6 /* Project object */; proxyType = 1; remoteGlobalIDString = 38CAD41C28C5BA2800150BA6; remoteInfo = "test-ios-app"; }; 38CAD43828C5BA2B00150BA6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 38CAD41528C5BA2800150BA6 /* Project object */; proxyType = 1; remoteGlobalIDString = 38CAD41C28C5BA2800150BA6; remoteInfo = "test-ios-app"; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 38CAD41D28C5BA2800150BA6 /* test-ios-app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "test-ios-app.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 38CAD42028C5BA2800150BA6 /* test_ios_appApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = test_ios_appApp.swift; sourceTree = ""; }; 38CAD42228C5BA2800150BA6 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 38CAD42428C5BA2B00150BA6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 38CAD42728C5BA2B00150BA6 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 38CAD42D28C5BA2B00150BA6 /* test-ios-appTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "test-ios-appTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 38CAD43728C5BA2B00150BA6 /* test-ios-appUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "test-ios-appUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 38CAD44B28C5CB0800150BA6 /* libtestglue.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtestglue.a; path = testglue/target/target/debug/libtestglue.a; sourceTree = ""; }; 38CAD44D28C5CB5C00150BA6 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; }; 38CAD44F28C5CBE800150BA6 /* Test-Glue-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Test-Glue-Header.h"; sourceTree = ""; }; 38CAD45028C5CCF800150BA6 /* TestGlueInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestGlueInterface.swift; sourceTree = ""; }; 38CAD45228C5CF5500150BA6 /* libtestglue.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtestglue.a; path = testglue/target/target/debug/libtestglue.a; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 38CAD41A28C5BA2800150BA6 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 38CAD45328C5CF6400150BA6 /* libtestglue.a in Frameworks */, 38CAD44E28C5CB6500150BA6 /* libresolv.tbd in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 38CAD42A28C5BA2B00150BA6 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 38CAD43428C5BA2B00150BA6 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 38CAD41428C5BA2800150BA6 = { isa = PBXGroup; children = ( 38CAD41F28C5BA2800150BA6 /* test-ios-app */, 38CAD41E28C5BA2800150BA6 /* Products */, 38CAD44A28C5CB0800150BA6 /* Frameworks */, ); sourceTree = ""; }; 38CAD41E28C5BA2800150BA6 /* Products */ = { isa = PBXGroup; children = ( 38CAD41D28C5BA2800150BA6 /* test-ios-app.app */, 38CAD42D28C5BA2B00150BA6 /* test-ios-appTests.xctest */, 38CAD43728C5BA2B00150BA6 /* test-ios-appUITests.xctest */, ); name = Products; sourceTree = ""; }; 38CAD41F28C5BA2800150BA6 /* test-ios-app */ = { isa = PBXGroup; children = ( 38CAD45028C5CCF800150BA6 /* TestGlueInterface.swift */, 38CAD42028C5BA2800150BA6 /* test_ios_appApp.swift */, 38CAD42228C5BA2800150BA6 /* ContentView.swift */, 38CAD44F28C5CBE800150BA6 /* Test-Glue-Header.h */, 38CAD42428C5BA2B00150BA6 /* Assets.xcassets */, 38CAD42628C5BA2B00150BA6 /* Preview Content */, ); path = "test-ios-app"; sourceTree = ""; }; 38CAD42628C5BA2B00150BA6 /* Preview Content */ = { isa = PBXGroup; children = ( 38CAD42728C5BA2B00150BA6 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; 38CAD44A28C5CB0800150BA6 /* Frameworks */ = { isa = PBXGroup; children = ( 38CAD45228C5CF5500150BA6 /* libtestglue.a */, 38CAD44D28C5CB5C00150BA6 /* libresolv.tbd */, 38CAD44B28C5CB0800150BA6 /* libtestglue.a */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 38CAD41C28C5BA2800150BA6 /* test-ios-app */ = { isa = PBXNativeTarget; buildConfigurationList = 38CAD44128C5BA2B00150BA6 /* Build configuration list for PBXNativeTarget "test-ios-app" */; buildPhases = ( 38CAD41928C5BA2800150BA6 /* Sources */, 38CAD41A28C5BA2800150BA6 /* Frameworks */, 38CAD41B28C5BA2800150BA6 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "test-ios-app"; productName = "test-ios-app"; productReference = 38CAD41D28C5BA2800150BA6 /* test-ios-app.app */; productType = "com.apple.product-type.application"; }; 38CAD42C28C5BA2B00150BA6 /* test-ios-appTests */ = { isa = PBXNativeTarget; buildConfigurationList = 38CAD44428C5BA2B00150BA6 /* Build configuration list for PBXNativeTarget "test-ios-appTests" */; buildPhases = ( 38CAD42928C5BA2B00150BA6 /* Sources */, 38CAD42A28C5BA2B00150BA6 /* Frameworks */, 38CAD42B28C5BA2B00150BA6 /* Resources */, ); buildRules = ( ); dependencies = ( 38CAD42F28C5BA2B00150BA6 /* PBXTargetDependency */, ); name = "test-ios-appTests"; productName = "test-ios-appTests"; productReference = 38CAD42D28C5BA2B00150BA6 /* test-ios-appTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 38CAD43628C5BA2B00150BA6 /* test-ios-appUITests */ = { isa = PBXNativeTarget; buildConfigurationList = 38CAD44728C5BA2B00150BA6 /* Build configuration list for PBXNativeTarget "test-ios-appUITests" */; buildPhases = ( 38CAD43328C5BA2B00150BA6 /* Sources */, 38CAD43428C5BA2B00150BA6 /* Frameworks */, 38CAD43528C5BA2B00150BA6 /* Resources */, ); buildRules = ( ); dependencies = ( 38CAD43928C5BA2B00150BA6 /* PBXTargetDependency */, ); name = "test-ios-appUITests"; productName = "test-ios-appUITests"; productReference = 38CAD43728C5BA2B00150BA6 /* test-ios-appUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 38CAD41528C5BA2800150BA6 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1340; LastUpgradeCheck = 1340; TargetAttributes = { 38CAD41C28C5BA2800150BA6 = { CreatedOnToolsVersion = 13.4.1; }; 38CAD42C28C5BA2B00150BA6 = { CreatedOnToolsVersion = 13.4.1; TestTargetID = 38CAD41C28C5BA2800150BA6; }; 38CAD43628C5BA2B00150BA6 = { CreatedOnToolsVersion = 13.4.1; TestTargetID = 38CAD41C28C5BA2800150BA6; }; }; }; buildConfigurationList = 38CAD41828C5BA2800150BA6 /* Build configuration list for PBXProject "test-ios-app" */; compatibilityVersion = "Xcode 13.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 38CAD41428C5BA2800150BA6; productRefGroup = 38CAD41E28C5BA2800150BA6 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 38CAD41C28C5BA2800150BA6 /* test-ios-app */, 38CAD42C28C5BA2B00150BA6 /* test-ios-appTests */, 38CAD43628C5BA2B00150BA6 /* test-ios-appUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 38CAD41B28C5BA2800150BA6 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 38CAD42828C5BA2B00150BA6 /* Preview Assets.xcassets in Resources */, 38CAD42528C5BA2B00150BA6 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 38CAD42B28C5BA2B00150BA6 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 38CAD43528C5BA2B00150BA6 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 38CAD41928C5BA2800150BA6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 38CAD45128C5CCF800150BA6 /* TestGlueInterface.swift in Sources */, 38CAD42328C5BA2800150BA6 /* ContentView.swift in Sources */, 38CAD42128C5BA2800150BA6 /* test_ios_appApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 38CAD42928C5BA2B00150BA6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 38CAD43328C5BA2B00150BA6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 38CAD42F28C5BA2B00150BA6 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 38CAD41C28C5BA2800150BA6 /* test-ios-app */; targetProxy = 38CAD42E28C5BA2B00150BA6 /* PBXContainerItemProxy */; }; 38CAD43928C5BA2B00150BA6 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 38CAD41C28C5BA2800150BA6 /* test-ios-app */; targetProxy = 38CAD43828C5BA2B00150BA6 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 38CAD43F28C5BA2B00150BA6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.5; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 38CAD44028C5BA2B00150BA6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.5; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; 38CAD44228C5BA2B00150BA6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"test-ios-app/Preview Content\""; DEVELOPMENT_TEAM = X66SW5KC6C; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/testglue/target/target/debug", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "in.rootnet.webbrowser.test-ios-app"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/test-ios-app/Test-Glue-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; name = Debug; }; 38CAD44328C5BA2B00150BA6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"test-ios-app/Preview Content\""; DEVELOPMENT_TEAM = X66SW5KC6C; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/testglue/target/target/debug", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "in.rootnet.webbrowser.test-ios-app"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/test-ios-app/Test-Glue-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 1; }; name = Release; }; 38CAD44528C5BA2B00150BA6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = X66SW5KC6C; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.5; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "in.rootnet.webbrowser.test-ios-appTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/test-ios-app.app/test-ios-app"; }; name = Debug; }; 38CAD44628C5BA2B00150BA6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = X66SW5KC6C; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.5; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "in.rootnet.webbrowser.test-ios-appTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/test-ios-app.app/test-ios-app"; }; name = Release; }; 38CAD44828C5BA2B00150BA6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = X66SW5KC6C; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "in.rootnet.webbrowser.test-ios-appUITests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = "test-ios-app"; }; name = Debug; }; 38CAD44928C5BA2B00150BA6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = X66SW5KC6C; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "in.rootnet.webbrowser.test-ios-appUITests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = "test-ios-app"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 38CAD41828C5BA2800150BA6 /* Build configuration list for PBXProject "test-ios-app" */ = { isa = XCConfigurationList; buildConfigurations = ( 38CAD43F28C5BA2B00150BA6 /* Debug */, 38CAD44028C5BA2B00150BA6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 38CAD44128C5BA2B00150BA6 /* Build configuration list for PBXNativeTarget "test-ios-app" */ = { isa = XCConfigurationList; buildConfigurations = ( 38CAD44228C5BA2B00150BA6 /* Debug */, 38CAD44328C5BA2B00150BA6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 38CAD44428C5BA2B00150BA6 /* Build configuration list for PBXNativeTarget "test-ios-appTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 38CAD44528C5BA2B00150BA6 /* Debug */, 38CAD44628C5BA2B00150BA6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 38CAD44728C5BA2B00150BA6 /* Build configuration list for PBXNativeTarget "test-ios-appUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( 38CAD44828C5BA2B00150BA6 /* Debug */, 38CAD44928C5BA2B00150BA6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 38CAD41528C5BA2800150BA6 /* Project object */; } webbrowser-0.8.10/tests/test_android.rs000064400000000000000000000070161046102023000162660ustar 00000000000000mod common; mod tests { const TEST_PLATFORM: &str = "android"; use super::common::check_request_received_using; use std::fs; use std::path::PathBuf; use std::process::Command; use webbrowser::Browser; // to run this test, run it as: // cargo test --test test_android -- --ignored // // For this to run, we need ANDROID_NDK_ROOT env defined, e.g. // ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/22.1.7171670 // #[ignore] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_android() { let mut app_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); app_dir.push("tests/test-android-app"); let uri = format!("/{}", TEST_PLATFORM); check_request_received_using(uri, "127.0.0.1", |url, port| { // modify android app code to use the correct url let mut lib_rs = PathBuf::from(&app_dir); lib_rs.push("src/lib.rs"); let old_code = fs::read_to_string(&lib_rs).expect("failed to read lib.rs for android app"); let new_code = old_code .split('\n') .map(|s| { if s.starts_with("const SERVER_URL") { format!("const SERVER_URL: &str = \"{}\";", url) } else { s.into() } }) .collect::>() .join("\n"); fs::write(&lib_rs, new_code).expect("failed to modify src/lib.rs"); log::debug!("modified src/lib.rs to use {}", url); // uninstall previous app version if existing let mut adb_cmd = Command::new("adb"); adb_cmd.arg("uninstall").arg("rust.test_android_app"); if let Ok(status) = adb_cmd.current_dir(&app_dir).status() { if status.success() { log::info!("adb uninstall successful"); } else { log::error!("adb uninstall failed"); } } else { log::error!("failed to run {:?}", adb_cmd); } let adb_reverse_port = format!("tcp:{}", port); let mut adb_cmd = Command::new("adb"); adb_cmd .arg("reverse") .arg(&adb_reverse_port) .arg(&adb_reverse_port); assert!( adb_cmd.status().expect("Failed to invoke").success(), "Failed to run {:?}", adb_cmd ); // invoke app in android let mut apk_run_cmd = Command::new("cargo"); apk_run_cmd.arg("apk").arg("run"); if let Some(target) = option_env!("ANDROID_TARGET") { apk_run_cmd.arg("--target").arg(target); } apk_run_cmd.arg("--no-logcat"); let apk_run_status = apk_run_cmd.current_dir(&app_dir).status(); // revert to the old code fs::write(&lib_rs, &old_code).expect("failed to modify src/lib.rs"); // check for apk run status assert!( apk_run_status.expect("Failed to invoke").success(), "failed to run {:?}", apk_run_cmd ); }) .await; } #[test] fn test_existence_default() { assert!(Browser::is_available(), "should have found a browser"); } #[test] fn test_non_existence_safari() { assert!(!Browser::Safari.exists(), "should not have found Safari"); } } webbrowser-0.8.10/tests/test_ios.rs000064400000000000000000000102001046102023000154250ustar 00000000000000#[cfg(target_os = "macos")] mod common; #[cfg(target_os = "macos")] mod tests { const TEST_PLATFORM: &str = "ios"; use super::common::check_request_received_using; use std::fs; use std::path::PathBuf; use std::process::{Command, Stdio}; use webbrowser::Browser; // to run this test, run it as: // cargo test --test test_ios -- --ignored // // MAKE SURE: an iOS simulator instance is already running #[ignore] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_ios() { let uri = format!("/{}", TEST_PLATFORM); let ipv4 = get_ipv4_address(); let mut app_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); app_dir.push("tests/test-ios-app"); // build test glue code let mut glue_dir = PathBuf::from(&app_dir); glue_dir.push("testglue"); run_cmd(&glue_dir, "glue code build failed", &["./build"]); // invoke server check_request_received_using(uri, &ipv4, |url, _port| { // modify ios app code to use the correct url let mut swift_src = PathBuf::from(&app_dir); swift_src.push("test-ios-app/ContentView.swift"); let old_code = fs::read_to_string(&swift_src).expect("failed to read ContentView.swift"); let new_code = old_code .split('\n') .map(|s| { if s.starts_with("let SERVER_URL") { format!("let SERVER_URL = \"{}\"", url) } else { s.into() } }) .collect::>() .join("\n"); fs::write(&swift_src, new_code).expect("failed to modify ContentView.swift"); // build app run_cmd( &app_dir, "failed to build ios app", &[ "xcrun", "xcodebuild", "-project", "test-ios-app.xcodeproj", "-scheme", "test-ios-app", "-configuration", "Debug", "-destination", "platform=iOS Simulator,name=iphone-latest", "-derivedDataPath", "build", ], ); // launch app on simulator run_cmd( &app_dir, "failed to install app on simulator", &[ "xcrun", "simctl", "install", "booted", "build/Build/Products/Debug-iphonesimulator/test-ios-app.app", ], ); run_cmd( &app_dir, "failed to launch app on simulator", &[ "xcrun", "simctl", "launch", "booted", "in.rootnet.webbrowser.test-ios-app", ], ); // revert to the old code fs::write(&swift_src, &old_code).expect("failed to modify ContentView.swift"); }) .await; } fn get_ipv4_address() -> String { let output = Command::new("sh") .arg("-c") .arg("ifconfig | grep 'inet ' | awk '{ print $2 }' | grep -v ^127.0.0") .output() .expect("failed to get non-local ipv4 address"); std::str::from_utf8(&output.stdout) .expect("unable to parse output into utf8") .split('\n') .next() .expect("no ip address found") .into() } #[test] fn test_existence_default() { assert!(Browser::is_available(), "should have found a browser"); } fn run_cmd(app_dir: &PathBuf, failure_msg: &str, args: &[&str]) { let _ = Command::new(args[0]) .args(&args[1..]) .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) .current_dir(app_dir) .status() .expect(failure_msg); } } webbrowser-0.8.10/tests/test_macos.rs000064400000000000000000000052261046102023000157510ustar 00000000000000#[cfg(target_os = "macos")] mod common; #[cfg(target_os = "macos")] mod tests { const TEST_PLATFORM: &str = "macos"; use super::common::*; use webbrowser::Browser; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_open_default() { check_browser(Browser::Default, TEST_PLATFORM).await; } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_open_safari() { check_browser(Browser::Safari, TEST_PLATFORM).await; } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[ignore] async fn test_open_firefox() { check_browser(Browser::Firefox, TEST_PLATFORM).await; } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[ignore] async fn test_open_chrome() { check_browser(Browser::Chrome, TEST_PLATFORM).await; } #[test] fn test_existence_default() { assert!(Browser::is_available(), "should have found a browser"); } #[test] fn test_existence_safari() { assert!(Browser::Safari.exists(), "should have found Safari"); } #[test] fn test_non_existence_webpositive() { assert!( !Browser::WebPositive.exists(), "should not have found WebPositive", ); } #[cfg(not(feature = "hardened"))] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_local_file_abs_path() { check_local_file(Browser::Default, None, |pb| { pb.as_os_str().to_string_lossy().into() }) .await; } #[cfg(not(feature = "hardened"))] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_local_file_rel_path() { let cwd = std::env::current_dir().expect("unable to get current dir"); check_local_file(Browser::Default, None, |pb| { pb.strip_prefix(cwd) .expect("strip prefix failed") .as_os_str() .to_string_lossy() .into() }) .await; } #[cfg(not(feature = "hardened"))] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_local_file_uri() { check_local_file(Browser::Default, None, |pb| { url::Url::from_file_path(pb) .expect("failed to convert path to url") .to_string() }) .await; } #[cfg(feature = "hardened")] #[test] fn test_hardened_mode() { let err = webbrowser::open("file:///etc/passwd") .expect_err("expected non-http url to fail in hardened mode"); assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); } } webbrowser-0.8.10/tests/test_unix.rs000064400000000000000000000041151046102023000156260ustar 00000000000000#[cfg(all(unix, not(target_os = "macos")))] mod common; #[cfg(all(unix, not(target_os = "macos")))] mod tests { const TEST_PLATFORM: &str = "unix"; use super::common::*; use serial_test::serial; use webbrowser::Browser; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[serial] async fn test_open_default() { check_browser(Browser::Default, TEST_PLATFORM).await; } #[test] fn test_existence_default() { assert!(Browser::is_available(), "should have found a browser"); } #[test] fn test_non_existence_safari() { assert!(!Browser::Safari.exists(), "should not have found Safari"); } #[cfg(not(feature = "hardened"))] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[serial] async fn test_local_file_abs_path() { check_local_file(Browser::Default, None, |pb| { pb.as_os_str().to_string_lossy().into() }) .await; } #[cfg(not(feature = "hardened"))] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[serial] async fn test_local_file_rel_path() { let cwd = std::env::current_dir().expect("unable to get current dir"); check_local_file(Browser::Default, None, |pb| { pb.strip_prefix(cwd) .expect("strip prefix failed") .as_os_str() .to_string_lossy() .into() }) .await; } #[cfg(not(feature = "hardened"))] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[serial] async fn test_local_file_uri() { check_local_file(Browser::Default, None, |pb| { url::Url::from_file_path(pb) .expect("failed to convert path to url") .to_string() }) .await; } #[cfg(feature = "hardened")] #[test] fn test_hardened_mode() { let err = webbrowser::open("file:///etc/passwd") .expect_err("expected non-http url to fail in hardened mode"); assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); } } webbrowser-0.8.10/tests/test_wasm.rs000064400000000000000000000033751046102023000156210ustar 00000000000000#[cfg(any(target_os = "macos", target_os = "linux"))] mod common; #[cfg(any(target_os = "macos", target_os = "linux"))] mod tests { const TEST_PLATFORM: &str = "wasm32"; use super::common::check_request_received_using; use std::fs; use std::path::PathBuf; // to run this test, run it as: // cargo test --test test_wasm32 -- --ignored // #[ignore] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_wasm32() { let uri = &format!("/{}", TEST_PLATFORM); let ipv4 = "127.0.0.1"; check_request_received_using(uri.into(), ipv4, |url, _port| { // modify html to use the correct url let mut app_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); app_dir.push("tests/test-wasm-app"); let mut src_html = PathBuf::from(&app_dir); src_html.push("test.html"); let mut dst_html = PathBuf::from(&app_dir); dst_html.push("pkg/test.html"); let old_html = fs::read_to_string(&src_html).expect("failed to read test.html"); let new_html = old_html.replace("DYNAMIC_URL_TBD", url); fs::write(&dst_html, new_html).expect("failed to update dst test.html"); // ensure favicon is present let mut favicon = PathBuf::from(&app_dir); favicon.push("pkg/favicon.ico"); fs::write(&favicon, "").expect("failed to create favicon.ico"); // open browser let html_url = url.replace(uri, "/static/wasm/test.html"); //println!("URL: {}", html_url); let status = webbrowser::open(&html_url); // check for browser run status status.expect("browser open failed"); }) .await; } } webbrowser-0.8.10/tests/test_windows.rs000064400000000000000000000042471046102023000163430ustar 00000000000000#[cfg(target_os = "windows")] mod common; #[cfg(target_os = "windows")] mod tests { const TEST_PLATFORM: &str = "windows"; use super::common::*; use webbrowser::Browser; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_open_default() { check_browser(Browser::Default, TEST_PLATFORM).await; } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[ignore] async fn test_open_internet_explorer() { check_browser(Browser::InternetExplorer, TEST_PLATFORM).await; } #[test] fn test_existence_default() { assert!(Browser::is_available(), "should have found a browser"); } #[test] fn test_non_existence_safari() { assert!(!Browser::Safari.exists(), "should not have found Safari"); } #[cfg(not(feature = "hardened"))] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_local_file_abs_path() { check_local_file(Browser::Default, None, |pb| { pb.as_os_str().to_string_lossy().into() }) .await; } #[cfg(not(feature = "hardened"))] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_local_file_rel_path() { let cwd = std::env::current_dir().expect("unable to get current dir"); check_local_file(Browser::Default, None, |pb| { pb.strip_prefix(cwd) .expect("strip prefix failed") .as_os_str() .to_string_lossy() .into() }) .await; } #[cfg(not(feature = "hardened"))] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_local_file_uri() { check_local_file(Browser::Default, None, |pb| { url::Url::from_file_path(pb) .expect("failed to convert path to url") .to_string() }) .await; } #[cfg(feature = "hardened")] #[test] fn test_hardened_mode() { let err = webbrowser::open("file:///blah.html") .expect_err("expected non-http url to fail in hardened mode"); assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); } }