Skip to content

Commit f5f6806

Browse files
feat(fs): access security scoped resources on iOS (#3185)
Co-authored-by: FabianLars <30730186+FabianLars@users.noreply.github.com>
1 parent 36d3d19 commit f5f6806

15 files changed

Lines changed: 893 additions & 94 deletions

.changes/security-scoped-ios.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"fs": minor
3+
"fs-js": minor
4+
---
5+
6+
Enable access for security-scoped resources on iOS by automatically calling `NSURL::startAccessingSecurityScopedResource` on resource access and adding the `stopAccessingSecurityScopedResource` API.

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/fs/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ serde_repr = "0.1"
3030
tauri = { workspace = true }
3131
thiserror = { workspace = true }
3232
url = { workspace = true }
33+
log = { workspace = true }
3334
anyhow = "1"
3435
glob = { workspace = true }
3536
# TODO: Remove `serialization-compat-6` in v3
@@ -41,5 +42,8 @@ notify-debouncer-full = { version = "0.6", optional = true }
4142
dunce = { workspace = true }
4243
percent-encoding = "2"
4344

45+
[target.'cfg(target_os = "ios")'.dependencies]
46+
objc2-foundation = { version = "0.3", features = ["NSURL", "NSString"] }
47+
4448
[features]
4549
watch = ["notify", "notify-debouncer-full"]

plugins/fs/api-iife.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/fs/build.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ const COMMANDS: &[(&str, &[&str])] = &[
104104
// TODO: Remove this in v3
105105
("unwatch", &[]),
106106
("size", &[]),
107+
("start_accessing_security_scoped_resource", &[]),
108+
("stop_accessing_security_scoped_resource", &[]),
107109
];
108110

109111
fn main() {

plugins/fs/guest-js/index.ts

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@
55
/**
66
* Access the file system.
77
*
8+
* ## iOS security-scoped resources
9+
*
10+
* On iOS, the `fs` plugin automatically manages access to security-scoped resources when a file URL is accessed.
11+
* This is required for files outside the app's sandbox (e.g., from file picker).
12+
*
13+
* @example
14+
* ```typescript
15+
* import { open } from '@tauri-apps/plugin-fs';
16+
*
17+
* const file = await open('file:///path/to/file.txt');
18+
* await file.close();
19+
* ```
20+
*
821
* ## Security
922
*
1023
* This module prevents path traversal, not allowing parent directory accessors to be used
@@ -1353,6 +1366,79 @@ async function size(path: string | URL): Promise<number> {
13531366
})
13541367
}
13551368

1369+
/**
1370+
* Starts accessing a security-scoped resource for the given file URL.
1371+
* This should be called when you're accessing a file that was opened
1372+
* using a security-scoped URL (e.g., from a file picker).
1373+
*
1374+
* Note that accessing security-scoped resources is automatically managed by the plugin on iOS, so you don't need to call this function
1375+
* unless you want to manage the scope manually.
1376+
*
1377+
* You must call {@linkcode stopAccessingSecurityScopedResource} when you're done accessing the resource.
1378+
*
1379+
* #### Platform-specific
1380+
*
1381+
* - **iOS:** Starts accessing the security-scoped resource.
1382+
* - **Other platforms:** does nothing.
1383+
*
1384+
* @example
1385+
* ```typescript
1386+
* import { startAccessingSecurityScopedResource } from '@tauri-apps/plugin-fs';
1387+
*
1388+
* const filePath = 'file:///path/to/file.txt';
1389+
* await startAccessingSecurityScopedResource(filePath);
1390+
* // ... use the resource ...
1391+
* ```
1392+
*
1393+
* @since 2.5.0
1394+
*/
1395+
async function startAccessingSecurityScopedResource(
1396+
path: string | URL
1397+
): Promise<void> {
1398+
if (path instanceof URL && path.protocol !== 'file:') {
1399+
throw new TypeError('Must be a file URL.')
1400+
}
1401+
1402+
await invoke('plugin:fs|start_accessing_security_scoped_resource', {
1403+
path: path instanceof URL ? path.toString() : path
1404+
})
1405+
}
1406+
1407+
/**
1408+
* Stops accessing a security-scoped resource for the given file URL.
1409+
* This should be called when you're done accessing a file that was opened
1410+
* using a security-scoped URL (e.g., from a file picker) when using manual tracking via {@linkcode startAccessingSecurityScopedResource}.
1411+
*
1412+
* #### Platform-specific
1413+
*
1414+
* - **iOS:** Stops accessing the security-scoped resource.
1415+
* - **Other platforms:** does nothing.
1416+
*
1417+
* @example
1418+
* ```typescript
1419+
* import { stopAccessingSecurityScopedResource } from '@tauri-apps/plugin-fs';
1420+
*
1421+
* const filePath = 'file:///path/to/file.txt';
1422+
* await startAccessingSecurityScopedResource(filePath);
1423+
* // ... use the resource ...
1424+
* // when you're done with the resource:
1425+
* await stopAccessingSecurityScopedResource(filePath);
1426+
* ```
1427+
*
1428+
* @since 2.5.0
1429+
*/
1430+
async function stopAccessingSecurityScopedResource(
1431+
path: string | URL
1432+
): Promise<void> {
1433+
if (path instanceof URL && path.protocol !== 'file:') {
1434+
throw new TypeError('Must be a file URL.')
1435+
}
1436+
1437+
await invoke('plugin:fs|stop_accessing_security_scoped_resource', {
1438+
path: path instanceof URL ? path.toString() : path
1439+
})
1440+
}
1441+
13561442
export type {
13571443
CreateOptions,
13581444
OpenOptions,
@@ -1401,5 +1487,7 @@ export {
14011487
exists,
14021488
watch,
14031489
watchImmediate,
1404-
size
1490+
size,
1491+
startAccessingSecurityScopedResource,
1492+
stopAccessingSecurityScopedResource
14051493
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Automatically generated - DO NOT EDIT!
2+
3+
"$schema" = "../../schemas/schema.json"
4+
5+
[[permission]]
6+
identifier = "allow-start-accessing-security-scoped-resource"
7+
description = "Enables the start_accessing_security_scoped_resource command without any pre-configured scope."
8+
commands.allow = ["start_accessing_security_scoped_resource"]
9+
10+
[[permission]]
11+
identifier = "deny-start-accessing-security-scoped-resource"
12+
description = "Denies the start_accessing_security_scoped_resource command without any pre-configured scope."
13+
commands.deny = ["start_accessing_security_scoped_resource"]
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Automatically generated - DO NOT EDIT!
2+
3+
"$schema" = "../../schemas/schema.json"
4+
5+
[[permission]]
6+
identifier = "allow-stop-accessing-security-scoped-resource"
7+
description = "Enables the stop_accessing_security_scoped_resource command without any pre-configured scope."
8+
commands.allow = ["stop_accessing_security_scoped_resource"]
9+
10+
[[permission]]
11+
identifier = "deny-stop-accessing-security-scoped-resource"
12+
description = "Denies the stop_accessing_security_scoped_resource command without any pre-configured scope."
13+
commands.deny = ["stop_accessing_security_scoped_resource"]

plugins/fs/permissions/autogenerated/reference.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3435,6 +3435,32 @@ Denies the size command without any pre-configured scope.
34353435
<tr>
34363436
<td>
34373437

3438+
`fs:allow-start-accessing-security-scoped-resource`
3439+
3440+
</td>
3441+
<td>
3442+
3443+
Enables the start_accessing_security_scoped_resource command without any pre-configured scope.
3444+
3445+
</td>
3446+
</tr>
3447+
3448+
<tr>
3449+
<td>
3450+
3451+
`fs:deny-start-accessing-security-scoped-resource`
3452+
3453+
</td>
3454+
<td>
3455+
3456+
Denies the start_accessing_security_scoped_resource command without any pre-configured scope.
3457+
3458+
</td>
3459+
</tr>
3460+
3461+
<tr>
3462+
<td>
3463+
34383464
`fs:allow-stat`
34393465

34403466
</td>
@@ -3461,6 +3487,32 @@ Denies the stat command without any pre-configured scope.
34613487
<tr>
34623488
<td>
34633489

3490+
`fs:allow-stop-accessing-security-scoped-resource`
3491+
3492+
</td>
3493+
<td>
3494+
3495+
Enables the stop_accessing_security_scoped_resource command without any pre-configured scope.
3496+
3497+
</td>
3498+
</tr>
3499+
3500+
<tr>
3501+
<td>
3502+
3503+
`fs:deny-stop-accessing-security-scoped-resource`
3504+
3505+
</td>
3506+
<td>
3507+
3508+
Denies the stop_accessing_security_scoped_resource command without any pre-configured scope.
3509+
3510+
</td>
3511+
</tr>
3512+
3513+
<tr>
3514+
<td>
3515+
34643516
`fs:allow-truncate`
34653517

34663518
</td>

plugins/fs/permissions/schemas/schema.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1860,6 +1860,18 @@
18601860
"const": "deny-size",
18611861
"markdownDescription": "Denies the size command without any pre-configured scope."
18621862
},
1863+
{
1864+
"description": "Enables the start_accessing_security_scoped_resource command without any pre-configured scope.",
1865+
"type": "string",
1866+
"const": "allow-start-accessing-security-scoped-resource",
1867+
"markdownDescription": "Enables the start_accessing_security_scoped_resource command without any pre-configured scope."
1868+
},
1869+
{
1870+
"description": "Denies the start_accessing_security_scoped_resource command without any pre-configured scope.",
1871+
"type": "string",
1872+
"const": "deny-start-accessing-security-scoped-resource",
1873+
"markdownDescription": "Denies the start_accessing_security_scoped_resource command without any pre-configured scope."
1874+
},
18631875
{
18641876
"description": "Enables the stat command without any pre-configured scope.",
18651877
"type": "string",
@@ -1872,6 +1884,18 @@
18721884
"const": "deny-stat",
18731885
"markdownDescription": "Denies the stat command without any pre-configured scope."
18741886
},
1887+
{
1888+
"description": "Enables the stop_accessing_security_scoped_resource command without any pre-configured scope.",
1889+
"type": "string",
1890+
"const": "allow-stop-accessing-security-scoped-resource",
1891+
"markdownDescription": "Enables the stop_accessing_security_scoped_resource command without any pre-configured scope."
1892+
},
1893+
{
1894+
"description": "Denies the stop_accessing_security_scoped_resource command without any pre-configured scope.",
1895+
"type": "string",
1896+
"const": "deny-stop-accessing-security-scoped-resource",
1897+
"markdownDescription": "Denies the stop_accessing_security_scoped_resource command without any pre-configured scope."
1898+
},
18751899
{
18761900
"description": "Enables the truncate command without any pre-configured scope.",
18771901
"type": "string",

0 commit comments

Comments
 (0)