Add WebDAV support with user folderOnly restrictions
This commit is contained in:
27
CHANGELOG.md
27
CHANGELOG.md
@@ -1,6 +1,31 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## Changes 4/19/2025
|
## Changes 4/21/2025 v1.2.2
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **`src/webdav/CurrentUser.php`**
|
||||||
|
– Introduces a `CurrentUser` singleton to capture and expose the authenticated WebDAV username for use in other components.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **`src/webdav/FileRiseDirectory.php`**
|
||||||
|
– Constructor now takes three parameters (`$path`, `$user`, `$folderOnly`).
|
||||||
|
– Implements “folder‑only” mode: non‑admin users only see their own subfolder under the uploads root.
|
||||||
|
– Passes the current user through to `FileRiseFile` so that uploads/deletions are attributed correctly.
|
||||||
|
|
||||||
|
- **`src/webdav/FileRiseFile.php`**
|
||||||
|
– Uses `CurrentUser::get()` when writing metadata to populate the `uploader` field.
|
||||||
|
– Metadata helper (`updateMetadata`) now records both upload and modified timestamps along with the actual username.
|
||||||
|
|
||||||
|
- **`public/webdav.php`**
|
||||||
|
– Adds a header‐shim at the top to pull Basic‑Auth credentials out of `Authorization` for all HTTP methods.
|
||||||
|
– In the auth callback, sets the `CurrentUser` for the rest of the request.
|
||||||
|
- Admins & unrestricted users see the full `/uploads` directory.
|
||||||
|
- “Folder‑only” users are scoped to `/uploads/{username}`.
|
||||||
|
– Configures SabreDAV with the new `FileRiseDirectory($rootPath, $user, $folderOnly)` signature and sets the base URI to `/webdav.php/`.
|
||||||
|
|
||||||
|
## Changes 4/19/2025 v1.2.1
|
||||||
|
|
||||||
- **Extended “Remember Me” cookie behavior**
|
- **Extended “Remember Me” cookie behavior**
|
||||||
In `AuthController::finalizeLogin()`, after setting `remember_me_token` re‑issued the PHP session cookie with the same 30‑day expiry and called `session_regenerate_id(true)`.
|
In `AuthController::finalizeLogin()`, after setting `remember_me_token` re‑issued the PHP session cookie with the same 30‑day expiry and called `session_regenerate_id(true)`.
|
||||||
|
|||||||
14
Dockerfile
14
Dockerfile
@@ -50,8 +50,18 @@ ARG PGID=100
|
|||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get upgrade -y && \
|
apt-get upgrade -y && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
apache2 php php-json php-curl php-zip php-mbstring php-gd \
|
apache2 \
|
||||||
ca-certificates curl git openssl && \
|
php \
|
||||||
|
php-json \
|
||||||
|
php-curl \
|
||||||
|
php-zip \
|
||||||
|
php-mbstring \
|
||||||
|
php-gd \
|
||||||
|
php-xml \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
git \
|
||||||
|
openssl && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|||||||
50
README.md
50
README.md
@@ -20,6 +20,8 @@ Upload, organize, and share files through a sleek web interface. **FileRise** is
|
|||||||
|
|
||||||
- 🗃️ **Folder Sharing & File Sharing:** Easily share entire folders via secure, expiring public links. Folder shares can be password-protected, and shared folders support file uploads from outside users with a separate, secure upload mechanism. Folder listings are paginated (10 items per page) with navigation controls, and file sizes are displayed in MB for clarity. Share files with others using one-time or expiring public links (with password protection if desired) – convenient for sending individual files without exposing the whole app.
|
- 🗃️ **Folder Sharing & File Sharing:** Easily share entire folders via secure, expiring public links. Folder shares can be password-protected, and shared folders support file uploads from outside users with a separate, secure upload mechanism. Folder listings are paginated (10 items per page) with navigation controls, and file sizes are displayed in MB for clarity. Share files with others using one-time or expiring public links (with password protection if desired) – convenient for sending individual files without exposing the whole app.
|
||||||
|
|
||||||
|
- 🔌 **WebDAV Support:** Mount FileRise as a network drive via WebDAV. Standard file operations (upload/download/rename/delete) work from any WebDAV‑compatible client. Per‑user scoping ensures folderOnly users see only their own folder, while others have full access.
|
||||||
|
|
||||||
- 📚 **API Documentation:** Fully auto‑generated OpenAPI spec (`openapi.json`) and interactive HTML docs (`api.html`) powered by Redoc.
|
- 📚 **API Documentation:** Fully auto‑generated OpenAPI spec (`openapi.json`) and interactive HTML docs (`api.html`) powered by Redoc.
|
||||||
|
|
||||||
- 📝 **Built-in Editor & Preview:** View images, videos, audio, and PDFs inline with a preview modal – no need to download just to see them. Edit text/code files right in your browser with a CodeMirror-based editor featuring syntax highlighting and line numbers. Great for config files or notes – tweak and save changes without leaving FileRise.
|
- 📝 **Built-in Editor & Preview:** View images, videos, audio, and PDFs inline with a preview modal – no need to download just to see them. Edit text/code files right in your browser with a CodeMirror-based editor featuring syntax highlighting and line numbers. Great for config files or notes – tweak and save changes without leaving FileRise.
|
||||||
@@ -34,7 +36,7 @@ Upload, organize, and share files through a sleek web interface. **FileRise** is
|
|||||||
|
|
||||||
- 🗑️ **Trash & File Recovery:** Mistakenly deleted files? No worries – deleted items go to the Trash instead of immediate removal. Admins can restore files from Trash or empty it to free space. FileRise auto-purges old trash entries (default 3 days) to keep your storage tidy.
|
- 🗑️ **Trash & File Recovery:** Mistakenly deleted files? No worries – deleted items go to the Trash instead of immediate removal. Admins can restore files from Trash or empty it to free space. FileRise auto-purges old trash entries (default 3 days) to keep your storage tidy.
|
||||||
|
|
||||||
- ⚙️ **Lightweight & Self-Contained:** FileRise runs on PHP 8.1+ with no external database required – data is stored in files (users, metadata) for simplicity. It’s a single-folder web app you can drop into any Apache/PHP server or run as a container. Docker & Unraid ready: use our pre-built image for a hassle-free setup. Memory and CPU footprint is minimal, yet the app scales to thousands of files with pagination and sorting features.
|
- ⚙️ **Lightweight & Self‑Contained:** FileRise runs on PHP 8.1+ with no external database required – data is stored in files (users, metadata) for simplicity. It’s a single‑folder web app you can drop into any Apache/PHP server or run as a container. Docker & Unraid ready: use our pre‑built image for a hassle‑free setup. Memory and CPU footprint is minimal, yet the app scales to thousands of files with pagination and sorting features.
|
||||||
|
|
||||||
(For a full list of features and detailed changelogs, see the [Wiki](https://github.com/error311/FileRise/wiki), [changelog](https://github.com/error311/FileRise/blob/master/CHANGELOG.md) or the [releases](https://github.com/error311/FileRise/releases) pages.)
|
(For a full list of features and detailed changelogs, see the [Wiki](https://github.com/error311/FileRise/wiki), [changelog](https://github.com/error311/FileRise/blob/master/CHANGELOG.md) or the [releases](https://github.com/error311/FileRise/releases) pages.)
|
||||||
|
|
||||||
@@ -145,6 +147,51 @@ Now navigate to the FileRise URL in your browser. On first load, you’ll be pro
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Quick‑start: Mount via WebDAV
|
||||||
|
|
||||||
|
Once FileRise is running, you can mount it like any other network drive:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Linux (GVFS/GIO)
|
||||||
|
gio mount dav://demo@your-host/webdav.php/
|
||||||
|
|
||||||
|
# macOS (Finder → Go → Connect to Server…)
|
||||||
|
dav://demo@your-host/webdav.php/
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows (File Explorer)
|
||||||
|
|
||||||
|
- Open **File Explorer** → Right-click **This PC** → **Map network drive…**
|
||||||
|
- Choose a drive letter (e.g., `Z:`).
|
||||||
|
- In **Folder**, enter:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://your-host/webdav.php/
|
||||||
|
```
|
||||||
|
|
||||||
|
- Check **Connect using different credentials**, and enter your FileRise username and password.
|
||||||
|
- Click **Finish**. The drive will now appear under **This PC**.
|
||||||
|
|
||||||
|
> **Important:**
|
||||||
|
> Windows requires HTTPS (SSL) for WebDAV connections by default.
|
||||||
|
> If your server uses plain HTTP, you must adjust a registry setting:
|
||||||
|
>
|
||||||
|
> 1. Open **Registry Editor** (`regedit.exe`).
|
||||||
|
> 2. Navigate to:
|
||||||
|
>
|
||||||
|
> ```text
|
||||||
|
> HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> 3. Find or create a `DWORD` value named **BasicAuthLevel**.
|
||||||
|
> 4. Set its value to `2`.
|
||||||
|
> 5. Restart the **WebClient** service or reboot your computer.
|
||||||
|
|
||||||
|
📖 For a full guide (including SSL setup, HTTP workaround, and troubleshooting), see the [WebDAV Usage Wiki](https://github.com/error311/FileRise/wiki/WebDAV).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## FAQ / Troubleshooting
|
## FAQ / Troubleshooting
|
||||||
|
|
||||||
- **“Upload failed” or large files not uploading:** Make sure `TOTAL_UPLOAD_SIZE` in config and PHP’s `post_max_size` / `upload_max_filesize` are all set high enough. For extremely large files, you might also need to increase max_execution_time in PHP or rely on the resumable upload feature in smaller chunks.
|
- **“Upload failed” or large files not uploading:** Make sure `TOTAL_UPLOAD_SIZE` in config and PHP’s `post_max_size` / `upload_max_filesize` are all set high enough. For extremely large files, you might also need to increase max_execution_time in PHP or rely on the resumable upload feature in smaller chunks.
|
||||||
@@ -185,6 +232,7 @@ Areas where you can help: translations, bug fixes, UI improvements, or building
|
|||||||
- **[phpseclib/phpseclib](https://github.com/phpseclib/phpseclib)** (v~3.0.7)
|
- **[phpseclib/phpseclib](https://github.com/phpseclib/phpseclib)** (v~3.0.7)
|
||||||
- **[robthree/twofactorauth](https://github.com/RobThree/TwoFactorAuth)** (v^3.0)
|
- **[robthree/twofactorauth](https://github.com/RobThree/TwoFactorAuth)** (v^3.0)
|
||||||
- **[endroid/qr-code](https://github.com/endroid/qr-code)** (v^5.0)
|
- **[endroid/qr-code](https://github.com/endroid/qr-code)** (v^5.0)
|
||||||
|
- **[sabre/dav"](https://github.com/sabre-io/dav)** (^4.4)
|
||||||
|
|
||||||
### Client-Side Libraries
|
### Client-Side Libraries
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"jumbojett/openid-connect-php": "^1.0.0",
|
"jumbojett/openid-connect-php": "^1.0.0",
|
||||||
"phpseclib/phpseclib": "~3.0.7",
|
"phpseclib/phpseclib": "~3.0.7",
|
||||||
"robthree/twofactorauth": "^3.0",
|
"robthree/twofactorauth": "^3.0",
|
||||||
"endroid/qr-code": "^5.0"
|
"endroid/qr-code": "^5.0",
|
||||||
|
"sabre/dav": "^4.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
497
composer.lock
generated
497
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "6b70aec0c1830ebb2b8f9bb625b04a22",
|
"content-hash": "3a9b8d9fcfdaaa865ba03eab392e88fd",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "bacon/bacon-qr-code",
|
"name": "bacon/bacon-qr-code",
|
||||||
@@ -451,6 +451,56 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-12-14T21:12:59+00:00"
|
"time": "2024-12-14T21:12:59+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/log",
|
||||||
|
"version": "3.0.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/log.git",
|
||||||
|
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
|
||||||
|
"reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.0.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "3.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Log\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "https://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for logging libraries",
|
||||||
|
"homepage": "https://github.com/php-fig/log",
|
||||||
|
"keywords": [
|
||||||
|
"log",
|
||||||
|
"psr",
|
||||||
|
"psr-3"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/php-fig/log/tree/3.0.2"
|
||||||
|
},
|
||||||
|
"time": "2024-09-11T13:17:53+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "robthree/twofactorauth",
|
"name": "robthree/twofactorauth",
|
||||||
"version": "v3.0.2",
|
"version": "v3.0.2",
|
||||||
@@ -531,6 +581,451 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-10-24T15:14:25+00:00"
|
"time": "2024-10-24T15:14:25+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sabre/dav",
|
||||||
|
"version": "4.7.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sabre-io/dav.git",
|
||||||
|
"reference": "074373bcd689a30bcf5aaa6bbb20a3395964ce7a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sabre-io/dav/zipball/074373bcd689a30bcf5aaa6bbb20a3395964ce7a",
|
||||||
|
"reference": "074373bcd689a30bcf5aaa6bbb20a3395964ce7a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-ctype": "*",
|
||||||
|
"ext-date": "*",
|
||||||
|
"ext-dom": "*",
|
||||||
|
"ext-iconv": "*",
|
||||||
|
"ext-json": "*",
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"ext-pcre": "*",
|
||||||
|
"ext-simplexml": "*",
|
||||||
|
"ext-spl": "*",
|
||||||
|
"lib-libxml": ">=2.7.0",
|
||||||
|
"php": "^7.1.0 || ^8.0",
|
||||||
|
"psr/log": "^1.0 || ^2.0 || ^3.0",
|
||||||
|
"sabre/event": "^5.0",
|
||||||
|
"sabre/http": "^5.0.5",
|
||||||
|
"sabre/uri": "^2.0",
|
||||||
|
"sabre/vobject": "^4.2.1",
|
||||||
|
"sabre/xml": "^2.0.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "^2.19",
|
||||||
|
"monolog/monolog": "^1.27 || ^2.0",
|
||||||
|
"phpstan/phpstan": "^0.12 || ^1.0",
|
||||||
|
"phpstan/phpstan-phpunit": "^1.0",
|
||||||
|
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-curl": "*",
|
||||||
|
"ext-imap": "*",
|
||||||
|
"ext-pdo": "*"
|
||||||
|
},
|
||||||
|
"bin": [
|
||||||
|
"bin/sabredav",
|
||||||
|
"bin/naturalselection"
|
||||||
|
],
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Sabre\\": "lib/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Evert Pot",
|
||||||
|
"email": "me@evertpot.com",
|
||||||
|
"homepage": "http://evertpot.com/",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "WebDAV Framework for PHP",
|
||||||
|
"homepage": "http://sabre.io/",
|
||||||
|
"keywords": [
|
||||||
|
"CalDAV",
|
||||||
|
"CardDAV",
|
||||||
|
"WebDAV",
|
||||||
|
"framework",
|
||||||
|
"iCalendar"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"forum": "https://groups.google.com/group/sabredav-discuss",
|
||||||
|
"issues": "https://github.com/sabre-io/dav/issues",
|
||||||
|
"source": "https://github.com/fruux/sabre-dav"
|
||||||
|
},
|
||||||
|
"time": "2024-10-29T11:46:02+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sabre/event",
|
||||||
|
"version": "5.1.7",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sabre-io/event.git",
|
||||||
|
"reference": "86d57e305c272898ba3c28e9bd3d65d5464587c2"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sabre-io/event/zipball/86d57e305c272898ba3c28e9bd3d65d5464587c2",
|
||||||
|
"reference": "86d57e305c272898ba3c28e9bd3d65d5464587c2",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.1 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "~2.17.1||^3.63",
|
||||||
|
"phpstan/phpstan": "^0.12",
|
||||||
|
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"lib/coroutine.php",
|
||||||
|
"lib/Loop/functions.php",
|
||||||
|
"lib/Promise/functions.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Sabre\\Event\\": "lib/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Evert Pot",
|
||||||
|
"email": "me@evertpot.com",
|
||||||
|
"homepage": "http://evertpot.com/",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "sabre/event is a library for lightweight event-based programming",
|
||||||
|
"homepage": "http://sabre.io/event/",
|
||||||
|
"keywords": [
|
||||||
|
"EventEmitter",
|
||||||
|
"async",
|
||||||
|
"coroutine",
|
||||||
|
"eventloop",
|
||||||
|
"events",
|
||||||
|
"hooks",
|
||||||
|
"plugin",
|
||||||
|
"promise",
|
||||||
|
"reactor",
|
||||||
|
"signal"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"forum": "https://groups.google.com/group/sabredav-discuss",
|
||||||
|
"issues": "https://github.com/sabre-io/event/issues",
|
||||||
|
"source": "https://github.com/fruux/sabre-event"
|
||||||
|
},
|
||||||
|
"time": "2024-08-27T11:23:05+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sabre/http",
|
||||||
|
"version": "5.1.12",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sabre-io/http.git",
|
||||||
|
"reference": "dedff73f3995578bc942fa4c8484190cac14f139"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sabre-io/http/zipball/dedff73f3995578bc942fa4c8484190cac14f139",
|
||||||
|
"reference": "dedff73f3995578bc942fa4c8484190cac14f139",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-ctype": "*",
|
||||||
|
"ext-curl": "*",
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"php": "^7.1 || ^8.0",
|
||||||
|
"sabre/event": ">=4.0 <6.0",
|
||||||
|
"sabre/uri": "^2.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "~2.17.1||^3.63",
|
||||||
|
"phpstan/phpstan": "^0.12",
|
||||||
|
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-curl": " to make http requests with the Client class"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"lib/functions.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Sabre\\HTTP\\": "lib/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Evert Pot",
|
||||||
|
"email": "me@evertpot.com",
|
||||||
|
"homepage": "http://evertpot.com/",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The sabre/http library provides utilities for dealing with http requests and responses. ",
|
||||||
|
"homepage": "https://github.com/fruux/sabre-http",
|
||||||
|
"keywords": [
|
||||||
|
"http"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"forum": "https://groups.google.com/group/sabredav-discuss",
|
||||||
|
"issues": "https://github.com/sabre-io/http/issues",
|
||||||
|
"source": "https://github.com/fruux/sabre-http"
|
||||||
|
},
|
||||||
|
"time": "2024-08-27T16:07:41+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sabre/uri",
|
||||||
|
"version": "2.3.4",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sabre-io/uri.git",
|
||||||
|
"reference": "b76524c22de90d80ca73143680a8e77b1266c291"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sabre-io/uri/zipball/b76524c22de90d80ca73143680a8e77b1266c291",
|
||||||
|
"reference": "b76524c22de90d80ca73143680a8e77b1266c291",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.4 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.63",
|
||||||
|
"phpstan/extension-installer": "^1.4",
|
||||||
|
"phpstan/phpstan": "^1.12",
|
||||||
|
"phpstan/phpstan-phpunit": "^1.4",
|
||||||
|
"phpstan/phpstan-strict-rules": "^1.6",
|
||||||
|
"phpunit/phpunit": "^9.6"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"lib/functions.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Sabre\\Uri\\": "lib/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Evert Pot",
|
||||||
|
"email": "me@evertpot.com",
|
||||||
|
"homepage": "http://evertpot.com/",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Functions for making sense out of URIs.",
|
||||||
|
"homepage": "http://sabre.io/uri/",
|
||||||
|
"keywords": [
|
||||||
|
"rfc3986",
|
||||||
|
"uri",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"forum": "https://groups.google.com/group/sabredav-discuss",
|
||||||
|
"issues": "https://github.com/sabre-io/uri/issues",
|
||||||
|
"source": "https://github.com/fruux/sabre-uri"
|
||||||
|
},
|
||||||
|
"time": "2024-08-27T12:18:16+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sabre/vobject",
|
||||||
|
"version": "4.5.7",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sabre-io/vobject.git",
|
||||||
|
"reference": "ff22611a53782e90c97be0d0bc4a5f98a5c0a12c"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sabre-io/vobject/zipball/ff22611a53782e90c97be0d0bc4a5f98a5c0a12c",
|
||||||
|
"reference": "ff22611a53782e90c97be0d0bc4a5f98a5c0a12c",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"php": "^7.1 || ^8.0",
|
||||||
|
"sabre/xml": "^2.1 || ^3.0 || ^4.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "~2.17.1",
|
||||||
|
"phpstan/phpstan": "^0.12 || ^1.12 || ^2.0",
|
||||||
|
"phpunit/php-invoker": "^2.0 || ^3.1",
|
||||||
|
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"hoa/bench": "If you would like to run the benchmark scripts"
|
||||||
|
},
|
||||||
|
"bin": [
|
||||||
|
"bin/vobject",
|
||||||
|
"bin/generate_vcards"
|
||||||
|
],
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "4.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Sabre\\VObject\\": "lib/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Evert Pot",
|
||||||
|
"email": "me@evertpot.com",
|
||||||
|
"homepage": "http://evertpot.com/",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dominik Tobschall",
|
||||||
|
"email": "dominik@fruux.com",
|
||||||
|
"homepage": "http://tobschall.de/",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ivan Enderlin",
|
||||||
|
"email": "ivan.enderlin@hoa-project.net",
|
||||||
|
"homepage": "http://mnt.io/",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects",
|
||||||
|
"homepage": "http://sabre.io/vobject/",
|
||||||
|
"keywords": [
|
||||||
|
"availability",
|
||||||
|
"freebusy",
|
||||||
|
"iCalendar",
|
||||||
|
"ical",
|
||||||
|
"ics",
|
||||||
|
"jCal",
|
||||||
|
"jCard",
|
||||||
|
"recurrence",
|
||||||
|
"rfc2425",
|
||||||
|
"rfc2426",
|
||||||
|
"rfc2739",
|
||||||
|
"rfc4770",
|
||||||
|
"rfc5545",
|
||||||
|
"rfc5546",
|
||||||
|
"rfc6321",
|
||||||
|
"rfc6350",
|
||||||
|
"rfc6351",
|
||||||
|
"rfc6474",
|
||||||
|
"rfc6638",
|
||||||
|
"rfc6715",
|
||||||
|
"rfc6868",
|
||||||
|
"vCalendar",
|
||||||
|
"vCard",
|
||||||
|
"vcf",
|
||||||
|
"xCal",
|
||||||
|
"xCard"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"forum": "https://groups.google.com/group/sabredav-discuss",
|
||||||
|
"issues": "https://github.com/sabre-io/vobject/issues",
|
||||||
|
"source": "https://github.com/fruux/sabre-vobject"
|
||||||
|
},
|
||||||
|
"time": "2025-04-17T09:22:48+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sabre/xml",
|
||||||
|
"version": "2.2.11",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/sabre-io/xml.git",
|
||||||
|
"reference": "01a7927842abf3e10df3d9c2d9b0cc9d813a3fcc"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/sabre-io/xml/zipball/01a7927842abf3e10df3d9c2d9b0cc9d813a3fcc",
|
||||||
|
"reference": "01a7927842abf3e10df3d9c2d9b0cc9d813a3fcc",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-dom": "*",
|
||||||
|
"ext-xmlreader": "*",
|
||||||
|
"ext-xmlwriter": "*",
|
||||||
|
"lib-libxml": ">=2.6.20",
|
||||||
|
"php": "^7.1 || ^8.0",
|
||||||
|
"sabre/uri": ">=1.0,<3.0.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "~2.17.1||3.63.2",
|
||||||
|
"phpstan/phpstan": "^0.12",
|
||||||
|
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"lib/Deserializer/functions.php",
|
||||||
|
"lib/Serializer/functions.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Sabre\\Xml\\": "lib/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Evert Pot",
|
||||||
|
"email": "me@evertpot.com",
|
||||||
|
"homepage": "http://evertpot.com/",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Markus Staab",
|
||||||
|
"email": "markus.staab@redaxo.de",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "sabre/xml is an XML library that you may not hate.",
|
||||||
|
"homepage": "https://sabre.io/xml/",
|
||||||
|
"keywords": [
|
||||||
|
"XMLReader",
|
||||||
|
"XMLWriter",
|
||||||
|
"dom",
|
||||||
|
"xml"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"forum": "https://groups.google.com/group/sabredav-discuss",
|
||||||
|
"issues": "https://github.com/sabre-io/xml/issues",
|
||||||
|
"source": "https://github.com/fruux/sabre-xml"
|
||||||
|
},
|
||||||
|
"time": "2024-09-06T07:37:46+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [],
|
"packages-dev": [],
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { sendRequest } from './networkUtils.js';
|
|||||||
import { t, applyTranslations, setLocale } from './i18n.js';
|
import { t, applyTranslations, setLocale } from './i18n.js';
|
||||||
import { loadAdminConfigFunc } from './auth.js';
|
import { loadAdminConfigFunc } from './auth.js';
|
||||||
|
|
||||||
const version = "v1.2.1"; // Update this version string as needed
|
const version = "v1.2.2"; // Update this version string as needed
|
||||||
const adminTitle = `${t("admin_panel")} <small style="font-size: 12px; color: gray;">${version}</small>`;
|
const adminTitle = `${t("admin_panel")} <small style="font-size: 12px; color: gray;">${version}</small>`;
|
||||||
|
|
||||||
let lastLoginData = null;
|
let lastLoginData = null;
|
||||||
|
|||||||
@@ -634,7 +634,7 @@ function updateSliderConstraints() {
|
|||||||
|
|
||||||
// Set maximum based on screen size.
|
// Set maximum based on screen size.
|
||||||
if (width < 600) { // small devices (phones)
|
if (width < 600) { // small devices (phones)
|
||||||
max = 2;
|
max = 1;
|
||||||
} else if (width < 1024) { // medium devices
|
} else if (width < 1024) { // medium devices
|
||||||
max = 3;
|
max = 3;
|
||||||
} else if (width < 1440) { // between medium and large devices
|
} else if (width < 1440) { // between medium and large devices
|
||||||
|
|||||||
61
public/webdav.php
Normal file
61
public/webdav.php
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
// public/webdav.php
|
||||||
|
|
||||||
|
if (
|
||||||
|
empty($_SERVER['PHP_AUTH_USER'])
|
||||||
|
&& !empty($_SERVER['HTTP_AUTHORIZATION'])
|
||||||
|
&& preg_match('#Basic\s+(.*)$#i', $_SERVER['HTTP_AUTHORIZATION'], $m)
|
||||||
|
) {
|
||||||
|
[$u, $p] = explode(':', base64_decode($m[1]), 2) + ['', ''];
|
||||||
|
$_SERVER['PHP_AUTH_USER'] = $u;
|
||||||
|
$_SERVER['PHP_AUTH_PW'] = $p;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../config/config.php'; // UPLOAD_DIR, META_DIR, DATE_TIME_FORMAT
|
||||||
|
require_once __DIR__ . '/../vendor/autoload.php'; // Composer & SabreDAV
|
||||||
|
require_once __DIR__ . '/../src/models/AuthModel.php'; // AuthModel::authenticate(), getUserRole(), loadFolderPermission()
|
||||||
|
|
||||||
|
// ─── 3) Load your WebDAV directory implementation ──────────────────────────
|
||||||
|
require_once __DIR__ . '/../src/webdav/FileRiseDirectory.php';
|
||||||
|
use Sabre\DAV\Server;
|
||||||
|
use Sabre\DAV\Auth\Backend\BasicCallBack;
|
||||||
|
use Sabre\DAV\Auth\Plugin as AuthPlugin;
|
||||||
|
use Sabre\DAV\Browser\Plugin as BrowserPlugin;
|
||||||
|
use Sabre\DAV\Locks\Plugin as LocksPlugin;
|
||||||
|
use Sabre\DAV\Locks\Backend\File as LocksFileBackend;
|
||||||
|
use FileRise\WebDAV\FileRiseDirectory;
|
||||||
|
|
||||||
|
$authBackend = new BasicCallBack(function(string $user, string $pass) {
|
||||||
|
return \AuthModel::authenticate($user, $pass) !== false;
|
||||||
|
});
|
||||||
|
$authPlugin = new AuthPlugin($authBackend, 'FileRise');
|
||||||
|
|
||||||
|
$user = $_SERVER['PHP_AUTH_USER'] ?? '';
|
||||||
|
$isAdmin = (\AuthModel::getUserRole($user) === '1');
|
||||||
|
$folderOnly = (bool)\AuthModel::loadFolderPermission($user);
|
||||||
|
|
||||||
|
if ($isAdmin || !$folderOnly) {
|
||||||
|
// admins or unrestricted users see the full /uploads
|
||||||
|
$rootPath = rtrim(UPLOAD_DIR, '/\\');
|
||||||
|
} else {
|
||||||
|
// folder‑only users see only /uploads/{username}
|
||||||
|
$rootPath = rtrim(UPLOAD_DIR, '/\\') . DIRECTORY_SEPARATOR . $user;
|
||||||
|
if (!is_dir($rootPath)) {
|
||||||
|
mkdir($rootPath, 0755, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = new Server([
|
||||||
|
new FileRiseDirectory($rootPath, $user, $folderOnly),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$server->addPlugin($authPlugin);
|
||||||
|
//$server->addPlugin(new BrowserPlugin()); // optional HTML browser UI
|
||||||
|
$server->addPlugin(
|
||||||
|
new LocksPlugin(
|
||||||
|
new LocksFileBackend(sys_get_temp_dir() . '/sabre-locksdb')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$server->setBaseUri('/webdav.php/');
|
||||||
|
$server->exec();
|
||||||
@@ -451,55 +451,56 @@ class FileController {
|
|||||||
|
|
||||||
// --- CSRF Protection ---
|
// --- CSRF Protection ---
|
||||||
$headersArr = array_change_key_case(getallheaders(), CASE_LOWER);
|
$headersArr = array_change_key_case(getallheaders(), CASE_LOWER);
|
||||||
$receivedToken = isset($headersArr['x-csrf-token']) ? trim($headersArr['x-csrf-token']) : '';
|
$receivedToken = $headersArr['x-csrf-token'] ?? '';
|
||||||
if (!isset($_SESSION['csrf_token']) || $receivedToken !== $_SESSION['csrf_token']) {
|
if (!isset($_SESSION['csrf_token']) || $receivedToken !== $_SESSION['csrf_token']) {
|
||||||
http_response_code(403);
|
http_response_code(403);
|
||||||
echo json_encode(["error" => "Invalid CSRF token"]);
|
echo json_encode(["error" => "Invalid CSRF token"]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure user is authenticated.
|
// --- Authentication Check ---
|
||||||
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
|
if (empty($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
|
||||||
http_response_code(401);
|
http_response_code(401);
|
||||||
echo json_encode(["error" => "Unauthorized"]);
|
echo json_encode(["error" => "Unauthorized"]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user is allowed to save files (not read-only).
|
|
||||||
$username = $_SESSION['username'] ?? '';
|
$username = $_SESSION['username'] ?? '';
|
||||||
|
// --- Read‑only check ---
|
||||||
$userPermissions = loadUserPermissions($username);
|
$userPermissions = loadUserPermissions($username);
|
||||||
if ($username && isset($userPermissions['readOnly']) && $userPermissions['readOnly'] === true) {
|
if ($username && !empty($userPermissions['readOnly'])) {
|
||||||
echo json_encode(["error" => "Read-only users are not allowed to save files."]);
|
echo json_encode(["error" => "Read-only users are not allowed to save files."]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get JSON input.
|
// --- Input parsing ---
|
||||||
$data = json_decode(file_get_contents("php://input"), true);
|
$data = json_decode(file_get_contents("php://input"), true);
|
||||||
|
if (empty($data) || !isset($data["fileName"], $data["content"])) {
|
||||||
if (!$data) {
|
http_response_code(400);
|
||||||
echo json_encode(["error" => "No data received"]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($data["fileName"]) || !isset($data["content"])) {
|
|
||||||
echo json_encode(["error" => "Invalid request data", "received" => $data]);
|
echo json_encode(["error" => "Invalid request data", "received" => $data]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$fileName = basename($data["fileName"]);
|
$fileName = basename($data["fileName"]);
|
||||||
// Determine the folder. Default to "root" if not provided.
|
|
||||||
$folder = isset($data["folder"]) ? trim($data["folder"]) : "root";
|
$folder = isset($data["folder"]) ? trim($data["folder"]) : "root";
|
||||||
|
|
||||||
// Validate folder if not root.
|
// --- Folder validation ---
|
||||||
if (strtolower($folder) !== "root" && !preg_match(REGEX_FOLDER_NAME, $folder)) {
|
if (strtolower($folder) !== "root" && !preg_match(REGEX_FOLDER_NAME, $folder)) {
|
||||||
echo json_encode(["error" => "Invalid folder name"]);
|
echo json_encode(["error" => "Invalid folder name"]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$folder = trim($folder, "/\\ ");
|
$folder = trim($folder, "/\\ ");
|
||||||
|
|
||||||
// Delegate to the model.
|
// --- Delegate to model, passing the uploader ---
|
||||||
$result = FileModel::saveFile($folder, $fileName, $data["content"]);
|
// Make sure FileModel::saveFile signature is:
|
||||||
|
// saveFile(string $folder, string $fileName, $content, ?string $uploader = null)
|
||||||
|
$result = FileModel::saveFile(
|
||||||
|
$folder,
|
||||||
|
$fileName,
|
||||||
|
$data["content"],
|
||||||
|
$username // ← pass the real uploader here
|
||||||
|
);
|
||||||
|
|
||||||
echo json_encode($result);
|
echo json_encode($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -383,72 +383,80 @@ class FileModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Saves file content to disk and updates folder metadata.
|
* Save a file’s contents *and* record its metadata, including who uploaded it.
|
||||||
*
|
*
|
||||||
* @param string $folder The target folder where the file is to be saved (e.g. "root" or a subfolder).
|
* @param string $folder Folder key (e.g. "root" or "invoices/2025")
|
||||||
* @param string $fileName The name of the file.
|
* @param string $fileName Basename of the file
|
||||||
* @param string $content The file content.
|
* @param resource|string $content File contents (stream or string)
|
||||||
* @return array Returns an associative array with either a "success" key or an "error" key.
|
* @param string|null $uploader Username of uploader (if null, falls back to session)
|
||||||
|
* @return array ["success"=>"…"] or ["error"=>"…"]
|
||||||
*/
|
*/
|
||||||
public static function saveFile($folder, $fileName, $content) {
|
public static function saveFile(string $folder, string $fileName, $content, ?string $uploader = null): array {
|
||||||
// Sanitize and determine the folder name.
|
// Sanitize inputs
|
||||||
$folder = trim($folder) ?: 'root';
|
$folder = trim($folder) ?: 'root';
|
||||||
$fileName = basename(trim($fileName));
|
$fileName = basename(trim($fileName));
|
||||||
|
|
||||||
// Validate folder: if not "root", must match REGEX_FOLDER_NAME.
|
// Validate folder name
|
||||||
if (strtolower($folder) !== 'root' && !preg_match(REGEX_FOLDER_NAME, $folder)) {
|
if (strtolower($folder) !== 'root' && !preg_match(REGEX_FOLDER_NAME, $folder)) {
|
||||||
return ["error" => "Invalid folder name"];
|
return ["error" => "Invalid folder name"];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine base upload directory.
|
// Determine target directory
|
||||||
$baseDir = rtrim(UPLOAD_DIR, '/\\');
|
$baseDir = rtrim(UPLOAD_DIR, '/\\');
|
||||||
if (strtolower($folder) === 'root' || $folder === "") {
|
$targetDir = strtolower($folder) === 'root'
|
||||||
$targetDir = $baseDir . DIRECTORY_SEPARATOR;
|
? $baseDir . DIRECTORY_SEPARATOR
|
||||||
} else {
|
: $baseDir . DIRECTORY_SEPARATOR . trim($folder, "/\\ ") . DIRECTORY_SEPARATOR;
|
||||||
$targetDir = $baseDir . DIRECTORY_SEPARATOR . trim($folder, "/\\ ") . DIRECTORY_SEPARATOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
// (Optional security check to ensure targetDir is within baseDir.)
|
// Security check
|
||||||
if (strpos(realpath($targetDir), realpath($baseDir)) !== 0) {
|
if (strpos(realpath($targetDir), realpath($baseDir)) !== 0) {
|
||||||
return ["error" => "Invalid folder path"];
|
return ["error" => "Invalid folder path"];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create target directory if it doesn't exist.
|
// Ensure directory exists
|
||||||
if (!is_dir($targetDir)) {
|
if (!is_dir($targetDir) && !mkdir($targetDir, 0775, true)) {
|
||||||
if (!mkdir($targetDir, 0775, true)) {
|
|
||||||
return ["error" => "Failed to create destination folder"];
|
return ["error" => "Failed to create destination folder"];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$filePath = $targetDir . $fileName;
|
$filePath = $targetDir . $fileName;
|
||||||
// Attempt to save the file.
|
|
||||||
if (file_put_contents($filePath, $content) === false) {
|
// ——— STREAM TO DISK ———
|
||||||
|
if (is_resource($content)) {
|
||||||
|
$out = fopen($filePath, 'wb');
|
||||||
|
if ($out === false) {
|
||||||
|
return ["error" => "Unable to open file for writing"];
|
||||||
|
}
|
||||||
|
stream_copy_to_stream($content, $out);
|
||||||
|
fclose($out);
|
||||||
|
} else {
|
||||||
|
if (file_put_contents($filePath, (string)$content) === false) {
|
||||||
return ["error" => "Error saving file"];
|
return ["error" => "Error saving file"];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update metadata.
|
// ——— UPDATE METADATA ———
|
||||||
// Build metadata file path for the folder.
|
$metadataKey = strtolower($folder) === "root" ? "root" : $folder;
|
||||||
$metadataKey = (strtolower($folder) === "root" || $folder === "") ? "root" : $folder;
|
|
||||||
$metadataFileName = str_replace(['/', '\\', ' '], '-', trim($metadataKey)) . '_metadata.json';
|
$metadataFileName = str_replace(['/', '\\', ' '], '-', trim($metadataKey)) . '_metadata.json';
|
||||||
$metadataFilePath = META_DIR . $metadataFileName;
|
$metadataFilePath = META_DIR . $metadataFileName;
|
||||||
|
|
||||||
|
// Load existing metadata
|
||||||
|
$metadata = [];
|
||||||
if (file_exists($metadataFilePath)) {
|
if (file_exists($metadataFilePath)) {
|
||||||
$metadata = json_decode(file_get_contents($metadataFilePath), true);
|
$existing = @json_decode(file_get_contents($metadataFilePath), true);
|
||||||
} else {
|
if (is_array($existing)) {
|
||||||
$metadata = [];
|
$metadata = $existing;
|
||||||
}
|
}
|
||||||
if (!is_array($metadata)) {
|
|
||||||
$metadata = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$currentTime = date(DATE_TIME_FORMAT);
|
$currentTime = date(DATE_TIME_FORMAT);
|
||||||
|
// Use passed-in uploader, or fall back to session
|
||||||
|
if ($uploader === null) {
|
||||||
$uploader = $_SESSION['username'] ?? "Unknown";
|
$uploader = $_SESSION['username'] ?? "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
// Update metadata for the file. If already exists, update its "modified" timestamp.
|
|
||||||
if (isset($metadata[$fileName])) {
|
if (isset($metadata[$fileName])) {
|
||||||
$metadata[$fileName]['modified'] = $currentTime;
|
$metadata[$fileName]['modified'] = $currentTime;
|
||||||
$metadata[$fileName]['uploader'] = $uploader; // optional: update uploader if desired.
|
$metadata[$fileName]['uploader'] = $uploader;
|
||||||
} else {
|
} else {
|
||||||
$metadata[$fileName] = [
|
$metadata[$fileName] = [
|
||||||
"uploaded" => $currentTime,
|
"uploaded" => $currentTime,
|
||||||
@@ -457,7 +465,6 @@ class FileModel {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write updated metadata.
|
|
||||||
if (file_put_contents($metadataFilePath, json_encode($metadata, JSON_PRETTY_PRINT)) === false) {
|
if (file_put_contents($metadataFilePath, json_encode($metadata, JSON_PRETTY_PRINT)) === false) {
|
||||||
return ["error" => "Failed to update metadata"];
|
return ["error" => "Failed to update metadata"];
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/webdav/CurrentUser.php
Normal file
16
src/webdav/CurrentUser.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
// src/webdav/CurrentUser.php
|
||||||
|
namespace FileRise\WebDAV;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton holder for the current WebDAV username.
|
||||||
|
*/
|
||||||
|
class CurrentUser {
|
||||||
|
private static string $user = 'Unknown';
|
||||||
|
public static function set(string $u): void {
|
||||||
|
self::$user = $u;
|
||||||
|
}
|
||||||
|
public static function get(): string {
|
||||||
|
return self::$user;
|
||||||
|
}
|
||||||
|
}
|
||||||
110
src/webdav/FileRiseDirectory.php
Normal file
110
src/webdav/FileRiseDirectory.php
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
namespace FileRise\WebDAV;
|
||||||
|
|
||||||
|
// Bootstrap constants and models
|
||||||
|
require_once __DIR__ . '/../../config/config.php'; // UPLOAD_DIR, META_DIR, DATE_TIME_FORMAT
|
||||||
|
require_once __DIR__ . '/../../vendor/autoload.php'; // SabreDAV
|
||||||
|
require_once __DIR__ . '/../../src/models/FolderModel.php';
|
||||||
|
require_once __DIR__ . '/../../src/models/FileModel.php';
|
||||||
|
require_once __DIR__ . '/FileRiseFile.php';
|
||||||
|
|
||||||
|
use Sabre\DAV\ICollection;
|
||||||
|
use Sabre\DAV\INode;
|
||||||
|
use Sabre\DAV\Exception\NotFound;
|
||||||
|
use Sabre\DAV\Exception\Forbidden;
|
||||||
|
use FileRise\WebDAV\FileRiseFile;
|
||||||
|
use FolderModel;
|
||||||
|
use FileModel;
|
||||||
|
|
||||||
|
class FileRiseDirectory implements ICollection, INode {
|
||||||
|
private string $path;
|
||||||
|
private string $user;
|
||||||
|
private bool $folderOnly;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path Absolute filesystem path (no trailing slash)
|
||||||
|
* @param string $user Authenticated username
|
||||||
|
* @param bool $folderOnly If true, non‑admins only see $path/{user}
|
||||||
|
*/
|
||||||
|
public function __construct(string $path, string $user, bool $folderOnly) {
|
||||||
|
$this->path = rtrim($path, '/\\');
|
||||||
|
$this->user = $user;
|
||||||
|
$this->folderOnly = $folderOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── INode ───────────────────────────────────────────
|
||||||
|
|
||||||
|
public function getName(): string {
|
||||||
|
return basename($this->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLastModified(): int {
|
||||||
|
return filemtime($this->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(): void {
|
||||||
|
throw new Forbidden('Cannot delete this node');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setName($name): void {
|
||||||
|
throw new Forbidden('Renaming not supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── ICollection ────────────────────────────────────
|
||||||
|
|
||||||
|
public function getChildren(): array {
|
||||||
|
$nodes = [];
|
||||||
|
foreach (new \DirectoryIterator($this->path) as $item) {
|
||||||
|
if ($item->isDot()) continue;
|
||||||
|
$full = $item->getPathname();
|
||||||
|
if ($item->isDir()) {
|
||||||
|
$nodes[] = new self($full, $this->user, $this->folderOnly);
|
||||||
|
} else {
|
||||||
|
$nodes[] = new FileRiseFile($full, $this->user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Apply folder‑only at the top level
|
||||||
|
if (
|
||||||
|
$this->folderOnly
|
||||||
|
&& realpath($this->path) === realpath(rtrim(UPLOAD_DIR,'/\\'))
|
||||||
|
) {
|
||||||
|
$nodes = array_filter($nodes, fn(INode $n)=> $n->getName() === $this->user);
|
||||||
|
}
|
||||||
|
return array_values($nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function childExists($name): bool {
|
||||||
|
return file_exists($this->path . DIRECTORY_SEPARATOR . $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getChild($name): INode {
|
||||||
|
$full = $this->path . DIRECTORY_SEPARATOR . $name;
|
||||||
|
if (!file_exists($full)) throw new NotFound("Not found: $name");
|
||||||
|
return is_dir($full)
|
||||||
|
? new self($full, $this->user, $this->folderOnly)
|
||||||
|
: new FileRiseFile($full, $this->user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createFile($name, $data = null): INode {
|
||||||
|
$full = $this->path . DIRECTORY_SEPARATOR . $name;
|
||||||
|
$content = is_resource($data) ? stream_get_contents($data) : (string)$data;
|
||||||
|
|
||||||
|
// Compute folder‑key relative to UPLOAD_DIR
|
||||||
|
$rel = substr($full, strlen(rtrim(UPLOAD_DIR,'/\\'))+1);
|
||||||
|
$parts = explode('/', str_replace('\\','/',$rel));
|
||||||
|
$filename = array_pop($parts);
|
||||||
|
$folder = empty($parts) ? 'root' : implode('/', $parts);
|
||||||
|
|
||||||
|
FileModel::saveFile($folder, $filename, $content, $this->user);
|
||||||
|
return new FileRiseFile($full, $this->user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createDirectory($name): INode {
|
||||||
|
$full = $this->path . DIRECTORY_SEPARATOR . $name;
|
||||||
|
$rel = substr($full, strlen(rtrim(UPLOAD_DIR,'/\\'))+1);
|
||||||
|
$parent = dirname(str_replace('\\','/',$rel));
|
||||||
|
if ($parent === '.' || $parent === '/') $parent = '';
|
||||||
|
FolderModel::createFolder($name, $parent, $this->user);
|
||||||
|
return new self($full, $this->user, $this->folderOnly);
|
||||||
|
}
|
||||||
|
}
|
||||||
115
src/webdav/FileRiseFile.php
Normal file
115
src/webdav/FileRiseFile.php
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
// src/webdav/FileRiseFile.php
|
||||||
|
|
||||||
|
namespace FileRise\WebDAV;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../config/config.php';
|
||||||
|
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||||
|
require_once __DIR__ . '/../../src/models/FileModel.php';
|
||||||
|
|
||||||
|
use Sabre\DAV\IFile;
|
||||||
|
use Sabre\DAV\INode;
|
||||||
|
use Sabre\DAV\Exception\Forbidden;
|
||||||
|
use FileModel;
|
||||||
|
|
||||||
|
class FileRiseFile implements IFile, INode {
|
||||||
|
private string $path;
|
||||||
|
|
||||||
|
public function __construct(string $path) {
|
||||||
|
$this->path = $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── INode ───────────────────────────────────────────
|
||||||
|
|
||||||
|
public function getName(): string {
|
||||||
|
return basename($this->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLastModified(): int {
|
||||||
|
return filemtime($this->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(): void {
|
||||||
|
$base = rtrim(UPLOAD_DIR, '/\\') . DIRECTORY_SEPARATOR;
|
||||||
|
$rel = substr($this->path, strlen($base));
|
||||||
|
$parts = explode(DIRECTORY_SEPARATOR, $rel);
|
||||||
|
$file = array_pop($parts);
|
||||||
|
$folder = empty($parts) ? 'root' : $parts[0];
|
||||||
|
FileModel::deleteFiles($folder, [$file]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setName($newName): void {
|
||||||
|
throw new Forbidden('Renaming files not supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── IFile ───────────────────────────────────────────
|
||||||
|
|
||||||
|
public function get() {
|
||||||
|
return fopen($this->path, 'rb');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function put($data): ?string {
|
||||||
|
// 1) Save incoming data
|
||||||
|
file_put_contents(
|
||||||
|
$this->path,
|
||||||
|
is_resource($data) ? stream_get_contents($data) : (string)$data
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2) Update metadata with CurrentUser
|
||||||
|
$this->updateMetadata();
|
||||||
|
|
||||||
|
// 3) Flush to client fast
|
||||||
|
if (function_exists('fastcgi_finish_request')) {
|
||||||
|
fastcgi_finish_request();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // no ETag
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSize(): int {
|
||||||
|
return filesize($this->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getETag(): string {
|
||||||
|
return '"' . md5($this->getLastModified() . $this->getSize()) . '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContentType(): ?string {
|
||||||
|
return mime_content_type($this->path) ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Metadata helper ───────────────────────────────────
|
||||||
|
|
||||||
|
private function updateMetadata(): void {
|
||||||
|
$base = rtrim(UPLOAD_DIR, '/\\') . DIRECTORY_SEPARATOR;
|
||||||
|
$rel = substr($this->path, strlen($base));
|
||||||
|
$parts = explode(DIRECTORY_SEPARATOR, $rel);
|
||||||
|
$fileName = array_pop($parts);
|
||||||
|
$folder = empty($parts) ? 'root' : $parts[0];
|
||||||
|
|
||||||
|
$metaFile = META_DIR
|
||||||
|
. ($folder === 'root'
|
||||||
|
? 'root_metadata.json'
|
||||||
|
: str_replace(['/', '\\', ' '], '-', $folder) . '_metadata.json');
|
||||||
|
|
||||||
|
$metadata = [];
|
||||||
|
if (file_exists($metaFile)) {
|
||||||
|
$decoded = json_decode(file_get_contents($metaFile), true);
|
||||||
|
if (is_array($decoded)) {
|
||||||
|
$metadata = $decoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$now = date(DATE_TIME_FORMAT);
|
||||||
|
$uploaded = $metadata[$fileName]['uploaded'] ?? $now;
|
||||||
|
$uploader = CurrentUser::get();
|
||||||
|
|
||||||
|
$metadata[$fileName] = [
|
||||||
|
'uploaded' => $uploaded,
|
||||||
|
'modified' => $now,
|
||||||
|
'uploader' => $uploader,
|
||||||
|
];
|
||||||
|
|
||||||
|
file_put_contents($metaFile, json_encode($metadata, JSON_PRETTY_PRINT));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user