Most of this is just gpt slop
This commit is contained in:
parent
9213ca6df6
commit
57afb8862d
12 changed files with 600 additions and 0 deletions
29
docker-compose.yaml
Normal file
29
docker-compose.yaml
Normal file
|
@ -0,0 +1,29 @@
|
|||
services:
|
||||
sabredav:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
DB_HOST: mariadb
|
||||
DB_PORT: 3306
|
||||
DB_NAME: sabredav
|
||||
DB_USER: sabreuser
|
||||
DB_PASS: sabrepass
|
||||
depends_on:
|
||||
- mariadb
|
||||
|
||||
mariadb:
|
||||
image: mariadb:10.11
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: rootpassword
|
||||
MYSQL_DATABASE: sabredav
|
||||
MYSQL_USER: sabreuser
|
||||
MYSQL_PASSWORD: sabrepass
|
||||
volumes:
|
||||
- mariadb-data:/var/lib/mysql
|
||||
|
||||
volumes:
|
||||
mariadb-data:
|
34
docker/Dockerfile
Normal file
34
docker/Dockerfile
Normal file
|
@ -0,0 +1,34 @@
|
|||
FROM registry.gitlab.com/dxcker/apache-php:latest
|
||||
|
||||
# Switch over to root user
|
||||
USER root
|
||||
|
||||
RUN apt update \
|
||||
&& apt -y install unzip git curl \
|
||||
&& apt -y install php-xml php-mbstring php-curl php-pdo php-mysql \
|
||||
&& apt autoclean
|
||||
|
||||
# Enable mod_rewrite and mod_headers. Should be upstream but hey.
|
||||
RUN a2enmod rewrite headers
|
||||
|
||||
# Reconfigure apache for this project
|
||||
RUN echo "DocumentRoot /var/www/public" >> /etc/apache2/apache2.conf
|
||||
RUN sed -i 's/\/var\/www\/html/\/var\/www\/public/g' /etc/apache2/apache2.conf
|
||||
|
||||
# Install Composer
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
# Copy over entrypoint
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
# Copy over files
|
||||
WORKDIR /var/www/
|
||||
|
||||
RUN mkdir -p data/files data && chown -R app:app data
|
||||
COPY src/ .
|
||||
RUN chown -R app:app /var/www/
|
||||
|
||||
USER app
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
8
docker/entrypoint.sh
Normal file
8
docker/entrypoint.sh
Normal file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
composer install --no-interaction --optimize-autoloader
|
||||
composer dump-autoload -o
|
||||
composer migrate
|
||||
|
||||
/usr/sbin/apache2 -DFOREGROUND
|
0
src/carddav/index.php
Normal file
0
src/carddav/index.php
Normal file
24
src/composer.json
Normal file
24
src/composer.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "j/wiredav",
|
||||
"description": "Minimal SabreDAV server with WebDAV, CalDAV, CardDAV and MariaDB auth",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"sabre/dav": "^4.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"robmorgan/phinx": "^0.14"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"WireDAV\\": "libs/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"migrate": "vendor/bin/phinx migrate",
|
||||
"rollback": "vendor/bin/phinx rollback"
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"license": "MIT"
|
||||
}
|
||||
|
145
src/db/migrations/2025063000_create_users_and_dav_tables.php
Normal file
145
src/db/migrations/2025063000_create_users_and_dav_tables.php
Normal file
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
use Phinx\Db\Adapter\MysqlAdapter;
|
||||
|
||||
final class CreateUsersAndDavTables extends AbstractMigration
|
||||
{
|
||||
public function change(): void
|
||||
{
|
||||
// === Users table ===
|
||||
$users = $this->table('users', ['id' => false, 'primary_key' => ['username']]);
|
||||
$users->addColumn('username', 'string', ['limit' => 50])
|
||||
->addColumn('digesta1', 'string', ['limit' => 255])
|
||||
->create();
|
||||
|
||||
// === Principals table (users and groups) ===
|
||||
$principals = $this->table('principals');
|
||||
$principals->addColumn('uri', 'string', ['limit' => 100])
|
||||
->addColumn('email', 'string', ['limit' => 255, 'null' => true])
|
||||
->addColumn('displayname', 'string', ['limit' => 255, 'null' => true])
|
||||
->addColumn('password', 'string', ['limit' => 255, 'null' => true])
|
||||
->addColumn('calendar_prefix', 'string', ['limit' => 20, 'null' => true])
|
||||
->addColumn('backend', 'string', ['limit' => 20, 'null' => true])
|
||||
->addIndex(['uri'], ['unique' => true])
|
||||
->create();
|
||||
|
||||
// Foreign key principals.uri → users.username (optional but typical)
|
||||
$principals->addForeignKey('uri', 'users', 'username', ['delete'=> 'CASCADE', 'update'=> 'CASCADE'])->save();
|
||||
|
||||
// === Calendars table (CalDAV) ===
|
||||
$calendars = $this->table('calendars');
|
||||
$calendars->addColumn('principaluri', 'string', ['limit' => 100])
|
||||
->addColumn('displayname', 'string', ['limit' => 100, 'null' => true])
|
||||
->addColumn('uri', 'string', ['limit' => 200])
|
||||
->addColumn('description', 'text', ['null' => true])
|
||||
->addColumn('calendarorder', 'integer', ['null' => true])
|
||||
->addColumn('calendarcolor', 'string', ['limit' => 10, 'null' => true])
|
||||
->addColumn('timezone', 'text', ['null' => true])
|
||||
->addColumn('components', 'string', ['limit' => 20, 'null' => true])
|
||||
->addColumn('transparent', 'boolean', ['null' => true])
|
||||
->addIndex(['principaluri', 'uri'], ['unique' => true])
|
||||
->create();
|
||||
|
||||
$calendars->addForeignKey('principaluri', 'principals', 'uri', ['delete'=> 'CASCADE', 'update'=> 'CASCADE'])->save();
|
||||
|
||||
// === Calendar Objects table (CalDAV) ===
|
||||
$calendarObjects = $this->table('calendarobjects');
|
||||
$calendarObjects->addColumn('calendarid', 'integer')
|
||||
->addColumn('uri', 'string', ['limit' => 200])
|
||||
->addColumn('calendardata', 'binary', ['limit' => MysqlAdapter::BLOB_MEDIUM, 'null' => true])
|
||||
->addColumn('lastmodified', 'integer', ['null' => true])
|
||||
->addColumn('etag', 'string', ['limit' => 32, 'null' => true])
|
||||
->addColumn('size', 'integer', ['null' => true])
|
||||
->addColumn('componenttype', 'string', ['limit' => 8, 'null' => true])
|
||||
->addColumn('firstoccurence', 'integer', ['null' => true])
|
||||
->addColumn('lastoccurence', 'integer', ['null' => true])
|
||||
->addColumn('uid', 'string', ['limit' => 200, 'null' => true])
|
||||
->addIndex(['calendarid', 'uri'], ['unique' => true])
|
||||
->create();
|
||||
|
||||
$calendarObjects->addForeignKey('calendarid', 'calendars', 'id', ['delete'=> 'CASCADE', 'update'=> 'CASCADE'])->save();
|
||||
|
||||
// === Addressbooks table (CardDAV) ===
|
||||
$addressbooks = $this->table('addressbooks');
|
||||
$addressbooks->addColumn('principaluri', 'string', ['limit' => 100])
|
||||
->addColumn('displayname', 'string', ['limit' => 100, 'null' => true])
|
||||
->addColumn('uri', 'string', ['limit' => 200])
|
||||
->addColumn('description', 'text', ['null' => true])
|
||||
->addIndex(['principaluri', 'uri'], ['unique' => true])
|
||||
->create();
|
||||
|
||||
$addressbooks->addForeignKey('principaluri', 'principals', 'uri', ['delete'=> 'CASCADE', 'update'=> 'CASCADE'])->save();
|
||||
|
||||
// === Addressbook Changes table (CardDAV) ===
|
||||
$addressbookChanges = $this->table('addressbookchanges');
|
||||
$addressbookChanges->addColumn('addressbookid', 'integer')
|
||||
->addColumn('uri', 'string', ['limit' => 200, 'null' => true])
|
||||
->addColumn('synctoken', 'integer', ['null' => true])
|
||||
->create();
|
||||
|
||||
$addressbookChanges->addForeignKey('addressbookid', 'addressbooks', 'id', ['delete'=> 'CASCADE', 'update'=> 'CASCADE'])->save();
|
||||
|
||||
// === Addressbook Objects table (CardDAV) ===
|
||||
$addressbookObjects = $this->table('addressbookobjects');
|
||||
$addressbookObjects->addColumn('addressbookid', 'integer')
|
||||
->addColumn('uri', 'string', ['limit' => 200])
|
||||
->addColumn('carddata', 'binary', ['limit' => MysqlAdapter::BLOB_MEDIUM, 'null' => true])
|
||||
->addColumn('lastmodified', 'integer', ['null' => true])
|
||||
->addColumn('etag', 'string', ['limit' => 32, 'null' => true])
|
||||
->addColumn('size', 'integer', ['null' => true])
|
||||
->addColumn('uid', 'string', ['limit' => 200, 'null' => true])
|
||||
->addIndex(['addressbookid', 'uri'], ['unique' => true])
|
||||
->create();
|
||||
|
||||
$addressbookObjects->addForeignKey('addressbookid', 'addressbooks', 'id', ['delete'=> 'CASCADE', 'update'=> 'CASCADE'])->save();
|
||||
|
||||
// === Locks table (WebDAV) ===
|
||||
$locks = $this->table('locks');
|
||||
$locks->addColumn('id', 'string', ['limit' => 32])
|
||||
->addColumn('token', 'string', ['limit' => 255, 'null' => true])
|
||||
->addColumn('principaluri', 'string', ['limit' => 100, 'null' => true])
|
||||
->addColumn('scope', 'string', ['limit' => 10, 'null' => true])
|
||||
->addColumn('depth', 'string', ['limit' => 10, 'null' => true])
|
||||
->addColumn('uri', 'string', ['limit' => 255, 'null' => true])
|
||||
->addColumn('timeout', 'integer', ['null' => true])
|
||||
->addColumn('created', 'integer', ['null' => true])
|
||||
->addColumn('owner', 'text', ['null' => true])
|
||||
->addColumn('exclusive', 'boolean', ['null' => true])
|
||||
->addIndex(['id'], ['unique' => true])
|
||||
->create();
|
||||
|
||||
// === Calendar Shares table ===
|
||||
$calendarShares = $this->table('calendarshares');
|
||||
$calendarShares->addColumn('calendarid', 'integer')
|
||||
->addColumn('principaluri', 'string', ['limit' => 100])
|
||||
->addColumn('access', 'integer') // e.g. 1=read, 2=write
|
||||
->addColumn('share_access', 'integer', ['null' => true])
|
||||
->addColumn('summary', 'string', ['limit' => 255, 'null' => true])
|
||||
->addColumn('href', 'string', ['limit' => 255, 'null' => true])
|
||||
->addColumn('uid', 'string', ['limit' => 200, 'null' => true])
|
||||
->addColumn('lastupdated', 'integer', ['null' => true])
|
||||
->addIndex(['calendarid', 'principaluri'], ['unique' => true])
|
||||
->create();
|
||||
|
||||
$calendarShares->addForeignKey('calendarid', 'calendars', 'id', ['delete'=> 'CASCADE', 'update'=> 'CASCADE'])->save();
|
||||
$calendarShares->addForeignKey('principaluri', 'principals', 'uri', ['delete'=> 'CASCADE', 'update'=> 'CASCADE'])->save();
|
||||
|
||||
// === Addressbook Shares table ===
|
||||
$addressbookShares = $this->table('addressbookshares');
|
||||
$addressbookShares->addColumn('addressbookid', 'integer')
|
||||
->addColumn('principaluri', 'string', ['limit' => 100])
|
||||
->addColumn('access', 'integer')
|
||||
->addColumn('share_access', 'integer', ['null' => true])
|
||||
->addColumn('summary', 'string', ['limit' => 255, 'null' => true])
|
||||
->addColumn('href', 'string', ['limit' => 255, 'null' => true])
|
||||
->addColumn('uid', 'string', ['limit' => 200, 'null' => true])
|
||||
->addColumn('lastupdated', 'integer', ['null' => true])
|
||||
->addIndex(['addressbookid', 'principaluri'], ['unique' => true])
|
||||
->create();
|
||||
|
||||
$addressbookShares->addForeignKey('addressbookid', 'addressbooks', 'id', ['delete'=> 'CASCADE', 'update'=> 'CASCADE'])->save();
|
||||
$addressbookShares->addForeignKey('principaluri', 'principals', 'uri', ['delete'=> 'CASCADE', 'update'=> 'CASCADE'])->save();
|
||||
}
|
||||
}
|
||||
|
36
src/libs/Auth/SQLAuthBackend.php
Normal file
36
src/libs/Auth/SQLAuthBackend.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace WireDAV\Auth;
|
||||
|
||||
use Sabre\DAV\Auth\Backend\AbstractDigest;
|
||||
use PDO;
|
||||
|
||||
class SQLAuthBackend extends AbstractDigest
|
||||
{
|
||||
private PDO $pdo;
|
||||
|
||||
/**
|
||||
* @param PDO $pdo A PDO instance connected to your DB
|
||||
*/
|
||||
public function __construct(PDO $pdo)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method must return the MD5 digest hash for the user in the format:
|
||||
* MD5(username:realm:password)
|
||||
*
|
||||
* @param string $realm
|
||||
* @param string $username
|
||||
* @return string|false The hash or false if user not found
|
||||
*/
|
||||
public function getDigestHash($realm, $username)
|
||||
{
|
||||
$stmt = $this->pdo->prepare('SELECT digesta1 FROM users WHERE username = ?');
|
||||
$stmt->execute([$username]);
|
||||
$hash = $stmt->fetchColumn();
|
||||
return $hash ?: false;
|
||||
}
|
||||
}
|
||||
|
41
src/libs/DB/PDOProvider.php
Normal file
41
src/libs/DB/PDOProvider.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace WireDAV\DB;
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use RuntimeException;
|
||||
|
||||
class PDOProvider
|
||||
{
|
||||
/**
|
||||
* Creates and returns a PDO instance with configured DB connection.
|
||||
*
|
||||
* @return PDO
|
||||
* @throws RuntimeException on connection failure
|
||||
*/
|
||||
public static function getPDO(): PDO
|
||||
{
|
||||
$dbHost = getenv('DB_HOST') ?: 'localhost';
|
||||
$dbPort = getenv('DB_PORT') ?: '3306';
|
||||
$dbName = getenv('DB_NAME') ?: 'sabredav';
|
||||
$dbUser = getenv('DB_USER') ?: 'sabreuser';
|
||||
$dbPass = getenv('DB_PASS') ?: 'sabrepass';
|
||||
$charset = 'utf8mb4';
|
||||
|
||||
$dsn = "mysql:host=$dbHost;port=$dbPort;dbname=$dbName;charset=$charset";
|
||||
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
|
||||
try {
|
||||
return new PDO($dsn, $dbUser, $dbPass, $options);
|
||||
} catch (PDOException $e) {
|
||||
throw new RuntimeException('Database connection failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
22
src/phinx.php
Normal file
22
src/phinx.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
// phinx.php
|
||||
|
||||
return [
|
||||
'paths' => [
|
||||
'migrations' => 'db/migrations',
|
||||
],
|
||||
'environments' => [
|
||||
'default_migration_table' => 'phinxlog',
|
||||
'default_environment' => 'development',
|
||||
'development' => [
|
||||
'adapter' => 'mysql',
|
||||
'host' => getenv('DB_HOST') ?: 'localhost',
|
||||
'name' => getenv('DB_NAME') ?: 'sabredav',
|
||||
'user' => getenv('DB_USER') ?: 'root',
|
||||
'pass' => getenv('DB_PASS') ?: '',
|
||||
'port' => getenv('DB_PORT') ?: '3306',
|
||||
'charset' => 'utf8mb4',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
200
src/public/admin/index.php
Normal file
200
src/public/admin/index.php
Normal file
|
@ -0,0 +1,200 @@
|
|||
<?php
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use WireDAV\DB\PDOProvider;
|
||||
|
||||
// Initiate basics
|
||||
$pdo = (new PDOProvider())->getPDO();
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$path = $_GET['action'] ?? '';
|
||||
|
||||
|
||||
// Helper Functions
|
||||
function getBasicAuthUserAndPass() {
|
||||
if (!isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
|
||||
header('WWW-Authenticate: Basic realm="Admin Area"');
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
exit('Authentication required');
|
||||
}
|
||||
return [$_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']];
|
||||
}
|
||||
|
||||
function isAdmin(PDO $pdo, $username, $password, $realm = 'SabreDAV') {
|
||||
$stmt = $pdo->prepare("SELECT digesta1 FROM users WHERE username = ?");
|
||||
$stmt->execute([$username]);
|
||||
$hash = $stmt->fetchColumn();
|
||||
if (!$hash) return false;
|
||||
|
||||
$digest = md5("$username:$realm:$password");
|
||||
return $digest === $hash; // Only checking password; add is_admin column if you want real admin check
|
||||
}
|
||||
|
||||
function anyUsersExist(PDO $pdo) {
|
||||
$stmt = $pdo->query("SELECT COUNT(*) FROM users");
|
||||
return $stmt->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
|
||||
// Request Routing
|
||||
if ($method === 'POST' && $path === 'create_user') {
|
||||
list($user, $pass) = getBasicAuthUserAndPass();
|
||||
|
||||
if (anyUsersExist($pdo) && !isAdmin($pdo, $user, $pass)) {
|
||||
http_response_code(403);
|
||||
exit(json_encode(['error' => 'Forbidden: admin required']));
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
if (empty($input['username']) || empty($input['password'])) {
|
||||
http_response_code(400);
|
||||
exit(json_encode(['error' => 'Missing username or password']));
|
||||
}
|
||||
|
||||
$newUser = $input['username'];
|
||||
$newPass = $input['password'];
|
||||
$realm = 'SabreDAV';
|
||||
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM users WHERE username = ?");
|
||||
$stmt->execute([$newUser]);
|
||||
if ($stmt->fetchColumn() > 0) {
|
||||
http_response_code(409);
|
||||
exit(json_encode(['error' => 'User already exists']));
|
||||
}
|
||||
|
||||
$digest = md5("$newUser:$realm:$newPass");
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO users (username, digesta1) VALUES (?, ?)");
|
||||
$stmt->execute([$newUser, $digest]);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => "User $newUser created"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($method === 'POST' && $path === 'change_password') {
|
||||
list($user, $pass) = getBasicAuthUserAndPass();
|
||||
|
||||
if (!isAdmin($pdo, $user, $pass)) {
|
||||
http_response_code(403);
|
||||
exit(json_encode(['error' => 'Forbidden: admin required']));
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
if (empty($input['username']) || empty($input['password'])) {
|
||||
http_response_code(400);
|
||||
exit(json_encode(['error' => 'Missing username or password']));
|
||||
}
|
||||
|
||||
$targetUser = $input['username'];
|
||||
$newPass = $input['password'];
|
||||
$realm = 'SabreDAV';
|
||||
|
||||
$digest = md5("$targetUser:$realm:$newPass");
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE users SET digesta1 = ? WHERE username = ?");
|
||||
$stmt->execute([$digest, $targetUser]);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => "Password changed for $targetUser"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($method === 'POST' && $path === 'delete_user') {
|
||||
list($user, $pass) = getBasicAuthUserAndPass();
|
||||
|
||||
if (!isAdmin($pdo, $user, $pass)) {
|
||||
http_response_code(403);
|
||||
exit(json_encode(['error' => 'Forbidden: admin required']));
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
if (empty($input['username'])) {
|
||||
http_response_code(400);
|
||||
exit(json_encode(['error' => 'Missing username']));
|
||||
}
|
||||
|
||||
$targetUser = $input['username'];
|
||||
|
||||
$stmt = $pdo->prepare("DELETE FROM users WHERE username = ?");
|
||||
$stmt->execute([$targetUser]);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => "User $targetUser deleted"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($method === 'POST' && $path === 'create_calendar') {
|
||||
list($username, $password) = getBasicAuthUserAndPass();
|
||||
|
||||
// Authenticate user (no admin check, calendar owned by user)
|
||||
$stmt = $pdo->prepare("SELECT digesta1 FROM users WHERE username = ?");
|
||||
$stmt->execute([$username]);
|
||||
$hash = $stmt->fetchColumn();
|
||||
$realm = 'SabreDAV';
|
||||
if (!$hash || md5("$username:$realm:$password") !== $hash) {
|
||||
http_response_code(403);
|
||||
exit(json_encode(['error' => 'Invalid credentials']));
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
if (empty($input['uri']) || empty($input['displayname'])) {
|
||||
http_response_code(400);
|
||||
exit(json_encode(['error' => 'Missing uri or displayname']));
|
||||
}
|
||||
|
||||
$calendarUri = $input['uri'];
|
||||
$displayName = $input['displayname'];
|
||||
$principalUri = "principals/$username";
|
||||
|
||||
// Check if calendar URI already exists for user
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM calendars WHERE principaluri = ? AND uri = ?");
|
||||
$stmt->execute([$principalUri, $calendarUri]);
|
||||
if ($stmt->fetchColumn() > 0) {
|
||||
http_response_code(409);
|
||||
exit(json_encode(['error' => 'Calendar URI already exists']));
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO calendars (principaluri, displayname, uri) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$principalUri, $displayName, $calendarUri]);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => "Calendar '$displayName' created"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($method === 'POST' && $path === 'delete_calendar') {
|
||||
list($username, $password) = getBasicAuthUserAndPass();
|
||||
|
||||
// Authenticate user
|
||||
$stmt = $pdo->prepare("SELECT digesta1 FROM users WHERE username = ?");
|
||||
$stmt->execute([$username]);
|
||||
$hash = $stmt->fetchColumn();
|
||||
$realm = 'SabreDAV';
|
||||
if (!$hash || md5("$username:$realm:$password") !== $hash) {
|
||||
http_response_code(403);
|
||||
exit(json_encode(['error' => 'Invalid credentials']));
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
if (empty($input['uri'])) {
|
||||
http_response_code(400);
|
||||
exit(json_encode(['error' => 'Missing calendar uri']));
|
||||
}
|
||||
|
||||
$calendarUri = $input['uri'];
|
||||
$principalUri = "principals/$username";
|
||||
|
||||
$stmt = $pdo->prepare("DELETE FROM calendars WHERE principaluri = ? AND uri = ?");
|
||||
$stmt->execute([$principalUri, $calendarUri]);
|
||||
|
||||
if ($stmt->rowCount() === 0) {
|
||||
http_response_code(404);
|
||||
exit(json_encode(['error' => 'Calendar not found']));
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'message' => "Calendar '$calendarUri' deleted"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
// Default fallback if no route matched
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Unknown endpoint']);
|
||||
exit;
|
||||
|
61
src/public/index.php
Normal file
61
src/public/index.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Sabre\DAV;
|
||||
use Sabre\CalDAV;
|
||||
use Sabre\CardDAV;
|
||||
use Sabre\DAVACL;
|
||||
use WireDAV\DB\PDOProvider;
|
||||
use WireDAV\Auth\SQLAuthBackend;
|
||||
|
||||
$pdo = PDOProvider::getPDO();
|
||||
|
||||
//Mapping PHP errors to exceptions
|
||||
function exception_error_handler($errno, $errstr, $errfile, $errline ) {
|
||||
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
|
||||
}
|
||||
set_error_handler("exception_error_handler");
|
||||
|
||||
// Backends
|
||||
$authBackend = new SQLAuthBackend($pdo);
|
||||
$principalBackend = new DAVACL\PrincipalBackend\PDO($pdo);
|
||||
$calendarBackend = new CalDAV\Backend\PDO($pdo);
|
||||
|
||||
// Directory tree
|
||||
$tree = array(
|
||||
new DAVACL\PrincipalCollection($principalBackend),
|
||||
new CalDAV\CalendarRoot($principalBackend, $calendarBackend)
|
||||
);
|
||||
|
||||
|
||||
// The object tree needs in turn to be passed to the server class
|
||||
$server = new DAV\Server($tree);
|
||||
|
||||
// You are highly encouraged to set your WebDAV server base url. Without it,
|
||||
// SabreDAV will guess, but the guess is not always correct. Putting the
|
||||
// server on the root of the domain will improve compatibility.
|
||||
$server->setBaseUri('/');
|
||||
|
||||
// Authentication plugin
|
||||
$authPlugin = new DAV\Auth\Plugin($authBackend,'SabreDAV');
|
||||
$server->addPlugin($authPlugin);
|
||||
|
||||
// CalDAV plugin
|
||||
$caldavPlugin = new CalDAV\Plugin();
|
||||
$server->addPlugin($caldavPlugin);
|
||||
|
||||
// CardDAV plugin
|
||||
$carddavPlugin = new CardDAV\Plugin();
|
||||
$server->addPlugin($carddavPlugin);
|
||||
|
||||
// ACL plugin
|
||||
$aclPlugin = new DAVACL\Plugin();
|
||||
$server->addPlugin($aclPlugin);
|
||||
|
||||
// Support for html frontend
|
||||
$browser = new DAV\Browser\Plugin();
|
||||
$server->addPlugin($browser);
|
||||
|
||||
// And off we go!
|
||||
$server->exec();
|
0
src/webdav/index.php
Normal file
0
src/webdav/index.php
Normal file
Loading…
Add table
Add a link
Reference in a new issue