mirror of
https://github.com/Kuingsmile/PicList.git
synced 2025-01-23 06:38:13 -05:00
✨ Feature: webdav picbed now support digest auth
This commit is contained in:
parent
09a636f414
commit
2e655a4ebd
@ -43,7 +43,7 @@
|
||||
"@octokit/rest": "^19.0.7",
|
||||
"@picgo/i18n": "^1.0.0",
|
||||
"@picgo/store": "^2.1.0",
|
||||
"@smithy/node-http-handler": "^2.1.2",
|
||||
"@smithy/node-http-handler": "^2.1.3",
|
||||
"@types/mime-types": "^2.1.1",
|
||||
"@videojs-player/vue": "^1.0.0",
|
||||
"ali-oss": "^6.18.1",
|
||||
@ -68,7 +68,7 @@
|
||||
"mitt": "^3.0.1",
|
||||
"node-ssh-no-cpu-features": "^1.0.1",
|
||||
"nodejs-file-downloader": "^4.12.1",
|
||||
"piclist": "^1.1.0",
|
||||
"piclist": "^1.1.1",
|
||||
"pinia": "^2.1.6",
|
||||
"pinia-plugin-persistedstate": "^3.2.0",
|
||||
"qiniu": "^7.9.0",
|
||||
@ -126,7 +126,7 @@
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"husky": "^3.1.0",
|
||||
"node-loader": "^2.0.0",
|
||||
"npm-check-updates": "^16.13.3",
|
||||
"npm-check-updates": "^16.14.0",
|
||||
"stylus": "^0.59.0",
|
||||
"stylus-loader": "^7.1.3",
|
||||
"typescript": "^4.9.5",
|
||||
|
@ -617,6 +617,7 @@ MANAGE_CONSTANT_WEBDAV_PROXY_PLACEHOLDER: 'e.g. http://127.0.0.1:1080'
|
||||
MANAGE_CONSTANT_WEBDAV_PROXY_TOOLTIP: If special network environment is required to access, please use proxy
|
||||
MANAGE_CONSTANT_WEBDAV_SSL_DESC: Use HTTPS Connection
|
||||
MANAGE_CONSTANT_WEBDAV_SSL_TOOLTIP: Depending on the configuration of your WebDAV server, if your server does not support HTTPS, please turn off this option
|
||||
MANAGE_CONSTANT_WEBDAV_AUTH_TYPE_DESC: Authentication Type
|
||||
MANAGE_CONSTANT_WEBDAV_EXPLAIN: 'WebDAV Configuration'
|
||||
MANAGE_CONSTANT_WEBDAV_REFER_TEXT: 'Refer to:'
|
||||
|
||||
|
@ -620,6 +620,7 @@ MANAGE_CONSTANT_WEBDAV_PROXY_PLACEHOLDER: '例如:http://127.0.0.1:1080'
|
||||
MANAGE_CONSTANT_WEBDAV_PROXY_TOOLTIP: 如果需要特殊网络环境才能访问,请使用代理
|
||||
MANAGE_CONSTANT_WEBDAV_SSL_DESC: 使用HTTPS连接
|
||||
MANAGE_CONSTANT_WEBDAV_SSL_TOOLTIP: 根据WebDAV服务器的配置,如果您的服务器不支持HTTPS,请关闭该选项
|
||||
MANAGE_CONSTANT_WEBDAV_AUTH_TYPE_DESC: 认证类型
|
||||
MANAGE_CONSTANT_WEBDAV_EXPLAIN: 'WebDAV配置'
|
||||
MANAGE_CONSTANT_WEBDAV_REFER_TEXT: '配置教程请参考: '
|
||||
|
||||
|
@ -617,6 +617,7 @@ MANAGE_CONSTANT_WEBDAV_PROXY_PLACEHOLDER: '例如:http://127.0.0.1:1080'
|
||||
MANAGE_CONSTANT_WEBDAV_PROXY_TOOLTIP: 如果需要特殊網路環境才能訪問,請使用代理
|
||||
MANAGE_CONSTANT_WEBDAV_SSL_DESC: 使用HTTPS連線
|
||||
MANAGE_CONSTANT_WEBDAV_SSL_TOOLTIP: 根據WebDAV伺服器的配置,如果您的伺服器不支援HTTPS,請關閉該選項
|
||||
MANAGE_CONSTANT_WEBDAV_AUTH_TYPE_DESC: 認證類型
|
||||
MANAGE_CONSTANT_WEBDAV_EXPLAIN: 'WebDAV配置'
|
||||
MANAGE_CONSTANT_WEBDAV_REFER_TEXT: '配置教程請參考: '
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
import ManageLogger from '../utils/logger'
|
||||
|
||||
// WebDAV 客户端库
|
||||
import { createClient, WebDAVClient, FileStat, ProgressEvent } from 'webdav'
|
||||
import { createClient, WebDAVClient, FileStat, ProgressEvent, AuthType, WebDAVClientOptions } from 'webdav'
|
||||
|
||||
// 错误格式化函数、端点地址格式化函数、获取内部代理、新的下载器、并发异步任务池
|
||||
import { formatError, formatEndpoint, getInnerAgent, NewDownloader, ConcurrencyPromisePool } from '../utils/common'
|
||||
@ -34,6 +34,7 @@ import path from 'path'
|
||||
|
||||
// 取消下载任务的加载文件列表、刷新下载文件传输列表
|
||||
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/manage/utils/static'
|
||||
import { getAuthHeader } from '@/manage/utils/digestAuth'
|
||||
|
||||
class WebdavplistApi {
|
||||
endpoint: string
|
||||
@ -42,29 +43,35 @@ class WebdavplistApi {
|
||||
sslEnabled: boolean
|
||||
proxy: string | undefined
|
||||
proxyStr: string | undefined
|
||||
authType: 'basic' | 'digest' | undefined
|
||||
logger: ManageLogger
|
||||
agent: https.Agent | http.Agent
|
||||
ctx: WebDAVClient
|
||||
|
||||
constructor (endpoint: string, username: string, password: string, sslEnabled: boolean, proxy: string | undefined, logger: ManageLogger) {
|
||||
constructor (endpoint: string, username: string, password: string, sslEnabled: boolean, proxy: string | undefined, authType: 'basic' | 'digest' | undefined, logger: ManageLogger) {
|
||||
this.endpoint = formatEndpoint(endpoint, sslEnabled)
|
||||
this.username = username
|
||||
this.password = password
|
||||
this.sslEnabled = sslEnabled
|
||||
this.proxy = proxy
|
||||
this.proxyStr = formatHttpProxy(proxy, 'string') as string | undefined
|
||||
this.authType = authType || 'basic'
|
||||
this.logger = logger
|
||||
this.agent = getInnerAgent(proxy, sslEnabled).agent
|
||||
const options: WebDAVClientOptions = {
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
maxBodyLength: 4 * 1024 * 1024 * 1024,
|
||||
maxContentLength: 4 * 1024 * 1024 * 1024,
|
||||
httpsAgent: sslEnabled ? this.agent : undefined,
|
||||
httpAgent: !sslEnabled ? this.agent : undefined
|
||||
}
|
||||
if (this.authType === 'digest') {
|
||||
options.authType = AuthType.Digest
|
||||
}
|
||||
this.ctx = createClient(
|
||||
this.endpoint,
|
||||
{
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
maxBodyLength: 4 * 1024 * 1024 * 1024,
|
||||
maxContentLength: 4 * 1024 * 1024 * 1024,
|
||||
httpsAgent: sslEnabled ? this.agent : undefined,
|
||||
httpAgent: !sslEnabled ? this.agent : undefined
|
||||
}
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
@ -275,7 +282,7 @@ class WebdavplistApi {
|
||||
})
|
||||
this.ctx.putFileContents(
|
||||
key,
|
||||
fs.createReadStream(filePath),
|
||||
this.authType === 'digest' ? fs.readFileSync(filePath) : fs.createReadStream(filePath),
|
||||
{
|
||||
overwrite: true,
|
||||
onUploadProgress: (progressEvent: ProgressEvent) => {
|
||||
@ -347,12 +354,21 @@ class WebdavplistApi {
|
||||
sourceFileName: fileName,
|
||||
targetFilePath: savedFilePath
|
||||
})
|
||||
const preSignedUrl = await this.getPreSignedUrl({
|
||||
let preSignedUrl = await this.getPreSignedUrl({
|
||||
key
|
||||
})
|
||||
const base64Str = Buffer.from(`${this.username}:${this.password}`).toString('base64')
|
||||
const headers = {
|
||||
Authorization: `Basic ${base64Str}`
|
||||
let headers = {} as IStringKeyMap
|
||||
if (this.authType === 'basic' || !this.authType) {
|
||||
const base64Str = Buffer.from(`${this.username}:${this.password}`).toString('base64')
|
||||
headers = {
|
||||
Authorization: `Basic ${base64Str}`
|
||||
}
|
||||
} else if (this.authType === 'digest') {
|
||||
const authHeader = await getAuthHeader('GET', this.endpoint, `/${key.replace(/^\/+/, '')}`, this.username, this.password)
|
||||
headers = {
|
||||
Authorization: authHeader
|
||||
}
|
||||
preSignedUrl = `${this.endpoint}/${key.replace(/^\/+/, '')}`
|
||||
}
|
||||
promises.push(() => new Promise((resolve, reject) => {
|
||||
NewDownloader(instance, preSignedUrl, id, savedFilePath, this.logger, this.proxyStr, headers)
|
||||
|
@ -75,7 +75,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
|
||||
case 'upyun':
|
||||
return new API.UpyunApi(this.currentPicBedConfig.bucketName, this.currentPicBedConfig.operator, this.currentPicBedConfig.password, this.logger, this.currentPicBedConfig.antiLeechToken, this.currentPicBedConfig.expireTime)
|
||||
case 'webdavplist':
|
||||
return new API.WebdavplistApi(this.currentPicBedConfig.endpoint, this.currentPicBedConfig.username, this.currentPicBedConfig.password, this.currentPicBedConfig.sslEnabled, this.currentPicBedConfig.proxy, this.logger)
|
||||
return new API.WebdavplistApi(this.currentPicBedConfig.endpoint, this.currentPicBedConfig.username, this.currentPicBedConfig.password, this.currentPicBedConfig.sslEnabled, this.currentPicBedConfig.proxy, this.currentPicBedConfig.authType, this.logger)
|
||||
default:
|
||||
return {} as any
|
||||
}
|
||||
|
@ -1,16 +1,20 @@
|
||||
import { createClient } from 'webdav'
|
||||
import { AuthType, WebDAVClientOptions, createClient } from 'webdav'
|
||||
import { formatEndpoint } from '~/main/manage/utils/common'
|
||||
|
||||
export default class WebdavApi {
|
||||
static async delete (configMap: IStringKeyMap): Promise<boolean> {
|
||||
const { fileName, config: { host, username, password, path, sslEnabled } } = configMap
|
||||
const { fileName, config: { host, username, password, path, sslEnabled, authType } } = configMap
|
||||
const endpoint = formatEndpoint(host, sslEnabled)
|
||||
const options: WebDAVClientOptions = {
|
||||
username,
|
||||
password
|
||||
}
|
||||
if (authType === 'digest') {
|
||||
options.authType = AuthType.Digest
|
||||
}
|
||||
const ctx = createClient(
|
||||
endpoint,
|
||||
{
|
||||
username,
|
||||
password
|
||||
}
|
||||
options
|
||||
)
|
||||
let key
|
||||
if (path === '/' || !path) {
|
||||
|
@ -23,6 +23,8 @@
|
||||
import { ref, onMounted, watch, computed } from 'vue'
|
||||
import { getFileIconPath } from '@/manage/utils/common'
|
||||
import { Loading } from '@element-plus/icons-vue'
|
||||
import { getAuthHeader } from '@/manage/utils/digestAuth'
|
||||
import { formatEndpoint } from '~/main/manage/utils/common'
|
||||
|
||||
const base64Url = ref('')
|
||||
const success = ref(false)
|
||||
@ -41,7 +43,7 @@ const props = defineProps(
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
headers: {
|
||||
config: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
@ -56,9 +58,31 @@ const imageSource = computed(() => {
|
||||
|
||||
const iconPath = computed(() => require(`../manage/pages/assets/icons/${getFileIconPath(props.item.fileName ?? '')}`))
|
||||
|
||||
async function getheaderOfWebdav (key: string) {
|
||||
let headers = {} as any
|
||||
if (props.config.authType === 'digest') {
|
||||
const authHeader = await getAuthHeader(
|
||||
'GET',
|
||||
formatEndpoint(props.config.endpoint, props.config.sslEnabled || false),
|
||||
`/${key.replace(/^\//, '')}`,
|
||||
props.config.username,
|
||||
props.config.password
|
||||
)
|
||||
headers = {
|
||||
Authorization: authHeader
|
||||
}
|
||||
} else {
|
||||
headers = {
|
||||
Authorization: 'Basic ' + Buffer.from(`${props.config.username}:${props.config.password}`).toString('base64')
|
||||
}
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
const fetchImage = async () => {
|
||||
try {
|
||||
const res = await fetch(props.url, { method: 'GET', headers: props.headers })
|
||||
const headers = await getheaderOfWebdav(props.item.key)
|
||||
const res = await fetch(props.url, { method: 'GET', headers })
|
||||
if (res.status >= 200 && res.status < 300) {
|
||||
const blob = await res.blob()
|
||||
success.value = true
|
||||
@ -72,7 +96,7 @@ const fetchImage = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => [props.url, props.headers], fetchImage, { deep: true })
|
||||
watch(() => [props.url, props.item], fetchImage, { deep: true })
|
||||
|
||||
onMounted(fetchImage)
|
||||
|
||||
|
102
src/renderer/components/ImageWebdavTsx.tsx
Normal file
102
src/renderer/components/ImageWebdavTsx.tsx
Normal file
@ -0,0 +1,102 @@
|
||||
import { defineComponent, ref, onMounted, watch, computed } from 'vue'
|
||||
import { getFileIconPath } from '@/manage/utils/common'
|
||||
import { Loading } from '@element-plus/icons-vue'
|
||||
import { getAuthHeader } from '@/manage/utils/digestAuth'
|
||||
import { formatEndpoint } from '~/main/manage/utils/common'
|
||||
import { ElImage, ElIcon } from 'element-plus'
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
isShowThumbnail: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
setup (props) {
|
||||
const base64Url = ref('')
|
||||
const success = ref(false)
|
||||
|
||||
const imageSource = computed(() => {
|
||||
return (props.isShowThumbnail && props.item.isImage && success.value)
|
||||
? base64Url.value
|
||||
: require(`../manage/pages/assets/icons/${getFileIconPath(props.item.fileName ?? '')}`)
|
||||
})
|
||||
const iconPath = computed(() => require(`../manage/pages/assets/icons/${getFileIconPath(props.item.fileName ?? '')}`))
|
||||
|
||||
async function getheaderOfWebdav (key: string) {
|
||||
let headers = {} as any
|
||||
if (props.config.authType === 'digest') {
|
||||
const authHeader = await getAuthHeader(
|
||||
'GET',
|
||||
formatEndpoint(props.config.endpoint, props.config.sslEnabled || false),
|
||||
`/${key.replace(/^\//, '')}`,
|
||||
props.config.username,
|
||||
props.config.password
|
||||
)
|
||||
headers = {
|
||||
Authorization: authHeader
|
||||
}
|
||||
} else {
|
||||
headers = {
|
||||
Authorization: 'Basic ' + Buffer.from(`${props.config.username}:${props.config.password}`).toString('base64')
|
||||
}
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
const fetchImage = async () => {
|
||||
try {
|
||||
const headers = await getheaderOfWebdav(props.item.key)
|
||||
const res = await fetch(props.url, { method: 'GET', headers })
|
||||
if (res.status >= 200 && res.status < 300) {
|
||||
const blob = await res.blob()
|
||||
success.value = true
|
||||
base64Url.value = URL.createObjectURL(blob)
|
||||
} else {
|
||||
throw new Error('Network response was not ok.')
|
||||
}
|
||||
} catch (err) {
|
||||
success.value = false
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
watch(() => [props.url, props.item], fetchImage, { deep: true })
|
||||
onMounted(fetchImage)
|
||||
|
||||
return () => (
|
||||
<ElImage
|
||||
src={imageSource.value}
|
||||
fit="contain"
|
||||
style="height: 100px;width: 100%;margin: 0 auto;"
|
||||
>
|
||||
{{
|
||||
placeholder: () => (
|
||||
<ElIcon>
|
||||
<Loading />
|
||||
</ElIcon>
|
||||
),
|
||||
error: () => (
|
||||
<ElImage
|
||||
src={iconPath.value}
|
||||
fit="contain"
|
||||
style="height: 100px;width: 100%;margin: 0 auto;"
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</ElImage>
|
||||
)
|
||||
}
|
||||
})
|
@ -546,7 +546,7 @@ https://www.baidu.com/img/bd_logo1.png"
|
||||
v-else-if="!item.isDir && currentPicBedName === 'webdavplist' && item.isImage"
|
||||
:is-show-thumbnail="isShowThumbnail"
|
||||
:item="item"
|
||||
:headers="getBase64ofWebdav()"
|
||||
:config="handleGetWebdavConfig()"
|
||||
:url="item.url"
|
||||
@click="handleClickFile(item)"
|
||||
/>
|
||||
@ -718,6 +718,7 @@ https://www.baidu.com/img/bd_logo1.png"
|
||||
:initial-index="getCurrentPreviewIndex"
|
||||
infinite
|
||||
hide-on-click-modal
|
||||
teleported
|
||||
@close="isShowImagePreview = false"
|
||||
/>
|
||||
<el-dialog
|
||||
@ -1542,6 +1543,7 @@ import { videoExt } from '../utils/videofile'
|
||||
// 组件
|
||||
import ImageWebdav from '@/components/ImageWebdav.vue'
|
||||
import ImageLocal from '@/components/ImageLocal.vue'
|
||||
import ImageWebdavTsx from '@/components/ImageWebdavTsx'
|
||||
|
||||
// 国际化函数
|
||||
import { T as $T } from '@/i18n'
|
||||
@ -1660,6 +1662,8 @@ const batchRenameReplace = ref('')
|
||||
const isRenameIncludeExt = ref(false)
|
||||
const isSingleRename = ref(false)
|
||||
const itemToBeRenamed = ref({} as any)
|
||||
let fileTransferInterval: NodeJS.Timer | null = null
|
||||
let downloadInterval: NodeJS.Timer | null = null
|
||||
|
||||
// 当前页面信息相关
|
||||
const currentPicBedName = computed<string>(() => manageStore.config.picBed[configMap.alias].picBedName)
|
||||
@ -1711,6 +1715,10 @@ function stopRefreshUploadTask () {
|
||||
refreshUploadTaskId.value && clearInterval(refreshUploadTaskId.value)
|
||||
}
|
||||
|
||||
function handleGetWebdavConfig () {
|
||||
return manageStore.config.picBed[configMap.alias]
|
||||
}
|
||||
|
||||
// 下载相关函数
|
||||
|
||||
function showDownloadDialog () {
|
||||
@ -1736,13 +1744,6 @@ function handleViewChange (val: 'list' | 'grid') {
|
||||
layoutStyle.value = val
|
||||
}
|
||||
|
||||
function getBase64ofWebdav () {
|
||||
const headers = {
|
||||
Authorization: 'Basic ' + Buffer.from(`${manageStore.config.picBed[configMap.alias].username}:${manageStore.config.picBed[configMap.alias].password}`).toString('base64')
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
// 上传文件选择相关
|
||||
|
||||
function openFileSelectDialog () {
|
||||
@ -2468,13 +2469,13 @@ async function handleFolderBatchDownload (item: any) {
|
||||
ipcRenderer.on(refreshDownloadFileTransferList, (evt: IpcRendererEvent, data) => {
|
||||
downloadFileTransferStore.refreshDownloadFileTransferList(data)
|
||||
})
|
||||
const interval = setInterval(() => {
|
||||
downloadInterval = setInterval(() => {
|
||||
const currentFileList = downloadFileTransferStore.getDownloadFileTransferList()
|
||||
currentDownloadFileList.length = 0
|
||||
currentDownloadFileList.push(...currentFileList)
|
||||
if (downloadFileTransferStore.isFinished()) {
|
||||
clearInterval(interval)
|
||||
if (downloadFileTransferStore.isFinished() && downloadInterval) {
|
||||
isLoadingDownloadData.value = false
|
||||
clearInterval(downloadInterval)
|
||||
if (downloadFileTransferStore.isSuccess()) {
|
||||
ElNotification.success({
|
||||
title: $T('MANAGE_BUCKET_DOWNLOAD_FOLDER_BOX_TIP'),
|
||||
@ -2845,7 +2846,7 @@ async function getBucketFileListBackStage () {
|
||||
ipcRenderer.on('refreshFileTransferList', (evt: IpcRendererEvent, data) => {
|
||||
fileTransferStore.refreshFileTransferList(data)
|
||||
})
|
||||
const interval = setInterval(() => {
|
||||
fileTransferInterval = setInterval(() => {
|
||||
const currentFileList = fileTransferStore.getFileTransferList()
|
||||
currentPageFilesInfo.splice(0, currentPageFilesInfo.length, ...currentFileList)
|
||||
const sortType = localStorage.getItem('sortType') as sortTypeList || 'init'
|
||||
@ -2857,9 +2858,9 @@ async function getBucketFileListBackStage () {
|
||||
fullList: currentPageFilesInfo
|
||||
}))
|
||||
})
|
||||
if (fileTransferStore.isFinished()) {
|
||||
clearInterval(interval)
|
||||
if (fileTransferStore.isFinished() && fileTransferInterval) {
|
||||
isLoadingData.value = false
|
||||
clearInterval(fileTransferInterval)
|
||||
if (fileTransferStore.isSuccess()) {
|
||||
ElNotification.success({
|
||||
title: $T('MANAGE_BUCKET_GET_FILE_BS_NOT_TITLE'),
|
||||
@ -3411,23 +3412,36 @@ const columns: Column<any>[] = [
|
||||
{{
|
||||
reference: () => (
|
||||
!item.isDir
|
||||
? <ElImage
|
||||
src={isShowThumbnail.value ? item.isImage ? item.url : require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`) : require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)}
|
||||
fit="contain"
|
||||
style={{ width: '20px', height: '20px' }}
|
||||
>
|
||||
{{
|
||||
placeholder: () => <ElIcon>
|
||||
<Loading />
|
||||
</ElIcon>,
|
||||
error: () =>
|
||||
<ElImage
|
||||
src={require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)}
|
||||
fit="contain"
|
||||
style={{ width: '20px', height: '20px' }}
|
||||
/>
|
||||
}}
|
||||
</ElImage>
|
||||
? currentPicBedName.value !== 'webdavplist'
|
||||
? <ElImage
|
||||
src={isShowThumbnail.value ? item.isImage ? item.url : require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`) : require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)}
|
||||
fit="contain"
|
||||
style={{ width: '20px', height: '20px' }}
|
||||
>
|
||||
{{
|
||||
placeholder: () => <ElIcon>
|
||||
<Loading />
|
||||
</ElIcon>,
|
||||
error: () =>
|
||||
<ElImage
|
||||
src={require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)}
|
||||
fit="contain"
|
||||
style={{ width: '20px', height: '20px' }}
|
||||
/>
|
||||
}}
|
||||
</ElImage>
|
||||
: item.isImage
|
||||
? <ImageWebdavTsx
|
||||
isShowThumbnail={isShowThumbnail.value}
|
||||
item={item}
|
||||
config={handleGetWebdavConfig()}
|
||||
url={item.url}
|
||||
/>
|
||||
: <ElImage
|
||||
src={require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)}
|
||||
fit="contain"
|
||||
style={{ width: '20px', height: '20px' }}
|
||||
></ElImage>
|
||||
: <ElImage
|
||||
src={require('./assets/icons/folder.webp')}
|
||||
fit="contain"
|
||||
@ -3435,22 +3449,29 @@ const columns: Column<any>[] = [
|
||||
/>
|
||||
),
|
||||
default: () => (
|
||||
<ElImage
|
||||
src={item.isImage ? item.url : require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`) }
|
||||
fit="contain"
|
||||
>
|
||||
{{
|
||||
placeholder: () => (<ElIcon>
|
||||
<Loading />
|
||||
</ElIcon>
|
||||
),
|
||||
error: () => (
|
||||
<ElIcon>
|
||||
<CircleClose />
|
||||
currentPicBedName.value === 'webdavplist' && item.isImage
|
||||
? <ImageWebdavTsx
|
||||
isShowThumbnail={isShowThumbnail.value}
|
||||
item={item}
|
||||
config={handleGetWebdavConfig()}
|
||||
url={item.url}
|
||||
/>
|
||||
: <ElImage
|
||||
src={item.isImage ? item.url : require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`) }
|
||||
fit="contain"
|
||||
>
|
||||
{{
|
||||
placeholder: () => (<ElIcon>
|
||||
<Loading />
|
||||
</ElIcon>
|
||||
)
|
||||
}}
|
||||
</ElImage>
|
||||
),
|
||||
error: () => (
|
||||
<ElIcon>
|
||||
<CircleClose />
|
||||
</ElIcon>
|
||||
)
|
||||
}}
|
||||
</ElImage>
|
||||
)
|
||||
}}
|
||||
</ElPopover>
|
||||
@ -3670,6 +3691,10 @@ onBeforeMount(async () => {
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('keydown', handleDetectShiftKey)
|
||||
document.removeEventListener('keyup', handleDetectShiftKey)
|
||||
fileTransferInterval && clearInterval(fileTransferInterval)
|
||||
downloadInterval && clearInterval(downloadInterval)
|
||||
refreshUploadTaskId.value && clearInterval(refreshUploadTaskId.value)
|
||||
refreshDownloadTaskId.value && clearInterval(refreshDownloadTaskId.value)
|
||||
if (isLoadingData.value) {
|
||||
ipcRenderer.send('cancelLoadingFileList', cancelToken.value)
|
||||
}
|
||||
|
@ -576,17 +576,20 @@ async function getCurrentConfigList () {
|
||||
const config = configList[pb]
|
||||
return config?.configList?.length ? config.configList.map((item: any) => ({ ...item, type: pb })) : []
|
||||
})
|
||||
await getAllConfigAliasArray()
|
||||
|
||||
const autoImport = await getPicBedsConfig<boolean>('settings.autoImport') || false
|
||||
if (!autoImport) return
|
||||
const autoImportPicBed = initArray(await getPicBedsConfig<string | string[]>('settings.autoImportPicBed') || '', [])
|
||||
await Promise.all(filteredConfigList.flatMap((config) => transUpToManage(config, config.type, autoImportPicBed)))
|
||||
if (Object.keys(importedNewConfig).length > 0) {
|
||||
const oldConfig = await getConfig<any>('picBed')
|
||||
const newConfig = { ...oldConfig, ...importedNewConfig }
|
||||
saveConfig('picBed', newConfig)
|
||||
await manageStore.refreshConfig()
|
||||
if (autoImport) {
|
||||
const autoImportPicBed = initArray(await getPicBedsConfig<string | string[]>('settings.autoImportPicBed') || '', [])
|
||||
await Promise.all(filteredConfigList.flatMap((config) => transUpToManage(config, config.type, autoImportPicBed)))
|
||||
if (Object.keys(importedNewConfig).length > 0) {
|
||||
const oldConfig = await getConfig<any>('picBed')
|
||||
const newConfig = { ...oldConfig, ...importedNewConfig }
|
||||
saveConfig('picBed', newConfig)
|
||||
await manageStore.refreshConfig()
|
||||
}
|
||||
}
|
||||
|
||||
await getAllConfigAliasArray()
|
||||
}
|
||||
|
||||
function isImported (alias: string) {
|
||||
@ -721,6 +724,7 @@ async function transUpToManage (config: IUploaderConfigListItem, picBedName: str
|
||||
webPath: config.webpath || '',
|
||||
customUrl: config.customUrl || '',
|
||||
sslEnabled: !!config.sslEnabled,
|
||||
authType: config.authType || 'basic',
|
||||
proxy: '',
|
||||
transformedConfig: JSON.stringify({
|
||||
webdav: {
|
||||
|
@ -779,10 +779,20 @@ export const supportedPicBedList: IStringKeyMap = {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
tooltip: $T('MANAGE_CONSTANT_WEBDAV_SSL_TOOLTIP')
|
||||
},
|
||||
authType: {
|
||||
required: true,
|
||||
description: $T('MANAGE_CONSTANT_WEBDAV_AUTH_TYPE_DESC'),
|
||||
default: 'basic',
|
||||
type: 'select',
|
||||
selectOptions: {
|
||||
basic: 'Basic',
|
||||
digest: 'Digest'
|
||||
}
|
||||
}
|
||||
},
|
||||
explain: $T('MANAGE_CONSTANT_WEBDAV_EXPLAIN'),
|
||||
options: ['alias', 'endpoint', 'username', 'password', 'bucketName', 'baseDir', 'customUrl', 'webPath', 'proxy', 'sslEnabled'],
|
||||
options: ['alias', 'endpoint', 'username', 'password', 'bucketName', 'baseDir', 'customUrl', 'webPath', 'proxy', 'sslEnabled', 'authType'],
|
||||
refLink: 'https://piclist.cn/manage.html#webdav',
|
||||
referenceText: $T('MANAGE_CONSTANT_WEBDAV_REFER_TEXT')
|
||||
},
|
||||
|
74
src/renderer/manage/utils/digestAuth.ts
Normal file
74
src/renderer/manage/utils/digestAuth.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import crypto from 'crypto'
|
||||
import axios from 'axios'
|
||||
|
||||
const AUTH_KEY_VALUE_RE = /(\w+)=["']?([^'"]{1,10000})["']?/
|
||||
let NC = 0
|
||||
const NC_PAD = '00000000'
|
||||
|
||||
function md5 (text: crypto.BinaryLike) {
|
||||
return crypto.createHash('md5').update(text).digest('hex')
|
||||
}
|
||||
|
||||
export function digestAuthHeader (method: string, uri: string, wwwAuthenticate: string, username: string, password: string) {
|
||||
const parts = wwwAuthenticate.split(',')
|
||||
const opts = {} as IStringKeyMap
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const m = AUTH_KEY_VALUE_RE.exec(parts[i])
|
||||
if (m) {
|
||||
opts[m[1]] = m[2].replace(/["']/g, '')
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts.realm || !opts.nonce) {
|
||||
return ''
|
||||
}
|
||||
|
||||
let qop = opts.qop || ''
|
||||
|
||||
const userpassArray = [username, password]
|
||||
|
||||
let nc = String(++NC)
|
||||
nc = NC_PAD.substring(nc.length) + nc
|
||||
const cnonce = crypto.randomBytes(8).toString('hex')
|
||||
|
||||
const ha1 = md5(userpassArray[0] + ':' + opts.realm + ':' + userpassArray[1])
|
||||
const ha2 = md5(method.toUpperCase() + ':' + uri)
|
||||
let s = ha1 + ':' + opts.nonce
|
||||
if (qop) {
|
||||
qop = qop.split(',')[0]
|
||||
s += ':' + nc + ':' + cnonce + ':' + qop
|
||||
}
|
||||
s += ':' + ha2
|
||||
const response = md5(s)
|
||||
let authstring =
|
||||
'Digest username="' +
|
||||
userpassArray[0] +
|
||||
'", realm="' +
|
||||
opts.realm +
|
||||
'", nonce="' +
|
||||
opts.nonce +
|
||||
'", uri="' +
|
||||
uri +
|
||||
'", response="' +
|
||||
response +
|
||||
'"'
|
||||
if (opts.opaque) {
|
||||
authstring += ', opaque="' + opts.opaque + '"'
|
||||
}
|
||||
if (qop) {
|
||||
authstring += ', qop=' + qop + ', nc=' + nc + ', cnonce="' + cnonce + '"'
|
||||
}
|
||||
return authstring
|
||||
}
|
||||
|
||||
export async function getAuthHeader (method: string, host: string, uri: string, username: string, password: string) {
|
||||
try {
|
||||
await axios.get(
|
||||
`${host}${uri}`
|
||||
)
|
||||
} catch (error: any) {
|
||||
if (error.response.status === 401 && error.response.headers['www-authenticate']) {
|
||||
return digestAuthHeader(method, uri, error.response.headers['www-authenticate'], username, password)
|
||||
}
|
||||
}
|
||||
}
|
1
src/universal/types/i18n.d.ts
vendored
1
src/universal/types/i18n.d.ts
vendored
@ -579,6 +579,7 @@ interface ILocales {
|
||||
MANAGE_CONSTANT_WEBDAV_PROXY_TOOLTIP: string
|
||||
MANAGE_CONSTANT_WEBDAV_SSL_DESC: string
|
||||
MANAGE_CONSTANT_WEBDAV_SSL_TOOLTIP: string
|
||||
MANAGE_CONSTANT_WEBDAV_AUTH_TYPE_DESC: string
|
||||
MANAGE_CONSTANT_WEBDAV_EXPLAIN: string
|
||||
MANAGE_CONSTANT_WEBDAV_REFER_TEXT: string
|
||||
MANAGE_CONSTANT_LOCAL_NAME: string
|
||||
|
71
yarn.lock
71
yarn.lock
@ -5006,6 +5006,14 @@ axios@^0.26.1:
|
||||
dependencies:
|
||||
follow-redirects "^1.14.8"
|
||||
|
||||
axios@^0.27.2:
|
||||
version "0.27.2"
|
||||
resolved "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
|
||||
integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
|
||||
dependencies:
|
||||
follow-redirects "^1.14.9"
|
||||
form-data "^4.0.0"
|
||||
|
||||
axios@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz#f02e4af823e2e46a9768cfc74691fdd0517ea267"
|
||||
@ -8475,7 +8483,7 @@ follow-redirects@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.6.tgz#8cfb281bbc035b3c067d6cd975b0f6ade6e855cd"
|
||||
integrity sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==
|
||||
|
||||
follow-redirects@^1.14.8, follow-redirects@^1.15.0, follow-redirects@^1.15.1:
|
||||
follow-redirects@^1.14.8, follow-redirects@^1.14.9, follow-redirects@^1.15.0, follow-redirects@^1.15.1:
|
||||
version "1.15.2"
|
||||
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
|
||||
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
||||
@ -9440,6 +9448,11 @@ hosted-git-info@^7.0.0:
|
||||
dependencies:
|
||||
lru-cache "^10.0.1"
|
||||
|
||||
hot-patcher@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/hot-patcher/-/hot-patcher-1.0.0.tgz#7124d2dc4ca71bcb58b1551603cd13e4fc3fcecd"
|
||||
integrity sha512-3H8VH0PreeNsKMZw16nTHbUp4YoHCnPlawpsPXGJUR4qENDynl79b6Xk9CIFvLcH1qungBsCuzKcWyzoPPalTw==
|
||||
|
||||
hot-patcher@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/hot-patcher/-/hot-patcher-2.0.0.tgz#10a21b5bb4f5757316c41fc98794c11192a0a41e"
|
||||
@ -10652,6 +10665,11 @@ launch-editor@^2.2.1, launch-editor@^2.3.0:
|
||||
picocolors "^1.0.0"
|
||||
shell-quote "^1.6.1"
|
||||
|
||||
layerr@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.npmjs.org/layerr/-/layerr-0.1.2.tgz#16c8e7fb042d3595ab15492bdad088f31d7afd15"
|
||||
integrity sha512-ob5kTd9H3S4GOG2nVXyQhOu9O8nBgP555XxWPkJI0tR0JeRilfyTp8WtPdIJHLXBmHMSdEq5+KMxiYABeScsIQ==
|
||||
|
||||
layerr@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/layerr/-/layerr-2.0.1.tgz#0c98e6f599de4f76b75c7a6522c54b8c6c591ff0"
|
||||
@ -11314,6 +11332,13 @@ minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimatch@^5.1.0:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
|
||||
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimatch@^7.4.6:
|
||||
version "7.4.6"
|
||||
resolved "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz#845d6f254d8f4a5e4fd6baf44d5f10c8448365fb"
|
||||
@ -11820,10 +11845,10 @@ npm-bundled@^3.0.0:
|
||||
dependencies:
|
||||
npm-normalize-package-bin "^3.0.0"
|
||||
|
||||
npm-check-updates@^16.13.3:
|
||||
version "16.13.3"
|
||||
resolved "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.13.3.tgz#136f0e008b82c34be4b758260c3582214a07527e"
|
||||
integrity sha512-l3FQtm+ZtDwqtK2r27vCuNdtnoDsXzk8D2WczvrAJy2bGPZJvRmuUa/Q9Gv+AbZV0IHSNJD2oHtQqUeqQRhEsw==
|
||||
npm-check-updates@^16.14.0:
|
||||
version "16.14.0"
|
||||
resolved "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-16.14.0.tgz#e34b5af1176be2347b012946ad9bd0133bfed13e"
|
||||
integrity sha512-0R4S0qsx2FhuSiIYloHc7RQwfZpzO4jdL3rUoYwbOkx5fBc9u77GHHS0FlXYpczHR/kPYmmB/CRkFElOofVeSg==
|
||||
dependencies:
|
||||
chalk "^5.3.0"
|
||||
cli-table3 "^0.6.3"
|
||||
@ -11853,6 +11878,7 @@ npm-check-updates@^16.13.3:
|
||||
semver-utils "^1.1.4"
|
||||
source-map-support "^0.5.21"
|
||||
spawn-please "^2.0.1"
|
||||
strip-ansi "^7.1.0"
|
||||
strip-json-comments "^5.0.1"
|
||||
untildify "^4.0.0"
|
||||
update-notifier "^6.0.2"
|
||||
@ -12554,10 +12580,10 @@ performance-now@^2.1.0:
|
||||
resolved "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||
integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
|
||||
|
||||
piclist@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/piclist/-/piclist-1.1.0.tgz#49a015de794c4b71eaf492fec724e6c862b875c0"
|
||||
integrity sha512-G5uywChIS+RcK3yGNMGoXY4RFLIHh6Q+klQHODnz3eTITsZ8lbJy+X9WeAiC1Olx6Cjj2hhZGZVTgHQI2xDNag==
|
||||
piclist@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/piclist/-/piclist-1.1.1.tgz#b4750a8ce0c381fc1591f2c6434b77973d79f973"
|
||||
integrity sha512-8uDvjGQJLzr+PZ6vwISR8UrvytFm/SxPkZPVMSEq8+QlKW0117B2d7TICvW8MfpXKX0WMZERM/vabHNjaa9HEQ==
|
||||
dependencies:
|
||||
"@picgo/i18n" "^1.0.0"
|
||||
"@picgo/store" "^2.1.0"
|
||||
@ -12588,6 +12614,7 @@ piclist@^1.1.0:
|
||||
text-to-svg "^3.1.5"
|
||||
tunnel "^0.0.6"
|
||||
uuid "^9.0.0"
|
||||
webdav "^4.11.2"
|
||||
|
||||
picocolors@^0.2.1:
|
||||
version "0.2.1"
|
||||
@ -14700,7 +14727,7 @@ strip-ansi@^5.1.0, strip-ansi@^5.2.0:
|
||||
dependencies:
|
||||
ansi-regex "^4.1.0"
|
||||
|
||||
strip-ansi@^7.0.1:
|
||||
strip-ansi@^7.0.1, strip-ansi@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||
integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
|
||||
@ -15581,6 +15608,11 @@ uri-js@^4.2.2:
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
url-join@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7"
|
||||
integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==
|
||||
|
||||
url-join@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz#c2f1e5cbd95fa91082a93b58a1f42fecb4bdbcf1"
|
||||
@ -15974,6 +16006,25 @@ web-streams-polyfill@^3.0.3:
|
||||
resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
|
||||
integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
|
||||
|
||||
webdav@^4.11.2:
|
||||
version "4.11.3"
|
||||
resolved "https://registry.npmjs.org/webdav/-/webdav-4.11.3.tgz#c8bf9b5ed1799d432d58958433925b0fa91db08e"
|
||||
integrity sha512-NIuREBXYo5xb+zB4zy502snynbhugWjepg5Oke0ByEb7J/Ofmbk+JgFYM+sZdnKG3+XQ87rictDIF+SHUJkwlg==
|
||||
dependencies:
|
||||
axios "^0.27.2"
|
||||
base-64 "^1.0.0"
|
||||
byte-length "^1.0.2"
|
||||
fast-xml-parser "^4.2.4"
|
||||
he "^1.2.0"
|
||||
hot-patcher "^1.0.0"
|
||||
layerr "^0.1.2"
|
||||
md5 "^2.3.0"
|
||||
minimatch "^5.1.0"
|
||||
nested-property "^4.0.0"
|
||||
path-posix "^1.0.0"
|
||||
url-join "^4.0.1"
|
||||
url-parse "^1.5.10"
|
||||
|
||||
webdav@^5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.npmjs.org/webdav/-/webdav-5.3.0.tgz#0be7690003884a250f7595ee42e583c71f01661d"
|
||||
|
Loading…
Reference in New Issue
Block a user