mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-20 08:03:55 +08:00
fix: Implement file sharing password processing and remote download file name processing (#13452)
This commit is contained in:
@@ -40,7 +40,7 @@ func Proxy() gin.HandlerFunc {
|
||||
|
||||
apiReq := c.GetBool("API_AUTH")
|
||||
|
||||
if !apiReq && !isLocalAPI(reqPath) && !isPublicFileShareAPI(reqPath) && !checkSession(c) {
|
||||
if !apiReq && !isLocalAPI(reqPath) && !middleware.IsPublicFileShareAPI(reqPath) && !checkSession(c) {
|
||||
data, _ := res.ErrorMsg.ReadFile("html/401.html")
|
||||
c.Data(401, "text/html; charset=utf-8", data)
|
||||
c.Abort()
|
||||
@@ -97,7 +97,3 @@ func checkSession(c *gin.Context) bool {
|
||||
func isLocalAPI(urlPath string) bool {
|
||||
return urlPath == "/api/v2/core/xpack/sync/ssl" || urlPath == "/api/v2/core/xpack/settings/search"
|
||||
}
|
||||
|
||||
func isPublicFileShareAPI(urlPath string) bool {
|
||||
return urlPath == "/api/v2/files/share/download" || urlPath == "/api/v2/files/share/check" || urlPath == "/api/v2/files/share/info"
|
||||
}
|
||||
|
||||
@@ -11,3 +11,14 @@ func ShouldProxyToAgent(reqPath string) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func IsPublicFileShareAPI(reqPath string) bool {
|
||||
switch reqPath {
|
||||
case "/api/v2/files/share/info",
|
||||
"/api/v2/files/share/check",
|
||||
"/api/v2/files/share/download":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@ func PasswordExpired() gin.HandlerFunc {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if IsPublicFileShareAPI(c.Request.URL.Path) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(c.Request.URL.Path, "/api/v2/core/auth") ||
|
||||
c.Request.URL.Path == "/api/v2/core/settings/search" ||
|
||||
c.Request.URL.Path == "/api/v2/core/settings/search/base" ||
|
||||
|
||||
@@ -200,3 +200,74 @@ export const resolveEditorLanguage = (path: string, extension = '', name = '') =
|
||||
|
||||
return 'yaml';
|
||||
};
|
||||
|
||||
const normalizeUrlFilename = (value: string) => {
|
||||
let decoded = value.trim().replace(/^['"]|['"]$/g, '');
|
||||
try {
|
||||
decoded = decodeURIComponent(decoded);
|
||||
} catch {
|
||||
// Keep the original value when it contains an incomplete escape sequence.
|
||||
}
|
||||
return (
|
||||
decoded
|
||||
.replace(/[\u0000-\u001f\u007f]/g, '')
|
||||
.split(/[\\/]/)
|
||||
.pop()
|
||||
?.trim() || ''
|
||||
);
|
||||
};
|
||||
|
||||
const getFilenameFromContentDisposition = (contentDisposition: string) => {
|
||||
const extendedMatch = contentDisposition.match(/(?:^|;)\s*filename\*\s*=\s*(?:"([^"]*)"|([^;]*))/i);
|
||||
const extendedValue = extendedMatch?.[1] || extendedMatch?.[2]?.trim();
|
||||
if (extendedValue) {
|
||||
const encodedValue = extendedValue.match(/^[^']*'[^']*'(.*)$/)?.[1] || extendedValue;
|
||||
return normalizeUrlFilename(encodedValue);
|
||||
}
|
||||
|
||||
const filenameMatch = contentDisposition.match(/(?:^|;)\s*filename\s*=\s*(?:"([^"]*)"|([^;]*))/i);
|
||||
return normalizeUrlFilename(filenameMatch?.[1] || filenameMatch?.[2] || '');
|
||||
};
|
||||
|
||||
export const getFilenameFromUrl = (value: string) => {
|
||||
const normalizedValue = value.trim();
|
||||
try {
|
||||
const url = new URL(normalizedValue);
|
||||
const dispositionKeys = ['response-content-disposition', 'rscd', 'content-disposition'];
|
||||
for (const key of dispositionKeys) {
|
||||
const disposition = Array.from(url.searchParams.entries()).find(
|
||||
([paramKey]) => paramKey.toLowerCase() === key,
|
||||
)?.[1];
|
||||
if (disposition) {
|
||||
const filename = getFilenameFromContentDisposition(disposition);
|
||||
if (filename) {
|
||||
return filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
return normalizeUrlFilename(url.pathname.slice(url.pathname.lastIndexOf('/') + 1));
|
||||
} catch {
|
||||
const urlWithoutParams = normalizedValue.replace(/[?#].*$/, '');
|
||||
return normalizeUrlFilename(urlWithoutParams.slice(urlWithoutParams.lastIndexOf('/') + 1));
|
||||
}
|
||||
};
|
||||
|
||||
const FILE_SHARE_PASSWORD_KEY = 'k';
|
||||
|
||||
export const withFileSharePassword = (value: string, password: string) => {
|
||||
const shareUrl = new URL(value);
|
||||
const hashParams = new URLSearchParams(shareUrl.hash.slice(1));
|
||||
const normalizedPassword = password.trim();
|
||||
if (normalizedPassword) {
|
||||
hashParams.set(FILE_SHARE_PASSWORD_KEY, normalizedPassword);
|
||||
} else {
|
||||
hashParams.delete(FILE_SHARE_PASSWORD_KEY);
|
||||
}
|
||||
shareUrl.hash = hashParams.toString();
|
||||
return shareUrl.toString();
|
||||
};
|
||||
|
||||
export const getFileSharePasswordFromHash = (hash: string) => {
|
||||
const hashParams = new URLSearchParams(hash.replace(/^#/, ''));
|
||||
return hashParams.get(FILE_SHARE_PASSWORD_KEY)?.trim() || '';
|
||||
};
|
||||
|
||||
@@ -100,7 +100,7 @@ import { File } from '@/api/interface/file';
|
||||
import { CopyDocument, Download, Picture } from '@element-plus/icons-vue';
|
||||
import i18n from '@/lang';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
import { buildFileSharePageUrl, buildFileShareQrCodeUrl } from '@/utils/file';
|
||||
import { buildFileSharePageUrl, buildFileShareQrCodeUrl, withFileSharePassword } from '@/utils/file';
|
||||
import { copyText } from '@/utils/clipboard';
|
||||
import { dateFormat as formatDateTime } from '@/utils/date';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
@@ -266,13 +266,7 @@ const cancelShare = async () => {
|
||||
|
||||
const copyLink = () => {
|
||||
if (shareUrl.value) {
|
||||
const password = form.sharePassword.trim();
|
||||
const content = password
|
||||
? `${i18n.global.t('file.shareLinkLabel')}:${shareUrl.value},${i18n.global.t(
|
||||
'file.sharePassword',
|
||||
)}:${password}`
|
||||
: `${i18n.global.t('file.shareLinkLabel')}:${shareUrl.value}`;
|
||||
copyText(content);
|
||||
copyText(withFileSharePassword(shareUrl.value, form.sharePassword));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ import { FormInstance, FormRules } from 'element-plus';
|
||||
import { reactive, ref } from 'vue';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { getFilenameFromUrl } from '@/utils/file';
|
||||
|
||||
interface WgetProps {
|
||||
path: string;
|
||||
@@ -143,8 +144,7 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||
};
|
||||
|
||||
const getFileName = (url: string) => {
|
||||
const paths = url.split('/');
|
||||
addForm.name = paths[paths.length - 1];
|
||||
addForm.name = getFilenameFromUrl(url);
|
||||
};
|
||||
|
||||
const acceptParams = (props: WgetProps) => {
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
import { checkFileShare, getPublicFileShareInfo } from '@/api/modules/files';
|
||||
import { File } from '@/api/interface/file';
|
||||
import i18n, { loadLocaleMessages } from '@/lang';
|
||||
import { buildFileShareDownloadUrl } from '@/utils/file';
|
||||
import { buildFileShareDownloadUrl, getFileSharePasswordFromHash } from '@/utils/file';
|
||||
import { dateFormat } from '@/utils/date';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
@@ -80,6 +80,15 @@ const triggerDownload = (pwd = '') => {
|
||||
window.location.href = buildFileShareDownloadUrl(code.value, currentNode.value, pwd);
|
||||
};
|
||||
|
||||
const applySharedPassword = () => {
|
||||
const sharedPassword = getFileSharePasswordFromHash(window.location.hash);
|
||||
if (!sharedPassword) {
|
||||
return;
|
||||
}
|
||||
password.value = sharedPassword;
|
||||
window.history.replaceState(window.history.state, '', `${window.location.pathname}${window.location.search}`);
|
||||
};
|
||||
|
||||
const resolveBrowserLocale = () => {
|
||||
if (typeof navigator === 'undefined') {
|
||||
return 'en';
|
||||
@@ -151,6 +160,7 @@ const downloadWithPassword = async () => {
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
applySharedPassword();
|
||||
await applyPublicLocale();
|
||||
await loadShareInfo();
|
||||
if (shareInfo.value && !shareInfo.value.hasPassword) {
|
||||
|
||||
Reference in New Issue
Block a user