Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion docs/api-v2-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1040,7 +1040,20 @@ leaving `homepage_url` out clears it. That is what the Subsonic
every call — and both surfaces are the same domain method, so an absent field
cannot mean two different things depending on the door.

**The server never connects to a station.** The address is validated as
**`image_url` is the one exception, and for the same reason turned around.**
A station may carry an image address, validated as `homepage_url` is. The
protocol has no image parameter, so a Subsonic edit says nothing about it; for
renaming a station in a Subsonic client not to take its image away, an absent
`image_url` leaves the image as it is, on `PUT` too. `null` or a blank value
clears it. The server fetches the image **once**, when it is set or changed
(an address already stored, with its image held, is not fetched again), into
the artwork store, and answers `artwork_hash` — read it from
`/api/v2/artwork/{hash}` like any cover, which serves it to the station's own
account only. A fetch that fails keeps `image_url` with `artwork_hash: null`;
saving the station again with the address retries it. Subsonic's
`internetRadioStation` is unchanged and carries no `coverArt`.

**The server never connects to a station's stream.** The address is validated as
absolute `http` or `https`, stored as typed and handed back; the player is
what reaches the station. That is the whole security answer for the feature: a
station is a URL somebody supplies, and a server that fetched one would be a
Expand Down
35 changes: 35 additions & 0 deletions docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,41 @@ never a feed address — a private feed carries its token in its path or its
query string. For the same reason only administrators are shown a feed's
address; other accounts see at most its scheme and host.

## Podcast covers and station images

**On by default.** The server fetches the cover a podcast feed names, and the
image an account gives one of its radio stations, into the artwork store
(`artwork/` in the data directory), so the web client and Subsonic clients
show them as they show album covers — the browser never loads them from
their own host. [RFC-025](rfcs/RFC-025-smart-playlists-and-podcasts.md)
carries the decisions; [privacy](privacy.md) says what is fetched and when.

| Variable | Default | What it does |
| --- | --- | --- |
| `WAVEFLOW_REMOTE_ARTWORK` | on | `off` fetches neither: an address is still stored, no image is fetched, and the images already stored stay shown |

**When.** A podcast cover only while `WAVEFLOW_PODCASTS` is on: when a channel
is subscribed, and on a refresh that finds the feed naming a cover it does not
hold — a new address, a failed last attempt, a file gone from the store, or a
copy older than a week. A station image once, when an account sets or changes
it, and only once the station is saved; saving the station again retries one
that failed. An account may have at most ten station images fetched an hour;
past that, a station is saved without its image until a later save. Never on a timer of its
own, never at boot, and never while anybody listens.

**Under which rules.** Those of the podcast fetcher: public addresses only,
checked before connecting and pinned, at most five redirects each checked
before it is followed, 10 MiB, and the bytes must be a JPEG, PNG or WebP —
a page of HTML is refused whatever it is labelled. A podcast cover may be
plain http only while `WAVEFLOW_PODCAST_ALLOW_HTTP` is on; a station image may
always be, as its stream may. **What it logs**: a verdict such as
`image over the size ceiling`, never the address.

**What it stores.** One file per image, `<hash>.<format>`, beside the scanned
covers. The artwork sweep collects it once nothing names it: when the channel
or the station is deleted, or when the feed or the account names another
image.

## Scanning

A scan reads only what changed. A file whose size and modification time are
Expand Down
82 changes: 67 additions & 15 deletions docs/privacy.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,25 @@
Every host a WaveFlow Server can contact, what it sends there, when, and the
setting that stops it. This was written from the code (RFC-019 decision 4), and
the code is the authority if the two ever disagree: `src/outbound.rs`,
`src/enrichment.rs`, `src/portrait.rs`, `src/lrclib.rs`, `src/podcast.rs` and
`src/scrobblers/`.
`src/enrichment.rs`, `src/portrait.rs`, `src/lrclib.rs`, `src/podcast.rs`,
`src/services/remote_artwork.rs` and `src/scrobblers/`.

**A server left at its defaults sends nothing.** Booting makes no request, and
no background task does unless an operator configured it *and*, for
scrobbling, an account linked a destination — or, for podcasts, an
**A server left at its defaults sends nothing on its own.** Booting makes no
request, and no background task does unless an operator configured it *and*,
for scrobbling, an account linked a destination — or, for podcasts, an
administrator subscribed to a feed. The clients are built at boot;
building one sends nothing.
building one sends nothing. The one request a default server makes is one an
account asks for by name: the image of a radio station, fetched once when the
account sets it (below; `WAVEFLOW_REMOTE_ARTWORK=off` stops it).

## Rules every request follows

All outbound clients are built by one function, `src/outbound.rs`:

