Repository: tauri-apps/tauri
File: crates/tauri/src/protocol/asset.rs (Rust)

Code before:
  if let Err(e) = SafePathBuf::new(path.clone().into()) {
    log::error!("asset protocol path \"{}\" is not valid: {}", path, e);
    return resp.status(403).body(Vec::new().into()).map_err(Into::into);
  }

  if !scope.is_allowed(&path) {
    log::error!("asset protocol not configured to allow the path: {}", path);
    return resp.status(403).body(Vec::new().into()).map_err(Into::into);
  }

  // On Android, log additional information for debugging external storage access
  #[cfg(target_os = "android")]
  {
    if path.starts_with("/storage/emulated/0/Android/data/") {
      log::debug!("Attempting to access Android external storage path: {}", path);
      
      // Check if the file exists before trying to open it
      if !std::path::Path::new(&path).exists() {
        log::error!("File does not exist at path: {}", path);
        return resp.status(404).body(Vec::new().into()).map_err(Into::into);
      }
      
      // Check if we have read permissions
      match std::fs::metadata(&path) {
        Ok(metadata) => {
          log::debug!("File metadata for {}: size={}, readonly={}", path, metadata.len(), metadata.permissions().readonly());
        }
        Err(e) => {
          log::error!("Failed to get metadata for {}: {}", path, e);
          return resp.status(500).body(format!("Failed to access file metadata: {}", e).into_bytes().into()).map_err(Into::into);
        }
      }
    }
  }

  let (mut file, len, mime_type, read_bytes) = crate::async_runtime::safe_block_on(async move {
    let mut file = match File::open(&path).await {
      Ok(file) => file,
      Err(e) => {
        #[cfg(target_os = "android")]
        {
          if path.starts_with("/storage/emulated/0/Android/data/") {
            log::error!("Failed to open Android external storage file '{}': {}. This may be due to missing storage permissions or the file being inaccessible from the webview context.", path, e);
            log::error!("Ensure your app has the necessary storage permissions and the file is in an accessible location.");
          }
        }
        return Err(e.into());
      }
    };

    // get file length

Review comment (suggestion):
This log looks more like the verbosity that's hidden behind the `tracing` feature flag but we're _very_ inconsistent about this :/

Code after:
  if let Err(e) = SafePathBuf::new(path.clone().into()) {
    log::error!("asset protocol path \"{}\" is not valid: {}", path, e);
    return resp.status(403).body(Vec::new().into()).map_err(Into::into);
  }

  if !scope.is_allowed(&path) {
    log::error!("asset protocol not configured to allow the path: {}", path);
    return resp.status(403).body(Vec::new().into()).map_err(Into::into);
  }

  // Separate block for easier error handling
  let mut file = match crate::async_runtime::safe_block_on(File::open(path.clone())) {
    Ok(file) => file,
    Err(e) => {
      #[cfg(target_os = "android")]
      {
        if path.starts_with("/storage/emulated/0/Android/data/") {
          log::error!("Failed to open Android external storage file '{}': {}. This may be due to missing storage permissions.", path, e);
        }
      }
      return if e.kind() == std::io::ErrorKind::NotFound {
        log::error!("File does not exist at path: {}", path);
        return resp.status(404).body(Vec::new().into()).map_err(Into::into);
      } else if e.kind() == std::io::ErrorKind::PermissionDenied {
        log::error!("Missing OS permission to access path \"{}\": {}", path, e);
        return resp.status(403).body(Vec::new().into()).map_err(Into::into);
      } else {
        Err(e.into())
      };
    }
  };

  let (mut file, len, mime_type, read_bytes) = crate::async_runtime::safe_block_on(async move {
    // get file length
    let len = {
      let old_pos = file.stream_position().await?;
      let len = file.seek(SeekFrom::End(0)).await?;
      file.seek(SeekFrom::Start(old_pos)).await?;
      len
    };

    // get file mime type
    let (mime_type, read_bytes) = {
      let nbytes = len.min(8192);
      let mut magic_buf = Vec::with_capacity(nbytes as usize);
      let old_pos = file.stream_position().await?;
      (&mut file).take(nbytes).read_to_end(&mut magic_buf).await?;
      file.seek(SeekFrom::Start(old_pos)).await?;
      (
        MimeType::parse(&magic_buf, &path),
        // return the `magic_bytes` if we read the whole file