{"id":21715557,"url":"https://github.com/ernolf/raw","last_synced_at":"2026-05-16T21:02:16.444Z","repository":{"id":263512063,"uuid":"890496110","full_name":"ernolf/raw","owner":"ernolf","description":null,"archived":false,"fork":false,"pushed_at":"2024-11-19T01:58:39.000Z","size":40,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-20T19:56:37.113Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ernolf.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-18T17:05:22.000Z","updated_at":"2025-01-26T18:20:17.000Z","dependencies_parsed_at":"2024-11-19T00:32:20.855Z","dependency_job_id":"580e9c01-dfd1-4db7-a6e4-8227635c2abe","html_url":"https://github.com/ernolf/raw","commit_stats":null,"previous_names":["ernolf/raw"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/ernolf/raw","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ernolf%2Fraw","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ernolf%2Fraw/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ernolf%2Fraw/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ernolf%2Fraw/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ernolf","download_url":"https://codeload.github.com/ernolf/raw/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ernolf%2Fraw/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264767444,"owners_count":23660966,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-26T00:45:44.498Z","updated_at":"2026-05-16T21:02:16.437Z","avatar_url":"https://github.com/ernolf.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `raw` — Nextcloud raw file server\n\n---\n\n\u003e [!IMPORTANT]\n\u003e **`raw` has been discontinued and will no longer work with Nextcloud 32 or later.**\n\u003e\n\u003e **Please migrate to [`ernolf/files_sharing_raw`](https://github.com/ernolf/files_sharing_raw) instead.** The successor app is available in the Nextcloud App Store and can be installed the usual way.\n\u003e\n\u003e **Already issued `raw` URLs may change slightly after migration.** Please review and update existing links where needed.\n\u003e\n\u003e See the [`files_sharing_raw` Readme](https://github.com/ernolf/files_sharing_raw/blob/main/Readme.md) for installation, migration notes, current URL forms, and updated security guidance.\n\n---\n\n**`raw`** serves files **as-is** so you can link directly to the file itself (i.e. without any of Nextcloud’s UI). This makes it easy to host static web pages, images, or other assets and embed/link them elsewhere.\n\n**Design goals**\n\n* **Minimal**: deliver bytes, not UI.\n* **Fast**: keep server work low (good for assets).\n* **Quiet failures**: plain 404 Not found (text/plain) for invalid/missing public shares (no Nextcloud HTML error pages), ideal for asset fetches.\n* **Privacy-friendly**: **cookie-free responses** (best effort).\n* **Allowlist-gated:** public `raw` access is opt-in — only explicitly allowlisted public share tokens (or wildcard matches) are served.\n* **Secure by default**: strict CSP with optional per-scope overrides. *)\n* **Efficient validators**: for `HEAD` and `304 Not Modified`, `raw` avoids reading file contents whenever possible (no unnecessary `getContent()`). It prefers “fast” validators (mtime+size) and only performs content-based MIME sniffing when it is actually needed for a final `200` response.\n* **MIME sniffing**: When content-based detection is needed, `raw` only sniffs a small prefix (currently 32 KiB) not the full file.\n* **Streaming by default**: for normal `GET` (`200`) responses, `raw` streams the body whenever possible instead of loading the entire file into memory, improving throughput under load.\n\n*) For security and privacy the content is served with a [Content-Security-Policy][] (CSP) header. You can configure CSP rules in detail via Nextcloud’s system [`config/{raw.}config.php`](#keep-raw-settings-in-a-dedicated-config-file) key `raw_csp`. See [Content Security Policy (raw_csp)](#content-security-policy-raw_csp) below.\n\n---\n\n## Table of contents\n\n* [Quickstart](#quickstart)\n\n* [URL forms](#url-forms)\n\n  * [Public shares](#public-shares)\n  * [Private user files](#private-user-files)\n  * [Root aliases (`/raw` and `/rss`)](#root-aliases-raw-and-rss)\n\n* [Access control: token allowlist](#access-control-token-allowlist)\n\n  * [`allowed_raw_tokens`](#allowed_raw_tokens)\n  * [`allowed_raw_token_wildcards`](#allowed_raw_token_wildcards)\n\n* [Content Security Policy (raw_csp)](#content-security-policy-raw_csp)\n\n  * [Matching priority](#matching-priority)\n  * [Policy formats accepted](#policy-formats-accepted)\n  * [Allowed directives](#allowed-directives)\n  * [Example PHP `config/{raw.}config.php` snippets](#example-php-configrawconfigphp-snippets)\n  * [Testing](#testing)\n\n* [Optional system-level tuning](#optional-system-level-tuning)\n\n  * [Cache-Control](#cache-control)\n  * [Webserver offload](#webserver-offload)\n\n    * [Offload debug header](#offload-debug-header)\n\n* [HTTP behavior \u0026 performance](#http-behavior--performance)\n\n  * [Cookie-free responses](#cookie-free-responses)\n  * [Caching: ETags and Last-Modified](#caching-etags-and-last-modified)\n  * [Directory handling (`index.html`)](#directory-handling-indexhtml)\n  * [HEAD requests](#head-requests)\n  * [Plain 404 for invalid public shares](#plain-404-for-invalid-public-shares)\n\n* [Notes \u0026 best practices](#notes--best-practices)\n\n* [Installation](#installation)\n\n  * [Updating](#updating)\n\n---\n\n## Quickstart\n\n1. [Install/enable the app.](#installation)\n2. Create a **public share link** (token) for a file or folder.\n3. Open the `raw` URL:\n\n   * `https://my-nextcloud/apps/raw/s/\u003ctoken\u003e`\n   * and for folders: `https://my-nextcloud/apps/raw/s/\u003ctoken\u003e/\u003cpath/to/file\u003e`\n4. Configure which share tokens are allowed:\n\n   * `allowed_raw_tokens` and/or `allowed_raw_token_wildcards`\n5. (Optional) Configure CSP policies via `raw_csp`.\n\n---\n\n## URL forms\n\n### Public shares\n\nIf the share link is:\n\n```\nhttps://my-nextcloud/s/aBc123DeF456xyZ\n```\n\nthen this app will serve the `raw` file at:\n\n```\nhttps://my-nextcloud/apps/raw/s/aBc123DeF456xyZ\n```\n\nIf the share is a folder, files within it are accessible as:\n\n```\nhttps://my-nextcloud/apps/raw/s/aBc123DeF456xyZ/path/to/file\n```\n\nThe `/s/` can also be omitted:\n\n```\nhttps://my-nextcloud/apps/raw/aBc123DeF456xyZ/path/to/file\n```\n\nalso works.\n\n### Private user files\n\nA user can access their own private files. For example, a file named `test.html` in anansi’s Documents folder would be available at:\n\n```\nhttps://my-nextcloud/apps/raw/u/anansi/Documents/test.html\n```\n\nThe `/u/` can **not** be omitted, so:\n\n```\nhttps://my-nextcloud/apps/raw/anansi/Documents/test.html\n```\n\ndoes **not** work.\n\n### Root aliases (`/raw` and `/rss`)\n\nIf your Nextcloud is configured to allow `raw` in the root namespace *), additional public URL forms become available:\n\n- `/raw/{token}`\n- `/raw/{token}/{path}`\n\nLegacy compatibility aliases:\n- `/raw/s/{token}`\n- `/raw/s/{token}/{path}\n\nSpecial namespace shortcut:\n- `/rss`            (behaves like `/raw/rss`)\n- `/rss/{path}`     (behaves like `/raw/rss/{path}`)\n\n\u003e [!NOTE]\n\u003e `/rss` and `/rss/{path}` are convenience shortcuts that internally behave exactly like `/raw/rss` and `/raw/rss/{path}`. They do not bypass the [token allowlist](#access-control-token-allowlist) — the underlying share token is still `rss`.\n\n\u003e [!IMPORTANT]\n\u003e *) These root aliases require a core configuration allowlist entry (Nextcloud `rootUrlApps` including `raw`) in the file *lib/private/AppFramework/Routing/RouteParser.php*\n\n\u003e [!NOTE]\n\u003e If `rootUrlApps` is not configured (or blocked by your setup), the canonical and always-available URLs remain under `/apps/raw/...`.\n\n---\n\n## Access control: token allowlist\n\nThe app uses a **token allowlist** to control which public share tokens are allowed to access `raw` content.\n\n\u003e [!IMPORTANT]\n\u003e **Only explicitly allowed tokens (or tokens matching configured wildcards) are served by `raw`.**\n\n\u003e [!NOTE]\n\u003e The wildcard matching applies to the **share token** (the public link id), not to file names or paths.\n\nOne or both of the following arrays in [`config/{raw.}config.php`](#keep-raw-settings-in-a-dedicated-config-file) must be defined to configure token-based allowlist restrictions\n(otherwise all public `raw` requests will return `Not found`):\n\n### `allowed_raw_tokens`\n\nAn array of explicitly allowed tokens. These tokens must exactly match the share token used in `raw` links.\n\n### `allowed_raw_token_wildcards`\n\nAn array of wildcard patterns (`*`) matched against the share token. Wildcards are translated into regular expressions for dynamic validation.\n\n#### Example configuration\n\n```php\n\u003c?php\n$CONFIG = array (\n// -\n  'allowed_raw_tokens' =\u003e\n  array (\n    0 =\u003e 'scripts',\n    1 =\u003e 'aBc123DeF456xyZ',\n    2 =\u003e 'includes',\n    3 =\u003e 'html',\n  ),\n  'allowed_raw_token_wildcards' =\u003e\n  array (\n    0 =\u003e '*suffix',\n    1 =\u003e 'prefix*',\n    2 =\u003e 'prefix*suffix',\n    3 =\u003e '*infix*',\n    4 =\u003e 'prefix*infix*',\n  ),\n// -\n);\n```\n\nIn this configuration:\n\n* Tokens such as `scripts`, `aBc123DeF456xyZ`, `includes`, and `html` are explicitly allowed.\n* Wildcards match the share token and can be used as:\n\n  * suffix: `*_json` → `data_json`\n  * prefix: `nc-*` → `nc-assets`\n  * infix: `*holiday_img*` → `2026-02-10-holiday_img.jpg`, `2026-02-12-holiday_img.png`\n  * combined: `site-*_asset_*` → `site-example.com_asset_script.js`, `site-other.example.com_asset_style.css`\n\n### Usage with human-readable tokens\n\nIn the example above, some share links were created as `custom public links`. Generating human-readable tokens (instead of randomly generated ones) makes links more meaningful and easier to manage.\n\nFor example:\n\n* Instead of a random token like `aBc123DeF456xyZ`, you can use a meaningful token such as `html`, `javascript` or `data_json` for shared directories, or prepend prefixes, append suffixes or include infixes to enable them as wildcard.\n\nThis approach enhances both usability and security by allowing administrators to control access to `raw` links more effectively while keeping token names meaningful and consistent.\n\n---\n\n## Content Security Policy (raw_csp)\n\n`raw` supports configurable Content-Security-Policy (CSP) rules via the Nextcloud system config key `raw_csp`. The CSP config lets admins tune how `raw` serves files from different paths, file extensions, or MIME types — and optionally per share token.\n\n\u003e [!NOTE]\n\u003e If `raw_csp` is not defined, `raw` falls back to this safe, very restrictive CSP:\n\u003e\n\u003e ```\n\u003e \"sandbox; default-src 'none'; style-src data: 'unsafe-inline'; img-src data:; media-src data:; font-src data:; frame-src data:\"\n\u003e ```\n\u003e\n\u003e This fallback is implemented hardcoded inside of the app (not in `config.php`).\n\n### Matching priority\n\nWhen deciding which CSP to send, `raw` evaluates selectors in this order:\n\n* `token` (optional) — exact match for a public share token (the share id that appears in public URLs).\n* `path_prefix` — longest matching prefix. Supports absolute prefixes (starting with `/apps/raw`) and relative prefixes (matched against the path after `/apps/raw/...`).\n* `path_contains` — substring match. The manager checks both the full request path and the path after `/apps/raw` so public and private URLs are covered.\n* `extension` — file extension match (e.g. `html`, `json`).\n* `mimetype` — MIME type match (e.g. `text/html`, `application/json`).\n* hard-coded fallback (if nothing matches).\n\n\u003e [!NOTE]\n\u003e `token` is the share token assigned by Nextcloud for public shares. Private user paths (`/apps/raw/u/...`) do not carry a share token, therefore `token` cannot match on private URLs.\n\n### Policy formats accepted\n\nA policy value for a selector may be one of:\n\n* *String* — a full, single-line CSP header value (passed through and sanitized).\n* *Indexed array* — list of directive strings; entries are joined with `;`.\n* *Associative array* (recommended) — `'directive' =\u003e sources`. `sources` may be a string (space separated) or an array of strings. The manager normalizes values, deduplicates and outputs a canonical single-line header.\n\n### Allowed directives\n\nAllowed directive names are deliberately limited (to keep policies sane and safe):\n\n* Fetch directives:\n\n  * Fallbacks: [`default-src`][], [`script-src`][], [`style-src`][], [`child-src`][]\n  * Common: [`connect-src`][], [`font-src`][], [`frame-src`][], [`img-src`][], [`manifest-src`][], [`media-src`][], [`object-src`][], [`worker-src`][]\n* Document directives: [`base-uri`][], [`sandbox`][]\n* Navigation directives: [`form-action`][], [`frame-ancestors`][]\n* Other directives: [`upgrade-insecure-requests`][]\n\nUnknown/unsupported directives are ignored by the manager.\n\n### Example PHP `config/{raw.}config.php` snippets\n\n1. Extension rule — make all `.html` permissive\n\n```php\n\u003c?php\n$CONFIG = array (\n// -\n  // Example: make all .html files more permissive\n  'raw_csp' =\u003e\n  array(\n    'extension' =\u003e\n    array(\n      'html' =\u003e\n      array(\n        'default-src' =\u003e [\"'self'\"],\n        'script-src'  =\u003e [\"'self'\", \"'unsafe-inline'\"],\n        'img-src'     =\u003e [\"'self'\", \"data:\"],\n        'media-src'   =\u003e [\"data:\"],\n        'style-src'   =\u003e [\"'self'\", \"'unsafe-inline'\"],\n        'font-src'    =\u003e [\"data:\"],\n        'frame-src'   =\u003e [\"'none'\"],\n      ),\n    ),\n  ),\n// -\n);\n```\n\n2. Path prefix — relative and absolute\n\n```php\n\u003c?php\n$CONFIG = array (\n// -\n  // Example: an absolute prefix and a relative prefix\n  'raw_csp' =\u003e\n  array(\n    'path_prefix' =\u003e\n    // absolute prefix: matches full URI starting at /apps/raw/...\n    array(\n      '/apps/raw/s/special-html/' =\u003e\n      array(\n        'default-src' =\u003e [\"'self'\"],\n        'script-src'  =\u003e [\"'self'\"],\n      ),\n      // relative prefix: matched against the path AFTER /apps/raw[/s/{token}] or /apps/raw/u/{user}/\n      'html/' =\u003e\n      array(\n        'default-src' =\u003e [\"'self'\"],\n        'script-src'  =\u003e [\"'self'\", \"'unsafe-inline'\"],\n        'img-src'     =\u003e [\"'self'\", \"data:\"],\n      ),\n    ),\n  ),\n// -\n);\n```\n\n3. Path contains — substring match (public + private)\n\n```php\n\u003c?php\n$CONFIG = array (\n// -\n  // Example: apply when '/html/' appears anywhere in the path\n  'raw_csp' =\u003e\n  array(\n    'path_contains' =\u003e\n    array(\n      '/html/' =\u003e\n      array(\n        'default-src' =\u003e [\"'self'\"],\n        'script-src'  =\u003e [\"'self'\"],\n        'img-src'     =\u003e [\"'self'\", \"data:\"],\n        'style-src'   =\u003e [\"'self'\", \"'unsafe-inline'\"],\n      ),\n    ),\n  ),\n// -\n);\n```\n\n4. Token — per share-token policy (optional)\n\n```php\n\u003c?php\n$CONFIG = array (\n// -\n  // Example: apply a policy only for the public share token 'abc123'\n  // This only applies when the public URL contains the token 'abc123'.\n  'raw_csp' =\u003e\n  array(\n    'token' =\u003e\n    array(\n      'abc123' =\u003e\n      array(\n        'default-src' =\u003e [\"'self'\"],\n        'img-src'     =\u003e [\"'self'\", \"data:\"],\n      ),\n    ),\n  ),\n// -\n);\n```\n\n5. Combined example\n\n```php\n\u003c?php\n$CONFIG = array (\n// -\n  'raw_csp' =\u003e\n  array(\n    'path_prefix' =\u003e\n    array(\n      'html/' =\u003e\n      array(\n        'default-src' =\u003e [\"'self'\"],\n        'script-src'  =\u003e [\"'self'\", \"'unsafe-inline'\"],\n      ),\n    ),\n    'path_contains' =\u003e\n    array(\n      '/public/static/' =\u003e\n      array(\n        'default-src' =\u003e [\"'self'\"],\n        'img-src'     =\u003e [\"'self'\", \"data:\"],\n      ),\n    ),\n    'extension' =\u003e\n    array(\n      'json' =\u003e\n      array(\n        'default-src' =\u003e [\"'none'\"],\n        'img-src'     =\u003e [\"data:\"],\n      ),\n    ),\n  ),\n// -\n);\n```\n\n**Important note about `path_contains` matching:**\n\nIf a pattern starts with a slash (for example '`/html/`'), the pattern is used verbatim as a substring search. '`/html/`' only matches when the exact sequence \"`/html/`\" appears in the request path (use this to target a folder segment precisely).\n\nIf a pattern does not start with a slash (for example '`html`'), the pattern is used as a plain substring (no leading slash is added). '`html`' therefore matches anywhere the characters `html` appear — e.g. `/some_html_text/`, `/some-html-data/`, `/htmlfile` and `/html/`.\n\nConsequence: `some-html-data` will match the pattern '`html`' but will not match '`/html/`'.\n\nRecommendation: use '`/folder/`' when you need to match a folder segment exactly; use a plain token like '`foo`' when you intentionally want a broad substring match.\n\nThe manager checks `path_contains` against both the full request path and the path portion after `/apps/raw`, so public and private URLs are covered.\n\n### Testing\n\nAfter you update [`config/{raw.}config.php`](#keep-raw-settings-in-a-dedicated-config-file) (or deploy changes), test with curl:\n\n- **Public share (token) URL**:\n  ```sh\n  curl -I 'https://your-instance.example/apps/raw/s/html/calc.html'\n  ```\n\n- **Private user URL**:\n  ```sh\n  curl -I 'https://your-instance.example/apps/raw/u/alice/Documents/html/calc.html'\n  ```\n\nInspect the `Content-Security-Policy:` response header. If you do not get the expected policy:\n\n* make sure the selector matches your URL form (token vs path vs extension),\n* check `nextcloud.log` for exceptions from `CspManager` or syntax errors in your config array,\n* remember that `token` only matches explicit share tokens.\n\n---\n\n## Optional system-level tuning\n\nThis app supports optional system-level tuning via Nextcloud [`config/{raw.}config.php`](#keep-raw-settings-in-a-dedicated-config-file) system values.\n\n### Cache-Control\n\nPublic responses use a configurable Cache-Control header:\n\n- `raw_cache_public_max_age` (int seconds, default: 300)\n- `raw_cache_public_stale_while_revalidate` (int seconds, default: 30; 0 disables)\n- `raw_cache_public_stale_if_error` (int seconds, default: 86400; 0 disables)\n\nPrivate `raw` URLs (`/apps/raw/u/...`) default to `private, max-age=0`\n\nOptionally enforce no-store for private URLs:\n- `raw_cache_private_no_store` (bool, default: false)\n\n\u003e [!NOTE]\n\u003e `304 Not Modified` responses apply the same Cache-Control policy (public vs. private) as normal `200` responses, so caches behave consistently across conditional requests.\n\n### Webserver offload\n\nFor large files you can optionally let the webserver send the file body (PHP returns early):\n\n- `raw_sendfile_backend` (off|apache|nginx default: off)\n- `raw_sendfile_allow_private` (bool, default: false) *)\n- `raw_sendfile_min_size_mb` (int, default: 0) **)\n- `raw_sendfile_nginx_prefix` (string, default: /_raw_sendfile)\n\n\u003e [!NOTE]\n\u003e *) By default, offload is disabled for private raw URLs (`/apps/raw/u/...`) to keep authenticated endpoints conservative by default. If you want the webserver to offload private raw responses too, enable `raw_sendfile_allow_private`.\n\n\u003e [!NOTE]\n\u003e **) If `raw_sendfile_min_size_mb` is set, offload is only attempted when the file size is known and meets the threshold. If the size cannot be determined (e.g. certain storage backends), offload is skipped.\n\n**Prerequisites** (webserver configuration required):\n\n- **Apache**:\n  - Requires `mod_xsendfile` *) (or an equivalent X-Sendfile implementation) to be installed and enabled.\n  - Enable it and configure the allowed path(s) to include your Nextcloud datadirectory:\n    ```apacheconf\n    XSendFile On\n    # Use the *real* Nextcloud datadirectory from `config/config.php` -\u003e 'datadirectory'\n    XSendFilePath /path/to/nextcloud/data\n    ```\n\u003e [!NOTE]\n\u003e *) Module naming varies by distribution; the key requirement is that your Apache build supports `X-Sendfile` and that the module is enabled for the vhost serving Nextcloud.\n\n- **Nginx**:\n  - Uses `X-Accel-Redirect` (built into nginx, no extra module needed).\n  - Requires an `internal` location that maps the configured prefix (default `/_raw_sendfile`) to Nextcloud’s data directory\n    via `alias` (and must ensure access is restricted to internal redirects only).\n  - Example (must match your `raw_sendfile_nginx_prefix` and Nextcloud datadirectory):\n    ```nginx\n    location /_raw_sendfile/ {\n        internal;\n        alias /path/to/nextcloud/data/;\n    }\n    ```\n\u003e [!TIP]\n\u003e `raw` builds the Nginx `X-Accel-Redirect` target by stripping the resolved (`realpath`) Nextcloud `datadirectory` prefix from the local file path. Ensure your Nginx `alias` uses the same resolved datadirectory path (and includes a trailing `/`). If `datadirectory` is a symlink but Nginx points to the symlink path (or vice versa), the mapping can mismatch and offload will be skipped.\n\nexample offload configuration in [`config/{raw.}config.php`](#keep-raw-settings-in-a-dedicated-config-file) (for apache2):\n```php\n\u003c?php\n$CONFIG = array (\n// -\n  // Private `raw` caching\n  'raw_cache_private_no_store' =\u003e false, // true = Never save in browser\n\n  // apache2\n  'raw_sendfile_backend' =\u003e 'apache',\n/*\n  // nginx\n  'raw_sendfile_backend' =\u003e 'nginx',\n  'raw_sendfile_nginx_prefix' =\u003e '/_raw_sendfile',\n*/\n\n  // allow offload also for /apps/raw/u/... (default false)\n  'raw_sendfile_allow_private' =\u003e false,\n\n  // only offload for files \u003e= X MB (default 0 = no threshold)\n  'raw_sendfile_min_size_mb' =\u003e 5,\n// -\n);\n```\n\nSecurity notes:\n- Offload is only attempted for files that can be resolved to a local filesystem path and are located inside Nextcloud's datadirectory.\n\n#### Offload debug header\n\nTo debug whether offload/streaming was used, send this request header:\n\n- `X-Raw-Offload-Debug: 1`\n\nThe response may include:\n- `X-Raw-Offload: \u003cstatus\u003e; reason=\u003creason\u003e`\n\n\u003e [!NOTE]\n\u003e When offload is active and actually used, the response may include an `X-Raw-Offload` header (e.g. `apache-xsendfile` / `nginx-x-accel`) even without debug enabled.\n\u003e If you send `X-Raw-Offload-Debug: 1`, the app adds `reason=...` and can also emit a “not offloaded” reason, which is useful to validate your config and thresholds.\n\n\n---\n\n## HTTP behavior \u0026 performance\n\n### Cookie-free responses\n\n`raw` intentionally aims to be **cookie-free**. It will best-effort prevent `Set-Cookie` from being emitted for `raw` responses (e.g. by closing any active session, disabling session cookies for the remainder of the request, and removing already queued `Set-Cookie` headers).\n\nThis keeps endpoints “naked” for asset serving and reduces overhead. (Best effort: a reverse proxy could still add cookies afterwards.)\n\n### Caching: ETags and Last-Modified\n\n`raw` supports conditional requests (cache validation) using ETags together with the `If-None-Match` header and also supports `Last-Modified` / `If-Modified-Since` semantics.\n\n\u003e [!NOTE]\n\u003e `raw` prefers “fast” validators (mtime + size) for ETag generation and only falls back to a content hash when needed.\n\n* **ETag / If-None-Match**: The server sends an `ETag` header identifying the current representation of the file. If the client sends `If-None-Match: \"\u003cETag\u003e\"` and the value matches, the server responds with `304 Not Modified` and no response body. The wildcard `If-None-Match: *` is also supported.\n* **Last-Modified / If-Modified-Since**: When the server can read file modification time (mtime) it sets a `Last-Modified` header. The server will honor `If-Modified-Since` when `If-None-Match` is not present. If the client date is equal to or newer than the file mtime, the server responds with `304 Not Modified`.\n* **Unix timestamp convenience**: For convenience, `If-Modified-Since` accepts either an RFC-style HTTP-date (recommended) **or** a plain Unix timestamp (seconds). The server will trim optional quotes. RFC-style dates are the standard and should be preferred for interoperability.\n\nExamples:\n\n- Get file and see headers + body (returns ETag and Last-Modified):\n\n   ```bash\n   curl -i 'https://your.nextcloud/apps/raw/.../file.ext'\n   ```\n\n- Conditional GET using ETag (replace `\u003cETag\u003e` with the ETag value returned by the server, should return 304 if it matches):\n\n   ```bash\n   curl -i -H 'If-None-Match: \"\u003cETag\u003e\"' 'https://your.nextcloud/apps/raw/.../file.ext'\n   ```\n\n- Conditional GET using HTTP-date:\n\n   ```bash\n   curl -i -H 'If-Modified-Since: \"Sun, 25 May 2025 21:40:02 GMT\"' 'https://your.nextcloud/apps/raw/.../file.ext'\n   ```\n\n- Conditional GET using Unix timestamp (convenience):\n\n   ```bash\n   curl -i -H 'If-Modified-Since: \"1748209203\"' 'https://your.nextcloud/apps/raw/.../file.ext'\n   ```\n\n- The wildcard `If-None-Match: *` is also supported (it matches any existing representation) and will return a 304 if the resource exists:\n\n   ```bash\n   curl -i -H 'If-None-Match: *' 'https://your.nextcloud/apps/raw/.../file.ext'\n   ```\n\n### Directory handling (`index.html`)\n\nIf the requested node is a directory, `raw` attempts to serve `index.html` from that directory.\n\n### HEAD requests\n\n`raw` supports `HEAD` requests (headers only, no response body).\n\n### Plain 404 for invalid public shares\n\nFor public endpoints, `raw` returns a minimal `text/plain` **404 Not found** response for disallowed tokens, missing shares, and missing paths. This avoids rendering large HTML error pages and keeps `raw` endpoints lightweight.\n\n---\n\n## Notes \u0026 best practices\n\n* Review and update `allowed_raw_tokens` and `allowed_raw_token_wildcards` periodically to align with your security requirements.\n* Use meaningful share tokens wherever possible for improved manageability.\n* Validate CSP rules and token configurations in a test environment before applying them in production.\n* Prefer `extension` or `path-based` matching for predictable results. `path_contains` with `'/html/'` is usually the safest way to target a folder named `html`.\n* Avoid `script-src 'unsafe-inline'` unless absolutely necessary. When you need inline scripts, prefer nonces or restrictive policies.\n* Keep the `token` selector only if you want per-share (per-token) policies. If you do not need that granularity, it is safe to remove `token` and rely on path/extension/mimetype rules.\n* The manager normalizes directives and removes duplicates; unknown directives are ignored (no crash but check logs).\n### Keep `raw` settings in a dedicated config file:\n  * Nextcloud can load settings from multiple files in `config/.` For `raw`, it’s recommended to keep all `raw` related directives like `allowed_raw_tokens`, `allowed_raw_token_wildcards`, `raw_csp` etc. in a dedicated **`config/raw.config.php`** (any *.config.php in config/ is loaded and overrides config/config.php).\n  * This keeps raw-specific security settings isolated, avoids accidental clutter in config.php, and plays nicely with config management.\n  * **Gotcha:** Nextcloud can consolidate config values into `config/config.php`. Don’t rely on `occ` for `raw` settings if `config/raw.config.php` exists — `raw.config.php` has precedence and will override later.\n\n---\n\n## Installation\n\n1) Clone this repo into your Nextcloud installation’s `/apps` (or `/custom_apps`) folder:\n   ```\n   git clone https://github.com/ernolf/raw\n   ```\n2) Enable the app:\n   ```\n   occ app:enable raw\n   ```\n   or log into Nextcloud as admin, find and enable it in the list of apps.\n\n## Updating\n\n1) Disable the app:\n   ```\n   occ app:disable raw\n   ```\n2) Update the code (e.g. git pull in the app directory, git clone again from scratch or unpack a new archive)\n3) Enable the app again:\n   ```\n   occ app:enable raw\n   ```\n\n---\n\nThis legacy app is no longer recommended for new installations. Use `files_sharing_raw` from the Nextcloud App Store instead. See the [`files_sharing_raw` Readme](https://github.com/ernolf/files_sharing_raw/blob/main/Readme.md) for current installation and migration details.\n\n---\n\n[Content-Security-Policy]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy\n[`child-src`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/child-src\n[`connect-src`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/connect-src\n[`default-src`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/default-src\n[`font-src`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/font-src\n[`frame-src`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/frame-src\n[`img-src`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/img-src\n[`manifest-src`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/manifest-src\n[`media-src`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/media-src\n[`object-src`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/object-src\n[`script-src`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/script-src\n[`style-src`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/style-src\n[`worker-src`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/worker-src\n[`base-uri`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/base-uri\n[`sandbox`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/sandbox\n[`form-action`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/form-action\n[`frame-ancestors`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/frame-ancestors\n[`upgrade-insecure-requests`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/upgrade-insecure-requests\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fernolf%2Fraw","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fernolf%2Fraw","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fernolf%2Fraw/lists"}