- **No redirect is followed by the client.** A provider cannot bounce a
request to another host. The one exception is the podcast fetcher, below:
it follows at most five redirects by hand, and puts every hop through the
request to another host. The one exception is the podcast fetcher, below,
and the podcast covers and station images fetched under its rules: it
follows at most five redirects by hand, and puts every hop through the
same scheme and public-address checks as the first address before
contacting it.
- **`HTTP_PROXY` and the other proxy variables are ignored.**
Expand All @@ -35,9 +38,10 @@ All outbound clients are built by one function, `src/outbound.rs`:
verdict, never with its URL or its body.

The server never turns a request it receives into a request of its own, with
one exception an operator switches on: an administrator subscribing to a
podcast, or asking for an episode, below. Radio
station URLs are stored and handed to clients, never fetched; remote entries in
two exceptions: an administrator subscribing to a podcast, or asking for an
episode, which an operator switches on; and an account setting a radio
station's image, which an operator can switch off — both below. Radio
station stream URLs are stored and handed to clients, never fetched; remote entries in
an imported playlist are dropped; FFmpeg and ffprobe are only ever given local
files inside a library or the data directory, and are told so: every input is
passed with `-protocol_whitelist file`, so neither can open a network or
Expand Down Expand Up @@ -213,9 +217,9 @@ one interval after boot, not at boot — and when one asks for an episode.
the server's disk, so the podcast host sees the server, once, and no listener.

**What.** A plain `GET` with the server's User-Agent and nothing about who
asked. The channel's cover address is stored and handed to clients, and never
fetched by the server; a client that shows it fetches it from the feed's host
itself.
asked. The channel's cover is fetched by the server too — see the next
section — and handed to clients by hash, so no client needs to contact the
feed's host for it.

**Kept.** The channel's title, description, links and cover address; each
episode's title, description, dates, duration and enclosure address; the
Expand All @@ -226,6 +230,52 @@ account is shown at most its scheme and host. Enclosure addresses are shown to
nobody. No feed or enclosure address, and no part of a response, is written to
the log.

## Podcast covers and station images

**On by default; `WAVEFLOW_REMOTE_ARTWORK=off` stops both**
([RFC-025](rfcs/RFC-025-smart-playlists-and-podcasts.md), amended). Off, the
addresses are still stored and nothing is fetched; images already fetched stay
shown.

**Which hosts.**

- **A podcast cover**: the host the feed names for its image — the feed's own
host, or another one the feed chose, such as a CDN — and every redirect it
answers with, up to five. Only while `WAVEFLOW_PODCASTS` is on.
- **A station image**: the host in the address an account typed as its
station's image, and its redirects, up to five. Nothing is fetched for a
station without one.

Every address is held to the podcast fetcher's rules, above: http or https
(plain http for a cover only while `WAVEFLOW_PODCAST_ALLOW_HTTP` is on; always
for a station image, as for its stream), no user name or password, the name
resolved first and every address public, the connection pinned to the address
checked, each redirect checked before it is followed. An answer is read up to
10 MiB within 20 seconds, and kept only if its bytes are a JPEG, PNG or WebP.

**When.** A podcast cover when a channel is subscribed, and on a refresh that
finds the feed naming a cover the server does not hold — a new address, one
whose last fetch failed, a file gone from the store, or a copy older than a
week; an unchanged address is otherwise not fetched again. A station image
once, when an account sets or changes it, and again only when the account
saves the station after a fetch that failed — at most ten an hour per account.
In both cases only after the write was accepted: a station or a channel
refused (a full list, a station that is not the account's) contacts nobody. Never at boot, never on a timer of
its own, and **never while anybody listens**: the image is served from the
server's artwork store, so the image's host sees the server, once, and no
listener — which is why the browser does not load it itself.

**What.** A plain `GET` with the server's User-Agent and nothing about who
asked or who listens.

**Kept.** The image, under `artwork/` in the data directory, named by the hash
of its bytes. A cover's address is shown to administrators only, as the feed's
is — a private feed's cover can carry its subscriber's token — and every other
account is given the hash alone. A station's image address is shown to the
account the station belongs to, and its image is served to that account alone.
Deleting the channel or the station releases the image, and the artwork sweep
removes it. No image address, and no part of an answer, is written to the log.

## What the browser contacts

The embedded web client loads nothing from a third party: its fonts are
Expand All @@ -235,7 +285,9 @@ to that (RFC-019 decision 2). Two exceptions, each deliberate:

- **Internet radio.** The browser plays a station straight from the station's
own server, at the URL a user entered. That host sees the listener's address.
Subsonic clients receive the same URLs and connect themselves.
Subsonic clients receive the same URLs and connect themselves. A station's
image is not part of this: the server fetched it, and the browser reads it
from the server.
- **Linking Last.fm**, above: a navigation the user starts.

**`/reference`**, the API reference page, was a third until RFC-023: it loaded
Expand Down
Loading
Loading