calternal_plugin_files::user_bytes
Response headers for user bytes: every route that streams a file a user wrote (downloads, versions, thumbnails, public links, video) builds its headers here, so one rule decides what a browser may do with them.
Threat (#107): these bytes are served on the app’s own origin. A
collaborator with edit access to a shared folder can plant report.html
or chart.svg with script. If the owner opens its inline URL and the
browser renders it as a page, the script runs as the owner and can call the
API with the owner’s session. Three independent layers stop that:
SANDBOX_POLICYon every response.sandboxgives a document an opaque origin and blocks script, so even a page that renders cannot use the session.default-src 'none'stops every fetch it could start. The image, media and inline-style sources keep a plain image, video or text view working when someone opens the URL directly.X-Content-Type-Options: nosniff, so a polyglot (a GIF header followed by HTML) is never sniffed into HTML.- Active types (HTML, XHTML, XML and XSLT, JavaScript, and SVG outside an
<img>) are never sent inline with their real type: they go out astext/plain; charset=utf-8, which shows the source. Quick Look reads text throughfetch, so it sees the same bytes.
The policy is on attachments too: it costs nothing and covers a browser
that ignores Content-Disposition.
The public link routes (public.rs: download, preview, thumbnail) use
the same helpers; the link page head uses strip_unsafe_name_chars.
Source: crates/plugins/files/src/user_bytes.rs
Disposition
Section titled “Disposition”pub enum DispositionHow the browser should treat the bytes.
Variants
Inline: Show in the page (Quick Look,<img>,<video>, note images).Attachment: Save as a file.
Implements: Clone, Copy, Debug, PartialEq, Eq
Disposition::requested
Section titled “Disposition::requested”pub fn requested(inline_query: bool, request: &HeaderMap) -> Selfinline when the request asks for it with ?inline=true (already
parsed by the caller) or the x-calternal-inline: true header.
Source: crates/plugins/files/src/user_bytes.rs:39
Functions
Section titled “Functions”content_disposition
Section titled “content_disposition”pub fn content_disposition(disposition: Disposition, name: &str) -> StringA Content-Disposition value with an ASCII filename fallback and the
exact UTF-8 name in filename* (RFC 6266, RFC 8187). No byte of the name
can end the quoted string or the header: quotes, backslashes and
non-ASCII become _ in the fallback and percent escapes in filename*.
Source: crates/plugins/files/src/user_bytes.rs:88
entity_tag
Section titled “entity_tag”pub fn entity_tag(hash: &str, guessed: &str, served: &str) -> StringThe entity tag for one representation of a file. A neutralized inline
response (text/plain instead of HTML or SVG) is a different
representation of the same bytes, so it gets a different tag. With one
tag for both, a browser that cached the text answer for a navigation
revalidates it for an <img> load, gets 304, and reuses the text
(Chromium keeps one cache entry per URL and revalidates on a Vary
mismatch).
Source: crates/plugins/files/src/user_bytes.rs:181
file_headers
Section titled “file_headers”pub fn file_headers( builder: Builder, disposition: Disposition, name: &str, served: &str,) -> BuilderThe full header set for a user file: the served type (from
served_type), the Content-Disposition for name,
Vary: Sec-Fetch-Dest on inline responses (the type can depend on it),
and guard.
Source: crates/plugins/files/src/user_bytes.rs:193
pub fn guard(builder: Builder) -> BuilderAdds the headers every user-bytes response carries, whatever its type:
nosniff and SANDBOX_POLICY. Use it alone for bytes the server
generated from user files (thumbnails, HLS) or whose type is fixed.
Source: crates/plugins/files/src/user_bytes.rs:168
guessed_type
Section titled “guessed_type”pub fn guessed_type(name: &str) -> StringThe type guessed from a file name (the same guess the Index stores).
Source: crates/plugins/files/src/user_bytes.rs:121
indexed_type
Section titled “indexed_type”pub fn indexed_type(mime: Option<&str>, name: &str) -> StringPrefer the MIME value verified and stored by the Files Index. Name-based fallback is for legacy and generated paths with no indexed content (#851).
Source: crates/plugins/files/src/user_bytes.rs:127
served_type
Section titled “served_type”pub fn served_type(guessed: &str, disposition: Disposition, request: &HeaderMap) -> StringThe content type to send. An attachment keeps its type (the browser
saves it). An inline active type becomes PLAIN_TEXT. SVG keeps its
type only for an <img> load (Sec-Fetch-Dest: image), where the browser
runs no script; a navigation, a fetch or an unknown browser gets text.
Source: crates/plugins/files/src/user_bytes.rs:143
strip_unsafe_name_chars
Section titled “strip_unsafe_name_chars”pub fn strip_unsafe_name_chars(name: &str) -> StringRemoves control and bidirectional format characters from a name, so
invoice\u{202E}txt.exe cannot read as invoiceexe.txt. Download names
and link previews share this rule.
Source: crates/plugins/files/src/user_bytes.rs:72
Constants
Section titled “Constants”PLAIN_TEXT
Section titled “PLAIN_TEXT”pub const PLAIN_TEXT: &strThe content type that replaces an active type on an inline response.
Source: crates/plugins/files/src/user_bytes.rs:35
SANDBOX_POLICY
Section titled “SANDBOX_POLICY”pub const SANDBOX_POLICY: &strThe Content-Security-Policy for every response that carries user bytes.