好友
阅读权限10
听众
最后登录1970-1-1
|
本帖最后由 ChiZhu 于 2026-3-23 12:51 编辑
都是AI编写
WebDAV 文件管理器 - 完整使用指南
📖 项目简介
这是一个基于纯 PHP 开发的 WebDAV 文件管理器,无需任何第三方 SDK,支持:
- ✅ 浏览目录和文件列表
- ✅ 文件上传(支持大文件流式上传)
- ✅ 文件下载(支持断点续传)
- ✅ 文件/目录删除
- ✅ 临时 Token 分享链接(24小时有效,不暴露密码)
- ✅ 图片直接显示(无需下载即可查看)
- ✅ 支持中文文件名
- ✅ 美观的现代化界面
🚀 功能特点
1. 文件浏览
- 点击文件夹图标可进入子目录
- 显示文件大小、最后修改时间
- 自动识别文件类型(目录/文件)
2. 文件上传
- 支持任意类型文件上传
- 进度提示(浏览器原生)
- 自动处理文件名编码
3. 文件下载
- 直接下载到本地
- 支持大文件(通过流式传输,内存占用小)
4. 临时分享链接 ⭐
- 生成 24 小时有效期的分享链接
- 链接中不包含 WebDAV 密码
- 图片链接可直接在浏览器中显示
- 文本文件可直接查看内容
5. 安全特性
- 所有操作需通过 WebDAV 认证
- 分享链接使用随机 Token,无法伪造
- 自动清理过期的分享 Token
- 路径遍历攻击防护
📦 安装部署
环境要求
- PHP 5.6 或更高版本
- cURL 扩展
- SimpleXML 扩展
- 文件写入权限(用于存储分享 Token)
快速安装
-
下载文件
将以下文件上传到您的 PHP 服务器同一目录:
index.php - 主文件管理器
share.php - 分享代理
-
配置认证信息
编辑 index.php 和 share.php,修改以下配置:
> php
> define('WEBDAV_URL', 'https://your-webdav-server.com/dav');
> define('WEBDAV_USER', 'your_username');
> define('WEBDAV_PASS', 'your_password');
首页
文件夹里 都可以创建分享链接
以infini-cloud写的,不知道其它webdav能不能用
index.php 修改以下
> // 设置管理密码(请修改为强密码)
> $admin_password = 'your_strong_password_here';
> define('WEBDAV_URL', 'webdav服务器链接');
> define('WEBDAV_USER', '用户名');
> define('WEBDAV_PASS', '密码');
[PHP] 纯文本查看 复制代码 <?php
/**
* WebDAV 文件管理器(含临时 Token 分享)
* 支持:浏览目录、上传、下载、删除、生成临时分享链接
*/
header('Content-Type: text/html; charset=utf-8');
session_start();
// 设置管理密码(请修改为强密码)
$admin_password = 'your_strong_password_here';
// 检查是否已登录
if (!isset($_SESSION['webdav_logged']) || $_SESSION['webdav_logged'] !== true) {
// 处理登录表单
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login_password']) && $_POST['login_password'] === $admin_password) {
$_SESSION['webdav_logged'] = true;
// 刷新页面
header('Location: ' . $_SERVER['PHP_SELF']);
exit;
}
// 显示登录界面
?>
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>登录</title></head>
<body style="font-family: sans-serif; text-align:center; margin-top:100px;">
<form method="post">
<h2>WebDAV 文件管理器 - 登录</h2>
<input type="password" name="login_password" placeholder="请输入管理密码" required>
<button type="submit">登录</button>
</form>
</body>
</html>
<?php
exit;
}
$missingExtensions = [];
if (!extension_loaded('curl')) $missingExtensions[] = 'cURL';
if (!extension_loaded('SimpleXML')) $missingExtensions[] = 'SimpleXML';
if (!empty($missingExtensions)) {
die('缺少必需的 PHP 扩展:' . implode(', ', $missingExtensions));
}
define('WEBDAV_URL', 'webdav服务器链接');
define('WEBDAV_USER', '用户名');
define('WEBDAV_PASS', '密码');
// Token 存储文件
$tokenFile = __DIR__ . '/share_tokens.json';
if (!file_exists($tokenFile)) {
file_put_contents($tokenFile, json_encode([]));
}
class WebDAVClient
{
private $baseUrl;
private $basePath;
private $username;
private $password;
public function __construct($baseUrl, $username, $password)
{
$this->baseUrl = rtrim($baseUrl, '/');
$this->basePath = parse_url($this->baseUrl, PHP_URL_PATH) ?: '/';
$this->username = $username;
$this->password = $password;
}
private function request($method, $path = '', $options = [])
{
$url = $this->baseUrl . '/' . ltrim($path, '/');
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, $this->username . ':' . $this->password);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$headers = isset($options['headers']) ? $options['headers'] : [];
if (!empty($options['body'])) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $options['body']);
$headers[] = 'Content-Length: ' . strlen($options['body']);
}
if (!empty($headers)) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
return ['body' => $response, 'code' => $httpCode, 'error' => $error];
}
public function listFiles($path = '')
{
$normPath = $path;
if ($normPath !== '' && substr($normPath, -1) !== '/') {
$normPath = $normPath . '/';
}
$result = $this->request('PROPFIND', $normPath, [
'headers' => ['Depth: 1', 'Content-Type: application/xml; charset=utf-8'],
'body' => '<?xml version="1.0" encoding="utf-8"?>
<propfind xmlns="DAV:">
<prop>
<resourcetype/>
<getcontentlength/>
<getlastmodified/>
</prop>
</propfind>'
]);
if ($result['code'] !== 207) {
throw new Exception("PROPFIND 失败: HTTP {$result['code']}");
}
$xml = simplexml_load_string($result['body']);
if ($xml === false) {
throw new Exception("无法解析 XML 响应");
}
$responses = $xml->xpath('//*[local-name()="response"]');
if (empty($responses)) {
return [];
}
$files = [];
foreach ($responses as $response) {
$href = '';
$hrefNodes = $response->xpath('.//*[local-name()="href"]');
if (!empty($hrefNodes)) {
$href = (string)$hrefNodes[0];
}
if (empty($href)) continue;
$hrefPath = parse_url($href, PHP_URL_PATH);
if (!$hrefPath) $hrefPath = $href;
$relative = ltrim(str_replace($this->basePath, '', $hrefPath), '/');
if ($relative === '' || $relative === $path || $relative === $path . '/') {
continue;
}
$name = basename($relative);
$isDir = false;
$size = 0;
$lastModified = '';
$propstats = $response->xpath('.//*[local-name()="propstat"]');
foreach ($propstats as $propstat) {
$statusNodes = $propstat->xpath('.//*[local-name()="status"]');
$status = !empty($statusNodes) ? (string)$statusNodes[0] : '';
if (strpos($status, '200') !== false) {
$propNodes = $propstat->xpath('.//*[local-name()="prop"]');
if (!empty($propNodes)) {
$prop = $propNodes[0];
$resourcetypeNodes = $prop->xpath('.//*[local-name()="resourcetype"]');
if (!empty($resourcetypeNodes)) {
$collection = $resourcetypeNodes[0]->xpath('.//*[local-name()="collection"]');
$isDir = !empty($collection);
}
$sizeNodes = $prop->xpath('.//*[local-name()="getcontentlength"]');
if (!empty($sizeNodes)) {
$size = (int)$sizeNodes[0];
}
$lastmodNodes = $prop->xpath('.//*[local-name()="getlastmodified"]');
if (!empty($lastmodNodes)) {
$lastModified = (string)$lastmodNodes[0];
}
}
break;
}
}
$files[] = [
'name' => urldecode($name),
'raw_name' => $name,
'type' => $isDir ? 'dir' : 'file',
'size' => $size,
'lastmodified' => $lastModified,
'href' => $href
];
}
return $files;
}
public function uploadFile($localPath, $remotePath)
{
if (!file_exists($localPath)) {
throw new Exception("本地文件不存在: $localPath");
}
$content = file_get_contents($localPath);
if ($content === false) {
throw new Exception("无法读取本地文件: $localPath");
}
$result = $this->request('PUT', $remotePath, [
'body' => $content,
'headers' => ['Content-Type' => 'application/octet-stream']
]);
if ($result['code'] !== 201 && $result['code'] !== 204) {
throw new Exception("上传失败: HTTP {$result['code']}");
}
return true;
}
public function downloadFile($remotePath, $localPath)
{
$result = $this->request('GET', $remotePath);
if ($result['code'] !== 200) {
throw new Exception("下载失败: HTTP {$result['code']}");
}
if (file_put_contents($localPath, $result['body']) === false) {
throw new Exception("无法写入本地文件: $localPath");
}
return true;
}
public function deleteFile($remotePath)
{
$result = $this->request('DELETE', $remotePath);
if ($result['code'] !== 200 && $result['code'] !== 204) {
throw new Exception("删除失败: HTTP {$result['code']}");
}
return true;
}
public function formatSize($bytes)
{
if ($bytes === 0) return '0 B';
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$i = floor(log($bytes, 1024));
return round($bytes / pow(1024, $i), 2) . ' ' . $units[$i];
}
}
// 处理操作
$action = isset($_GET['action']) ? $_GET['action'] : '';
$path = isset($_GET['path']) ? trim($_GET['path'], '/') : '';
$file = isset($_GET['file']) ? $_GET['file'] : '';
$deleteFile = isset($_GET['delete']) ? $_GET['delete'] : '';
$client = new WebDAVClient(WEBDAV_URL, WEBDAV_USER, WEBDAV_PASS);
$message = '';
$error = '';
try {
// 生成分享 Token 的 AJAX 接口
if ($action === 'create_share' && isset($_POST['file_path'])) {
$filePath = $_POST['file_path'];
$token = bin2hex(random_bytes(16));
$expire = time() + 86400; // 24小时
$tokens = json_decode(file_get_contents($tokenFile), true);
$tokens[$token] = ['path' => $filePath, 'expire' => $expire];
file_put_contents($tokenFile, json_encode($tokens));
$shareUrl = (isset($_SERVER['HTTPS']) ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . dirname($_SERVER['SCRIPT_NAME']) . '/share.php?token=' . $token;
header('Content-Type: application/json');
echo json_encode(['url' => $shareUrl]);
exit;
}
if ($action === 'delete' && !empty($deleteFile)) {
$targetPath = $path ? $path . '/' . $deleteFile : $deleteFile;
$client->deleteFile($targetPath);
$message = "已删除: " . htmlspecialchars($deleteFile);
header('Location: ?path=' . urlencode($path));
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['upload_file'])) {
$uploadFile = $_FILES['upload_file'];
if ($uploadFile['error'] === UPLOAD_ERR_OK) {
$remotePath = $path ? $path . '/' . basename($uploadFile['name']) : basename($uploadFile['name']);
$client->uploadFile($uploadFile['tmp_name'], $remotePath);
$message = "上传成功: " . htmlspecialchars(basename($uploadFile['name']));
} else {
$error = "上传失败: " . $uploadFile['error'];
}
header('Location: ?path=' . urlencode($path));
exit;
}
if ($action === 'download' && !empty($file)) {
$remotePath = $path ? $path . '/' . $file : $file;
$ch = curl_init(WEBDAV_URL . '/' . ltrim($remotePath, '/'));
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, WEBDAV_USER . ':' . WEBDAV_PASS);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . rawurlencode($file) . '"');
curl_exec($ch);
curl_close($ch);
exit;
}
$files = $client->listFiles($path);
} catch (Exception $e) {
$error = $e->getMessage();
$files = [];
}
$parentPath = '';
if (!empty($path)) {
$parts = explode('/', $path);
array_pop($parts);
$parentPath = implode('/', $parts);
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebDAV 文件管理器</title>
<style>
body { font-family: 'Segoe UI', Arial, sans-serif; margin: 20px; background: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
h2 { color: #333; margin-top: 0; }
.nav-bar { margin-bottom: 20px; padding: 10px; background: #f0f0f0; border-radius: 5px; }
.current-path { font-family: monospace; background: #e9ecef; padding: 5px 10px; border-radius: 3px; display: inline-block; }
.btn { display: inline-block; padding: 6px 12px; margin: 2px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; border: none; cursor: pointer; font-size: 14px; }
.btn-danger { background: #dc3545; }
.btn-success { background: #28a745; }
.btn-sm { padding: 3px 8px; font-size: 12px; }
.btn:hover { opacity: 0.9; }
table { border-collapse: collapse; width: 100%; background: white; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-top: 15px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #4CAF50; color: white; }
tr:nth-child(even) { background-color: #f9f9f9; }
tr:hover { background-color: #f1f1f1; }
.dir { color: #2196F3; font-weight: bold; }
.file { color: #333; }
.size { text-align: right; }
.date { font-family: monospace; }
.message { padding: 10px; margin-bottom: 15px; border-radius: 4px; background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.error { background: #f8d7da; color: #721c24; border-color: #f5c6cb; }
.upload-form { margin-top: 20px; padding: 15px; background: #f8f9fa; border-radius: 5px; border: 1px solid #ddd; }
.action-links a { margin-right: 8px; text-decoration: none; }
.modal { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); z-index: 1000; min-width: 400px; }
.overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 999; }
</style>
</head>
<body>
<div class="container">
<h2>📁 WebDAV 文件管理器</h2>
<?php if ($message): ?>
<div class="message"><?= htmlspecialchars($message) ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="error"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<div class="nav-bar">
<strong>当前路径:</strong>
<span class="current-path">/<?= htmlspecialchars($path) ?></span>
<?php if ($parentPath !== '' || !empty($path)): ?>
<a href="?path=<?= urlencode($parentPath) ?>" class="btn btn-sm">⬆ 返回上级</a>
<?php endif; ?>
<a href="?path=<?= urlencode($path) ?>" class="btn btn-sm">🔄 刷新</a>
</div>
<?php if (empty($files)): ?>
<p>此目录为空。</p>
<?php else: ?>
<table>
<thead>
<tr><th>名称</th><th>类型</th><th>大小</th><th>最后修改时间</th><th>操作</th></tr>
</thead>
<tbody>
<?php foreach ($files as $item): ?>
<tr>
<td class="<?= $item['type'] === 'dir' ? 'dir' : 'file' ?>">
<?php if ($item['type'] === 'dir'): ?>
<a href="?path=<?= urlencode($path ? $path . '/' . $item['raw_name'] : $item['raw_name']) ?>">
📁 <?= htmlspecialchars($item['name']) ?>
</a>
<?php else: ?>
📄 <?= htmlspecialchars($item['name']) ?>
<?php endif; ?>
</td>
<td><?= $item['type'] === 'dir' ? '目录' : '文件' ?></td>
<td class="size"><?= $item['type'] === 'dir' ? '-' : $client->formatSize($item['size']) ?></td>
<td class="date"><?= htmlspecialchars($item['lastmodified']) ?></td>
<td class="action-links">
<?php if ($item['type'] === 'file'): ?>
<a href="?action=download&path=<?= urlencode($path) ?>&file=<?= urlencode($item['raw_name']) ?>" class="btn btn-sm">⬇ 下载</a>
<button class="btn btn-sm btn-success">🔗 分享</button>
<?php endif; ?>
<a href="?action=delete&path=<?= urlencode($path) ?>&delete=<?= urlencode($item['raw_name']) ?>" class="btn btn-sm btn-danger">🗑 删除</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
<div class="upload-form">
<h3>上传文件到当前目录</h3>
<form method="post" enctype="multipart/form-data">
<input type="file" name="upload_file" required>
<button type="submit" class="btn btn-success">上传</button>
</form>
</div>
</div>
<div id="shareModal" class="modal">
<h3>分享文件:<span id="shareFileName"></span></h3>
<p>链接(24小时有效,不含密码):</p>
<input type="text" id="shareLink" readonly style="width:100%; padding:8px; margin:10px 0; border:1px solid #ccc; border-radius:4px;">
<button class="btn btn-sm btn-success">复制链接</button>
<button class="btn btn-sm">关闭</button>
</div>
<div id="modalOverlay" class="overlay"></div>
<script>
function createShare(filePath, fileName) {
fetch('?action=create_share', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'file_path=' + encodeURIComponent(filePath)
})
.then(res => res.json())
.then(data => {
document.getElementById('shareFileName').innerText = fileName;
document.getElementById('shareLink').value = data.url;
document.getElementById('shareModal').style.display = 'block';
document.getElementById('modalOverlay').style.display = 'block';
})
.catch(err => alert('生成分享链接失败: ' + err));
}
function copyLink() {
const link = document.getElementById('shareLink');
link.select();
document.execCommand('copy');
alert('链接已复制到剪贴板');
}
function closeModal() {
document.getElementById('shareModal').style.display = 'none';
document.getElementById('modalOverlay').style.display = 'none';
}
document.getElementById('modalOverlay').onclick = closeModal;
</script>
</body>
</html>
share.php 修改以下
> define('WEBDAV_URL', 'webdav服务器链接');
> define('WEBDAV_USER', '用户名');
> define('WEBDAV_PASS', '密码');
[PHP] 纯文本查看 复制代码 <?php
// share.php - 分享代理,独立运行,不包含其他文件
if (ob_get_level()) ob_end_clean(); // 清除可能存在的输出缓冲
define('WEBDAV_URL', '链接');
define('WEBDAV_USER', '用户名');
define('WEBDAV_PASS', '密码');
$token = $_GET['token'] ?? '';
if (empty($token)) {
http_response_code(400);
exit('Missing token');
}
$tokenFile = __DIR__ . '/share_tokens.json';
if (!file_exists($tokenFile)) {
http_response_code(404);
exit('Invalid token');
}
$tokens = json_decode(file_get_contents($tokenFile), true);
if (!isset($tokens[$token])) {
http_response_code(404);
exit('Invalid token');
}
$data = $tokens[$token];
if ($data['expire'] < time()) {
unset($tokens[$token]);
file_put_contents($tokenFile, json_encode($tokens));
http_response_code(410);
exit('Share link expired');
}
$filePath = $data['path'];
if (strpos($filePath, '..') !== false) {
http_response_code(403);
exit('Invalid path');
}
// 根据扩展名设置 Content-Type
$ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
$mimeTypes = [
'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png',
'gif' => 'image/gif', 'webp' => 'image/webp', 'svg' => 'image/svg+xml',
'txt' => 'text/plain', 'html' => 'text/html', 'css' => 'text/css',
'js' => 'application/javascript', 'pdf' => 'application/pdf',
'mp4' => 'video/mp4', 'mp3' => 'audio/mpeg',
];
$contentType = $mimeTypes[$ext] ?? 'application/octet-stream';
// 发送正确的响应头(确保在此之前没有任何输出)
header('Content-Type: ' . $contentType);
header('Content-Disposition: inline; filename="' . rawurlencode(basename($filePath)) . '"');
header('Cache-Control: public, max-age=86400');
// 从 WebDAV 获取并输出文件
$url = WEBDAV_URL . '/' . ltrim($filePath, '/');
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, WEBDAV_USER . ':' . WEBDAV_PASS);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_exec($ch);
curl_close($ch);
如Infinityfree这种虚拟主机也是可以正常使用的
如果分享的文件是图片
如果是txt 会直接显示
其它格式会直接下载
|
免费评分
-
查看全部评分
|