In plugins/fs/src/commands.rs resolve_path / is_forbidden, only paths
that already existed were canonicalized, so a new file below a symlinked
directory in the scope was matched by its unresolved path and written
outside of the scope. Relative symlink targets were resolved against the
process CWD, only the link target was checked against deny patterns, and
resolution errors made is_forbidden fail open.
resolve_path now computes the real path (canonical nearest existing
ancestor + remaining components, following dangling symlinks relative to
their parent, bounded to 40 links) and checks it against the allow and deny
patterns; deny patterns also match the path as given. Resolution errors
fail closed. Unit tests cover symlinked ancestors, relative and dangling
links and loops.
In plugins/fs/android/src/main/java/FsPlugin.kt getFileDescriptor, for
uncompressed assets AssetManager.openFd returns an AssetFileDescriptor
whose parcel descriptor is the whole APK; its startOffset/length were
dropped and Rust read from offset 0, i.e. APK bytes. Only compressed
assets (openFd throws) took the cache copy path.
All assets now use the cache copy path. The cache file path is also checked
to stay below cacheDir/_assets. Kotlin not compiled here: unverified, needs
an on-device check.
In plugins/fs/src/lib.rs on_event, every path dropped onto any window is
added to the global runtime scope, directories recursively, with no
opt-out.
Adds the `scopeDroppedPaths` plugin config option (default true, the
current behaviour) so apps can turn it off. Changing the default or
scoping it per window is deferred to v3.
In plugins/fs/src/android.rs resolve_content_uri, a null `fd` from
getFileDescriptor (openAssetFileDescriptor returning null, e.g. for
virtual files) hit unimplemented!() and panicked inside the command handler.
It now returns an Unsupported I/O error. Also switches the two
io::Error::new(Other, ..) calls to io::Error::other, which clippy flags on
the Android target. Checked with cargo clippy --target aarch64-linux-android.
In plugins/fs/src/lib.rs OpenOptions, custom_flags was deserializable
from the `open` command arguments even though it is not in the TS types,
so a webview with a read-only permission set could pass arbitrary open(2)
flags such as O_TRUNC.
The field is now #[serde(skip)]; Rust callers still set it through
OpenOptionsExt. Rejecting write/truncate/create options of `open` under
read-only sets changes permission semantics and is deferred to v3.
In plugins/fs/src/commands.rs seek, `SeekFrom::Start(offset as u64)`
turned -1 into u64::MAX. The offset is now converted with u64::try_from
and a negative one is an error. E2E spec added.
In plugins/fs/src/commands.rs read_until_bytes / LinesBytes, the line
feed sequence was matched at any byte offset, so UTF-16LE "ਗĀ"
(17 0a 00 01) or UTF-16BE "Āਗ" (01 00 0a 17) split mid code unit and every
following line was decoded misaligned.
Line feed and carriage return sequences now only match on a multiple of
their length from the start of the line. Unit tests with both examples
(they fail without the fix).
In plugins/fs/guest-js/index.ts writeFile, the stream path opened the
file with {write, create} but no truncate, unlike the buffer path which
truncates unless `append` is set (commands.rs write_file_inner), so
shorter content left stale trailing bytes.
The stream path now passes `truncate: !options?.append`. api-iife.js
regenerated. E2E spec covers overwrite and append with a stream.
In plugins/fs/src/commands.rs read, `vec![0; len]` used the length sent
by the webview as is, so a huge `len` aborted the process on allocation
failure, and the whole buffer (not only the bytes read) was sent back.
The length is now capped to the rest of the file (at least 64 KiB) for
regular files and to 64 MiB otherwise, the allocation uses try_reserve so a
failure is an error, and the buffer is truncated to the bytes read. The JS
side only ever used the first `nread` bytes. Unit test for the cap.
* ci: only run what a change affects, consolidate jobs
- detect affected plugins from changed files and Cargo.lock dependency trees
- test/lint Rust per platform in shards instead of one job per plugin
- fold check-generated-files into lint-javascript, fmt into a single job
- skip fat LTO in integration test builds, single ABI in test-android
* ci: run e2e on bumps, use published tauri CLI, PR-safe pnpm cache
- trigger e2e on Cargo/pnpm lockfile and manifest changes
- install the latest 2.x tauri-cli with cargo-binstall in integration tests
- add setup-pnpm action: restore the store everywhere, save only on v2/v3
* fix clippy, ios build
* fix bumps [skip ci]
* feat(notification): report actions on desktop
A click on a notification (tap) or on one of the actions of its action
type now reaches the JavaScript onAction listeners on desktop, and the
new Rust Notification::on_action on every platform. On Linux and the
BSDs the click comes from the notification server's ActionInvoked
signal; on Windows and macOS from notify-rust's wait_for_response,
which needs notify-rust 4.18.
register_action_types, remove_active (Linux and the BSDs) and the
listener commands are implemented for desktop, so they no longer fail
with "command not found".
The api example sends a notification with two actions and shows the
action it hears.
Closes#2150, closes#1903.
* fix(notification): complete desktop action API
* cleanup
---------
Co-authored-by: Haex <hexxx@ok.de>
Co-authored-by: Lucas Nogueira <lucas@tauri.app>
* fix(ci): Android e2e flakes, shell `Terminated` sent before output
- close "isn't responding" system dialogs before each spec and hide error dialogs on the CI emulator:
a launcher ANR dialog takes input focus from the app, and Android then denies clipboard reads
- retry switching to the webview context until it succeeds instead of running the spec in the
native context when the first switch fails with "No such context found"
- save the focused window, a screenshot and logcat when an Android test fails
- shell: wait for the pipe readers to finish before emitting `Terminated`; the readers took their
read lock on their own thread, so a child exiting quickly could have `close` emitted before
its output
* Apply suggestion from @lucasfernog
* fix(updater): restore the current app when a macOS install fails
On macOS `install_inner` moved the installed app into a `TempDir` backup and
then renamed the new bundle into place. Neither of the two steps after that
move restored the app on failure, and the `TempDir` deleted the backup on
every exit path, so a failed final rename left the user with no app at the
install path and no way back. The privileged fallback had the same shape:
`rm -rf` the app, then `mv` the new one in.
Both moves are now `replace_bundle`, which renames the current app to the
backup, renames the staged bundle into place, and renames the backup back if
that second step fails. The privileged script moves the current app aside,
moves the new one in, and restores on failure; it deletes the previous bundle
only after the new one is in place. The temp and install locations are
compared with `st_dev` before anything moves, returning the existing
`TempDirNotOnSameMountPoint` rather than failing after the app has already
been moved, and the staged bundle root is set to 0755 because `tempfile`
creates it 0700 and that mode followed it into `/Applications`.
Two unit tests cover `replace_bundle`: the staged bundle ends up in place with
the previous one in the backup, and a failed second rename leaves the current
bundle where it was and returns that error.
Closes#3505, closes#3506.
* fix(updater): exchange the current and new bundles atomically on APFS
Even with the restore in place there was a moment between the two renames
where the install path held nothing, and a process killed in that moment
left the previous app in the temp dir rather than where it belonged.
`swap_bundle` calls `renamex_np` with `RENAME_SWAP`, which exchanges the two
directories in one step so the install path always holds one of the two. The
previous app ends up in the extraction temp dir and is removed with it. Where
the file system does not support the swap (`ENOTSUP`, or `EINVAL` on older
systems) the two-rename path with the restore is used as before, and a
`PermissionDenied` from either still goes to the privileged fallback.
`libc` is added for the macOS target only; it was already in the lockfile
through tauri. One macOS test checks the exchange and skips on a file system
that reports `ENOTSUP`.
* fix(updater): keep the previous app when it cannot be restored
If the restore rename also failed, the previous app was left only in the backup TempDir and deleted with it. Keep the backup in that case and return Error::PreviousAppNotRestored with its path.
* fix(updater): quote the paths in the privileged macOS install script
The paths went into single-quoted sh words inside an AppleScript string unescaped, so a quote in the app path broke the command (run as root) or panicked on the compile expect. Quote them for sh and AppleScript, and report script failures instead of panicking.
* fix(updater): fall back to moving the bundles on any swap error
Only ENOTSUP and EINVAL got the two-rename fallback, so volumes reporting EOPNOTSUPP or ENOSYS for RENAME_SWAP failed every update. Fall back on anything but a permission error, and have the swap test skip only off APFS.
* fix(updater): stage the macOS update on the app's volume
When the system temp dir was on another volume than the app, every update was refused. Create the temp dirs next to the app in that case, and compare the install path itself rather than what a symlink there points to.
* test(updater): update a macOS app on another volume than the temp dir
Runs the app from APFS and HFS+ disk images, covering the atomic swap and the two-rename fallback with the update staged next to the app.
* fix(updater): swap the bundles atomically in the privileged macOS install
The admin path ran two mv calls, leaving the install path empty between them. Run renamex_np with RENAME_SWAP as root through osascript's JavaScript bridge, and only fall back to the moves where the swap fails.
* add test
* cleanup
---------
Co-authored-by: Lucas Nogueira <lucas@tauri.app>
* chore(autostart): update auto-launch to 0.6
* migrate to `set_macos_launch_mode`
* back to zzzgydi main branch
* use crates.io version of auto-launch
* Add change file
* revert unrelated bumps