mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-19 10:05:11 +00:00
d6fd5a917d
Uploads live outside the web root, so PHP authorizes every download and then hands the file to the web server with a header naming it. Four routes decided that for themselves and all four hard-coded nginx's spelling. On Apache or LiteSpeed nothing acts on the header, so the empty body PHP sent goes to the visitor: files upload fine, thumbnails are broken images, and downloads arrive as 0 bytes, with every other page working. Reported as #1765 from an Apache 2.4 install, and before that as #1266, #1215, #870 and #1271. It is also a regression from v1, which had a download_method setting -- php, apache_xsendfile, litespeed, nginx_xaccel -- defaulting to php. v1 therefore worked on any server out of the box and v2 did not, and a v1 Apache user migrating lost every download with nothing to tell them why. So the four sites now go through one FileDelivery, and it picks: auto (default) nginx when SERVER_SOFTWARE says nginx, else php nginx X-Accel-Redirect, a URL path via the internal location xsendfile X-Sendfile, an absolute path (Apache mod_xsendfile, LiteSpeed) php BinaryFileResponse Defaulting to auto rather than nginx is the point of the change: a default that assumes nginx leaves an Apache install exactly as broken as it is today until somebody reads INSTALL.md. Slow beats empty. Auto never picks xsendfile, even where the module is loaded. mod_xsendfile also needs XSendFilePath to allow the storage directory, which cannot be seen from here, and choosing it on the strength of the module being present would trade a silent failure an administrator can diagnose from the dashboard for one nobody can. BinaryFileResponse rather than a readfile loop because it answers Range requests. nginx does that itself on the fast path, so hand-rolling it would have broken seeking through a video on exactly the installations this fallback exists for. Verified end to end: 206 with the right Content-Range through the live stack. Two guards. Every method checks the path cannot climb out of the storage area -- nginx resolves `..` in the URL it is handed as happily as PHP would -- and the two methods that hand over a filesystem path resolve it and prove it lands inside the root. Callers pass paths from rows they just authorized, so this is a backstop; it is here because the cost of being wrong once is handing over any file the web server can read. The dashboard's System panel names the method, with a warning icon and a dialog when PHP is doing the sending: what is happening, what it costs (one worker held for the whole of each download, so a few large simultaneous ones can occupy every worker while the processor sits idle), why it is set that way, and the three ways out. Written to be accurate rather than reassuring -- nothing is broken, it does not scale -- and the notice stays even when php was chosen deliberately, because the trade-off is the same either way. /system/settings/downloads repeats it, which is where somebody coming from v1 goes looking for the dropdown. An environment variable rather than a stored setting: it describes the server this installation runs on, not a preference, and a value in the database travels to a different server in a restore and is wrong there. Read only in config/projectsend.php, so config:cache cannot blank it. The suite pins itself to nginx. Left at auto it would detect no server at all, fall back to php, and quietly retire the coverage of the mechanism most installations actually use.
193 lines
8.0 KiB
PHP
193 lines
8.0 KiB
PHP
<?php
|
|
|
|
use App\Modules\Platform\Capabilities\Edition;
|
|
|
|
return [
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Edition
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Which edition this installation runs as. Edition is configuration, not a
|
|
| code branch: every behavioural difference between editions must flow
|
|
| through the capability registry, never through ad-hoc edition checks.
|
|
|
|
|
| Supported: "community", "cloud"
|
|
|
|
|
*/
|
|
|
|
'edition' => Edition::from((string) env('PROJECTSEND_EDITION', 'community')),
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Uploads
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Where a chunked upload's parts wait while the transfer is running,
|
|
| before they are assembled onto the storage disk. Leave this unset:
|
|
| it exists so the test suite can give each parallel worker its own
|
|
| directory, since parts are real files on a real path rather than a
|
|
| faked disk, and session ids restart at 1 in every worker's database.
|
|
|
|
|
*/
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Platform seats
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| How many staff accounts and how many clients this installation may
|
|
| hold. Unset means unlimited, which is every self-hosted install: this
|
|
| exists for a managed one, where the operator sold a number and the
|
|
| application is the only process that can actually count against it.
|
|
|
|
|
| An operator stating the installation's own limit is not the same as
|
|
| the application inventing a plan tier — the distinction config/api.php
|
|
| draws when it declines to key a rate limit off billing. Nothing here
|
|
| knows what a plan is; it accepts a number and refuses to exceed it.
|
|
|
|
|
*/
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Capabilities this installation has been told it may not use
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Comma-separated capability keys, subtracted from what the edition
|
|
| grants. Only ever subtracted: nothing here can switch a capability on,
|
|
| because a variable that could would put the hosted edition's screens
|
|
| one line of .env away on every self-hosted install.
|
|
|
|
|
| For a managed installation whose plan does not include something its
|
|
| edition otherwise has -- branding.customize on a free plan is the case
|
|
| this was built for. Unknown keys are ignored rather than fatal: the
|
|
| variable outlives both the plan that wrote it and the release that
|
|
| named the key, and an instance refusing to boot over a stale one would
|
|
| be an outage on upgrade day.
|
|
|
|
|
*/
|
|
|
|
'capabilities_disabled' => env('PROJECTSEND_CAPABILITIES_DISABLED'),
|
|
|
|
'platform' => [
|
|
'max_staff_users' => env('PROJECTSEND_PLATFORM_MAX_STAFF_USERS'),
|
|
'max_clients' => env('PROJECTSEND_PLATFORM_MAX_CLIENTS'),
|
|
|
|
// Seeded into Setting::TwoFactorEnforcement on first boot and never
|
|
// afterwards — see SeedSettingsCommand. Here rather than read from
|
|
// env() at the point of use, because config:cache stops .env being
|
|
// read at all and that is how TRUSTED_PROXIES came to silently do
|
|
// nothing.
|
|
'two_factor_enforcement' => env('PROJECTSEND_TWO_FACTOR_ENFORCEMENT'),
|
|
],
|
|
|
|
'uploads' => [
|
|
'parts_path' => env('UPLOAD_PARTS_PATH'),
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| How downloads leave the server
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Uploads live outside the web root, so PHP authorizes every download
|
|
| before any byte moves. What differs is what happens next: PHP can
|
|
| stream the file itself, or hand the web server a header naming the
|
|
| file and let it do the work. The header is faster and each server
|
|
| spells it differently — a server that does not recognise the one it
|
|
| is sent serves the empty body instead, which is a 0-byte download.
|
|
|
|
|
| 'auto' (the default) uses nginx's X-Accel-Redirect when the server
|
|
| says it is nginx, and PHP streaming otherwise. 'nginx', 'xsendfile'
|
|
| (Apache with mod_xsendfile, or LiteSpeed) and 'php' state it
|
|
| outright. Read here rather than through env() elsewhere, so that
|
|
| `php artisan config:cache` does not silently blank it.
|
|
|
|
|
*/
|
|
|
|
'file_delivery' => env('PROJECTSEND_FILE_DELIVERY', 'auto'),
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Chunked upload part size (MB)
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Each resumable-upload part travels as one request of this size;
|
|
| web-server/PHP body limits only need to cover a single part.
|
|
|
|
|
*/
|
|
|
|
'upload_part_size_mb' => 20,
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| CAPTCHA
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Two things live here rather than in the settings store, for two
|
|
| different reasons.
|
|
|
|
|
| "disabled" is an escape hatch: a wrong secret key cannot lock anybody
|
|
| out (see CaptchaResult), but an operator who has managed it some other
|
|
| way needs a fix that touches no database and needs no working login.
|
|
|
|
|
| The managed keys are the platform's own, applied to every tenant on
|
|
| cloud and absent everywhere else. In config rather than the tenant
|
|
| database so a database dump never carries our credential, and so
|
|
| rotating it is one fleet-wide change instead of a migration. They do
|
|
| nothing without Capability::CaptchaManagedKeys.
|
|
|
|
|
*/
|
|
|
|
'captcha' => [
|
|
'disabled' => (bool) env('PROJECTSEND_CAPTCHA_DISABLED', false),
|
|
|
|
'managed' => [
|
|
'provider' => env('PROJECTSEND_CAPTCHA_MANAGED_PROVIDER'),
|
|
'site_key' => env('PROJECTSEND_CAPTCHA_MANAGED_SITE_KEY'),
|
|
'secret_key' => env('PROJECTSEND_CAPTCHA_MANAGED_SECRET_KEY'),
|
|
'score_threshold' => (float) env('PROJECTSEND_CAPTCHA_MANAGED_SCORE_THRESHOLD', 0.5),
|
|
],
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Release identity
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Per-release facts that ship with the code. Not settings: they never
|
|
| vary per install or tenant.
|
|
|
|
|
*/
|
|
|
|
'version' => '2.2.1',
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Official links
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
// Read through App\Modules\Platform\OfficialLinks rather than
|
|
// directly: which of the two front doors "website" means, and whether
|
|
// the donation link is offered at all, both depend on the edition.
|
|
'links' => [
|
|
'website' => 'https://www.projectsend.org/',
|
|
// The hosted service's own front door. A managed installation
|
|
// links here instead — including from the "Powered by" line on
|
|
// client-facing pages and outgoing email.
|
|
'website_cloud' => 'https://www.projectsend.cloud/',
|
|
// Where this code lives, and the same repository
|
|
// CheckForUpdatesCommand asks for the latest release. v1 remains
|
|
// available at github.com/projectsend/legacy.
|
|
'source' => 'https://github.com/projectsend/projectsend',
|
|
'open_collective' => 'https://opencollective.com/projectsend',
|
|
// Kept identical to the invitation update.sh prints when an update
|
|
// finishes — the two are the same offer, made in the terminal and
|
|
// then again on the screen the administrator lands on.
|
|
'discord' => 'https://discord.gg/VT9n6cyvXT',
|
|
],
|
|
|
|
];
|