tauri-apps/tauri

[bug] `fetch` and `XHR` response has no `content-range` header

Closed this issue · 7 comments

chrox commented

Describe the bug

I don't know if it is intended or not but in both asset protocol and the http protocol the content-range header is discarded in fetch response. Without the content-range header successive range requests cannot be performed such as in pdf.js.

This is a normal fetch in macOS Safari browser:
image

And this is a fetch in Tauri app in macOS:
image

The static HTTP server http://localhost:9000 is started with http-server -p 9000 -d . --cors for testing purpose.


And the asset protocol is also affected in Tauri app.

image

Note that in the network inspector the content-range header is present but it's like being discarded somewhere in the fetch response.

image


I also try to figure out where the response headers are intercepted. At least the wry layer hold the intact headers:

[2024-10-15][14:44:11][wry::wkwebview::class::url_scheme_handler][INFO] wry::custom_protocol::handle; uri="asset://localhost/%2FUsers%2Fchrox%2FDocuments%2FReadest%2FBooks%2F3ec18259cf5e8a916a553f14ebf4f7d3%2Fl11manual_zh.pdf"
[2024-10-15][14:44:11][tracing::span][INFO] wry::custom_protocol::call_handler;
response headers: {
    "content-type": "application/pdf",
    "content-range": "bytes 0-65535/91848117",
    "accept-ranges": "bytes",
    "application/pdf": "content-type",
    "content-length": "65536",
    "65536": "content-length",
    "access-control-allow-origin": "http://localhost:3000",
}

Let's set aside for now these two malformed headers "65536": "content-length" and "application/pdf": "content-type" that were introduced by recent code refactoring of wry; the wry url_scheme_handler writes the correct content-range header. I tried to add Access-Control-Allow-Headers: * or Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Range in tauri/src/protocol/asset.ts but it makes no difference on the response headers of fetch and XHR. I have no clue on how the response headers are sent to the Javascript world. Could someone give a hint?

Reproduction

No response

Expected behavior

No response

Full tauri info output

> tauri "info"


[✔] Environment
    - OS: Mac OS 14.3.1 arm64 (X64)
    ✔ Xcode Command Line Tools: installed
    ✔ rustc: 1.81.0 (eeb90cda1 2024-09-04)
    ✔ cargo: 1.81.0 (2dbb1af80 2024-08-20)
    ✔ rustup: 1.27.1 (54dd3d00f 2024-04-24)
    ✔ Rust toolchain: stable-aarch64-apple-darwin (default)
    - node: 22.9.0
    - pnpm: 9.12.1
    - yarn: 1.22.22
    - npm: 10.8.3

[-] Packages
    - tauri 🦀: 2.0.3
    - tauri-build 🦀: 2.0.1
    - wry 🦀: 0.46.1
    - tao 🦀: 0.30.3
    - @tauri-apps/api : 2.0.2
    - @tauri-apps/cli : 2.0.3

[-] Plugins
    - tauri-plugin-http 🦀: 2.0.1
    - @tauri-apps/plugin-http : 2.0.0
    - tauri-plugin-os 🦀: 2.0.1
    - @tauri-apps/plugin-os : 2.0.0
    - tauri-plugin-fs 🦀: 2.0.1
    - @tauri-apps/plugin-fs : 2.0.0
    - tauri-plugin-log 🦀: 2.0.1
    - @tauri-apps/plugin-log : 2.0.0
    - tauri-plugin-dialog 🦀: 2.0.1
    - @tauri-apps/plugin-dialog : 2.0.0

[-] App
    - build-type: bundle
    - CSP: style-src 'self' 'unsafe-inline' blob: asset: http://asset.localhost; default-src 'self' 'unsafe-inline' blob: customprotocol: asset: http://asset.localhost ipc: http://ipc.localhost; frame-src 'self' blob: asset: http://asset.localhost; img-src 'self' blob: data: asset: http://asset.localhost
    - frontendDist: ../out
    - devUrl: http://localhost:3000/
    - framework: React (Next.js)
    - bundler: Webpack

Stack trace

No response

Additional context

No response

chrox commented

This is my app.security in tauri.conf.json:

    "security": {
      "csp": {
        "default-src": "'self' 'unsafe-inline' blob: customprotocol: asset: http://asset.localhost ipc: http://ipc.localhost",
        "img-src": "'self' blob: data: asset: http://asset.localhost",
        "style-src": "'self' 'unsafe-inline' blob: asset: http://asset.localhost",
        "frame-src": "'self' blob: asset: http://asset.localhost"
      },
      "assetProtocol": {
        "enable": true,
        "scope": {
          "allow": ["$RESOURCE/**", "$DOCUMENT/**/*", "$APPDATA/**/*", "$TEMP/**/*"],
          "deny": []
        }
      }
    }

And this is the capability config:

 {
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "description": "enables the default permissions",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "fs:default",
    {
      "identifier": "fs:scope-document-recursive",
      "allow": [
        {
          "path": "$DOCUMENT/**/*"
        }
      ],
      "deny": []
    },
    {
      "identifier": "fs:scope-appdata-recursive",
      "allow": [
        {
          "path": "$APPDATA/Readest/**/*"
        }
      ],
      "deny": []
    },
    {
      "identifier": "fs:allow-appdata-read",
      "allow": [
        {
          "path": "$APPDATA/settings.json"
        }
      ]
    },
    {
      "identifier": "fs:allow-appdata-write",
      "allow": [
        {
          "path": "$APPDATA/settings.json"
        }
      ]
    },
    "dialog:default",
    "os:default",
    "http:default"
  ]
}

chrox commented

This might be related to #10426.

chrox commented

UPDATE:
Adding a access-control-expose-headers: content-range solves this problem.
image

If there is no security concern about this I will send a PR later after more testing.

@chrox feel free to open a PR and @tweidinger will take a look from security prespective.

Let's set aside for now these two malformed headers "65536": "content-length" and "application/pdf": "content-type" that were introduced by recent code refactoring of wry;

Could you open an issue or better a PR if you have a fix?

chrox commented

Sure.

chrox commented

According to MDN on Access-Control-Expose-Headers, content-range is not a simple response header and not exposed to client scripts by default in CORS response except that it's listed in the Access-Control-Expose-Headers header. The first example I provided above however is not a CORS request thus not affected.

It sounds pretty safe to expose the content-range header in a range request in the asset protocol. I'm opening a PR to do this.