Skip to content

Commit

Permalink
适配webdav
Browse files Browse the repository at this point in the history
  • Loading branch information
realhuhu committed Aug 7, 2024
1 parent e4613cd commit 5f0e1a2
Show file tree
Hide file tree
Showing 18 changed files with 562 additions and 289 deletions.
3 changes: 2 additions & 1 deletion node-proxy/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import http from 'http'
import Koa from 'koa'
import serve from 'koa-static'

import { port } from '@/config'
import { logger } from '@/common/logger'
import alisRouter from '@/router/alist'
import otherRouter from '@/router/other'
Expand All @@ -25,7 +26,7 @@ app.use(otherRouter.routes())

const server = http.createServer(app.callback())
server.maxConnections = 1000
server.listen(5343, () => logger.info('服务启动成功: ' + 5343))
server.listen(port, () => logger.info('服务启动成功: ' + port))

setInterval(() => {
logger.debug('server_connections', server.connections, Date.now())
Expand Down
3 changes: 1 addition & 2 deletions node-proxy/src/@types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,11 @@ declare global {
//preProxy后将以下属性添加到ctx.state中供middleware使用
type ProxiedState<T extends AlistServer | WebdavServer> = {
isWebdav: boolean
selfHost: string
urlAddr: string
serverAddr: string
serverConfig: T
selfHost: string
origin: string
fileSize: number
passwdInfo?: PasswdInfo
}
}
20 changes: 20 additions & 0 deletions node-proxy/src/@types/webdav.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
declare namespace webdav {
type FileInfo = {
href: string
propstat: {
prop: {
displayname: string
getcontentlength?: number
}
status: string
}
}

type PropfindResp<T = FileInfo[] | FileInfo> = {
multistatus: {
response: T
}
}
}

export { webdav }
11 changes: 10 additions & 1 deletion node-proxy/src/dao/fileDao.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import nedb from '@/dao/levelDB'

import type { alist } from '@/@types/alist'
import { logger } from '@/common/logger'

export const fileInfoTable = 'fileInfoTable_'

Expand All @@ -11,11 +12,19 @@ const cacheTime = 60 * 24
export async function cacheFileInfo(fileInfo: alist.FileInfo | WebdavFileInfo) {
fileInfo.path = decodeURIComponent(fileInfo.path)
const pathKey = fileInfoTable + fileInfo.path
logger.info(`FileDao 保存文件信息: ${pathKey}`)
await nedb.setExpire(pathKey, fileInfo, 1000 * 60 * cacheTime)
}

// 获取文件信息,偶尔要清理一下缓存
export async function getFileInfo(path: string) {
export async function getFileInfo(path: string): Promise<alist.FileInfo | WebdavFileInfo | null> {
const pathKey = decodeURIComponent(fileInfoTable + path)
logger.info(`FileDao 获取文件信息: ${pathKey}`)
return await nedb.getValue(pathKey)
}

export async function deleteFileInfo(path: string) {
const pathKey = decodeURIComponent(fileInfoTable + path)
logger.info(`FileDao 删除文件信息: ${pathKey}`)
await nedb.datastore.removeMany({ key: pathKey }, {})
}
4 changes: 2 additions & 2 deletions node-proxy/src/dao/levelDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,15 @@ class Nedb {
//存值,无过期时间
async setValue(key: string, value: Value) {
await this.datastore.removeMany({ key }, {})
logger.info('存储键值(无过期时间)', key, JSON.stringify(value))
logger.trace('存储键值(无过期时间)', key, JSON.stringify(value))
await this.datastore.insert({ key, expire: -1, value })
}

// 存值,有过期时间
async setExpire(key: string, value: Value, second = 6 * 10) {
await this.datastore.removeMany({ key }, {})
const expire = Date.now() + second * 1000
logger.info(`存储键值(过期时间${expire})`, key, JSON.stringify(value))
logger.trace(`存储键值(过期时间${expire})`, key, JSON.stringify(value))
await this.datastore.insert({ key, expire, value })
}

Expand Down
40 changes: 20 additions & 20 deletions node-proxy/src/middleware/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,14 @@ export const exceptionMiddleware: Middleware = async (
try {
await next()
if (ctx.status === 404) {
ctx.throw(404, '请求资源未找到!')
if (ctx.state?.isWebdav) {
logger.warn(ctx.state?.urlAddr, '404')
} else {
ctx.throw(404, '请求资源未找到!')
}
}
} catch (err) {
logger.error('@@err')
console.trace(err)
logger.error(err)

const status = err.status || 500
// 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
Expand All @@ -111,37 +114,36 @@ export const exceptionMiddleware: Middleware = async (
}

export const proxyHandler: Middleware = async <T extends AlistServer & WebdavServer>(ctx: ParameterizedContext<ProxiedState<T>>) => {
const { state, req: request, res: response } = ctx

const { headers } = request
const state = ctx.state
const { method, headers } = ctx.req
// 要定位请求文件的位置 bytes=98304-
const range = headers.range
const start = range ? Number(range.replace('bytes=', '').split('-')[0]) : 0
const urlPath = new URL(state.urlAddr).pathname
// 检查路径是否满足加密要求,要拦截的路径可能有中文
const { passwdInfo } = pathFindPasswd(state.serverConfig.passwdList, decodeURIComponent(request.url))
const { passwdInfo } = pathFindPasswd(state.serverConfig.passwdList, decodeURIComponent(urlPath))

logger.info('匹配密码信息', passwdInfo === null ? '无密码' : passwdInfo.password)

let encryptTransform: Transform, decryptTransform: Transform
// fix webdav move file
if (request.method.toLocaleUpperCase() === 'MOVE' && headers.destination) {
if (method.toLocaleUpperCase() === 'MOVE' && headers.destination) {
let destination = flat(headers.destination)
destination = state.serverAddr + destination.substring(destination.indexOf(path.dirname(request.url)), destination.length)
request.headers.destination = destination
destination = state.serverAddr + destination.substring(destination.indexOf(path.dirname(urlPath)), destination.length)
headers.destination = destination
}

// 如果是上传文件,那么进行流加密,目前只支持webdav上传,如果alist页面有上传功能,那么也可以兼容进来
if (request.method.toLocaleUpperCase() === 'PUT' && passwdInfo) {
if (method.toLocaleUpperCase() === 'PUT' && passwdInfo) {
// 兼容macos的webdav客户端x-expected-entity-length
ctx.state.fileSize = Number(headers['content-length'] || flat(headers['x-expected-entity-length']) || 0)
// 需要知道文件长度,等于0 说明不用加密,这个来自webdav奇怪的请求
if (ctx.state.fileSize !== 0) {
encryptTransform = new FlowEnc(passwdInfo.password, passwdInfo.encType, ctx.state.fileSize).encryptTransform()
}
}

// 如果是下载文件,那么就进行判断是否解密
if ('GET,HEAD,POST'.includes(request.method.toLocaleUpperCase()) && passwdInfo) {
if ('GET,HEAD,POST'.includes(method.toLocaleUpperCase()) && passwdInfo) {
// 根据文件路径来获取文件的大小
let filePath = ctx.req.url.split('?')[0]
// 如果是alist的话,那么必然有这个文件的size缓存(进过list就会被缓存起来)
Expand Down Expand Up @@ -173,9 +175,9 @@ export const proxyHandler: Middleware = async <T extends AlistServer & WebdavSer

if (fileInfo) {
state.fileSize = fileInfo.size
} else if (request.headers.authorization) {
} else if (headers.authorization) {
// 这里要判断是否webdav进行请求, 这里默认就是webdav请求了
const authorization = request.headers.authorization
const authorization = headers.authorization
const webdavFileInfo = await getWebdavFileInfo(state.urlAddr, authorization)
logger.info('@@webdavFileInfo:', filePath, webdavFileInfo)
if (webdavFileInfo) {
Expand All @@ -188,8 +190,6 @@ export const proxyHandler: Middleware = async <T extends AlistServer & WebdavSer
}
}

state.passwdInfo = passwdInfo

// logger.info('@@@@request.filePath ', request.filePath, result)
if (state.fileSize !== 0) {
const flowEnc = new FlowEnc(passwdInfo.password, passwdInfo.encType, state.fileSize)
Expand All @@ -202,10 +202,10 @@ export const proxyHandler: Middleware = async <T extends AlistServer & WebdavSer

await httpFlowClient({
urlAddr: state.urlAddr,
passwdInfo: state.passwdInfo,
passwdInfo,
fileSize: state.fileSize,
request,
response,
request: ctx.req,
response: ctx.res,
encryptTransform,
decryptTransform,
})
Expand Down
5 changes: 3 additions & 2 deletions node-proxy/src/router/alist/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ alistRouter.all<ProxiedState<AlistServer>, ParsedContext<alist.FsGetRequestBody>

const encrypted = passwdInfo && passwdInfo.encName

logger.info(`文件路径: ${filePath} 是否被加密 ${encrypted}`)
logger.info(`文件路径: ${filePath} 是否被加密 ${Boolean(encrypted)}`)

if (encrypted) {
// reset content-length length
Expand Down Expand Up @@ -155,7 +155,6 @@ alistRouter.put<ProxiedState<AlistServer>, EmptyObj>('/fs/put', emptyMiddleware,
const state = ctx.state
const uploadPath = headers['file-path'] ? decodeURIComponent(headers['file-path'] as string) : '/-'
const { passwdInfo } = pathFindPasswd(state.serverConfig.passwdList, uploadPath)
state.fileSize = Number(headers['content-length'] || 0)

const encrypted = passwdInfo && passwdInfo.encName
logger.info(`上传文件: ${uploadPath} 加密${Boolean(encrypted)}`)
Expand Down Expand Up @@ -219,6 +218,7 @@ alistRouter.all<ProxiedState<AlistServer>, ParsedContext<alist.FsRenameRequestBo
urlAddr,
reqBody: JSON.stringify(reqBody),
request: ctx.req,
response: ctx.res,
})
})

Expand Down Expand Up @@ -252,6 +252,7 @@ alistRouter.all<ProxiedState<AlistServer>, ParsedContext<alist.FsRemoveRequestBo
urlAddr: state.urlAddr,
reqBody: JSON.stringify(reqBody),
request: ctx.req,
response: ctx.res,
})
})

Expand Down
9 changes: 6 additions & 3 deletions node-proxy/src/router/alist/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,12 @@ export const copyOrMoveFileMiddleware: Middleware<
fileNames = Object.assign([], names)
}

const reqBody = { dst_dir: dstDir, src_dir: srcDir, names: fileNames }
// reset content-length length
delete ctx.req.headers['content-length']

ctx.body = await httpClient({ urlAddr: state.urlAddr, reqBody: JSON.stringify(reqBody), request: ctx.req })
ctx.body = await httpClient({
urlAddr: state.urlAddr,
reqBody: JSON.stringify({ dst_dir: dstDir, src_dir: srcDir, names: fileNames }),
request: ctx.req,
response: ctx.res,
})
}
6 changes: 2 additions & 4 deletions node-proxy/src/router/other/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ otherRouter.all(/^\/images\/*/, compose(bodyParserMiddleware, preProxy(alistServ

return await httpFlowClient({
urlAddr: state.urlAddr,
passwdInfo: state.passwdInfo,
fileSize: state.fileSize,
request: ctx.req,
response: ctx.res,
Expand Down Expand Up @@ -92,14 +91,14 @@ otherRouter.all('/redirect/:key', async (ctx) => {
})

otherRouter.all<ProxiedState<AlistServer>, EmptyObj>(new RegExp(alistServer.path), preProxy(alistServer, false), async (ctx) => {
let respBody = await httpClient({
const body = await httpClient({
urlAddr: ctx.state.urlAddr,
reqBody: JSON.stringify(ctx.request.body),
request: ctx.req,
response: ctx.res,
})

respBody = respBody.replace(
ctx.body = body.replace(
'<body>',
`<body>
<div style="position: fixed;z-index:10010; top:7px; margin-left: 50%">
Expand All @@ -113,7 +112,6 @@ otherRouter.all<ProxiedState<AlistServer>, EmptyObj>(new RegExp(alistServer.path
</a>
</div>`
)
ctx.body = respBody
})

export default otherRouter
8 changes: 4 additions & 4 deletions node-proxy/src/router/webdav/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ import Router from 'koa-router'

import { preProxy } from '@/utils/common'
import { alistServer, webdavServer } from '@/config'
import { encDavMiddleware } from '@/router/webdav/middlewares'
import { compose, proxyHandler } from '@/middleware/common'
import { webdavHookMiddleware } from '@/router/webdav/middlewares'

const webdavRouter = new Router<EmptyObj>()

// 初始化webdav路由
webdavServer.forEach((webdavConfig) => {
if (webdavConfig.enable) {
webdavRouter.all(new RegExp(webdavConfig.path), compose(preProxy(webdavConfig, true), encDavMiddleware), proxyHandler)
webdavRouter.all<ProxiedState<WebdavServer>, EmptyObj>(new RegExp(webdavConfig.path), preProxy(webdavConfig, true), webdavHookMiddleware)
}
})

// 单独处理alist的所有/dav
webdavRouter.all(/^\/dav\/*/, compose(preProxy(alistServer, false), encDavMiddleware), proxyHandler)
// webdavRouter.all(/^\/dav\/*/, compose(preProxy(alistServer, false), encDavMiddleware), proxyHandler)
webdavRouter.all<ProxiedState<AlistServer>, EmptyObj>(/^\/dav\/*/, preProxy(alistServer, true), webdavHookMiddleware)

export default webdavRouter
Loading

0 comments on commit 5f0e1a2

Please sign in to comment.