pub struct FsScope { /* private fields */ }
Expand description
Scope for filesystem access.
Implementations§
source§impl Scope
impl Scope
sourcepub fn allowed_patterns(&self) -> HashSet<Pattern>
pub fn allowed_patterns(&self) -> HashSet<Pattern>
The list of allowed patterns.
sourcepub fn forbidden_patterns(&self) -> HashSet<Pattern>
pub fn forbidden_patterns(&self) -> HashSet<Pattern>
The list of forbidden patterns.
sourcepub fn listen<F: Fn(&Event) + Send + 'static>(&self, f: F) -> Uuid
pub fn listen<F: Fn(&Event) + Send + 'static>(&self, f: F) -> Uuid
Listen to an event on this scope.
sourcepub fn allow_directory<P: AsRef<Path>>(
&self,
path: P,
recursive: bool
) -> Result<()>
pub fn allow_directory<P: AsRef<Path>>(
&self,
path: P,
recursive: bool
) -> Result<()>
Extend the allowed patterns with the given directory.
After this function has been called, the frontend will be able to use the Tauri API to read
the directory and all of its files. If recursive
is true
, subdirectories will be accessible too.
sourcepub fn allow_file<P: AsRef<Path>>(&self, path: P) -> Result<()>
pub fn allow_file<P: AsRef<Path>>(&self, path: P) -> Result<()>
Extend the allowed patterns with the given file path.
After this function has been called, the frontend will be able to use the Tauri API to read the contents of this file.
sourcepub fn forbid_directory<P: AsRef<Path>>(
&self,
path: P,
recursive: bool
) -> Result<()>
pub fn forbid_directory<P: AsRef<Path>>(
&self,
path: P,
recursive: bool
) -> Result<()>
Set the given directory path to be forbidden by this scope.
Note: this takes precedence over allowed paths, so its access gets denied always.
sourcepub fn forbid_file<P: AsRef<Path>>(&self, path: P) -> Result<()>
pub fn forbid_file<P: AsRef<Path>>(&self, path: P) -> Result<()>
Set the given file path to be forbidden by this scope.
Note: this takes precedence over allowed paths, so its access gets denied always.
sourcepub fn is_allowed<P: AsRef<Path>>(&self, path: P) -> bool
pub fn is_allowed<P: AsRef<Path>>(&self, path: P) -> bool
Determines if the given path is allowed on this scope.
Examples found in repository?
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
fn resolve_path<R: Runtime>(
config: &Config,
package_info: &PackageInfo,
window: &Window<R>,
path: SafePathBuf,
dir: Option<BaseDirectory>,
) -> super::Result<SafePathBuf> {
let env = window.state::<Env>().inner();
match crate::api::path::resolve_path(config, package_info, env, &path, dir) {
Ok(path) => {
if window.state::<Scopes>().fs.is_allowed(&path) {
Ok(
// safety: the path is resolved by Tauri so it is safe
unsafe { SafePathBuf::new_unchecked(path) },
)
} else {
Err(anyhow::anyhow!(
crate::Error::PathNotAllowed(path).to_string()
))
}
}
Err(e) => super::Result::<SafePathBuf>::Err(e.into())
.with_context(|| format!("path: {}, base dir: {:?}", path.display(), dir)),
}
}
More examples
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
pub(crate) fn read_dir_with_options<P: AsRef<Path>>(
path: P,
recursive: bool,
options: ReadDirOptions<'_>,
) -> crate::api::Result<Vec<DiskEntry>> {
let mut files_and_dirs: Vec<DiskEntry> = vec![];
for entry in fs::read_dir(path)? {
let path = entry?.path();
let path_as_string = path.display().to_string();
if let Ok(flag) = is_dir(&path_as_string) {
files_and_dirs.push(DiskEntry {
path: path.clone(),
children: if flag {
Some(
if recursive
&& (!is_symlink(&path_as_string)?
|| options.scope.map(|s| s.is_allowed(&path)).unwrap_or(true))
{
read_dir_with_options(&path_as_string, true, options)?
} else {
vec![]
},
)
} else {
None
},
name: path
.file_name()
.map(|name| name.to_string_lossy())
.map(|name| name.to_string()),
});
}
}
Result::Ok(files_and_dirs)
}
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
async fn http_request<R: Runtime>(
context: InvokeContext<R>,
client_id: ClientId,
options: Box<HttpRequestBuilder>,
) -> super::Result<ResponseData> {
use crate::Manager;
let scopes = context.window.state::<crate::Scopes>();
if scopes.http.is_allowed(&options.url) {
let client = clients()
.lock()
.unwrap()
.get(&client_id)
.ok_or_else(|| crate::Error::HttpClientNotInitialized.into_anyhow())?
.clone();
let options = *options;
if let Some(crate::api::http::Body::Form(form)) = &options.body {
for value in form.0.values() {
if let crate::api::http::FormPart::File {
file: crate::api::http::FilePart::Path(path),
..
} = value
{
if crate::api::file::SafePathBuf::new(path.clone()).is_err()
|| !scopes.fs.is_allowed(&path)
{
return Err(crate::Error::PathNotAllowed(path.clone()).into_anyhow());
}
}
}
}
let response = client.send(options).await?;
Ok(response.read().await?)
} else {
Err(crate::Error::UrlNotAllowed(options.url).into_anyhow())
}
}
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
fn prepare_pending_window(
&self,
mut pending: PendingWindow<EventLoopMessage, R>,
label: &str,
window_labels: &[String],
app_handle: AppHandle<R>,
web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
) -> crate::Result<PendingWindow<EventLoopMessage, R>> {
let is_init_global = self.inner.config.build.with_global_tauri;
let plugin_init = self
.inner
.plugins
.lock()
.expect("poisoned plugin store")
.initialization_script();
let pattern_init = PatternJavascript {
pattern: self.pattern().into(),
}
.render_default(&Default::default())?;
let ipc_init = IpcJavascript {
isolation_origin: &match self.pattern() {
#[cfg(feature = "isolation")]
Pattern::Isolation { schema, .. } => crate::pattern::format_real_schema(schema),
_ => "".to_string(),
},
}
.render_default(&Default::default())?;
let mut webview_attributes = pending.webview_attributes;
let mut window_labels = window_labels.to_vec();
let l = label.to_string();
if !window_labels.contains(&l) {
window_labels.push(l);
}
webview_attributes = webview_attributes
.initialization_script(&self.inner.invoke_initialization_script)
.initialization_script(&format!(
r#"
Object.defineProperty(window, '__TAURI_METADATA__', {{
value: {{
__windows: {window_labels_array}.map(function (label) {{ return {{ label: label }} }}),
__currentWindow: {{ label: {current_window_label} }}
}}
}})
"#,
window_labels_array = serde_json::to_string(&window_labels)?,
current_window_label = serde_json::to_string(&label)?,
))
.initialization_script(&self.initialization_script(&ipc_init.into_string(),&pattern_init.into_string(),&plugin_init, is_init_global)?)
;
#[cfg(feature = "isolation")]
if let Pattern::Isolation { schema, .. } = self.pattern() {
webview_attributes = webview_attributes.initialization_script(
&IsolationJavascript {
origin: self.get_browser_origin(),
isolation_src: &crate::pattern::format_real_schema(schema),
style: tauri_utils::pattern::isolation::IFRAME_STYLE,
}
.render_default(&Default::default())?
.into_string(),
);
}
pending.webview_attributes = webview_attributes;
let mut registered_scheme_protocols = Vec::new();
for (uri_scheme, protocol) in &self.inner.uri_scheme_protocols {
registered_scheme_protocols.push(uri_scheme.clone());
let protocol = protocol.clone();
let app_handle = Mutex::new(app_handle.clone());
pending.register_uri_scheme_protocol(uri_scheme.clone(), move |p| {
(protocol.protocol)(&app_handle.lock().unwrap(), p)
});
}
let window_url = Url::parse(&pending.url).unwrap();
let window_origin =
if cfg!(windows) && window_url.scheme() != "http" && window_url.scheme() != "https" {
format!("https://{}.localhost", window_url.scheme())
} else {
format!(
"{}://{}{}",
window_url.scheme(),
window_url.host().unwrap(),
if let Some(port) = window_url.port() {
format!(":{}", port)
} else {
"".into()
}
)
};
if !registered_scheme_protocols.contains(&"tauri".into()) {
pending.register_uri_scheme_protocol(
"tauri",
self.prepare_uri_scheme_protocol(&window_origin, web_resource_request_handler),
);
registered_scheme_protocols.push("tauri".into());
}
#[cfg(protocol_asset)]
if !registered_scheme_protocols.contains(&"asset".into()) {
use crate::api::file::SafePathBuf;
use tokio::io::{AsyncReadExt, AsyncSeekExt};
use url::Position;
let asset_scope = self.state().get::<crate::Scopes>().asset_protocol.clone();
pending.register_uri_scheme_protocol("asset", move |request| {
let parsed_path = Url::parse(request.uri())?;
let filtered_path = &parsed_path[..Position::AfterPath];
#[cfg(target_os = "windows")]
let path = filtered_path
.strip_prefix("asset://localhost/")
// the `strip_prefix` only returns None when a request is made to `https://tauri.$P` on Windows
// where `$P` is not `localhost/*`
.unwrap_or("");
// safe to unwrap: request.uri() always starts with this prefix
#[cfg(not(target_os = "windows"))]
let path = filtered_path.strip_prefix("asset://").unwrap();
let path = percent_encoding::percent_decode(path.as_bytes())
.decode_utf8_lossy()
.to_string();
if let Err(e) = SafePathBuf::new(path.clone().into()) {
debug_eprintln!("asset protocol path \"{}\" is not valid: {}", path, e);
return HttpResponseBuilder::new().status(403).body(Vec::new());
}
if !asset_scope.is_allowed(&path) {
debug_eprintln!("asset protocol not configured to allow the path: {}", path);
return HttpResponseBuilder::new().status(403).body(Vec::new());
}
let path_ = path.clone();
let mut response =
HttpResponseBuilder::new().header("Access-Control-Allow-Origin", &window_origin);
// handle 206 (partial range) http request
if let Some(range) = request
.headers()
.get("range")
.and_then(|r| r.to_str().map(|r| r.to_string()).ok())
{
let (headers, status_code, data) = crate::async_runtime::safe_block_on(async move {
let mut headers = HashMap::new();
let mut buf = Vec::new();
// open the file
let mut file = match tokio::fs::File::open(path_.clone()).await {
Ok(file) => file,
Err(e) => {
debug_eprintln!("Failed to open asset: {}", e);
return (headers, 404, buf);
}
};
// Get the file size
let file_size = match file.metadata().await {
Ok(metadata) => metadata.len(),
Err(e) => {
debug_eprintln!("Failed to read asset metadata: {}", e);
return (headers, 404, buf);
}
};
// parse the range
let range = match crate::runtime::http::HttpRange::parse(
&if range.ends_with("-*") {
range.chars().take(range.len() - 1).collect::<String>()
} else {
range.clone()
},
file_size,
) {
Ok(r) => r,
Err(e) => {
debug_eprintln!("Failed to parse range {}: {:?}", range, e);
return (headers, 400, buf);
}
};
// FIXME: Support multiple ranges
// let support only 1 range for now
let status_code = if let Some(range) = range.first() {
let mut real_length = range.length;
// prevent max_length;
// specially on webview2
if range.length > file_size / 3 {
// max size sent (400ko / request)
// as it's local file system we can afford to read more often
real_length = std::cmp::min(file_size - range.start, 1024 * 400);
}
// last byte we are reading, the length of the range include the last byte
// who should be skipped on the header
let last_byte = range.start + real_length - 1;
headers.insert("Connection", "Keep-Alive".into());
headers.insert("Accept-Ranges", "bytes".into());
headers.insert("Content-Length", real_length.to_string());
headers.insert(
"Content-Range",
format!("bytes {}-{}/{}", range.start, last_byte, file_size),
);
if let Err(e) = file.seek(std::io::SeekFrom::Start(range.start)).await {
debug_eprintln!("Failed to seek file to {}: {}", range.start, e);
return (headers, 422, buf);
}
if let Err(e) = file.take(real_length).read_to_end(&mut buf).await {
debug_eprintln!("Failed read file: {}", e);
return (headers, 422, buf);
}
// partial content
206
} else {
200
};
(headers, status_code, buf)
});
for (k, v) in headers {
response = response.header(k, v);
}
let mime_type = MimeType::parse(&data, &path);
response.mimetype(&mime_type).status(status_code).body(data)
} else {
match crate::async_runtime::safe_block_on(async move { tokio::fs::read(path_).await }) {
Ok(data) => {
let mime_type = MimeType::parse(&data, &path);
response.mimetype(&mime_type).body(data)
}
Err(e) => {
debug_eprintln!("Failed to read file: {}", e);
response.status(404).body(Vec::new())
}
}
}
});
}
#[cfg(feature = "isolation")]
if let Pattern::Isolation {
assets,
schema,
key: _,
crypto_keys,
} = &self.inner.pattern
{
let assets = assets.clone();
let schema_ = schema.clone();
let url_base = format!("{}://localhost", schema_);
let aes_gcm_key = *crypto_keys.aes_gcm().raw();
pending.register_uri_scheme_protocol(schema, move |request| {
match request_to_path(request, &url_base).as_str() {
"index.html" => match assets.get(&"index.html".into()) {
Some(asset) => {
let asset = String::from_utf8_lossy(asset.as_ref());
let template = tauri_utils::pattern::isolation::IsolationJavascriptRuntime {
runtime_aes_gcm_key: &aes_gcm_key,
};
match template.render(asset.as_ref(), &Default::default()) {
Ok(asset) => HttpResponseBuilder::new()
.mimetype("text/html")
.body(asset.into_string().as_bytes().to_vec()),
Err(_) => HttpResponseBuilder::new()
.status(500)
.mimetype("text/plain")
.body(Vec::new()),
}
}
None => HttpResponseBuilder::new()
.status(404)
.mimetype("text/plain")
.body(Vec::new()),
},
_ => HttpResponseBuilder::new()
.status(404)
.mimetype("text/plain")
.body(Vec::new()),
}
});
}
Ok(pending)
}