126 lines
6.7 KiB
Text
Executable file
126 lines
6.7 KiB
Text
Executable file
OFFLINE PORTAL FILE SHARING
|
|
|
|
INSTALL
|
|
Extract the files directory into the portal web root. No CSS/JS required.
|
|
Add this exact entry to the fixed include list in index.php:
|
|
'files/files.php',
|
|
|
|
Commands (adjust /var/www/portal if necessary):
|
|
sudo chown -R root:root /var/www/portal/files
|
|
sudo find /var/www/portal/files -type d -exec chmod 755 {} \;
|
|
sudo find /var/www/portal/files -type f -exec chmod 644 {} \;
|
|
sudo mkdir -p /var/www/portal/files/data/uploads
|
|
sudo chown -R www-data:www-data /var/www/portal/files/data
|
|
sudo find /var/www/portal/files/data -type d -exec chmod 750 {} \;
|
|
sudo find /var/www/portal/files/data -type f -exec chmod 640 {} \;
|
|
|
|
NGINX — REQUIRED BEFORE USE
|
|
Place these inside the existing server block. This example assumes the portal
|
|
is served at /. For /portal/, change both location paths to start /portal/files/.
|
|
Use the same root and fastcgi_pass socket as the existing index.php handler.
|
|
Never enable PHP execution for the entire files directory.
|
|
|
|
location ^~ /files/data/ {
|
|
deny all;
|
|
}
|
|
location = /files/download.php {
|
|
include fastcgi_params;
|
|
fastcgi_param SCRIPT_FILENAME $document_root/files/download.php;
|
|
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
|
|
}
|
|
|
|
The socket above is an example: find the actual socket with:
|
|
ls /run/php/*-fpm.sock
|
|
|
|
Keep your existing rule returning 404 for every other PHP request except
|
|
/index.php. Exact download location takes precedence. common.php must never
|
|
be served as plaintext or executed directly. Keep directory autoindex off.
|
|
For an aliased portal, use the actual filesystem path in SCRIPT_FILENAME
|
|
instead of $document_root. Do not blindly replace your server configuration.
|
|
|
|
Set in the existing server block (or raise an existing lower limit):
|
|
client_max_body_size 55m;
|
|
|
|
Set in the active PHP-FPM php.ini, not CLI php.ini:
|
|
file_uploads = On
|
|
upload_max_filesize = 50M
|
|
post_max_size = 55M
|
|
max_file_uploads = 1
|
|
max_input_time = 300
|
|
|
|
Reload the actual PHP-FPM service after editing, for example:
|
|
sudo systemctl reload php8.4-fpm
|
|
sudo nginx -t && sudo systemctl reload nginx
|
|
|
|
Large/slow uploads may also require adjusting existing web-server/proxy
|
|
request timeouts. PHP upload temporary storage must have sufficient space.
|
|
|
|
USE
|
|
Visitors choose one file and click Upload; click its name to download.
|
|
Administrator sessions additionally show Delete buttons. Deletion is immediate.
|
|
After a successful upload, reload without resubmitting the POST before another
|
|
upload. No redirect is used because the feature runs after portal output.
|
|
The global upload cooldown often catches immediate refresh resubmissions but
|
|
is not an idempotency guarantee.
|
|
|
|
SETTINGS
|
|
Edit the four FILES_* constants at the top of common.php. If changing the
|
|
per-file size also change the displayed help text/error, PHP limits, and Nginx
|
|
request limit. Private store.json is created on first successful upload.
|
|
Do not hand-edit metadata while the site is accepting changes.
|
|
|
|
DESIGN CHOICES
|
|
- Three small PHP files: portal include, standalone download endpoint, shared
|
|
feature-only storage helpers. No external feature dependencies.
|
|
- All uploaded bytes are private .bin objects keyed by random IDs. Download
|
|
filenames preserve ASCII letters, digits, spaces, dots, parentheses, hyphens,
|
|
and underscores, capped at 140 characters. Other characters become underscores.
|
|
Path components and leading/trailing dots/spaces are removed. Empty names
|
|
become download. Duplicate display filenames do not overwrite files.
|
|
- Any file content is accepted. MIME types and extensions are NOT trusted as
|
|
security boundaries. Uploaded bytes are never included/executed or served
|
|
directly; downloads are attachments with nosniff. These protections depend
|
|
on the required Nginx private-directory deny and PHP execution restrictions.
|
|
Downloads are not scanned, and can be dangerous if opened on a client.
|
|
- Public upload limit is global: one upload every 10 seconds. Not a login or
|
|
per-user abuse-prevention system. Public uploads are intentionally anonymous
|
|
and have no session CSRF token; untrusted clients can consume the public quota.
|
|
- 50 MiB per file, 500 metadata entries, 1 GiB stored file budget, 1 MiB metadata,
|
|
plus a 10 MiB free-space margin. No zero-byte uploads.
|
|
- Admin delete uses the explicitly required csrf field exception to the general
|
|
prefixed-field rule. All feature-specific request names start files_.
|
|
- No Range/resume, streaming upload, thumbnails, preview, download counter,
|
|
confirmations, or JavaScript. Download responses support GET and HEAD.
|
|
- Serialized mutations use a separate flock lock and atomic metadata rename.
|
|
On upload metadata-save failure the new object is removed. A process crash
|
|
can still leave an unlisted object; these count against the storage budget.
|
|
Delete removes bytes before metadata: if metadata saving fails, a stale entry
|
|
remains with a clear save error; repeat Delete once storage is repaired.
|
|
This is not a transactional database and does not promise power-loss durability.
|
|
- No read or write of portal config.json and no calls into admin.php.
|
|
- Over post_max_size PHP can discard POST entirely: the feature cannot identify
|
|
that request. Nginx rejects requests above its own limit; lower PHP limits
|
|
may cause a plain page response with no feature error. Align the supplied limits.
|
|
|
|
MANUAL TEST CHECKLIST (NOT EXECUTED HERE)
|
|
[ ] php -l files/files.php; php -l files/common.php; php -l files/download.php
|
|
[ ] Upload text/image/archive; download and compare hashes and displayed names.
|
|
[ ] Upload duplicate names; confirm both remain independently downloadable.
|
|
[ ] Upload awkward paths, quotes, Unicode names; inspect sanitized names.
|
|
[ ] Submit array-shaped/missing/invalid fields and IDs; expect clear errors.
|
|
[ ] Empty file, >50 MiB, full count/quota, rapid upload; reject without success.
|
|
[ ] Missing or unwritable data/uploads; show error; restore permissions afterward.
|
|
[ ] Corrupt store.json temporarily; display error and do not replace its contents.
|
|
[ ] Wrong/missing admin csrf, or public delete POST; must not delete anything.
|
|
[ ] Anonymous direct download works; unknown ID gives 404; POST gives 405.
|
|
[ ] HEAD gives attachment headers with no body.
|
|
[ ] /files/data/store.json and guessed uploads return 403/404.
|
|
[ ] /files/files.php and /files/common.php return 404, never source text.
|
|
[ ] Remove admin.php: no Delete buttons, uploads/downloads still work.
|
|
[ ] Remove files directory: portal include is skipped without warnings.
|
|
[ ] Browser network panel: only local requests.
|
|
[ ] Test mobile layout, dark/light themes, and accent button.
|
|
|
|
TEST STATUS
|
|
Generated for review; PHP syntax and runtime behavior, Nginx, concurrent requests,
|
|
permissions, and browser behavior have NOT been executed/tested here.
|