fix: harden cloud storage security
This commit is contained in:
@@ -1040,6 +1040,13 @@
|
||||
<span v-else> | 有效期: <strong class="share-expire-time valid">永久有效</strong></span>
|
||||
</p>
|
||||
|
||||
<div v-if="shareInfo.share_type !== 'file'" class="share-meta-bar" style="justify-content: space-between;">
|
||||
<span>当前位置:/{{ currentPath || '' }}</span>
|
||||
<button v-if="currentPath" class="btn btn-secondary" style="width: auto; padding: 8px 14px;" @click="goParentDirectory">
|
||||
<i class="fas fa-arrow-left"></i> 返回上级
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 视图切换按钮 (多文件时才显示) -->
|
||||
<div v-if="files.length > 1" class="view-controls">
|
||||
<button class="btn" :class="viewMode === 'grid' ? 'btn-primary' : 'btn-secondary'" @click="viewMode = 'grid'">
|
||||
@@ -1061,6 +1068,9 @@
|
||||
<i class="single-file-icon fas" :class="getFileIcon(viewingFile || files[0])" :style="getIconColor(viewingFile || files[0])"></i>
|
||||
<div class="single-file-name">{{ (viewingFile || files[0]).name }}</div>
|
||||
<div class="single-file-size">{{ (viewingFile || files[0]).sizeFormatted }}</div>
|
||||
<button v-if="(viewingFile || files[0]).isDirectory" class="btn single-file-download" @click="enterDirectory(viewingFile || files[0])">
|
||||
<i class="fas fa-folder-open"></i> 进入文件夹
|
||||
</button>
|
||||
<button v-if="!(viewingFile || files[0]).isDirectory" class="btn single-file-download" @click="downloadFile(viewingFile || files[0])">
|
||||
<i class="fas fa-download"></i> 下载文件
|
||||
</button>
|
||||
@@ -1083,7 +1093,7 @@
|
||||
|
||||
<!-- 列表视图 -->
|
||||
<ul v-else-if="!viewingFile" class="file-list">
|
||||
<li v-for="file in files" :key="file.name" class="file-item">
|
||||
<li v-for="file in files" :key="file.name" class="file-item" @click="handleFileClick(file)" style="cursor: pointer;">
|
||||
<div class="file-info">
|
||||
<i class="file-icon fas" :class="getFileIcon(file)" :style="getIconColor(file)"></i>
|
||||
<div class="file-name-container">
|
||||
@@ -1126,6 +1136,7 @@
|
||||
shareNotFound: false,
|
||||
shareInfo: null,
|
||||
files: [],
|
||||
currentPath: '',
|
||||
loading: true,
|
||||
errorMessage: '',
|
||||
downloadAlertMessage: '',
|
||||
@@ -1196,6 +1207,8 @@
|
||||
async verifyShare() {
|
||||
this.errorMessage = '';
|
||||
this.downloadAlertMessage = '';
|
||||
this.currentPath = '';
|
||||
this.viewingFile = null;
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
@@ -1210,40 +1223,45 @@
|
||||
// 如果是单文件分享且后端已返回文件信息,直接使用,无需再次请求
|
||||
if (response.data.file) {
|
||||
this.files = [response.data.file];
|
||||
this.loading = false;
|
||||
} else {
|
||||
// 目录分享,需要加载文件列表
|
||||
await this.loadFiles();
|
||||
}
|
||||
} else {
|
||||
this.errorMessage = response.data.message || '验证失败';
|
||||
}
|
||||
} catch (error) {
|
||||
// 404错误 - 分享不存在
|
||||
if (error.response?.status === 404) {
|
||||
this.shareNotFound = true;
|
||||
this.loading = false;
|
||||
}
|
||||
// 需要密码
|
||||
else if (error.response?.data?.needPassword) {
|
||||
this.needPassword = true;
|
||||
this.loading = false;
|
||||
}
|
||||
// 其他错误
|
||||
else {
|
||||
this.errorMessage = error.response?.data?.message || '验证失败';
|
||||
this.loading = false;
|
||||
}
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async loadFiles() {
|
||||
async loadFiles(path = this.currentPath || '') {
|
||||
this.loading = true;
|
||||
this.currentPath = String(path || '').replace(/^\/+|\/+$/g, '');
|
||||
this.viewingFile = null;
|
||||
try {
|
||||
const response = await axios.post(`${this.apiBase}/api/share/${this.shareCode}/list`, {
|
||||
password: this.password,
|
||||
path: ''
|
||||
path: this.currentPath
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
this.files = response.data.items;
|
||||
} else {
|
||||
this.errorMessage = response.data.message || '加载文件失败';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载文件失败:', error);
|
||||
@@ -1255,10 +1273,32 @@
|
||||
|
||||
// 处理文件点击 - 显示文件详情页面
|
||||
handleFileClick(file) {
|
||||
// 所有文件类型都显示详情页面(分享页面不提供媒体预览)
|
||||
if (file?.isDirectory) {
|
||||
this.enterDirectory(file);
|
||||
return;
|
||||
}
|
||||
this.viewFileDetail(file);
|
||||
},
|
||||
|
||||
joinSharePath(basePath, name) {
|
||||
return [basePath, name]
|
||||
.map(part => String(part || '').replace(/^\/+|\/+$/g, ''))
|
||||
.filter(Boolean)
|
||||
.join('/');
|
||||
},
|
||||
|
||||
enterDirectory(file) {
|
||||
if (!file?.isDirectory) return;
|
||||
this.loadFiles(this.joinSharePath(this.currentPath, file.name));
|
||||
},
|
||||
|
||||
goParentDirectory() {
|
||||
if (!this.currentPath) return;
|
||||
const parts = this.currentPath.split('/').filter(Boolean);
|
||||
parts.pop();
|
||||
this.loadFiles(parts.join('/'));
|
||||
},
|
||||
|
||||
// 查看文件详情(放大显示)
|
||||
viewFileDetail(file) {
|
||||
this.viewingFile = file;
|
||||
@@ -1279,8 +1319,9 @@
|
||||
filePath = this.shareInfo.share_path;
|
||||
} else {
|
||||
// 目录分享,组合路径
|
||||
const basePath = this.shareInfo.share_path;
|
||||
filePath = basePath === '/' ? `/${file.name}` : `${basePath}/${file.name}`;
|
||||
const basePath = String(this.shareInfo.share_path || '/').replace(/\/+$/g, '') || '/';
|
||||
const relativePath = this.joinSharePath(this.currentPath, file.name);
|
||||
filePath = basePath === '/' ? `/${relativePath}` : `${basePath}/${relativePath}`;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -1295,11 +1336,10 @@
|
||||
// OSS 直连下载:新窗口打开
|
||||
console.log("[分享下载] OSS 直连下载");
|
||||
|
||||
// 仅直连下载需要单独记录下载次数(本地代理下载在后端接口内已计数)
|
||||
axios.post(`${this.apiBase}/api/share/${this.shareCode}/download`)
|
||||
.catch(err => console.error('记录下载次数失败:', err));
|
||||
|
||||
window.open(data.downloadUrl, '_blank');
|
||||
const downloadWindow = window.open(data.downloadUrl, '_blank', 'noopener,noreferrer');
|
||||
if (downloadWindow) {
|
||||
downloadWindow.opener = null;
|
||||
}
|
||||
} else {
|
||||
// 本地存储:通过后端下载
|
||||
console.log("[分享下载] 后端代理下载");
|
||||
@@ -1474,7 +1514,7 @@
|
||||
const csrfToken = document.cookie
|
||||
.split('; ')
|
||||
.find(row => row.startsWith('csrf_token='))
|
||||
?.split('=')[1];
|
||||
?.substring('csrf_token='.length);
|
||||
|
||||
if (csrfToken && ['POST', 'PUT', 'DELETE', 'PATCH'].includes(config.method?.toUpperCase())) {
|
||||
config.headers['X-CSRF-Token'] = csrfToken;
|
||||
|
||||
Reference in New Issue
Block a user