LAN_Portal/files/README.txt
2026-10-16 16:22:59 -04:00

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.