diff --git a/node-proxy/.env b/node-proxy/.env
index 923c1f0..fed1294 100644
--- a/node-proxy/.env
+++ b/node-proxy/.env
@@ -1 +1,2 @@
-RUN_MODE=DEV
\ No newline at end of file
+RUN_MODE=DEV
+LOG_LEVEL=debug
diff --git a/node-proxy/.eslintrc.js b/node-proxy/.eslintrc.js
index f7e1a0d..589fbd0 100644
--- a/node-proxy/.eslintrc.js
+++ b/node-proxy/.eslintrc.js
@@ -23,11 +23,10 @@ module.exports = {
function prettierrc() {
const prettierrc_id = require.resolve('./.prettierrc')
- var stat = fs.statSync(prettierrc_id)
+ const stat = fs.statSync(prettierrc_id)
if (stat.mtimeMs > (process.prettierrc_file_mtimeMs || 0)) {
process.prettierrc_file_mtimeMs = stat.mtimeMs
require.cache[prettierrc_id] = undefined
}
- const conf = require('./.prettierrc')
- return conf
+ return require('./.prettierrc')
}
diff --git a/node-proxy/app.js b/node-proxy/app.js
deleted file mode 100644
index cf2bf9b..0000000
--- a/node-proxy/app.js
+++ /dev/null
@@ -1,344 +0,0 @@
-'use strict'
-import { convertFile } from '@/utils/convertFile'
-const arg = process.argv.slice(2)
-if (arg.length > 1) {
- // convertFile command
- convertFile(...arg)
- return
-}
-
-import Koa from 'koa'
-import Router from 'koa-router'
-import http from 'http'
-import crypto from 'crypto'
-import path from 'path'
-import { httpProxy, httpClient } from '@/utils/httpClient'
-import bodyparser from 'koa-bodyparser'
-import FlowEnc from '@/utils/flowEnc'
-import levelDB from '@/utils/levelDB'
-import { webdavServer, alistServer, port, version } from '@/config'
-import { pathExec, pathFindPasswd } from '@/utils/commonUtil'
-import globalHandle from '@/middleware/globalHandle'
-import encApiRouter from '@/router'
-import encNameRouter from '@/encNameRouter'
-import encDavHandle from '@/encDavHandle'
-
-import { cacheFileInfo, getFileInfo } from '@/dao/fileDao'
-import { getWebdavFileInfo } from '@/utils/webdavClient'
-import staticServer from 'koa-static'
-import { logger } from '@/common/logger'
-import { encodeName } from '@/utils/commonUtil'
-
-async function sleep(time) {
- return new Promise((resolve) => {
- setTimeout(() => {
- resolve()
- }, time || 3000)
- })
-}
-
-const proxyRouter = new Router()
-const app = new Koa()
-// compatible ncc and pkg
-const pkgDirPath = path.dirname(process.argv[1])
-
-app.use(staticServer(pkgDirPath, 'public'))
-app.use(globalHandle)
-// bodyparser解析body
-const bodyparserMw = bodyparser({ enableTypes: ['json', 'form', 'text'] })
-
-// ======================/proxy是实现本服务的业务==============================
-// 短地址
-encApiRouter.redirect('/index', '/public/index.html', 302)
-app.use(encApiRouter.routes()).use(encApiRouter.allowedMethods())
-
-// ======================下面是实现webdav代理的业务==============================
-
-// 可能是302跳转过来的下载的,/redirect?key=34233&decode=0
-proxyRouter.all('/redirect/:key', async (ctx) => {
- const request = ctx.req
- const response = ctx.res
- // 这里还是要encodeURIComponent ,因为http服务器会自动对url进行decodeURIComponent
- const data = await levelDB.getValue(ctx.params.key)
- if (data === null) {
- ctx.body = 'no found'
- return
- }
- const { passwdInfo, redirectUrl, fileSize } = data
- // 要定位请求文件的位置 bytes=98304-
- const range = request.headers.range
- const start = range ? range.replace('bytes=', '').split('-')[0] * 1 : 0
- const flowEnc = new FlowEnc(passwdInfo.password, passwdInfo.encType, fileSize)
- if (start) {
- await flowEnc.setPosition(start)
- }
- // 设置请求地址和是否要解密
- const decode = ctx.query.decode
- // 修改百度头
- if (~redirectUrl.indexOf('baidupcs.com')) {
- request.headers['User-Agent'] = 'pan.baidu.com'
- }
- request.url = decodeURIComponent(ctx.query.lastUrl)
- request.urlAddr = redirectUrl
- delete request.headers.host
- // aliyun不允许这个referer,不然会出现403
- delete request.headers.referer
- request.passwdInfo = passwdInfo
- // 123网盘和天翼网盘多次302
- request.fileSize = fileSize
- // authorization 是alist网页版的token,不是webdav的,这里修复天翼云无法获取资源的问题
- delete request.headers.authorization
-
- // 默认判断路径来识别是否要解密,如果有decode参数,那么则按decode来处理,这样可以让用户手动处理是否解密?(那还不如直接在alist下载)
- let decryptTransform = passwdInfo.enable && pathExec(passwdInfo.encPath, request.url) ? flowEnc.decryptTransform() : null
- if (decode) {
- decryptTransform = decode !== '0' ? flowEnc.decryptTransform() : null
- }
- // 请求实际服务资源
- await httpProxy(request, response, null, decryptTransform)
- logger.info('----finish redirect---', decode, request.urlAddr, decryptTransform === null)
-})
-
-// 预处理 request,处理地址,加密钥匙等
-function preProxy(webdavConfig, isWebdav) {
- // 必包变量
- // let authorization = isWebdav
- return async (ctx, next) => {
- const { serverHost, serverPort, https } = webdavConfig
- const request = ctx.req
- if (isWebdav) {
- // 不能把authorization缓存起来,单线程
- request.isWebdav = isWebdav
- // request.headers.authorization = request.headers.authorization ? (authorization = request.headers.authorization) : authorization
- }
- // 原来的host保留,以后可能会用到
- request.selfHost = request.headers.host
- request.origin = request.headers.origin
- request.headers.host = serverHost + ':' + serverPort
- const protocol = https ? 'https' : 'http'
- request.urlAddr = `${protocol}://${request.headers.host}${request.url}`
- request.serverAddr = `${protocol}://${request.headers.host}`
- request.webdavConfig = webdavConfig
- await next()
- }
-}
-// webdav or http handle
-async function proxyHandle(ctx, next) {
- const request = ctx.req
- const response = ctx.res
- const { passwdList } = request.webdavConfig
- const { headers } = request
- // 要定位请求文件的位置 bytes=98304-
- const range = headers.range
- const start = range ? range.replace('bytes=', '').split('-')[0] * 1 : 0
- // 检查路径是否满足加密要求,要拦截的路径可能有中文
- const { passwdInfo, pathInfo } = pathFindPasswd(passwdList, decodeURIComponent(request.url))
- logger.debug('@@@@passwdInfo', pathInfo)
- // fix webdav move file
- if (request.method.toLocaleUpperCase() === 'MOVE' && headers.destination) {
- let destination = headers.destination
- destination = request.serverAddr + destination.substring(destination.indexOf(path.dirname(request.url)), destination.length)
- request.headers.destination = destination
- }
- // 如果是上传文件,那么进行流加密,目前只支持webdav上传,如果alist页面有上传功能,那么也可以兼容进来
- if (request.method.toLocaleUpperCase() === 'PUT' && passwdInfo) {
- // 兼容macos的webdav客户端x-expected-entity-length
- const contentLength = headers['content-length'] || headers['x-expected-entity-length'] || 0
- request.fileSize = contentLength * 1
- // 需要知道文件长度,等于0 说明不用加密,这个来自webdav奇怪的请求
- if (request.fileSize === 0) {
- return await httpProxy(request, response)
- }
- const flowEnc = new FlowEnc(passwdInfo.password, passwdInfo.encType, request.fileSize)
- return await httpProxy(request, response, flowEnc.encryptTransform())
- }
- // 如果是下载文件,那么就进行判断是否解密
- if ('GET,HEAD,POST'.includes(request.method.toLocaleUpperCase()) && passwdInfo) {
- // 根据文件路径来获取文件的大小
- const urlPath = ctx.req.url.split('?')[0]
- let filePath = urlPath
- // 如果是alist的话,那么必然有这个文件的size缓存(进过list就会被缓存起来)
- request.fileSize = 0
- // 这里需要处理掉/p 路径
- if (filePath.indexOf('/p/') === 0) {
- filePath = filePath.replace('/p/', '/')
- }
- if (filePath.indexOf('/d/') === 0) {
- filePath = filePath.replace('/d/', '/')
- }
- // 尝试获取文件信息,如果未找到相应的文件信息,则对文件名进行加密处理后重新尝试获取文件信息
- let fileInfo = await getFileInfo(filePath);
-
- if (fileInfo === null) {
- const rawFileName = decodeURIComponent(path.basename(filePath));
- const ext = path.extname(rawFileName);
- const encodedRawFileName = encodeURIComponent(rawFileName);
- const encFileName = encodeName(passwdInfo.password, passwdInfo.encType, rawFileName);
- const newFileName = encFileName + ext;
-
- filePath = filePath.replace(encodedRawFileName, newFileName);
- request.urlAddr = request.urlAddr.replace(encodedRawFileName, newFileName);
-
- fileInfo = await getFileInfo(filePath);
- }
- logger.info('@@getFileInfo:', filePath, fileInfo, request.urlAddr)
- if (fileInfo) {
- request.fileSize = fileInfo.size * 1
- } else if (request.headers.authorization) {
- // 这里要判断是否webdav进行请求, 这里默认就是webdav请求了
- const authorization = request.headers.authorization
- const webdavFileInfo = await getWebdavFileInfo(request.urlAddr, authorization)
- logger.info('@@webdavFileInfo:', filePath, webdavFileInfo)
- if (webdavFileInfo) {
- webdavFileInfo.path = filePath
- // 某些get请求返回的size=0,不要缓存起来
- if (webdavFileInfo.size * 1 > 0) {
- cacheFileInfo(webdavFileInfo)
- }
- request.fileSize = webdavFileInfo.size * 1
- }
- }
- request.passwdInfo = passwdInfo
- // logger.info('@@@@request.filePath ', request.filePath, result)
- if (request.fileSize === 0) {
- // 说明不用加密
- return await httpProxy(request, response)
- }
- const flowEnc = new FlowEnc(passwdInfo.password, passwdInfo.encType, request.fileSize)
- if (start) {
- await flowEnc.setPosition(start)
- }
- return await httpProxy(request, response, null, flowEnc.decryptTransform())
- }
- await httpProxy(request, response)
-}
-
-// 初始化webdav路由,这里可以优化成动态路由,只不过没啥必要,修改配置后直接重启就好了
-webdavServer.forEach((webdavConfig) => {
- if (webdavConfig.enable) {
- proxyRouter.all(new RegExp(webdavConfig.path), preProxy(webdavConfig, true), encDavHandle, proxyHandle)
- }
-})
-
-/* =================================== 单独处理alist的逻辑 ====================================== */
-
-// 单独处理alist的所有/dav
-proxyRouter.all(/^\/dav\/*/, preProxy(alistServer, true), encDavHandle, proxyHandle)
-
-// 其他的代理request预处理,处理要跳转的路径等
-proxyRouter.all(/\/*/, preProxy(alistServer, false))
-// check enc filename
-proxyRouter.use(encNameRouter.routes()).use(encNameRouter.allowedMethods())
-
-// 处理文件下载的302跳转
-proxyRouter.get(/^\/d\/*/, proxyHandle)
-// 文件直接下载
-proxyRouter.get(/^\/p\/*/, proxyHandle)
-
-// 处理在线视频播放的问题,修改它的返回播放地址 为本代理的地址。
-proxyRouter.all('/api/fs/get', bodyparserMw, async (ctx, next) => {
- const { path } = ctx.request.body
- // 判断打开的文件是否要解密,要解密则替换url,否则透传
- ctx.req.reqBody = JSON.stringify(ctx.request.body)
-
- const respBody = await httpClient(ctx.req)
- const result = JSON.parse(respBody)
- const { headers } = ctx.req
- const { passwdInfo } = pathFindPasswd(alistServer.passwdList, path)
-
- if (passwdInfo) {
- // 修改返回的响应,匹配到要解密,就302跳转到本服务上进行代理流量
- logger.info('@@getFile ', path, ctx.req.reqBody, result)
- const key = crypto.randomUUID()
- await levelDB.setExpire(key, { redirectUrl: result.data.raw_url, passwdInfo, fileSize: result.data.size }, 60 * 60 * 72) // 缓存起来,默认3天,足够下载和观看了
- result.data.raw_url = `${
- headers.origin || (headers['x-forwarded-proto'] || ctx.protocol) + '://' + ctx.req.selfHost
- }/redirect/${key}?decode=1&lastUrl=${encodeURIComponent(path)}`
- if (result.data.provider === 'AliyundriveOpen') result.data.provider = 'Local'
- }
- ctx.body = result
-})
-
-// 缓存alist的文件信息
-proxyRouter.all('/api/fs/list', bodyparserMw, async (ctx, next) => {
- const { path } = ctx.request.body
- // 判断打开的文件是否要解密,要解密则替换url,否则透传
- ctx.req.reqBody = JSON.stringify(ctx.request.body)
- const respBody = await httpClient(ctx.req)
- // logger.info('@@@respBody', respBody)
- const result = JSON.parse(respBody)
- if (!result.data) {
- ctx.body = result
- return
- }
- const content = result.data.content
- if (!content) {
- ctx.body = result
- return
- }
- for (let i = 0; i < content.length; i++) {
- const fileInfo = content[i]
- fileInfo.path = path + '/' + fileInfo.name
- // 这里要注意闭包问题,mad
- // logger.debug('@@cacheFileInfo', fileInfo.path)
- cacheFileInfo(fileInfo)
- }
- // waiting cacheFileInfo a moment
- if (content.length > 100) {
- await sleep(50)
- }
- logger.info('@@@fs/list', content.length)
- ctx.body = result
-})
-
-// that is not work when upload txt file if enable encName
-proxyRouter.put('/api/fs/put-back', async (ctx, next) => {
- const request = ctx.req
- const { headers, webdavConfig } = request
- const contentLength = headers['content-length'] || 0
- request.fileSize = contentLength * 1
-
- const uploadPath = headers['file-path'] ? decodeURIComponent(headers['file-path']) : '/-'
- const { passwdInfo } = pathFindPasswd(webdavConfig.passwdList, uploadPath)
- if (passwdInfo) {
- const flowEnc = new FlowEnc(passwdInfo.password, passwdInfo.encType, request.fileSize)
- return await httpProxy(ctx.req, ctx.res, flowEnc.encryptTransform())
- }
- return await httpProxy(ctx.req, ctx.res)
-})
-
-// 修复alist 图标不显示的问题
-proxyRouter.all(/^\/images\/*/, async (ctx, next) => {
- delete ctx.req.headers.host
- return await httpProxy(ctx.req, ctx.res)
-})
-
-// 初始化alist的路由
-proxyRouter.all(new RegExp(alistServer.path), async (ctx, next) => {
- let respBody = await httpClient(ctx.req, ctx.res)
- respBody = respBody.replace(
- '
',
- `
- `
- )
- ctx.body = respBody
-})
-// 使用路由控制
-app.use(proxyRouter.routes()).use(proxyRouter.allowedMethods())
-
-// 配置创建好了,就启动 else {
-const server = http.createServer(app.callback())
-server.maxConnections = 1000
-server.listen(port, () => logger.info('服务启动成功: ' + port))
-setInterval(() => {
- logger.debug('server_connections', server._connections, Date.now())
-}, 600 * 1000)
diff --git a/node-proxy/app.ts b/node-proxy/app.ts
new file mode 100644
index 0000000..388f408
--- /dev/null
+++ b/node-proxy/app.ts
@@ -0,0 +1,32 @@
+import path from 'path'
+import http from 'http'
+
+import Koa from 'koa'
+import serve from 'koa-static'
+
+import { logger } from '@/common/logger'
+import alisRouter from '@/router/alist'
+import otherRouter from '@/router/other'
+import webdavRouter from '@/router/webdav'
+import staticRouter from '@/router/static'
+import webuiRouter from '@/router/webui'
+import { exceptionMiddleware, loggerMiddleware } from '@/middleware/common'
+
+const app = new Koa()
+
+app.use(loggerMiddleware)
+app.use(exceptionMiddleware)
+app.use(serve(path.dirname(process.argv[1]), { root: 'public' }))
+app.use(alisRouter.routes())
+app.use(webuiRouter.routes())
+app.use(webdavRouter.routes())
+app.use(staticRouter.routes())
+app.use(otherRouter.routes())
+
+const server = http.createServer(app.callback())
+server.maxConnections = 1000
+server.listen(5343, () => logger.info('服务启动成功: ' + 5343))
+
+setInterval(() => {
+ logger.debug('server_connections', server.connections, Date.now())
+}, 600 * 1000)
diff --git a/node-proxy/btest.js b/node-proxy/btest.js
deleted file mode 100644
index 6d40b63..0000000
--- a/node-proxy/btest.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import crypto from 'crypto'
-import path from 'path'
-import { logger } from '@/common/logger'
-import ChaCha20Poly from '@/utils/chaCha20Poly'
-import { chownSync, copyFileSync } from 'fs'
-import CRCN from '@/utils/crc6-8'
-import fs from 'fs'
-import { encodeName, decodeName } from '@/utils/commonUtil'
-import { getWebdavFileInfo } from '@/utils/webdavClient'
-
-getWebdavFileInfo(
- 'http://192.168.8.240:5244/dav/aliyun%E4%BA%91%E7%9B%98/atest/d%E5%AF%B9%E6%96%B9%E6%88%91testrclone/kline_d%2Bata12342%E6%AD%A3%E6%96%87%E7%9A%84%E7%9A%84%E5%89%AF%E6%9C%AC.txt',
- 'Basic YWRtaW46WWl1Tkg3bHk='
-).then((res) => {
- console.log(res)
-})
-
-console.log('@@dd', path.isAbsolute('/ddf'))
-const content = 'fileInfoTable_/dav/aliyun%Evfnnz%BA%91%E7%9B%98/atest/12%E5%A4%A7%E5%A4%B4%E7%9A%84%E6%97%8F%E6%96%87%E4%BB%B6_8Xn78oZjs7VSr~qjdzVH4/4'
-
-console.log('@@content', decodeURIComponent(content))
-const reg = 'test'
-
-const enw = content.replace(new RegExp(reg, 'g'), '@@')
-console.log(enw)
-
-const ext = ''.trim() || path.extname('/dfdf.df')
-
-const encname = encodeName('123456', 'aesctr', '3wd.tex')
-
-const decname = decodeName('123456', 'aesctr', encname)
-console.log('##', ext, decname)
-
-logger.debug('dfeeeef')
diff --git a/node-proxy/package-lock.json b/node-proxy/package-lock.json
index 104d790..850b1ec 100644
--- a/node-proxy/package-lock.json
+++ b/node-proxy/package-lock.json
@@ -23,6 +23,9 @@
"typescript": "^5.1.6"
},
"devDependencies": {
+ "@types/koa-bodyparser": "^4.3.12",
+ "@types/koa-router": "^7.4.8",
+ "@types/koa-static": "^4.0.4",
"@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/parser": "^5.55.0",
"copy-webpack-plugin": "^11.0.0",
@@ -359,6 +362,52 @@
"resolved": "https://registry.npmmirror.com/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="
},
+ "node_modules/@types/accepts": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmmirror.com/@types/accepts/-/accepts-1.3.7.tgz",
+ "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/body-parser": {
+ "version": "1.19.5",
+ "resolved": "https://registry.npmmirror.com/@types/body-parser/-/body-parser-1.19.5.tgz",
+ "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
+ "dev": true,
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmmirror.com/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/content-disposition": {
+ "version": "0.5.8",
+ "resolved": "https://registry.npmmirror.com/@types/content-disposition/-/content-disposition-0.5.8.tgz",
+ "integrity": "sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==",
+ "dev": true
+ },
+ "node_modules/@types/cookies": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmmirror.com/@types/cookies/-/cookies-0.9.0.tgz",
+ "integrity": "sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==",
+ "dev": true,
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/express": "*",
+ "@types/keygrip": "*",
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/eslint": {
"version": "8.44.2",
"resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-8.44.2.tgz",
@@ -385,23 +434,166 @@
"integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==",
"dev": true
},
+ "node_modules/@types/express": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmmirror.com/@types/express/-/express-4.17.21.tgz",
+ "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^4.17.33",
+ "@types/qs": "*",
+ "@types/serve-static": "*"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "4.19.5",
+ "resolved": "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz",
+ "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/http-assert": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmmirror.com/@types/http-assert/-/http-assert-1.5.5.tgz",
+ "integrity": "sha512-4+tE/lwdAahgZT1g30Jkdm9PzFRde0xwxBNUyRsCitRvCQB90iuA2uJYdUnhnANRcqGXaWOGY4FEoxeElNAK2g==",
+ "dev": true
+ },
+ "node_modules/@types/http-errors": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmmirror.com/@types/http-errors/-/http-errors-2.0.4.tgz",
+ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
+ "dev": true
+ },
"node_modules/@types/json-schema": {
"version": "7.0.12",
"resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.12.tgz",
"integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==",
"dev": true
},
+ "node_modules/@types/keygrip": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmmirror.com/@types/keygrip/-/keygrip-1.0.6.tgz",
+ "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==",
+ "dev": true
+ },
+ "node_modules/@types/koa": {
+ "version": "2.15.0",
+ "resolved": "https://registry.npmmirror.com/@types/koa/-/koa-2.15.0.tgz",
+ "integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==",
+ "dev": true,
+ "dependencies": {
+ "@types/accepts": "*",
+ "@types/content-disposition": "*",
+ "@types/cookies": "*",
+ "@types/http-assert": "*",
+ "@types/http-errors": "*",
+ "@types/keygrip": "*",
+ "@types/koa-compose": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/koa-bodyparser": {
+ "version": "4.3.12",
+ "resolved": "https://registry.npmmirror.com/@types/koa-bodyparser/-/koa-bodyparser-4.3.12.tgz",
+ "integrity": "sha512-hKMmRMVP889gPIdLZmmtou/BijaU1tHPyMNmcK7FAHAdATnRcGQQy78EqTTxLH1D4FTsrxIzklAQCso9oGoebQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/koa": "*"
+ }
+ },
+ "node_modules/@types/koa-compose": {
+ "version": "3.2.8",
+ "resolved": "https://registry.npmmirror.com/@types/koa-compose/-/koa-compose-3.2.8.tgz",
+ "integrity": "sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==",
+ "dev": true,
+ "dependencies": {
+ "@types/koa": "*"
+ }
+ },
+ "node_modules/@types/koa-router": {
+ "version": "7.4.8",
+ "resolved": "https://registry.npmmirror.com/@types/koa-router/-/koa-router-7.4.8.tgz",
+ "integrity": "sha512-SkWlv4F9f+l3WqYNQHnWjYnyTxYthqt8W9az2RTdQW7Ay8bc00iRZcrb8MC75iEfPqnGcg2csEl8tTG1NQPD4A==",
+ "dev": true,
+ "dependencies": {
+ "@types/koa": "*"
+ }
+ },
+ "node_modules/@types/koa-send": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmmirror.com/@types/koa-send/-/koa-send-4.1.6.tgz",
+ "integrity": "sha512-vgnNGoOJkx7FrF0Jl6rbK1f8bBecqAchKpXtKuXzqIEdXTDO6dsSTjr+eZ5m7ltSjH4K/E7auNJEQCAd0McUPA==",
+ "dev": true,
+ "dependencies": {
+ "@types/koa": "*"
+ }
+ },
+ "node_modules/@types/koa-static": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmmirror.com/@types/koa-static/-/koa-static-4.0.4.tgz",
+ "integrity": "sha512-j1AUzzl7eJYEk9g01hNTlhmipFh8RFbOQmaMNLvLcNNAkPw0bdTs3XTa3V045XFlrWN0QYnblbDJv2RzawTn6A==",
+ "dev": true,
+ "dependencies": {
+ "@types/koa": "*",
+ "@types/koa-send": "*"
+ }
+ },
+ "node_modules/@types/mime": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmmirror.com/@types/mime/-/mime-1.3.5.tgz",
+ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+ "dev": true
+ },
"node_modules/@types/node": {
"version": "20.5.6",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-20.5.6.tgz",
"integrity": "sha512-Gi5wRGPbbyOTX+4Y2iULQ27oUPrefaB0PxGQJnfyWN3kvEDGM3mIB5M/gQLmitZf7A9FmLeaqxD3L1CXpm3VKQ=="
},
+ "node_modules/@types/qs": {
+ "version": "6.9.15",
+ "resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.9.15.tgz",
+ "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==",
+ "dev": true
+ },
+ "node_modules/@types/range-parser": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz",
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+ "dev": true
+ },
"node_modules/@types/semver": {
"version": "7.5.0",
"resolved": "https://registry.npmmirror.com/@types/semver/-/semver-7.5.0.tgz",
"integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==",
"dev": true
},
+ "node_modules/@types/send": {
+ "version": "0.17.4",
+ "resolved": "https://registry.npmmirror.com/@types/send/-/send-0.17.4.tgz",
+ "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
+ "dev": true,
+ "dependencies": {
+ "@types/mime": "^1",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "1.15.7",
+ "resolved": "https://registry.npmmirror.com/@types/serve-static/-/serve-static-1.15.7.tgz",
+ "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
+ "dev": true,
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*",
+ "@types/send": "*"
+ }
+ },
"node_modules/@types/strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/@types/strip-bom/-/strip-bom-3.0.0.tgz",
diff --git a/node-proxy/package.json b/node-proxy/package.json
index 0fa8065..bb67455 100644
--- a/node-proxy/package.json
+++ b/node-proxy/package.json
@@ -2,10 +2,10 @@
"name": "alist-encrypt",
"version": "1.0.0",
"description": "",
- "main": "app.js",
+ "main": "app.ts",
"scripts": {
- "dev": "ts-node-dev -r tsconfig-paths/register app.js",
- "serve": "ts-node -r tsconfig-paths/register app.js",
+ "dev": "ts-node-dev -r tsconfig-paths/register app.ts",
+ "serve": "ts-node -r tsconfig-paths/register app.ts",
"test": "echo \"Error: no test specified\" && exit 1",
"webpack": "npx webpack",
"build": "npm run webpack && pkg --compress GZip dist",
@@ -31,6 +31,9 @@
"typescript": "^5.1.6"
},
"devDependencies": {
+ "@types/koa-bodyparser": "^4.3.12",
+ "@types/koa-router": "^7.4.8",
+ "@types/koa-static": "^4.0.4",
"@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/parser": "^5.55.0",
"copy-webpack-plugin": "^11.0.0",
diff --git a/node-proxy/src/@types/alist.d.ts b/node-proxy/src/@types/alist.d.ts
new file mode 100644
index 0000000..7a68152
--- /dev/null
+++ b/node-proxy/src/@types/alist.d.ts
@@ -0,0 +1,87 @@
+declare namespace alist {
+ //alist中的文件类型
+ export enum FileType {
+ UNKNOWN,
+ FOLDER,
+ // OFFICE,
+ VIDEO,
+ AUDIO,
+ TEXT,
+ IMAGE,
+ }
+
+ //alist中的文件属性
+ export interface FileInfo {
+ name: string
+ size: number
+ is_dir: boolean
+ modified: string
+ created: string
+ sign: string
+ thumb: string
+ type: FileType
+ hash_info: string | null
+
+ path?: string //非原生属性,由proxy添加
+ }
+
+ //alist中的 /api/fs/get 的请求体
+ type FsGetRequestBody = {
+ path: string
+ password: string
+ }
+
+ //alist中的 /api/fs/list 的请求体
+ type FsListRequestBody = {
+ path: string
+ password: string
+ page: number
+ per_page: number
+ refresh: boolean
+ }
+
+ //alist中的 /api/fs/remove 的请求体
+ type FsRemoveRequestBody = {
+ dir: string
+ names: string[]
+ }
+
+ //alist中的 /api/fs/move 的请求体
+ type FsMoveRequestBody = {
+ src_dir: string
+ dst_dir: string
+ names: string[]
+ }
+
+ //alist中的 /api/fs/copy 的请求体
+ type FsCopyRequestBody = {
+ src_dir: string
+ dst_dir: string
+ names: string[]
+ }
+
+ //alist中的 /api/fs/rename 的请求体
+ type FsRenameRequestBody = {
+ path: string
+ name: string
+ }
+
+ //alist中的响应结构
+ export interface Resp {
+ code: number
+ message: string
+ data: T
+ }
+
+ //alist中的 /api/fs/list 的响应体
+ type FsListResponseBody = Resp<{
+ content: FileInfo[] | null
+ total: number
+ readme: string
+ header: string
+ write: boolean
+ provider: string
+ }>
+}
+
+export { alist }
diff --git a/node-proxy/src/@types/index.d.ts b/node-proxy/src/@types/index.d.ts
index 0fd1847..bee15c1 100644
--- a/node-proxy/src/@types/index.d.ts
+++ b/node-proxy/src/@types/index.d.ts
@@ -1,9 +1,102 @@
+import { Transform } from 'stream'
+
export {}
declare global {
- interface PasswdInfo {
- encPath: string
- enable: string
+ //使得对象中的属性R的类型变为U
+ type ModifyPropType = {
+ [K in keyof T]: K extends R ? U : T[K]
+ }
+
+ //用到的加解密类型
+ type EncryptType = 'mix' | 'rc4' | 'aesctr'
+
+ //具有加解密功能类的接口
+ interface EncryptMethod {
password: string
- encType: string
+ passwdOutward: string
+
+ encrypt: (arg1: Buffer) => Buffer
+ decrypt: (arg1: Buffer) => Buffer
+ }
+
+ //具有流式解密功能类的接口
+ interface EncryptFlow extends EncryptMethod {
+ encryptTransform: () => Transform
+ decryptTransform: () => Transform
+ setPositionAsync: (arg1: number) => Promise
+ }
+
+ //webui中设置的密码信息
+ type PasswdInfo = {
+ password: string
+ describe: string
+ enable: boolean
+ encType: EncryptType
+ encName: boolean
+ encSuffix: string
+ encPath: string[]
+ }
+
+ //getWebdavFileInfo的返回值
+ type WebdavFileInfo = {
+ size: number
+ name: string
+ is_dir: boolean
+ path: string
+ }
+
+ //定义空对象,相当于{}
+ type EmptyObj = Record
+
+ //经过bodyParserMiddleware处理后的ParsedContext,ctx.request.body变为对象
+ type ParsedContext = {
+ request: {
+ body: T
+ }
+ }
+
+ //webui中设置的单个webdav配置信息
+ type WebdavServer = {
+ id: string
+ name: string
+ path: string
+ describe: string
+ serverHost: string
+ serverPort: number
+ https: boolean
+ enable: boolean
+ passwdList: PasswdInfo[]
+ }
+
+ //webui中设置的alist配置信息
+ type AlistServer = {
+ name: string
+ path: string
+ describe: string
+ serverHost: string
+ serverPort: number
+ https: boolean
+ passwdList: PasswdInfo[]
+ _snapshot?: {
+ name: string
+ path: string
+ describe: string
+ serverHost: string
+ serverPort: number
+ https: boolean
+ passwdList: PasswdInfo[]
+ }
+ }
+
+ //preProxy后将以下属性添加到ctx.state中供middleware使用
+ type ProxiedState = {
+ isWebdav: boolean
+ urlAddr: string
+ serverAddr: string
+ serverConfig: T
+ selfHost: string
+ origin: string
+ fileSize: number
+ passwdInfo?: PasswdInfo
}
}
diff --git a/node-proxy/src/@types/webui.d.ts b/node-proxy/src/@types/webui.d.ts
new file mode 100644
index 0000000..b08c702
--- /dev/null
+++ b/node-proxy/src/@types/webui.d.ts
@@ -0,0 +1,24 @@
+declare namespace webui {
+ //webui中登录后用户的信息
+ type UserInfo = {
+ username: string
+ headImgUrl: string
+ password: string | null
+ roleId: string
+ }
+
+ //通过userInfoMiddleware将用户信息添加到ctx.state中
+ type State = {
+ userInfo: UserInfo
+ }
+
+ //webui中响应结果
+ type ResponseBody = {
+ flag: boolean //是否请求成功
+ msg?: string //提示消息
+ code: number //响应码
+ data?: any //返回数据
+ }
+}
+
+export { webui }
diff --git a/node-proxy/src/common/logger.js b/node-proxy/src/common/logger.ts
similarity index 95%
rename from node-proxy/src/common/logger.js
rename to node-proxy/src/common/logger.ts
index f2763ce..18ed792 100644
--- a/node-proxy/src/common/logger.js
+++ b/node-proxy/src/common/logger.ts
@@ -1,6 +1,7 @@
import log4js from 'log4js'
import dotenv from 'dotenv'
-dotenv.config('./env')
+
+dotenv.config()
log4js.configure({
appenders: {
diff --git a/node-proxy/src/config.js b/node-proxy/src/config.ts
similarity index 73%
rename from node-proxy/src/config.js
rename to node-proxy/src/config.ts
index 0f45c20..2a26dd1 100644
--- a/node-proxy/src/config.js
+++ b/node-proxy/src/config.ts
@@ -1,28 +1,24 @@
import fs from 'fs'
-import { addUserInfo, getUserInfo } from './dao/userDao'
-import nedb from './utils/levelDB'
+import path from 'path'
-// inti config, fix ncc get local conf
-function getConfPath() {
- return process.cwd() + '/conf'
-}
+import nedb from '@/dao/levelDB'
+import { logger } from '@/common/logger'
+import { addUserInfo, getUserInfo } from '@/dao/userDao'
+
+let serverHost = '192.168.1.100'
+let serverPort = 5244
-// 初始化目录
-if (!fs.existsSync(getConfPath())) {
- // fs.mkdirSync(path.resolve('conf'))
- fs.mkdirSync(process.cwd() + '/conf')
-}
// 从环境变量上读取配置信息,docker首次启动时候可以直接进行配置
const serverAddr = process.env.ALIST_HOST
-const serverHost = '192.168.1.100'
-const serverPort = 5244
if (serverAddr && serverAddr.indexOf(':') > 6) {
serverHost = serverAddr.split(':')[0]
- serverPort = serverAddr.split(':')[1]
+ serverPort = Number(serverAddr.split(':')[1])
}
-console.log('@@serverAddr:', serverAddr)
+
+logger.info(`Alist地址: ${serverHost}:${serverPort}`)
+
/** 全局代理alist,包括它的webdav和http服务,要配置上 */
-const alistServerTemp = {
+const alistServerTemp: AlistServer = {
name: 'alist',
path: '/*', // 默认就是代理全部,保留字段
describe: 'alist 配置',
@@ -33,8 +29,8 @@ const alistServerTemp = {
{
password: '123456',
describe: 'my video', // 加密内容描述
- encType: 'aesctr', // 算法类型,可选mix,rc4,默认aesctr
enable: true, // enable encrypt
+ encType: 'aesctr', // 算法类型,可选mix,rc4,默认aesctr
encName: false, // encrypt file name
encSuffix: '', //
encPath: ['encrypt_folder/*', 'movie_encrypt/*'], // 路径支持正则表达式,常用的就是 尾巴带*,此目录的所文件都加密
@@ -43,7 +39,7 @@ const alistServerTemp = {
}
/** 支持其他普通的webdav,当然也可以挂载alist的webdav,但是上面配置更加适合 */
-const webdavServerTemp = [
+const webdavServerTemp: WebdavServer[] = [
{
id: 'abcdefg',
name: 'other-webdav',
@@ -56,11 +52,11 @@ const webdavServerTemp = [
passwdList: [
{
password: '123456',
- encType: 'aesctr', // 密码类型,mix:速度更快适合电视盒子之类,rc4: 更安全,速度比mix慢一点,几乎无感知。
describe: 'my video',
enable: false,
+ encType: 'aesctr', // 密码类型,mix:速度更快适合电视盒子之类,rc4: 更安全,速度比mix慢一点,几乎无感知。
encName: false, // encrypt file name
- encNameSuffix: '', //
+ encSuffix: '', //
encPath: ['encrypt_folder/*', '/dav/189cloud/*'], // 子路径
},
],
@@ -68,19 +64,23 @@ const webdavServerTemp = [
]
// inti config, fix ncc get local conf
-function getConfFilePath() {
- return process.cwd() + '/conf/config.json'
+const confPath = path.join(process.cwd(), 'conf')
+const confFile = path.join(confPath, 'config.json')
+
+nedb.init(path.join(confPath, ``, 'nedb', 'datafile'))
+
+// 初始化目录
+if (!fs.existsSync(confPath)) {
+ fs.mkdirSync(confPath)
}
-const exist = fs.existsSync(getConfFilePath())
-if (!exist) {
- // 把默认数据写入到config.json
+if (!fs.existsSync(confFile)) {
const configData = { alistServer: alistServerTemp, webdavServer: webdavServerTemp, port: 5344 }
- fs.writeFileSync(getConfFilePath(), JSON.stringify(configData, '', '\t'))
+ fs.writeFileSync(confFile, JSON.stringify(configData, undefined, '\t'))
}
+
// 读取配置文件
-const configJson = fs.readFileSync(getConfFilePath(), 'utf8')
-const configData = JSON.parse(configJson)
+const configData = JSON.parse(fs.readFileSync(confFile, 'utf8'))
// 兼容之前的数据进来,保留2个版
if (configData.alistServer.flowPassword) {
@@ -95,11 +95,11 @@ if (configData.alistServer.flowPassword) {
delete alistServer.encryptType
delete alistServer.encPath
configData.webdavServer = webdavServerTemp
- fs.writeFileSync(process.cwd() + '/conf/config.json', JSON.stringify(configData, '', '\t'))
+ fs.writeFileSync(confFile, JSON.stringify(configData, undefined, '\t'))
}
/** 初始化用户的数据库 */
-async function init() {
+;(async () => {
try {
await nedb.load()
let admin = await getUserInfo('admin')
@@ -108,14 +108,14 @@ async function init() {
admin = { username: 'admin', headImgUrl: '/public/logo.svg', password: '123456', roleId: '[13]' }
await addUserInfo(admin)
}
- console.log('@@init', admin)
+ logger.info('管理员信息: ', admin)
} catch (e) {}
-}
-init()
+})().then()
// 副本用于前端更新, Object.assign({}, configData.alistServer) 只有第一层的拷贝
configData.alistServer._snapshot = JSON.parse(JSON.stringify(configData.alistServer))
-export function initAlistConfig(alistServerConfig) {
+
+export function initAlistConfig(alistServerConfig: AlistServer) {
// 初始化alist的路由,新增/d/* 路由
let downloads = []
for (const passwdData of alistServerConfig.passwdList) {
@@ -130,6 +130,7 @@ export function initAlistConfig(alistServerConfig) {
}
return alistServerConfig
}
+
/** 初始化alist的一些路径 */
initAlistConfig(configData.alistServer)
@@ -138,8 +139,8 @@ export const port = configData.port || 5344
export const version = '0.3.0'
-export const alistServer = configData.alistServer || alistServerTemp
+export const alistServer: AlistServer = configData.alistServer || alistServerTemp
-export const webdavServer = configData.webdavServer || webdavServerTemp
+export const webdavServer: WebdavServer[] = configData.webdavServer || webdavServerTemp
-console.log('configData ', configData)
+logger.info('代理配置 ', configData)
diff --git a/node-proxy/src/dao/configDao.js b/node-proxy/src/dao/configDao.js
deleted file mode 100644
index a87d937..0000000
--- a/node-proxy/src/dao/configDao.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import levelDB from '@/utils/levelDB'
-
-export const configTable = 'configTable'
-
-export async function initConfigTable() {
-}
-// alist配置
-const alistConfigKey = '_alist_config_key'
-export async function updateAlistConfig(config) {
- await levelDB.setValue(alistConfigKey, config)
-}
-export async function getAlistConfig() {
- return await levelDB.getValue(alistConfigKey)
-}
-
-// 缓存文件信息
-export async function addOrUpdateWebdav(config) {
- const id = config.id
- const value = await levelDB.getValue(configTable)
- value[id] = config
- await levelDB.setValue(configTable, value)
-}
-
-export async function delWebdavConfig(config) {
- const id = config.id
- const value = await levelDB.getValue(configTable)
- delete value[id]
- await levelDB.setValue(configTable, value)
-}
diff --git a/node-proxy/src/dao/fileDao.js b/node-proxy/src/dao/fileDao.js
deleted file mode 100644
index dbc546c..0000000
--- a/node-proxy/src/dao/fileDao.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import levelDB from '@/utils/levelDB'
-import crypto from 'crypto'
-
-export const fileInfoTable = 'fileInfoTable_'
-
-// 缓存多少分钟
-const cacheTime = 60 * 24
-
-export async function initFileTable() {}
-
-// 缓存文件信息
-export async function cacheFileInfo(fileInfo) {
- fileInfo.path = decodeURIComponent(fileInfo.path)
- const pathKey = fileInfoTable + fileInfo.path
- fileInfo.table = fileInfoTable
- await levelDB.setExpire(pathKey, fileInfo, 1000 * 60 * cacheTime)
-}
-
-// 获取文件信息,偶尔要清理一下缓存
-export async function getFileInfo(path) {
- const pathKey = decodeURIComponent(fileInfoTable + path)
- const value = await levelDB.getValue(pathKey)
- return value
-}
-
-// 获取文件信息
-export async function getAllFileInfo() {
- const value = await levelDB.getValue({ table: fileInfoTable })
- return value
-}
diff --git a/node-proxy/src/dao/fileDao.ts b/node-proxy/src/dao/fileDao.ts
new file mode 100644
index 0000000..8fddcfb
--- /dev/null
+++ b/node-proxy/src/dao/fileDao.ts
@@ -0,0 +1,21 @@
+import nedb from '@/dao/levelDB'
+
+import type { alist } from '@/@types/alist'
+
+export const fileInfoTable = 'fileInfoTable_'
+
+// 缓存多少分钟
+const cacheTime = 60 * 24
+
+// 缓存文件信息
+export async function cacheFileInfo(fileInfo: alist.FileInfo | WebdavFileInfo) {
+ fileInfo.path = decodeURIComponent(fileInfo.path)
+ const pathKey = fileInfoTable + fileInfo.path
+ await nedb.setExpire(pathKey, fileInfo, 1000 * 60 * cacheTime)
+}
+
+// 获取文件信息,偶尔要清理一下缓存
+export async function getFileInfo(path: string) {
+ const pathKey = decodeURIComponent(fileInfoTable + path)
+ return await nedb.getValue(pathKey)
+}
diff --git a/node-proxy/src/dao/levelDB.ts b/node-proxy/src/dao/levelDB.ts
new file mode 100644
index 0000000..7421859
--- /dev/null
+++ b/node-proxy/src/dao/levelDB.ts
@@ -0,0 +1,81 @@
+import Datastore from 'nedb-promises'
+
+import { logger } from '@/common/logger'
+
+type Value = any
+
+interface Document {
+ _id: string //NeDB 自动添加 _id
+ key: string
+ value: Value
+ expire?: number
+}
+
+/**
+ * 封装新方法
+ */
+class Nedb {
+ datastore: Datastore
+
+ init(dbFile: string) {
+ this.datastore = Datastore.create(dbFile)
+ }
+
+ async load() {
+ if (!this.datastore) {
+ logger.error('请先init Nedb')
+ }
+
+ await this.datastore.load()
+
+ setInterval(async () => {
+ const allData = await nedb.datastore.find({})
+ for (const data of allData) {
+ const { key, expire } = data
+ if (expire && expire > 0 && expire < Date.now()) {
+ logger.info('删除过期键值', key, expire, Date.now())
+ await nedb.datastore.remove({ key }, {})
+ }
+ }
+ }, 30 * 1000)
+ }
+
+ //存值,无过期时间
+ async setValue(key: string, value: Value) {
+ await this.datastore.removeMany({ key }, {})
+ logger.info('存储键值(无过期时间)', 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))
+ await this.datastore.insert({ key, expire, value })
+ }
+
+ // 取值
+ async getValue(key: string): Promise {
+ try {
+ const { expire, value } = await this.datastore.findOne({ key })
+ // 没有限制时间
+ if (expire < 0) {
+ return value
+ }
+
+ if (expire && expire > Date.now()) {
+ return value
+ }
+
+ await this.datastore.remove({ key }, {})
+ return null
+ } catch (e) {
+ return null
+ }
+ }
+}
+
+const nedb = new Nedb()
+
+export default nedb
diff --git a/node-proxy/src/dao/userDao.js b/node-proxy/src/dao/userDao.js
deleted file mode 100644
index 5c77bbe..0000000
--- a/node-proxy/src/dao/userDao.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import levelDB from '@/utils/levelDB'
-
-export const userTable = 'userTable'
-
-export async function initUserTable() {
- // const value = await levelDB.getValue(userTable)
- // if (value == null) {
- // await levelDB.setValue(userTable, {})
- // }
-}
-// 获取用户信息
-export async function getUserInfo(username) {
- const value = await levelDB.getValue(userTable)
- if (value == null) {
- return null
- }
- return value[username]
-}
-
-// 获取用户信息
-export async function getUserByToken(token) {
- return levelDB.getValue(token)
-}
-// 缓存用户信息
-export async function cacheUserToken(token, userInfo) {
- const value = await levelDB.setExpire(token, userInfo, 60 * 60 * 24)
- return value
-}
-
-export async function addUserInfo(userInfo) {
- const value = await levelDB.getValue(userTable) || {}
- value[userInfo.username] = userInfo
- await levelDB.setValue(userTable, value)
-}
-
-export async function updateUserInfo(userInfo) {
- const value = await levelDB.getValue(userTable)
- if (value[userInfo.username]) {
- value[userInfo.username] = userInfo
- levelDB.setValue(userTable, value)
- }
-}
-
-export async function delectUserInfo(userInfo) {
- const value = await levelDB.getValue(userTable)
- if (value[userInfo.username]) {
- delete value[userInfo.username]
- await levelDB.setValue(userTable, value)
- }
-}
diff --git a/node-proxy/src/dao/userDao.ts b/node-proxy/src/dao/userDao.ts
new file mode 100644
index 0000000..78a8480
--- /dev/null
+++ b/node-proxy/src/dao/userDao.ts
@@ -0,0 +1,48 @@
+import nedb from '@/dao/levelDB'
+
+import type { webui } from '@/@types/webui'
+
+export const userTable = 'userTable'
+
+// 获取用户信息
+export async function getUserInfo(username: string): Promise {
+ const value = await nedb.getValue(userTable)
+
+ if (value == null) {
+ return null
+ }
+
+ return value[username]
+}
+
+// 获取用户信息
+export async function getUserByToken(token: string): Promise {
+ return await nedb.getValue(token)
+}
+
+// 缓存用户信息
+export async function cacheUserToken(token: string, userInfo: webui.UserInfo) {
+ return await nedb.setExpire(token, userInfo, 60 * 60 * 24)
+}
+
+export async function addUserInfo(userInfo: webui.UserInfo) {
+ const value = (await nedb.getValue(userTable)) || {}
+ value[userInfo.username] = userInfo
+ await nedb.setValue(userTable, value)
+}
+
+export async function updateUserInfo(userInfo: webui.UserInfo) {
+ const value = await nedb.getValue(userTable)
+ if (value[userInfo.username]) {
+ value[userInfo.username] = userInfo
+ await nedb.setValue(userTable, value)
+ }
+}
+
+export async function deleteUserInfo(userInfo: webui.UserInfo) {
+ const value = await nedb.getValue(userTable)
+ if (value[userInfo.username]) {
+ delete value[userInfo.username]
+ await nedb.setValue(userTable, value)
+ }
+}
diff --git a/node-proxy/src/encNameRouter.js b/node-proxy/src/encNameRouter.js
deleted file mode 100644
index 0a762b0..0000000
--- a/node-proxy/src/encNameRouter.js
+++ /dev/null
@@ -1,247 +0,0 @@
-'use strict'
-
-import Router from 'koa-router'
-import bodyparser from 'koa-bodyparser'
-import { encodeName, pathFindPasswd, convertShowName, convertRealName } from './utils/commonUtil'
-import path from 'path'
-import { httpClient, httpProxy } from './utils/httpClient'
-import FlowEnc from './utils/flowEnc'
-import { logger } from './common/logger'
-import { getFileInfo } from './dao/fileDao'
-
-// bodyparser解析body
-const bodyparserMw = bodyparser({ enableTypes: ['json', 'form', 'text'] })
-
-const encNameRouter = new Router()
-const origPrefix = 'orig_'
-
-// 拦截全部
-encNameRouter.all('/api/fs/list', async (ctx, next) => {
- console.log('@@encrypt file name ', ctx.req.url)
- await next()
- const result = ctx.body
- const { passwdList } = ctx.req.webdavConfig
- if (result.code === 200 && result.data) {
- const content = result.data.content
- if (!content) {
- return
- }
- for (let i = 0; i < content.length; i++) {
- const fileInfo = content[i]
- if (fileInfo.is_dir) {
- continue
- }
- // Check path if the file name needs to be encrypted
- const { passwdInfo } = pathFindPasswd(passwdList, decodeURI(fileInfo.path))
- if (passwdInfo && passwdInfo.encName) {
- fileInfo.name = convertShowName(passwdInfo.password, passwdInfo.encType, fileInfo.name)
- }
- }
-
- const coverNameMap = {} //根据不含后缀的视频文件名找到对应的含后缀的封面文件名
- const omitNames = [] //用于隐藏封面文件
- const { path } = JSON.parse(ctx.req.reqBody)
- result.data.content.forEach((fileInfo) => {
- if (fileInfo.is_dir) {
- return
- }
- if (fileInfo.type === 5) {
- coverNameMap[fileInfo.name.split('.')[0]] = fileInfo.name
- }
- })
- result.data.content.forEach((fileInfo) => {
- if (fileInfo.is_dir) {
- return
- }
- const coverName = coverNameMap[fileInfo.name.split('.')[0]]
- if (fileInfo.type === 2 && coverName) {
- omitNames.push(coverName)
- fileInfo.thumb = `/d${path}/${coverName}`
- }
- })
- //不展示封面文件,也许可以添加个配置让用户选择是否展示封面源文件
- result.data.content = result.data.content.filter((fileInfo) => !omitNames.includes(fileInfo.name))
- }
-})
-
-// 处理网页上传文件
-encNameRouter.put('/api/fs/put', async (ctx, next) => {
- const request = ctx.req
- const { headers, webdavConfig } = request
- const contentLength = headers['content-length'] || 0
- request.fileSize = contentLength * 1
-
- const uploadPath = headers['file-path'] ? decodeURIComponent(headers['file-path']) : '/-'
- const { passwdInfo } = pathFindPasswd(webdavConfig.passwdList, uploadPath)
- if (passwdInfo) {
- const fileName = path.basename(uploadPath)
- // you can custom Suffix
- if (passwdInfo.encName) {
- const ext = passwdInfo.encSuffix || path.extname(fileName)
- const encName = encodeName(passwdInfo.password, passwdInfo.encType, fileName)
- const filePath = path.dirname(uploadPath) + '/' + encName + ext
- console.log('@@@encfileName', fileName, uploadPath, filePath)
- headers['file-path'] = encodeURIComponent(filePath)
- }
- const flowEnc = new FlowEnc(passwdInfo.password, passwdInfo.encType, request.fileSize)
- return await httpProxy(ctx.req, ctx.res, flowEnc.encryptTransform())
- }
- return await httpProxy(ctx.req, ctx.res)
-})
-
-// remove
-encNameRouter.all('/api/fs/remove', bodyparserMw, async (ctx, next) => {
- const { dir, names } = ctx.request.body
- const { webdavConfig } = ctx.req
- const { passwdInfo } = pathFindPasswd(webdavConfig.passwdList, dir)
- // maybe a folder,remove anyway the name
- const fileNames = Object.assign([], names)
- if (passwdInfo && passwdInfo.encName) {
- for (const name of names) {
- // is not enc name
- const realName = convertRealName(passwdInfo.password, passwdInfo.encType, name)
- fileNames.push(realName)
- }
- }
- const reqBody = { dir, names: fileNames }
- logger.info('@@reqBody remove', reqBody)
- ctx.req.reqBody = JSON.stringify(reqBody)
- // reset content-length length
- delete ctx.req.headers['content-length']
- const respBody = await httpClient(ctx.req)
- ctx.body = respBody
-})
-
-const copyOrMoveFile = async (ctx, next) => {
- const { dst_dir: dstDir, src_dir: srcDir, names } = ctx.request.body
- const { webdavConfig } = ctx.req
- const { passwdInfo } = pathFindPasswd(webdavConfig.passwdList, srcDir)
- let fileNames = []
- if (passwdInfo && passwdInfo.encName) {
- logger.info('@@move encName', passwdInfo.encName)
- for (const name of names) {
- // is not enc name
- if (name.indexOf(origPrefix) === 0) {
- const origName = name.replace(origPrefix, '')
- fileNames.push(origName)
- break
- }
- const fileName = path.basename(name)
- // you can custom Suffix
- const ext = passwdInfo.encSuffix || path.extname(fileName)
- const encName = encodeName(passwdInfo.password, passwdInfo.encType, fileName)
- const newFileName = encName + ext
- fileNames.push(newFileName)
- }
- } else {
- fileNames = Object.assign([], names)
- }
- const reqBody = { dst_dir: dstDir, src_dir: srcDir, names: fileNames }
- ctx.req.reqBody = JSON.stringify(reqBody)
- logger.info('@@move reqBody', ctx.req.reqBody)
- // reset content-length length
- delete ctx.req.headers['content-length']
- const respBody = await httpClient(ctx.req)
- ctx.body = respBody
-}
-
-encNameRouter.all('/api/fs/move', bodyparserMw, copyOrMoveFile)
-encNameRouter.all('/api/fs/copy', bodyparserMw, copyOrMoveFile)
-
-encNameRouter.all('/api/fs/get', bodyparserMw, async (ctx, next) => {
- const { path: filePath } = ctx.request.body
- const { webdavConfig } = ctx.req
- const { passwdInfo } = pathFindPasswd(webdavConfig.passwdList, filePath)
- if (passwdInfo && passwdInfo.encName) {
- // reset content-length length
- delete ctx.req.headers['content-length']
- // check fileName is not enc
- const fileName = path.basename(filePath)
- const fileInfo = await getFileInfo(encodeURIComponent(filePath))
- if (fileInfo && fileInfo.is_dir) {
- await next()
- return
- }
- // Check if it is a directory
- const realName = convertRealName(passwdInfo.password, passwdInfo.encType, fileName)
- const fpath = path.dirname(filePath) + '/' + realName
- console.log('@@@getFilePath', fpath)
- ctx.request.body.path = fpath
- }
- await next()
- if (passwdInfo && passwdInfo.encName) {
- // return showName
- const showName = convertShowName(passwdInfo.password, passwdInfo.encType, ctx.body.data.name)
- ctx.body.data.name = showName
- }
-})
-
-encNameRouter.all('/api/fs/rename', bodyparserMw, async (ctx, next) => {
- const { path: filePath, name } = ctx.request.body
- const { webdavConfig } = ctx.req
- const { passwdInfo } = pathFindPasswd(webdavConfig.passwdList, filePath)
- const reqBody = { path: filePath, name }
- ctx.req.reqBody = reqBody
- // reset content-length length
- delete ctx.req.headers['content-length']
-
- let fileInfo = await getFileInfo(encodeURIComponent(filePath))
- if (fileInfo == null && passwdInfo && passwdInfo.encName) {
- // mabay a file
- const realName = convertRealName(passwdInfo.password, passwdInfo.encType, filePath)
- const realFilePath = path.dirname(filePath) + '/' + realName
- fileInfo = await getFileInfo(encodeURIComponent(realFilePath))
- }
- if (passwdInfo && passwdInfo.encName && fileInfo && !fileInfo.is_dir) {
- // reset content-length length
- // you can custom Suffix
- const ext = passwdInfo.encSuffix || path.extname(name)
- const realName = convertRealName(passwdInfo.password, passwdInfo.encType, filePath)
- const fpath = path.dirname(filePath) + '/' + realName
- const newName = encodeName(passwdInfo.password, passwdInfo.encType, name)
- reqBody.path = fpath
- reqBody.name = newName + ext
- }
- ctx.req.reqBody = reqBody
- console.log('@@@rename', reqBody)
- const respBody = await httpClient(ctx.req)
- ctx.body = respBody
-})
-
-const handleDownload = async (ctx, next) => {
- const request = ctx.req
- const { webdavConfig } = ctx.req
-
- const urlPath = ctx.req.url.split('?')[0]
- let filePath = urlPath
- // 如果是alist的话,那么必然有这个文件的size缓存(进过list就会被缓存起来)
- request.fileSize = 0
- // 这里需要处理掉/p 路径
- if (filePath.indexOf('/d/') === 0) {
- filePath = filePath.replace('/d/', '/')
- }
- // 这个不需要处理
- if (filePath.indexOf('/p/') === 0) {
- filePath = filePath.replace('/p/', '/')
- }
- const { passwdInfo } = pathFindPasswd(webdavConfig.passwdList, filePath)
- if (passwdInfo && passwdInfo.encName) {
- // reset content-length length
- delete ctx.req.headers['content-length']
- // check fileName is not enc or it is dir
- const fileName = path.basename(filePath)
- const realName = convertRealName(passwdInfo.password, passwdInfo.encType, fileName)
- ctx.req.url = path.dirname(ctx.req.url) + '/' + realName
- ctx.req.urlAddr = path.dirname(ctx.req.urlAddr) + '/' + realName
- logger.debug('@@@@download-fileName', ctx.req.url, fileName, realName)
- await next()
- return
- }
- await next()
-}
-
-encNameRouter.get(/^\/d\/*/, bodyparserMw, handleDownload)
-encNameRouter.get(/\/p\/*/, bodyparserMw, handleDownload)
-
-// restRouter.all(/\/enc-api\/*/, router.routes(), restRouter.allowedMethods())
-export default encNameRouter
diff --git a/node-proxy/src/middleware/common.ts b/node-proxy/src/middleware/common.ts
new file mode 100644
index 0000000..f802e0a
--- /dev/null
+++ b/node-proxy/src/middleware/common.ts
@@ -0,0 +1,212 @@
+import path from 'path'
+import type { Transform } from 'stream'
+
+import bodyparser from 'koa-bodyparser'
+import type { Middleware, Next, ParameterizedContext } from 'koa'
+
+import FlowEnc from '@/utils/flowEnc'
+import { flat } from '@/utils/common'
+import { logger } from '@/common/logger'
+import { httpFlowClient } from '@/utils/httpClient'
+import { getWebdavFileInfo } from '@/utils/webdavClient'
+import { cacheFileInfo, getFileInfo } from '@/dao/fileDao'
+import { encodeName, pathFindPasswd } from '@/utils/cryptoUtil'
+
+export const compose = (...middlewares: Middleware[]): Middleware => {
+ if (!Array.isArray(middlewares)) throw new TypeError('Middleware stack must be an array!')
+ for (const fn of middlewares) {
+ if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
+ }
+
+ // 最后一个中间件需要调用 next() 来确保响应可以结束
+ return function (context, next) {
+ // 创建一个新的函数,这个函数会依次调用中间件
+ let index = -1
+
+ function dispatch(i: number) {
+ if (i <= index) return Promise.reject(new Error('next() called multiple times'))
+ index = i
+
+ let fn = middlewares[i]
+ if (i === middlewares.length) fn = next // 如果没有更多中间件,则调用原始的 next()
+
+ if (!fn) return Promise.resolve()
+
+ try {
+ return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
+ } catch (err) {
+ return Promise.reject(err)
+ }
+ }
+
+ return dispatch(0) // 开始执行第一个中间件
+ }
+}
+
+export const emptyMiddleware: Middleware = async (_, next) => {
+ await next()
+}
+
+export const loggerMiddleware: Middleware = async (ctx, next) => {
+ const reqPath = decodeURIComponent(ctx.path)
+
+ let show = true
+ if (reqPath.startsWith('/public')) {
+ show = false
+ }
+
+ if (['.html', '.js', '.css'].includes(path.extname(reqPath))) {
+ show = false
+ }
+
+ if (show) {
+ logger.info(`-------------------开始 ${reqPath}-------------------`)
+ }
+
+ await next()
+
+ if (show) {
+ logger.info(`-------------------结束 ${reqPath}-------------------`)
+ }
+}
+
+export const bodyParserMiddleware = bodyparser({ enableTypes: ['json', 'form', 'text'] })
+
+export const exceptionMiddleware: Middleware = async (
+ ctx: ParameterizedContext<
+ EmptyObj,
+ EmptyObj,
+ {
+ code: number
+ success: boolean
+ message: string
+ }
+ >,
+ next: Next
+) => {
+ try {
+ await next()
+ if (ctx.status === 404) {
+ ctx.throw(404, '请求资源未找到!')
+ }
+ } catch (err) {
+ logger.error('@@err')
+ console.trace(err)
+
+ const status = err.status || 500
+ // 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
+ const error = status === 500 && process.env.RUN_MODE === 'DEV' ? err.message : 'Internal Server Error'
+ // 从 error 对象上读出各个属性,设置到响应中
+ ctx.body = {
+ success: false,
+ message: error,
+ code: status, // 服务端自身的处理逻辑错误(包含框架错误500 及 自定义业务逻辑错误533开始 ) 客户端请求参数导致的错误(4xx开始),设置不同的状态码
+ }
+ // 406 是能让用户看到的错误,参数校验失败也不能让用户看到(一般不存在参数校验失败)
+ if (status === 403 || status === 406) {
+ ctx.body.message = error
+ }
+ ctx.status = 200
+ }
+}
+
+export const proxyHandler: Middleware = async (ctx: ParameterizedContext>) => {
+ const { state, req: request, res: response } = ctx
+
+ const { headers } = request
+ // 要定位请求文件的位置 bytes=98304-
+ const range = headers.range
+ const start = range ? Number(range.replace('bytes=', '').split('-')[0]) : 0
+ // 检查路径是否满足加密要求,要拦截的路径可能有中文
+ const { passwdInfo } = pathFindPasswd(state.serverConfig.passwdList, decodeURIComponent(request.url))
+
+ logger.info('匹配密码信息', passwdInfo === null ? '无密码' : passwdInfo.password)
+
+ let encryptTransform: Transform, decryptTransform: Transform
+ // fix webdav move file
+ if (request.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
+ }
+
+ // 如果是上传文件,那么进行流加密,目前只支持webdav上传,如果alist页面有上传功能,那么也可以兼容进来
+ if (request.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) {
+ // 根据文件路径来获取文件的大小
+ let filePath = ctx.req.url.split('?')[0]
+ // 如果是alist的话,那么必然有这个文件的size缓存(进过list就会被缓存起来)
+ state.fileSize = 0
+ // 这里需要处理掉/p 路径
+ if (filePath.indexOf('/p/') === 0) {
+ filePath = filePath.replace('/p/', '/')
+ }
+ if (filePath.indexOf('/d/') === 0) {
+ filePath = filePath.replace('/d/', '/')
+ }
+ // 尝试获取文件信息,如果未找到相应的文件信息,则对文件名进行加密处理后重新尝试获取文件信息
+ let fileInfo = await getFileInfo(filePath)
+
+ if (fileInfo === null) {
+ const rawFileName = decodeURIComponent(path.basename(filePath))
+ const ext = path.extname(rawFileName)
+ const encodedRawFileName = encodeURIComponent(rawFileName)
+ const encFileName = encodeName(passwdInfo.password, passwdInfo.encType, rawFileName)
+ const newFileName = encFileName + ext
+
+ filePath = filePath.replace(encodedRawFileName, newFileName)
+ state.urlAddr = state.urlAddr.replace(encodedRawFileName, newFileName)
+
+ fileInfo = await getFileInfo(filePath)
+ }
+
+ logger.info('获取文件信息:', filePath, JSON.stringify(fileInfo))
+
+ if (fileInfo) {
+ state.fileSize = fileInfo.size
+ } else if (request.headers.authorization) {
+ // 这里要判断是否webdav进行请求, 这里默认就是webdav请求了
+ const authorization = request.headers.authorization
+ const webdavFileInfo = await getWebdavFileInfo(state.urlAddr, authorization)
+ logger.info('@@webdavFileInfo:', filePath, webdavFileInfo)
+ if (webdavFileInfo) {
+ webdavFileInfo.path = filePath
+ // 某些get请求返回的size=0,不要缓存起来
+ if (webdavFileInfo.size > 0) {
+ await cacheFileInfo(webdavFileInfo)
+ }
+ state.fileSize = webdavFileInfo.size
+ }
+ }
+
+ state.passwdInfo = passwdInfo
+
+ // logger.info('@@@@request.filePath ', request.filePath, result)
+ if (state.fileSize !== 0) {
+ const flowEnc = new FlowEnc(passwdInfo.password, passwdInfo.encType, state.fileSize)
+ if (start) {
+ await flowEnc.setPosition(start)
+ }
+ decryptTransform = flowEnc.decryptTransform()
+ }
+ }
+
+ await httpFlowClient({
+ urlAddr: state.urlAddr,
+ passwdInfo: state.passwdInfo,
+ fileSize: state.fileSize,
+ request,
+ response,
+ encryptTransform,
+ decryptTransform,
+ })
+}
diff --git a/node-proxy/src/middleware/globalHandle.js b/node-proxy/src/middleware/globalHandle.js
deleted file mode 100644
index c70c3c4..0000000
--- a/node-proxy/src/middleware/globalHandle.js
+++ /dev/null
@@ -1,32 +0,0 @@
-'use strict'
-
-export default async function (ctx, next) {
- const env = 'dev'
- try {
- await next()
- // 兼容webdav中401的时候,body = ''
- if (!ctx.body) {
- return
- }
- // 参数转换, 转换成自己的数据格式
- } catch (err) {
- // 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志
- // app.emit('error', err, this);
- const status = err.status || 500
- // 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
- const error = status === 500 && env === 'prod' ? 'Internal Server Error' : err.message
- console.error('@@err', err)
- // 从 error 对象上读出各个属性,设置到响应中
- ctx.body = {
- success: false,
- message: error,
- code: status, // 服务端自身的处理逻辑错误(包含框架错误500 及 自定义业务逻辑错误533开始 ) 客户端请求参数导致的错误(4xx开始),设置不同的状态码
- data: null,
- }
- // 406 是能让用户看到的错误,参数校验失败也不能让用户看到(一般不存在参数校验失败)
- if (status === '403' || status === '406') {
- ctx.body.message = error
- }
- ctx.status = 200
- }
-}
diff --git a/node-proxy/src/middleware/responseHandle.js b/node-proxy/src/middleware/responseHandle.js
deleted file mode 100644
index 033a9b8..0000000
--- a/node-proxy/src/middleware/responseHandle.js
+++ /dev/null
@@ -1,41 +0,0 @@
-'use strict'
-
-export default async function (ctx, next) {
- const env = 'dev'
- try {
- await next()
- // 参数转换, 转换成自己的数据格式
- if (typeof ctx.body === 'object') {
- const { flag, msg, code, data } = ctx.body
- const body = {
- flag: flag || true,
- msg,
- code: code || 200,
- data,
- }
- ctx.body = body
- }
- if (ctx.status === 404) {
- ctx.throw(404, '请求资源未找到!')
- }
- } catch (err) {
- // 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志
- console.log(err)
- const status = err.status || 500
- // 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
- const error = status === 500 && env === 'prod' ? 'Internal Server Error' : err.message
- // 从 error 对象上读出各个属性,设置到响应中
- ctx.body = {
- flag: false,
- msg: error,
- code: status, // 服务端自身的处理逻辑错误(包含框架错误500 及 自定义业务逻辑错误533开始 ) 客户端请求参数导致的错误(4xx开始),设置不同的状态码
- data: null,
- }
-
- // 406 是能让用户看到的错误,参数校验失败也不能让用户看到(一般不存在参数校验失败)
- if (status === '403' || status === '406') {
- ctx.body.message = error
- }
- ctx.status = 200
- }
-}
diff --git a/node-proxy/src/router.js b/node-proxy/src/router.js
deleted file mode 100644
index cb3a5f8..0000000
--- a/node-proxy/src/router.js
+++ /dev/null
@@ -1,203 +0,0 @@
-'use strict'
-
-import Router from 'koa-router'
-import bodyparser from 'koa-bodyparser'
-import crypto from 'crypto'
-import fs from 'fs'
-import { alistServer, webdavServer, port, initAlistConfig, version } from './config'
-import { getUserInfo, cacheUserToken, getUserByToken, updateUserInfo } from './dao/userDao'
-import responseHandle from './middleware/responseHandle'
-import { encodeFolderName, decodeFolderName } from './utils/commonUtil'
-import { encryptFile, searchFile } from './utils/convertFile'
-
-// bodyparser解析body
-const bodyparserMw = bodyparser({ enableTypes: ['json', 'form', 'text'] })
-
-// 总路径,添加所有的子路由
-const allRouter = new Router()
-// 拦截全部
-allRouter.all(/^\/enc-api\/*/, bodyparserMw, responseHandle, async (ctx, next) => {
- console.log('@@log request-url: ', ctx.req.url)
- await next()
-})
-
-// 白名单路由
-allRouter.all('/enc-api/login', async (ctx, next) => {
- const { username, password } = ctx.request.body
- console.log(username, password)
- const userInfo = await getUserInfo(username)
- console.log(userInfo)
- if (userInfo && password === userInfo.password) {
- // 创建token
- const token = crypto.randomUUID()
- // 异步执行
- cacheUserToken(token, userInfo)
- userInfo.password = null
- ctx.body = { data: { userInfo, jwtToken: token } }
- return
- }
- ctx.body = { msg: 'passwword error', code: 500 }
-})
-
-// 拦截登录
-allRouter.all(/^\/enc-api\/*/, async (ctx, next) => {
- // nginx不支持下划线headers
- const { authorizetoken: authorizeToken } = ctx.request.headers
- // 查询数据库是否有密码
- const userInfo = await getUserByToken(authorizeToken)
- if (userInfo == null) {
- ctx.body = { code: 401, msg: 'user unlogin' }
- return
- }
- ctx.userInfo = userInfo
- await next()
-})
-
-// 设置前缀
-const router = new Router({ prefix: '/enc-api' })
-
-// 用户信息
-router.all('/getUserInfo', async (ctx, next) => {
- const userInfo = ctx.userInfo
- console.log('@@getUserInfo', userInfo)
- userInfo.password = null
- const data = {
- codes: [16, 9, 10, 11, 12, 13, 15],
- userInfo,
- menuList: [],
- roles: ['admin'],
- version,
- }
- ctx.body = { data }
-})
-
-// 更新用户信息
-router.all('/updatePasswd', async (ctx, next) => {
- const { password, newpassword, username } = ctx.request.body
- if (newpassword.length < 7) {
- ctx.body = { msg: 'password too short, at less 8 digits', code: 500 }
- return
- }
- const userInfo = await getUserInfo(username)
- if (password !== userInfo.password) {
- ctx.body = { msg: 'password error', code: 500 }
- return
- }
- userInfo.password = newpassword
- updateUserInfo(userInfo)
- ctx.body = { msg: 'update success' }
-})
-
-router.all('/getAlistConfig', async (ctx, next) => {
- ctx.body = { data: alistServer._snapshot }
-})
-
-router.all('/saveAlistConfig', async (ctx, next) => {
- let alistConfig = ctx.request.body
- for (const index in alistConfig.passwdList) {
- const passwdInfo = alistConfig.passwdList[index]
- if (typeof passwdInfo.encPath === 'string') {
- passwdInfo.encPath = passwdInfo.encPath.split(',')
- }
- }
- const _snapshot = JSON.parse(JSON.stringify(alistConfig))
- // 写入到文件中,这里并不是真正的同步,,
- fs.writeFileSync(process.cwd() + '/conf/config.json', JSON.stringify({ alistServer: _snapshot, webdavServer, port }, '', '\t'))
- alistConfig = initAlistConfig(alistConfig)
- Object.assign(alistServer, alistConfig)
- alistServer._snapshot = _snapshot
- ctx.body = { msg: 'save ok' }
-})
-
-router.all('/getWebdavonfig', async (ctx, next) => {
- ctx.body = { data: webdavServer }
-})
-
-router.all('/saveWebdavConfig', async (ctx, next) => {
- const config = ctx.request.body
- for (const index in config.passwdList) {
- const passwdInfo = config.passwdList[index]
- if (typeof passwdInfo.encPath === 'string') {
- passwdInfo.encPath = passwdInfo.encPath.split(',')
- }
- }
- config.id = crypto.randomUUID()
- webdavServer.push(config)
- fs.writeFileSync(process.cwd() + '/conf/config.json', JSON.stringify({ alistServer: alistServer._snapshot, webdavServer, port }, '', '\t'))
- ctx.body = { data: webdavServer }
-})
-
-router.all('/updateWebdavConfig', async (ctx, next) => {
- const config = ctx.request.body
- for (const index in config.passwdList) {
- const passwdInfo = config.passwdList[index]
- if (typeof passwdInfo.encPath === 'string') {
- passwdInfo.encPath = passwdInfo.encPath.split(',')
- }
- }
-
- for (const index in webdavServer) {
- if (webdavServer[index].id === config.id) {
- webdavServer[index] = config
- }
- }
- fs.writeFileSync(process.cwd() + '/conf/config.json', JSON.stringify({ alistServer: alistServer._snapshot, webdavServer, port }, '', '\t'))
- ctx.body = { data: webdavServer }
-})
-
-router.all('/delWebdavConfig', async (ctx, next) => {
- const { id } = ctx.request.body
- for (const index in webdavServer) {
- if (webdavServer[index].id === id) {
- webdavServer.splice(index, 1)
- }
- }
- fs.writeFileSync(process.cwd() + '/conf/config.json', JSON.stringify({ alistServer: alistServer._snapshot, webdavServer, port }, '', '\t'))
- ctx.body = { data: webdavServer }
-})
-
-// get folder passwd encode
-router.all('/encodeFoldName', async (ctx, next) => {
- const { password, encType, folderPasswd, folderEncType } = ctx.request.body
- const folderNameEnc = encodeFolderName(password, encType, folderPasswd, folderEncType)
- ctx.body = { data: { folderNameEnc } }
- console.log('@@encodeFoldName', password, folderNameEnc)
-})
-
-router.all('/decodeFoldName', async (ctx, next) => {
- const { password, folderNameEnc, encType } = ctx.request.body
- const arr = folderNameEnc.split('_')
- if (arr.length < 2) {
- ctx.body = { msg: 'folderName not encdoe', code: 500 }
- return
- }
- const data = decodeFolderName(password, encType, folderNameEnc)
- if (!data) {
- ctx.body = { msg: 'folderName is error', code: 500 }
- return
- }
- const { folderEncType, folderPasswd } = data
- ctx.body = { data: { folderEncType, folderPasswd } }
-})
-
-// encrypt or decrypt file
-router.all('/encryptFile', async (ctx, next) => {
- const { folderPath, outPath, encType, password, operation, encName } = ctx.request.body
- if (!fs.existsSync(folderPath)) {
- ctx.body = { msg: 'encrypt file path not exists', code: 500 }
- return
- }
- const files = searchFile(folderPath)
- if (files.length > 10000) {
- ctx.body = { msg: 'too maney file, exceeding 10000', code: 500 }
- return
- }
- encryptFile(password, encType, operation, folderPath, outPath, encName)
- ctx.body = { msg: 'waiting operation' }
-})
-
-// 用这种方式代替前缀的功能,{ prefix: } 不能和正则联合使用
-allRouter.use(router.routes(), router.allowedMethods())
-
-// restRouter.all(/\/enc-api\/*/, router.routes(), restRouter.allowedMethods())
-export default allRouter
diff --git a/node-proxy/src/router/alist/index.ts b/node-proxy/src/router/alist/index.ts
new file mode 100644
index 0000000..1c46efe
--- /dev/null
+++ b/node-proxy/src/router/alist/index.ts
@@ -0,0 +1,262 @@
+import path from 'path'
+import crypto from 'crypto'
+import type { Transform } from 'stream'
+
+import Router from 'koa-router'
+
+import nedb from '@/dao/levelDB'
+import FlowEnc from '@/utils/flowEnc'
+import { logger } from '@/common/logger'
+import { preProxy } from '@/utils/common'
+import { alistServer } from '@/config'
+import { copyOrMoveFileMiddleware } from '@/router/alist/utils'
+import { cacheFileInfo, getFileInfo } from '@/dao/fileDao'
+import { httpClient, httpFlowClient } from '@/utils/httpClient'
+import { bodyParserMiddleware, emptyMiddleware } from '@/middleware/common'
+import { convertRealName, convertShowName, encodeName, pathFindPasswd } from '@/utils/cryptoUtil'
+import type { alist } from '@/@types/alist'
+
+const alistRouter = new Router({ prefix: '/api' })
+// 其他的代理request预处理,添加到ctx.state中
+alistRouter.use(preProxy(alistServer, false))
+
+alistRouter.all, ParsedContext>('/fs/list', bodyParserMiddleware, async (ctx, next) => {
+ logger.info('从alist获取文件列表 ', ctx.req.url)
+
+ await next()
+ const { path } = ctx.request.body
+
+ const response = JSON.parse(ctx.response.body) as alist.FsListResponseBody
+
+ logger.info(`已从alist获取文件列表,路径:${path} 文件数量:${response.data.content?.length || 0}`)
+ logger.trace(`原始文件信息: `, response.data)
+
+ if (response.code !== 200) return
+
+ const files = response.data.content
+ if (!files) return
+
+ const state = ctx.state
+ let encrypted = false
+
+ for (let i = 0; i < files.length; i++) {
+ const file = files[i]
+ file.path = path + '/' + file.name
+ await cacheFileInfo(file)
+
+ if (file.is_dir) continue
+
+ const { passwdInfo } = pathFindPasswd(state.serverConfig.passwdList, decodeURI(file.path))
+
+ if (passwdInfo && passwdInfo.encName) {
+ encrypted = true
+ file.name = convertShowName(passwdInfo.password, passwdInfo.encType, file.name)
+ }
+ }
+
+ logger.info(encrypted ? '解密完成' : '无需解密内容,跳过解密')
+
+ const coverNameMap = {} //根据不含后缀的视频文件名找到对应的含后缀的封面文件名
+ const omitNames = [] //用于隐藏封面文件
+
+ files
+ .filter((fileInfo) => fileInfo.type === 5)
+ .forEach((fileInfo) => {
+ coverNameMap[fileInfo.name.split('.')[0]] = fileInfo.name
+ })
+
+ files
+ .filter((fileInfo) => fileInfo.type === 2)
+ .forEach((fileInfo) => {
+ const coverName = coverNameMap[fileInfo.name.split('.')[0]]
+
+ if (coverName) {
+ omitNames.push(coverName)
+ fileInfo.thumb = `/d${path}/${coverName}`
+ }
+ })
+
+ //不展示封面文件,也许可以添加个配置让用户选择是否展示封面源文件
+ response.data.content = files.filter((fileInfo) => !omitNames.includes(fileInfo.name))
+ ctx.body = response
+
+ logger.info(`返回文件列表信息`)
+ logger.trace(`处理后文件信息: `, ctx.body.data)
+})
+
+alistRouter.all, ParsedContext>('/fs/get', bodyParserMiddleware, async (ctx, next) => {
+ const { path: filePath } = ctx.request.body
+ const state = ctx.state
+ const { passwdInfo } = pathFindPasswd(state.serverConfig.passwdList, filePath)
+
+ const encrypted = passwdInfo && passwdInfo.encName
+
+ logger.info(`文件路径: ${filePath} 是否被加密 ${encrypted}`)
+
+ if (encrypted) {
+ // reset content-length length
+ delete ctx.req.headers['content-length']
+ // check fileName is not enc
+ const fileName = path.basename(filePath)
+ const fileInfo = await getFileInfo(encodeURIComponent(filePath))
+ // Check if it is a directory
+ if (fileInfo && fileInfo.is_dir) {
+ await next()
+ return
+ }
+
+ const realName = convertRealName(passwdInfo.password, passwdInfo.encType, fileName)
+ const fileUri = path.dirname(filePath) + '/' + realName
+ logger.info(`构造原始路径: ${filePath} -> ${fileUri}`)
+ ctx.request.body.path = fileUri
+ }
+
+ logger.info('获取文件内容...')
+
+ await next()
+
+ ctx.body = JSON.parse(ctx.body)
+
+ logger.info(encrypted ? '开始解密文件信息' : '文件未加密,跳过解密')
+
+ if (encrypted) {
+ // return showName
+ const respBody = ctx.body
+ respBody.data.name = convertShowName(passwdInfo.password, passwdInfo.encType, respBody.data.name)
+
+ const { headers } = ctx.request
+ const key = crypto.randomUUID()
+
+ await nedb.setExpire(
+ key,
+ {
+ redirectUrl: respBody.data.raw_url,
+ passwdInfo,
+ fileSize: respBody.data.size,
+ },
+ 60 * 60 * 72
+ ) // 缓存起来,默认3天,足够下载和观看了
+
+ respBody.data.raw_url = `${
+ headers.origin || (headers['x-forwarded-proto'] || ctx.protocol) + '://' + ctx.state.selfHost
+ }/redirect/${key}?decode=1&lastUrl=${encodeURIComponent(ctx.request.body.path)}`
+
+ if (respBody.data.provider === 'AliyundriveOpen') respBody.data.provider = 'Local'
+
+ ctx.body = respBody
+ }
+
+ logger.info(`返回文件信息`)
+})
+
+// 处理网页上传文件
+alistRouter.put, EmptyObj>('/fs/put', emptyMiddleware, async (ctx) => {
+ const { headers } = ctx.request
+ 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)}`)
+
+ let encryptTransform: Transform
+ if (encrypted) {
+ const fileName = path.basename(uploadPath)
+ const ext = passwdInfo.encSuffix || path.extname(fileName)
+ const encName = encodeName(passwdInfo.password, passwdInfo.encType, fileName)
+ const filePath = path.dirname(uploadPath) + '/' + encName + ext
+ logger.info('加密后路径: ', filePath)
+ headers['file-path'] = encodeURIComponent(filePath)
+ const flowEnc = new FlowEnc(passwdInfo.password, passwdInfo.encType, ctx.state.fileSize)
+ encryptTransform = flowEnc.encryptTransform()
+ }
+
+ return await httpFlowClient({
+ urlAddr: state.urlAddr,
+ passwdInfo,
+ fileSize: state.fileSize,
+ request: ctx.req,
+ response: ctx.res,
+ encryptTransform,
+ })
+})
+
+alistRouter.all, ParsedContext>('/fs/rename', bodyParserMiddleware, async (ctx) => {
+ const { path: filePath, name } = ctx.request.body
+ const { serverConfig: config, urlAddr } = ctx.state
+ const { passwdInfo } = pathFindPasswd(config.passwdList, filePath)
+
+ const reqBody: alist.FsRenameRequestBody = { path: filePath, name }
+
+ logger.info(`重命名文件 ${filePath} -> ${name}`)
+
+ // reset content-length length
+ delete ctx.req.headers['content-length']
+
+ let fileInfo = await getFileInfo(encodeURIComponent(filePath))
+
+ if (fileInfo == null && passwdInfo && passwdInfo.encName) {
+ // maybe a file
+ const realName = convertRealName(passwdInfo.password, passwdInfo.encType, filePath)
+ const realFilePath = path.dirname(filePath) + '/' + realName
+ logger.info(`转化为原始文件路径: ${filePath} ${realFilePath}`)
+ fileInfo = await getFileInfo(encodeURIComponent(realFilePath))
+ }
+
+ if (passwdInfo && passwdInfo.encName && fileInfo && !fileInfo.is_dir) {
+ // reset content-length length
+ const ext = passwdInfo.encSuffix || path.extname(name)
+ const realName = convertRealName(passwdInfo.password, passwdInfo.encType, filePath)
+ const fileUri = path.dirname(filePath) + '/' + realName
+ const newName = encodeName(passwdInfo.password, passwdInfo.encType, name)
+ reqBody.path = fileUri
+ reqBody.name = newName + ext
+ }
+
+ logger.info(`重命名: ${reqBody.path} -> ${reqBody.name}`)
+ ctx.body = await httpClient({
+ urlAddr,
+ reqBody: JSON.stringify(reqBody),
+ request: ctx.req,
+ })
+})
+
+// remove
+alistRouter.all, ParsedContext>('/fs/remove', bodyParserMiddleware, async (ctx) => {
+ const { dir, names } = ctx.request.body
+ const state = ctx.state
+
+ logger.info(`删除文件: 路径${dir} 文件名${JSON.stringify(names)}`)
+
+ const { passwdInfo } = pathFindPasswd(state.serverConfig.passwdList, dir)
+
+ // maybe a folder,remove anyway the name
+ const fileNames = Object.assign([], names) //TODO 并没有判断是否加密,而是尝试同时删除加密前与加密后的文件,应该修改为判断是否加密再删除
+ if (passwdInfo && passwdInfo.encName) {
+ for (const name of names) {
+ // is not enc name
+ const realName = convertRealName(passwdInfo.password, passwdInfo.encType, name)
+ fileNames.push(realName)
+ }
+
+ logger.info('转化为原始文件名: ', JSON.stringify(fileNames))
+ }
+
+ const reqBody = { dir, 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,
+ })
+})
+
+alistRouter.all, ParsedContext>('/fs/move', bodyParserMiddleware, copyOrMoveFileMiddleware)
+
+alistRouter.all, ParsedContext>('/fs/copy', bodyParserMiddleware, copyOrMoveFileMiddleware)
+
+export default alistRouter
diff --git a/node-proxy/src/router/alist/utils.ts b/node-proxy/src/router/alist/utils.ts
new file mode 100644
index 0000000..82de339
--- /dev/null
+++ b/node-proxy/src/router/alist/utils.ts
@@ -0,0 +1,50 @@
+import path from 'path'
+
+import type { Middleware } from 'koa'
+
+import { logger } from '@/common/logger'
+import { httpClient } from '@/utils/httpClient'
+import { encodeName, pathFindPasswd } from '@/utils/cryptoUtil'
+import { alist } from '@/@types/alist'
+
+const origPrefix = 'orig_'
+
+export const copyOrMoveFileMiddleware: Middleware<
+ ProxiedState,
+ ParsedContext
+> = async (ctx) => {
+ const state = ctx.state
+ const { dst_dir: dstDir, src_dir: srcDir, names } = ctx.request.body
+ const { passwdInfo } = pathFindPasswd(state.serverConfig.passwdList, srcDir)
+
+ logger.info(`复制/移动文件: ${JSON.stringify(names)}`)
+ logger.info(`原文件夹:${srcDir} -> 目标文件夹:${dstDir}`)
+
+ let fileNames = []
+ if (passwdInfo && passwdInfo.encName) {
+ for (const name of names) {
+ // is not enc name
+ if (name.indexOf(origPrefix) === 0) {
+ const origName = name.replace(origPrefix, '')
+ fileNames.push(origName)
+ break
+ }
+ const fileName = path.basename(name)
+ // you can custom Suffix
+ const ext = passwdInfo.encSuffix || path.extname(fileName)
+ const encName = encodeName(passwdInfo.password, passwdInfo.encType, fileName)
+ const newFileName = encName + ext
+ fileNames.push(newFileName)
+ }
+
+ logger.info('转化为原始文件名: ', JSON.stringify(fileNames))
+ } else {
+ 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 })
+}
diff --git a/node-proxy/src/router/other/index.ts b/node-proxy/src/router/other/index.ts
new file mode 100644
index 0000000..2f13334
--- /dev/null
+++ b/node-proxy/src/router/other/index.ts
@@ -0,0 +1,119 @@
+import Router from 'koa-router'
+import { flat, preProxy } from '@/utils/common'
+import { alistServer, version } from '@/config'
+import levelDB from '@/dao/levelDB'
+import FlowEnc from '@/utils/flowEnc'
+import { pathExec } from '@/utils/cryptoUtil'
+import { httpClient, httpFlowClient } from '@/utils/httpClient'
+import { logger } from '@/common/logger'
+import { bodyParserMiddleware, compose, proxyHandler } from '@/middleware/common'
+import { downloadMiddleware } from '@/router/other/utils'
+
+const otherRouter = new Router()
+
+otherRouter.get, EmptyObj>(
+ /^\/d\/*/,
+ compose(bodyParserMiddleware, preProxy(alistServer, false), downloadMiddleware),
+ proxyHandler
+)
+
+otherRouter.get, EmptyObj>(
+ /^\/p\/*/,
+ compose(bodyParserMiddleware, preProxy(alistServer, false), downloadMiddleware),
+ proxyHandler
+)
+
+// 修复alist 图标不显示的问题
+otherRouter.all(/^\/images\/*/, compose(bodyParserMiddleware, preProxy(alistServer, false)), async (ctx) => {
+ const state = ctx.state
+
+ delete ctx.req.headers.host
+
+ return await httpFlowClient({
+ urlAddr: state.urlAddr,
+ passwdInfo: state.passwdInfo,
+ fileSize: state.fileSize,
+ request: ctx.req,
+ response: ctx.res,
+ })
+})
+
+otherRouter.all('/redirect/:key', async (ctx) => {
+ const request = ctx.req
+ const response = ctx.res
+ // 这里还是要encodeURIComponent ,因为http服务器会自动对url进行decodeURIComponent
+ const data = await levelDB.getValue(ctx.params.key)
+
+ if (data === null) {
+ ctx.body = 'no found'
+ return
+ }
+
+ const { passwdInfo, redirectUrl, fileSize } = data
+ // 要定位请求文件的位置 bytes=98304-
+ const range = request.headers.range
+ const start = range ? Number(range.replace('bytes=', '').split('-')[0]) : 0
+ const decode = ctx.query.decode
+
+ logger.info(`重定向: ${ctx.path} -> ${redirectUrl}, 解密${decode !== '0'}`)
+
+ const flowEnc = new FlowEnc(passwdInfo.password, passwdInfo.encType, fileSize)
+
+ if (start) {
+ await flowEnc.setPosition(start)
+ }
+
+ // 设置请求地址和是否要解密
+ // 修改百度头
+ if (~redirectUrl.indexOf('baidupcs.com')) {
+ request.headers['User-Agent'] = 'pan.baidu.com'
+ }
+ request.url = decodeURIComponent(flat(ctx.query.lastUrl))
+ delete request.headers.host
+ delete request.headers.referer
+ // 123网盘和天翼网盘多次302
+ // authorization 是alist网页版的token,不是webdav的,这里修复天翼云无法获取资源的问题
+ delete request.headers.authorization
+
+ // 默认判断路径来识别是否要解密,如果有decode参数,那么则按decode来处理,这样可以让用户手动处理是否解密?(那还不如直接在alist下载)
+ let decryptTransform = passwdInfo.enable && pathExec(passwdInfo.encPath, request.url) ? flowEnc.decryptTransform() : null
+ if (decode) {
+ decryptTransform = decode !== '0' ? flowEnc.decryptTransform() : null
+ }
+ // 请求实际服务资源
+ await httpFlowClient({
+ urlAddr: redirectUrl,
+ passwdInfo,
+ fileSize,
+ request,
+ response,
+ decryptTransform,
+ })
+})
+
+otherRouter.all, EmptyObj>(new RegExp(alistServer.path), preProxy(alistServer, false), async (ctx) => {
+ let respBody = await httpClient({
+ urlAddr: ctx.state.urlAddr,
+ reqBody: JSON.stringify(ctx.request.body),
+ request: ctx.req,
+ response: ctx.res,
+ })
+
+ respBody = respBody.replace(
+ '',
+ `
+ `
+ )
+ ctx.body = respBody
+})
+
+export default otherRouter
diff --git a/node-proxy/src/router/other/utils.ts b/node-proxy/src/router/other/utils.ts
new file mode 100644
index 0000000..4812eaa
--- /dev/null
+++ b/node-proxy/src/router/other/utils.ts
@@ -0,0 +1,42 @@
+import path from 'path'
+
+import type { Middleware } from 'koa'
+
+import { logger } from '@/common/logger'
+import { convertRealName, pathFindPasswd } from '@/utils/cryptoUtil'
+
+export const downloadMiddleware: Middleware> = async (ctx, next) => {
+ const state = ctx.state
+
+ let filePath = ctx.req.url.split('?')[0]
+ // 如果是alist的话,那么必然有这个文件的size缓存(进过list就会被缓存起来)
+ state.fileSize = 0
+ // 这里需要处理掉/p 路径
+ if (filePath.indexOf('/d/') === 0) {
+ filePath = filePath.replace('/d/', '/')
+ }
+
+ // 这个不需要处理
+ if (filePath.indexOf('/p/') === 0) {
+ filePath = filePath.replace('/p/', '/')
+ }
+
+ const { passwdInfo } = pathFindPasswd(state.serverConfig.passwdList, filePath)
+ if (passwdInfo && passwdInfo.encName) {
+ // reset content-length length
+ delete ctx.req.headers['content-length']
+ // check fileName is not enc or it is dir
+ const fileName = path.basename(filePath)
+ const realName = convertRealName(passwdInfo.password, passwdInfo.encType, fileName)
+ logger.info(`转换为原始文件名: ${fileName} -> ${realName}`)
+
+ ctx.req.url = path.dirname(ctx.req.url) + '/' + realName
+ ctx.state.urlAddr = path.dirname(ctx.state.urlAddr) + '/' + realName
+ logger.info('准备获取文件', ctx.req.url)
+ await next()
+
+ return
+ }
+
+ await next()
+}
diff --git a/node-proxy/src/router/static/index.ts b/node-proxy/src/router/static/index.ts
new file mode 100644
index 0000000..2f52954
--- /dev/null
+++ b/node-proxy/src/router/static/index.ts
@@ -0,0 +1,7 @@
+import Router from 'koa-router'
+
+const staticRouter = new Router({ prefix: '/index' })
+
+staticRouter.redirect('/', '/public/index.html', 302)
+
+export default staticRouter
diff --git a/node-proxy/src/router/webdav/index.ts b/node-proxy/src/router/webdav/index.ts
new file mode 100644
index 0000000..f7e4cf9
--- /dev/null
+++ b/node-proxy/src/router/webdav/index.ts
@@ -0,0 +1,20 @@
+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'
+
+const webdavRouter = new Router()
+
+// 初始化webdav路由
+webdavServer.forEach((webdavConfig) => {
+ if (webdavConfig.enable) {
+ webdavRouter.all(new RegExp(webdavConfig.path), compose(preProxy(webdavConfig, true), encDavMiddleware), proxyHandler)
+ }
+})
+
+// 单独处理alist的所有/dav
+webdavRouter.all(/^\/dav\/*/, compose(preProxy(alistServer, false), encDavMiddleware), proxyHandler)
+
+export default webdavRouter
diff --git a/node-proxy/src/encDavHandle.js b/node-proxy/src/router/webdav/middlewares.ts
similarity index 62%
rename from node-proxy/src/encDavHandle.js
rename to node-proxy/src/router/webdav/middlewares.ts
index 0099888..53f90f4 100644
--- a/node-proxy/src/encDavHandle.js
+++ b/node-proxy/src/router/webdav/middlewares.ts
@@ -1,70 +1,23 @@
-'use strict'
-
-import { pathFindPasswd, convertRealName, convertShowName } from './utils/commonUtil'
-import { cacheFileInfo, getFileInfo } from './dao/fileDao'
-import { logger } from './common/logger'
import path from 'path'
-import { httpClient } from './utils/httpClient'
+
import { XMLParser } from 'fast-xml-parser'
-// import { escape } from 'querystring'
+import type { Middleware } from 'koa'
-async function sleep(time) {
- return new Promise((resolve) => {
- setTimeout(() => {
- resolve()
- }, time || 3000)
- })
-}
+import { flat, sleep } from '@/utils/common'
+import { logger } from '@/common/logger'
+import { httpClient } from '@/utils/httpClient'
+import { cacheFileInfo, getFileInfo } from '@/dao/fileDao'
+import { convertRealName, convertShowName, pathFindPasswd } from '@/utils/cryptoUtil'
+import { cacheWebdavFileInfo, getFileNameForShow } from '@/router/webdav/utils'
-// bodyparser解析body
const parser = new XMLParser({ removeNSPrefix: true })
-function getFileNameForShow(fileInfo, passwdInfo) {
- let getcontentlength = -1
- const href = fileInfo.href
- const fileName = path.basename(href)
- if (fileInfo.propstat instanceof Array) {
- getcontentlength = fileInfo.propstat[0].prop.getcontentlength
- } else if (fileInfo.propstat.prop) {
- getcontentlength = fileInfo.propstat.prop.getcontentlength
- }
- // logger.debug('@@fileInfo_show', JSON.stringify(fileInfo))
- // is not dir
- if (getcontentlength !== undefined && getcontentlength > -1) {
- const showName = convertShowName(passwdInfo.password, passwdInfo.encType, href)
- return { fileName, showName }
- }
- // cache this folder info
- return {}
-}
+export const encDavMiddleware: Middleware> = async (ctx, next) => {
+ const request = ctx.req
+ const state = ctx.state
-function cacheWebdavFileInfo(fileInfo) {
- let getcontentlength = -1
- const href = fileInfo.href
- const fileName = path.basename(href)
- if (fileInfo.propstat instanceof Array) {
- getcontentlength = fileInfo.propstat[0].prop.getcontentlength
- } else if (fileInfo.propstat.prop) {
- getcontentlength = fileInfo.propstat.prop.getcontentlength
- }
- // logger.debug('@@@cacheWebdavFileInfo', href, fileName)
- // it is a file
- if (getcontentlength !== undefined && getcontentlength > -1) {
- const fileDetail = { path: href, name: fileName, is_dir: false, size: getcontentlength }
- cacheFileInfo(fileDetail)
- return fileDetail
- }
- // cache this folder info
- const fileDetail = { path: href, name: fileName, is_dir: true, size: 0 }
- cacheFileInfo(fileDetail)
- return fileDetail
-}
+ const { passwdInfo } = pathFindPasswd(state.serverConfig.passwdList, decodeURIComponent(request.url))
-// 拦截全部
-const handle = async (ctx, next) => {
- const request = ctx.req
- const { passwdList } = request.webdavConfig
- const { passwdInfo } = pathFindPasswd(passwdList, decodeURIComponent(request.url))
if (ctx.method.toLocaleUpperCase() === 'PROPFIND' && passwdInfo && passwdInfo.encName) {
// check dir, convert url
const url = request.url
@@ -73,29 +26,32 @@ const handle = async (ctx, next) => {
const reqFileName = path.basename(url)
// cache source file info, realName has execute encodeUrl(),this '(' '+' can't encodeUrl.
const realName = convertRealName(passwdInfo.password, passwdInfo.encType, url)
- // when the name contain the + , ! ,
+ // when the name contain the '+,!' ,
const sourceUrl = path.dirname(url) + '/' + realName
const sourceFileInfo = await getFileInfo(sourceUrl)
logger.debug('@@@sourceFileInfo', sourceFileInfo, reqFileName, realName, url, sourceUrl)
// it is file, convert file name
if (sourceFileInfo && !sourceFileInfo.is_dir) {
request.url = path.dirname(request.url) + '/' + realName
- request.urlAddr = path.dirname(request.urlAddr) + '/' + realName
+ ctx.state.urlAddr = path.dirname(ctx.state.urlAddr) + '/' + realName
}
}
// decrypt file name
- let respBody = await httpClient(ctx.req, ctx.res)
+ let respBody = await httpClient({
+ urlAddr: state.urlAddr,
+ request: ctx.req,
+ response: ctx.res,
+ })
const respData = parser.parse(respBody)
// convert file name for show
if (respData.multistatus) {
const respJson = respData.multistatus.response
if (respJson instanceof Array) {
// console.log('@@respJsonArray', respJson)
- respJson.forEach((fileInfo) => {
- // cache real file info,include forder name
- cacheWebdavFileInfo(fileInfo)
+ for (const fileInfo of respJson) {
+ await cacheWebdavFileInfo(fileInfo)
if (passwdInfo && passwdInfo.encName) {
- const { fileName, showName } = getFileNameForShow(fileInfo, passwdInfo)
+ const { fileName, showName } = await getFileNameForShow(fileInfo, passwdInfo)
// logger.debug('@@getFileNameForShow1 list', passwdInfo.password, fileName, decodeURI(fileName), showName)
if (fileName) {
const showXmlName = showName.replace(/&/g, '&').replace(/ {
respBody = respBody.replace(`${decodeURI(fileName)}`, `${decodeURI(showXmlName)}`)
}
}
- })
+ }
// waiting cacheWebdavFileInfo a moment
await sleep(50)
} else if (passwdInfo && passwdInfo.encName) {
- const fileInfo = respJson
- const { fileName, showName } = getFileNameForShow(fileInfo, passwdInfo)
+ const { fileName, showName } = await getFileNameForShow(respJson, passwdInfo)
// logger.debug('@@getFileNameForShow2 file', fileName, showName, url, respJson.propstat)
if (fileName) {
const showXmlName = showName.replace(/&/g, '&').replace(/ {
ctx.body = respBody
return
}
- // copy or move file
+
if ('COPY,MOVE'.includes(request.method.toLocaleUpperCase()) && passwdInfo && passwdInfo.encName) {
const url = request.url
const realName = convertRealName(passwdInfo.password, passwdInfo.encType, url)
- request.headers.destination = path.dirname(request.headers.destination) + '/' + encodeURI(realName)
+ request.headers.destination = path.dirname(flat(request.headers.destination)) + '/' + encodeURI(realName)
request.url = path.dirname(request.url) + '/' + encodeURI(realName)
- request.urlAddr = path.dirname(request.urlAddr) + '/' + encodeURI(realName)
+ state.urlAddr = path.dirname(state.urlAddr) + '/' + encodeURI(realName)
}
// upload file
@@ -149,8 +104,12 @@ const handle = async (ctx, next) => {
const realName = convertRealName(passwdInfo.password, passwdInfo.encType, url)
// maybe from aliyundrive, check this req url while get file list from enc folder
if (url.endsWith('/') && 'GET,DELETE'.includes(request.method.toLocaleUpperCase())) {
- let respBody = await httpClient(ctx.req, ctx.res)
- if(request.method.toLocaleUpperCase() === 'GET'){
+ let respBody = await httpClient({
+ urlAddr: state.urlAddr,
+ request: ctx.req,
+ response: ctx.res,
+ })
+ if (request.method.toLocaleUpperCase() === 'GET') {
const aurlArr = respBody.match(/href="[^"]*"/g)
// logger.debug('@@aurlArr', aurlArr)
if (aurlArr && aurlArr.length) {
@@ -173,9 +132,9 @@ const handle = async (ctx, next) => {
// console.log('@@convert file name', fileName, realName)
request.url = path.dirname(request.url) + '/' + realName
- request.urlAddr = path.dirname(request.urlAddr) + '/' + realName
+ state.urlAddr = path.dirname(state.urlAddr) + '/' + realName
// cache file before upload in next(), rclone cmd 'copy' will PROPFIND this file when the file upload success right now
- const contentLength = request.headers['content-length'] || request.headers['x-expected-entity-length'] || 0
+ const contentLength = Number(flat(request.headers['content-length'] || request.headers['x-expected-entity-length']) || 0)
const fileDetail = { path: url, name: fileName, is_dir: false, size: contentLength }
logger.info('@@@put url', url)
// 在页面上传文件,rclone会重复上传,所以要进行缓存文件信息,也不能在next() 因为rclone copy命令会出异常
@@ -183,5 +142,3 @@ const handle = async (ctx, next) => {
}
await next()
}
-
-export default handle
diff --git a/node-proxy/src/router/webdav/utils.ts b/node-proxy/src/router/webdav/utils.ts
new file mode 100644
index 0000000..afe0377
--- /dev/null
+++ b/node-proxy/src/router/webdav/utils.ts
@@ -0,0 +1,44 @@
+import path from 'path'
+import { cacheFileInfo } from '@/dao/fileDao'
+import { convertShowName } from '@/utils/cryptoUtil'
+
+export const cacheWebdavFileInfo = async (fileInfo) => {
+ let contentLength = -1
+ const href = fileInfo.href
+ const fileName = path.basename(href)
+ if (fileInfo.propstat instanceof Array) {
+ contentLength = fileInfo.propstat[0].prop.getcontentlength
+ } else if (fileInfo.propstat.prop) {
+ contentLength = fileInfo.propstat.prop.getcontentlength
+ }
+ // logger.debug('@@@cacheWebdavFileInfo', href, fileName)
+ // it is a file
+ if (contentLength !== undefined && contentLength > -1) {
+ const fileDetail = { path: href, name: fileName, is_dir: false, size: contentLength }
+ await cacheFileInfo(fileDetail)
+ return fileDetail
+ }
+ // cache this folder info
+ const fileDetail = { path: href, name: fileName, is_dir: true, size: 0 }
+ await cacheFileInfo(fileDetail)
+ return fileDetail
+}
+
+export const getFileNameForShow = async (fileInfo, passwdInfo: PasswdInfo) => {
+ let contentLength = -1
+ const href = fileInfo.href
+ const fileName = path.basename(href)
+ if (fileInfo.propstat instanceof Array) {
+ contentLength = fileInfo.propstat[0].prop.getcontentlength
+ } else if (fileInfo.propstat.prop) {
+ contentLength = fileInfo.propstat.prop.getcontentlength
+ }
+ // logger.debug('@@fileInfo_show', JSON.stringify(fileInfo))
+ // is not dir
+ if (contentLength !== undefined && contentLength > -1) {
+ const showName = convertShowName(passwdInfo.password, passwdInfo.encType, href)
+ return { fileName, showName }
+ }
+ // cache this folder info
+ return {}
+}
diff --git a/node-proxy/src/router/webui/index.ts b/node-proxy/src/router/webui/index.ts
new file mode 100644
index 0000000..84f18fd
--- /dev/null
+++ b/node-proxy/src/router/webui/index.ts
@@ -0,0 +1,323 @@
+import fs from 'fs'
+import crypto from 'crypto'
+
+import Router from 'koa-router'
+
+import { logger } from '@/common/logger'
+import { userInfoMiddleware } from '@/router/webui/middlewares'
+import { encryptFile, searchFile } from '@/utils/convertFile'
+import { encodeFolderName, decodeFolderName } from '@/utils/cryptoUtil'
+import { emptyMiddleware, bodyParserMiddleware } from '@/middleware/common'
+import { RawPasswdInfo, response, splitEncPath } from '@/router/webui/utils'
+import { getUserInfo, cacheUserToken, updateUserInfo } from '@/dao/userDao'
+import { alistServer, webdavServer, port, initAlistConfig, version } from '@/config'
+import type { webui } from '@/@types/webui'
+
+const webuiRouter = new Router({ prefix: '/enc-api' })
+webuiRouter.use(bodyParserMiddleware) //目前webui不涉及二进制数据的响应,因此全都使用bodyParser解析request.body为对象
+
+//登录路由
+webuiRouter.all<
+ EmptyObj,
+ ParsedContext<{
+ username: string
+ password: string
+ }>
+>('/login', emptyMiddleware, async (ctx) => {
+ const { username, password } = ctx.request.body
+ const userInfo = await getUserInfo(username)
+ logger.info('用户信息', JSON.stringify(userInfo))
+
+ if (!userInfo || password !== userInfo.password) {
+ ctx.body = response({ msg: 'password error', code: 500 })
+ return
+ }
+
+ const token = crypto.randomUUID()
+ await cacheUserToken(token, userInfo)
+ userInfo.password = null
+ ctx.body = response({ data: { userInfo, jwtToken: token } })
+})
+
+// 用户信息
+webuiRouter.all('/getUserInfo', userInfoMiddleware, async (ctx) => {
+ const userInfo = ctx.state.userInfo
+ logger.info('用户信息', JSON.stringify(userInfo))
+
+ userInfo.password = null
+ const data = {
+ codes: [16, 9, 10, 11, 12, 13, 15],
+ userInfo,
+ menuList: [],
+ roles: ['admin'],
+ version,
+ }
+ ctx.body = response({ data })
+})
+
+// 更新用户信息
+webuiRouter.all<
+ webui.State,
+ ParsedContext<{
+ username: string
+ password: string
+ newpassword: string
+ }>
+>('/updatePasswd', userInfoMiddleware, async (ctx) => {
+ const { password, newpassword, username } = ctx.request.body
+ logger.info(`用户名: ${username} 原密码:${password} 新密码:${newpassword}`)
+
+ if (newpassword.length < 7) {
+ ctx.body = response({ msg: 'password too short, at less 8 digits', code: 500 })
+ return
+ }
+
+ const userInfo = await getUserInfo(username)
+ if (password !== userInfo.password) {
+ ctx.body = response({ msg: 'password error', code: 500 })
+ return
+ }
+
+ userInfo.password = newpassword
+ await updateUserInfo(userInfo)
+ ctx.body = response({ msg: 'update success' })
+
+ logger.info('已更新用户信息', JSON.stringify(userInfo))
+})
+
+webuiRouter.all('/getAlistConfig', userInfoMiddleware, async (ctx) => {
+ ctx.body = response({ data: alistServer._snapshot })
+})
+
+webuiRouter.all>>(
+ '/saveAlistConfig',
+ userInfoMiddleware,
+ async (ctx) => {
+ let alistConfig: AlistServer = {
+ ...ctx.request.body,
+ passwdList: ctx.request.body.passwdList.map((p) => splitEncPath(p)),
+ }
+
+ logger.info('保存alist配置信息', JSON.stringify(alistConfig))
+ const _snapshot = JSON.parse(JSON.stringify(alistConfig))
+ // 写入到文件中,这里并不是真正的同步
+ fs.writeFileSync(
+ process.cwd() + '/conf/config.json',
+ JSON.stringify(
+ {
+ alistServer: _snapshot,
+ webdavServer,
+ port,
+ },
+ undefined,
+ '\t'
+ )
+ )
+ alistConfig = initAlistConfig(alistConfig)
+ Object.assign(alistServer, alistConfig)
+ alistServer._snapshot = _snapshot
+ logger.info('alist配置信息已更新')
+ ctx.body = response({ msg: 'save ok' })
+ }
+)
+
+//TODO 纠正拼写错误的路由 getWebdavonfig->getWebdavConfig
+webuiRouter.all('/getWebdavonfig', userInfoMiddleware, async (ctx) => {
+ logger.info('获取webdav配置信息: ', JSON.stringify(webdavServer))
+ ctx.body = response({ data: webdavServer })
+})
+
+webuiRouter.all>>(
+ '/saveWebdavConfig',
+ userInfoMiddleware,
+ async (ctx) => {
+ const newWebdavServer: WebdavServer = {
+ ...ctx.request.body,
+ passwdList: ctx.request.body.passwdList.map((p) => splitEncPath(p)),
+ }
+
+ newWebdavServer.id = crypto.randomUUID()
+ logger.info('新增webdav配置信息', JSON.stringify(newWebdavServer))
+
+ webdavServer.push(newWebdavServer)
+ fs.writeFileSync(
+ process.cwd() + '/conf/config.json',
+ JSON.stringify(
+ {
+ alistServer: alistServer._snapshot,
+ webdavServer,
+ port,
+ },
+ undefined,
+ '\t'
+ )
+ )
+
+ logger.info('webdav配置信息已更新')
+ ctx.body = response({ data: webdavServer })
+ }
+)
+
+webuiRouter.all>>(
+ '/updateWebdavConfig',
+ userInfoMiddleware,
+ async (ctx) => {
+ const config = ctx.request.body
+ logger.info('更新webdav配置信息', config.id, config)
+
+ for (const index in webdavServer) {
+ if (webdavServer[index].id === config.id) {
+ webdavServer[index] = {
+ ...config,
+ passwdList: ctx.request.body.passwdList.map((p) => splitEncPath(p)),
+ }
+ }
+ }
+
+ fs.writeFileSync(
+ process.cwd() + '/conf/config.json',
+ JSON.stringify(
+ {
+ alistServer: alistServer._snapshot,
+ webdavServer,
+ port,
+ },
+ undefined,
+ '\t'
+ )
+ )
+
+ logger.info('webdav配置信息已更新')
+ ctx.body = response({ data: webdavServer })
+ }
+)
+
+webuiRouter.all<
+ webui.State,
+ ParsedContext<{
+ id: string
+ passwdList: PasswdInfo[]
+ }>
+>('/delWebdavConfig', userInfoMiddleware, async (ctx) => {
+ const { id } = ctx.request.body
+
+ logger.info('删除webdav配置信息', id)
+
+ let indexToDelete = -1 // 初始化索引为-1,表示未找到
+ for (const server of webdavServer) {
+ if (server.id === id) {
+ indexToDelete = webdavServer.indexOf(server) // 找到匹配的元素后,获取其索引
+ break // 找到第一个匹配项后退出循环
+ }
+ }
+
+ if (indexToDelete !== -1) {
+ webdavServer.splice(indexToDelete, 1) // 如果找到了匹配的索引,则删除该元素
+ }
+
+ fs.writeFileSync(
+ process.cwd() + '/conf/config.json',
+ JSON.stringify(
+ {
+ alistServer: alistServer._snapshot,
+ webdavServer,
+ port,
+ },
+ undefined,
+ '\t'
+ )
+ )
+
+ logger.info('webdav配置信息已删除', id)
+ ctx.body = response({ data: webdavServer })
+})
+
+// get folder passwd encode
+webuiRouter.all<
+ webui.State,
+ ParsedContext<{
+ password: string
+ encType: EncryptType
+ folderEncType: string
+ folderPasswd: string
+ }>
+>('/encodeFoldName', userInfoMiddleware, async (ctx) => {
+ const { password, encType, folderPasswd, folderEncType } = ctx.request.body
+
+ logger.info('加密文件夹', password, encType, folderPasswd)
+
+ const result = encodeFolderName(password, encType, folderPasswd, folderEncType)
+ ctx.body = response({
+ data: {
+ folderNameEnc: result,
+ },
+ })
+
+ logger.info('加密结果: ', result)
+})
+
+webuiRouter.all<
+ webui.State,
+ ParsedContext<{
+ password: string
+ encType: EncryptType
+ folderNameEnc: string
+ }>
+>('/decodeFoldName', userInfoMiddleware, async (ctx) => {
+ const { password, folderNameEnc, encType } = ctx.request.body
+ const arr = folderNameEnc.split('_')
+ logger.info('解密文件夹', password, encType, folderNameEnc)
+
+ if (arr.length < 2) {
+ ctx.body = response({ msg: 'folderName not encoded', code: 500 })
+ return
+ }
+
+ const data = decodeFolderName(password, encType, folderNameEnc)
+ if (!data) {
+ ctx.body = response({ msg: 'folderName is error', code: 500 })
+ return
+ }
+
+ const { folderEncType, folderPasswd } = data
+ ctx.body = response({ data: { folderEncType, folderPasswd } })
+
+ logger.info('解密结果: ', data)
+})
+
+// encrypt or decrypt file
+webuiRouter.all<
+ webui.State,
+ ParsedContext<{
+ folderPath: string
+ outPath: string
+ encType: EncryptType
+ encName: string
+ password: string
+ operation: 'enc' | 'dec'
+ }>
+>('/encryptFile', userInfoMiddleware, async (ctx) => {
+ const { folderPath, outPath, encType, password, operation, encName } = ctx.request.body
+
+ logger.info(`加密本地文件: 源文件夹${folderPath} 目标文件夹:${outPath}`)
+
+ if (!fs.existsSync(folderPath)) {
+ ctx.body = response({ msg: 'encrypt file path not exists', code: 500 })
+ return
+ }
+
+ const files = searchFile(folderPath)
+
+ if (files.length > 10000) {
+ ctx.body = response({ msg: 'too many file, exceeding 10000', code: 500 })
+ return
+ }
+
+ encryptFile(password, encType, operation, folderPath, outPath, encName).then()
+
+ logger.info('加密中,请稍后...')
+
+ ctx.body = response({ msg: 'waiting operation' })
+})
+
+export default webuiRouter
diff --git a/node-proxy/src/router/webui/middlewares.ts b/node-proxy/src/router/webui/middlewares.ts
new file mode 100644
index 0000000..e8b6c7a
--- /dev/null
+++ b/node-proxy/src/router/webui/middlewares.ts
@@ -0,0 +1,23 @@
+import type { Middleware } from 'koa'
+
+import { flat } from '@/utils/common'
+import { response } from '@/router/webui/utils'
+import { getUserByToken } from '@/dao/userDao'
+
+//获取用户信息的中间件
+export const userInfoMiddleware: Middleware = async (ctx, next) => {
+ // nginx不支持下划线headers
+ const { authorizetoken: authorizeToken } = ctx.request.headers
+
+ // 查询数据库是否有密码
+ const userInfo = await getUserByToken(flat(authorizeToken))
+
+ if (userInfo == null) {
+ ctx.body = response({ code: 401, msg: 'user not login' })
+ return
+ }
+
+ ctx.state.userInfo = userInfo //放入ctx.state,之后可以用ctx.state,userInfo获取登录信息
+
+ await next()
+}
diff --git a/node-proxy/src/router/webui/utils.ts b/node-proxy/src/router/webui/utils.ts
new file mode 100644
index 0000000..669bea0
--- /dev/null
+++ b/node-proxy/src/router/webui/utils.ts
@@ -0,0 +1,17 @@
+import type { webui } from '@/@types/webui'
+
+export type RawPasswdInfo = ModifyPropType
+
+//将文件目录字符串转化为列表
+export const splitEncPath = (raw: RawPasswdInfo): PasswdInfo => {
+ return {
+ ...raw,
+ encPath: Array.isArray(raw.encPath) ? raw.encPath : raw.encPath.split(','),
+ }
+}
+
+//构造webui返回响应的body
+export const response = (raw: Partial): webui.ResponseBody => {
+ const { flag, msg, code, data } = raw
+ return { flag: flag || true, msg, code: code || 200, data }
+}
diff --git a/node-proxy/src/utils/chaCha20.js b/node-proxy/src/utils/chaCha20.js
deleted file mode 100644
index bfeb541..0000000
--- a/node-proxy/src/utils/chaCha20.js
+++ /dev/null
@@ -1,206 +0,0 @@
-/**
- *
- * @param {Uint8Array} key
- * @param {Uint8Array} nonce
- * @param {number} counter
- * @throws {Error}
- *
- * @constructor
- */
-const JSChaCha20 = function (key, nonce, counter) {
- if (typeof counter === 'undefined') {
- counter = 0
- }
-
- if (!(key instanceof Uint8Array) || key.length !== 32) {
- throw new Error('Key should be 32 byte array!')
- }
-
- if (!(nonce instanceof Uint8Array) || nonce.length !== 12) {
- throw new Error('Nonce should be 12 byte array!')
- }
-
- this._rounds = 20
- // Constants
- this._sigma = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]
-
- // param construction
- this._param = [
- this._sigma[0],
- this._sigma[1],
- this._sigma[2],
- this._sigma[3],
- // key
- this._get32(key, 0),
- this._get32(key, 4),
- this._get32(key, 8),
- this._get32(key, 12),
- this._get32(key, 16),
- this._get32(key, 20),
- this._get32(key, 24),
- this._get32(key, 28),
- // counter
- counter,
- // nonce
- this._get32(nonce, 0),
- this._get32(nonce, 4),
- this._get32(nonce, 8),
- ]
-
- // init 64 byte keystream block //
- this._keystream = [
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- ]
-
- // internal byte counter //
- this._byteCounter = 0
-}
-
-JSChaCha20.prototype._chacha = function () {
- var mix = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- var i = 0
- var b = 0
-
- // copy param array to mix //
- for (i = 0; i < 16; i++) {
- mix[i] = this._param[i]
- }
-
- // mix rounds //
- for (i = 0; i < this._rounds; i += 2) {
- this._quarterround(mix, 0, 4, 8, 12)
- this._quarterround(mix, 1, 5, 9, 13)
- this._quarterround(mix, 2, 6, 10, 14)
- this._quarterround(mix, 3, 7, 11, 15)
-
- this._quarterround(mix, 0, 5, 10, 15)
- this._quarterround(mix, 1, 6, 11, 12)
- this._quarterround(mix, 2, 7, 8, 13)
- this._quarterround(mix, 3, 4, 9, 14)
- }
-
- for (i = 0; i < 16; i++) {
- // add
- mix[i] += this._param[i]
-
- // store keystream
- this._keystream[b++] = mix[i] & 0xff
- this._keystream[b++] = (mix[i] >>> 8) & 0xff
- this._keystream[b++] = (mix[i] >>> 16) & 0xff
- this._keystream[b++] = (mix[i] >>> 24) & 0xff
- }
-}
-
-/**
- * The basic operation of the ChaCha algorithm is the quarter round.
- * It operates on four 32-bit unsigned integers, denoted a, b, c, and d.
- *
- * @param {Array} output
- * @param {number} a
- * @param {number} b
- * @param {number} c
- * @param {number} d
- * @private
- */
-JSChaCha20.prototype._quarterround = function (output, a, b, c, d) {
- output[d] = this._rotl(output[d] ^ (output[a] += output[b]), 16)
- output[b] = this._rotl(output[b] ^ (output[c] += output[d]), 12)
- output[d] = this._rotl(output[d] ^ (output[a] += output[b]), 8)
- output[b] = this._rotl(output[b] ^ (output[c] += output[d]), 7)
-
- // JavaScript hack to make UINT32 :) //
- output[a] >>>= 0
- output[b] >>>= 0
- output[c] >>>= 0
- output[d] >>>= 0
-}
-
-/**
- * Little-endian to uint 32 bytes
- *
- * @param {Uint8Array|[number]} data
- * @param {number} index
- * @return {number}
- * @private
- */
-JSChaCha20.prototype._get32 = function (data, index) {
- return data[index++] ^ (data[index++] << 8) ^ (data[index++] << 16) ^ (data[index] << 24)
-}
-
-/**
- * Cyclic left rotation
- *
- * @param {number} data
- * @param {number} shift
- * @return {number}
- * @private
- */
-JSChaCha20.prototype._rotl = function (data, shift) {
- return (data << shift) | (data >>> (32 - shift))
-}
-
-/**
- * Encrypt data with key and nonce
- *
- * @param {Uint8Array} data
- * @return {Uint8Array}
- */
-JSChaCha20.prototype.encrypt = function (data) {
- return this._update(data)
-}
-
-/**
- * Decrypt data with key and nonce
- *
- * @param {Uint8Array} data
- * @return {Uint8Array}
- */
-JSChaCha20.prototype.decrypt = function (data) {
- return this._update(data)
-}
-
-/**
- * Encrypt or Decrypt data with key and nonce
- *
- * @param {Uint8Array} data
- * @return {Uint8Array}
- * @private
- */
-JSChaCha20.prototype._update = function (data) {
- if (!(data instanceof Uint8Array) || data.length === 0) {
- throw new Error('Data should be type of bytes (Uint8Array) and not empty!')
- }
-
- // core function, build block and xor with input data //
- for (let i = data.length; i--; ) {
- if (this._byteCounter === 0 || this._byteCounter === 64) {
- // generate new block //
- this._chacha()
- // counter increment //
- this._param[12]++
- // reset internal counter //
- this._byteCounter = 0
- }
- data[i] ^= this._keystream[this._byteCounter++]
- }
- return data
-}
-
-JSChaCha20.prototype.setPosition = function (length) {
- // core function, build block and xor with input data //
- for (let i = length; i--; ) {
- if (this._byteCounter === 0 || this._byteCounter === 64) {
- // generate new block //
- this._chacha()
- // counter increment //
- this._param[12]++
- // reset internal counter //
- this._byteCounter = 0
- }
- this._byteCounter++
- }
-}
-
-// EXPORT //
-export default JSChaCha20
diff --git a/node-proxy/src/utils/chaCha20Poly.js b/node-proxy/src/utils/chaCha20Poly.js
deleted file mode 100644
index 1225b04..0000000
--- a/node-proxy/src/utils/chaCha20Poly.js
+++ /dev/null
@@ -1,106 +0,0 @@
-import crypto from 'crypto'
-import { Transform } from 'stream'
-
-class ChaCha20Poly {
- constructor(password, sizeSalt) {
- this.password = password
- this.sizeSalt = sizeSalt
- // share you folder passwdOutward safety
- this.passwdOutward = password
- if (password.length !== 32) {
- // add 'RC4' as salt
- const sha256 = crypto.createHash('sha256')
- const key = sha256.update(password + 'CHA20').digest('hex')
- this.passwdOutward = crypto.createHash('md5').update(key).digest('hex')
- }
- // add salt
- const passwdSalt = this.passwdOutward + sizeSalt
- // fileHexKey: file passwd,could be share
- const fileHexKey = crypto.createHash('sha256').update(passwdSalt).digest()
- const iv = crypto.pbkdf2Sync(this.passwdOutward, sizeSalt + '', 10000, 12, 'sha256')
- this.cipher = crypto.createCipheriv('chacha20-poly1305', fileHexKey, iv, {
- authTagLength: 16,
- })
- this.decipher = crypto.createDecipheriv('chacha20-poly1305', fileHexKey, iv, {
- authTagLength: 16,
- })
- }
-
- async setPositionAsync(_position) {
- const buf = Buffer.alloc(1024)
- const position = parseInt(_position / 1024)
- const mod = _position % 1024
- for (let i = 0; i < position; i++) {
- this.decChaPoly(buf)
- }
- const modBuf = Buffer.alloc(mod)
- for (let i = 0; i < mod; i++) {
- this.decChaPoly(modBuf)
- }
- }
-
- encryptTransform() {
- return new Transform({
- transform: (chunk, encoding, next) => {
- next(null, this.encChaPoly(chunk))
- },
- })
- }
-
- decryptTransform() {
- return new Transform({
- transform: (chunk, encoding, next) => {
- next(null, this.decChaPoly(chunk, false))
- },
- })
- }
-
- encChaPoly(data) {
- if (typeof data === 'string') {
- data = Buffer.from(data, 'utf8')
- }
- try {
- const encrypted = this.cipher.update(data)
- return encrypted
- } catch (err) {
- console.log(err)
- }
- }
-
- encChaPolyFinal() {
- return this.cipher.final()
- }
-
- getAuthTag() {
- return this.cipher.getAuthTag()
- }
-
- decChaPoly(bufferData, authTag) {
- try {
- if (authTag) {
- this.decipher.setAuthTag(authTag)
- }
- if (authTag === true) {
- this.decipher.setAuthTag(this.cipher.getAuthTag())
- }
- if (typeof authTag === 'string') {
- this.decipher.setAuthTag(Buffer.from(authTag))
- }
-
- return this.decipher.update(bufferData)
- // const decryptData = Buffer.concat([this.decipher.update(bufferData), this.decipher.final()]).toString('utf8')
- } catch (err) {
- console.log(err)
- }
- }
-
- decChaPolyFinal() {
- try {
- this.decipher.final()
- } catch (err) {
- console.log(err)
- }
- }
-}
-
-export default ChaCha20Poly
diff --git a/node-proxy/src/utils/common.ts b/node-proxy/src/utils/common.ts
new file mode 100644
index 0000000..5ec3148
--- /dev/null
+++ b/node-proxy/src/utils/common.ts
@@ -0,0 +1,41 @@
+import type { Middleware } from 'koa'
+
+//平整化
+export const flat = (value: T | T[]): T => {
+ return Array.isArray(value) ? value[0] : value
+}
+
+//存储请求的部分原始信息,供代理使用
+export function preProxy(serverConfig: WebdavServer, isWebdav: true): Middleware
+export function preProxy(serverConfig: AlistServer, isWebdav: false): Middleware
+export function preProxy(serverConfig: WebdavServer | AlistServer, isWebdav: boolean): Middleware {
+ return async (ctx, next) => {
+ const { serverHost, serverPort, https } = serverConfig
+
+ if (isWebdav) {
+ // 不能把authorization缓存起来,单线程
+ ctx.state.isWebdav = isWebdav
+ // request.headers.authorization = request.headers.authorization ? (authorization = request.headers.authorization) : authorization
+ }
+
+ const request = ctx.request
+ const protocol = https ? 'https' : 'http'
+
+ ctx.state.selfHost = request.headers.host // 原来的host保留,以后可能会用到
+ ctx.state.origin = request.headers.origin
+ request.headers.host = serverHost + ':' + serverPort
+ ctx.state.urlAddr = `${protocol}://${request.headers.host}${request.url}`
+ ctx.state.serverAddr = `${protocol}://${request.headers.host}`
+ ctx.state.serverConfig = serverConfig
+
+ await next()
+ }
+}
+
+export const sleep = (time: number) => {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve()
+ }, time || 3000)
+ })
+}
diff --git a/node-proxy/src/utils/convertFile.ts b/node-proxy/src/utils/convertFile.ts
index f701a9e..8604b23 100644
--- a/node-proxy/src/utils/convertFile.ts
+++ b/node-proxy/src/utils/convertFile.ts
@@ -1,11 +1,12 @@
-'use strict'
import fs from 'fs'
import path from 'path'
import mkdirp from 'mkdirp'
import FlowEnc from './flowEnc'
-import { encodeName, decodeName } from './commonUtil'
+import { logger } from '@/common/logger'
+import { encodeName, decodeName } from './cryptoUtil'
+//找出文件夹下的文件
export function searchFile(filePath: string) {
const fileArray: { size: number; filePath: string }[] = []
const files = fs.readdirSync(filePath)
@@ -23,10 +24,10 @@ export function searchFile(filePath: string) {
return fileArray
}
-// encrypt
+// 加密文件夹下的全部文件
export async function encryptFile(
password: string,
- encType: string,
+ encType: EncryptType,
enc: 'enc' | 'dec',
encPath: string,
outPath?: string,
@@ -34,18 +35,18 @@ export async function encryptFile(
) {
const start = Date.now()
const interval = setInterval(() => {
- console.log(new Date(), 'waiting finish!!!')
+ logger.warn(new Date(), 'waiting finish!!!')
}, 2000)
if (!path.isAbsolute(encPath)) {
encPath = path.join(process.cwd(), encPath)
}
outPath = outPath || path.join(process.cwd(), 'outFile', Date.now().toString())
- console.log('you input:', password, encType, enc, encPath)
+ logger.info('文件加密配置: ', password, encType, enc, encPath)
if (!fs.existsSync(encPath)) {
- console.log('you input filePath is not exists ')
+ logger.warn('文件夹不存在!')
return
}
- // init outpath dir
+ // init outPath dir
if (!fs.existsSync(outPath)) {
mkdirp.sync(outPath)
}
@@ -84,10 +85,10 @@ export async function encryptFile(
// console.log('@@outFilePath', outFilePath, encType, size)
const writeStream = fs.createWriteStream(outFilePathTemp)
const readStream = fs.createReadStream(filePath)
- const promise = new Promise((resolve, reject) => {
+ const promise = new Promise((resolve) => {
readStream.pipe(enc === 'enc' ? flowEnc.encryptTransform() : flowEnc.decryptTransform()).pipe(writeStream)
readStream.on('end', () => {
- console.log('@@finish filePath', filePath, outFilePathTemp)
+ logger.info(`加密完成: ${filePath} -> ${outFilePathTemp}`)
fs.renameSync(outFilePathTemp, outFilePath)
resolve()
})
@@ -100,19 +101,21 @@ export async function encryptFile(
}
await Promise.all(promiseArr)
fs.rmSync(tempDir, { recursive: true })
- console.log('@@all finish', ((Date.now() - start) / 1000).toFixed(2) + 's')
+ logger.info('全部文件加密完成', ((Date.now() - start) / 1000).toFixed(2) + 's')
clearInterval(interval)
}
-export function convertFile(...args: [password: string, encType: string, enc: 'enc' | 'dec', encPath: string, outPath?: string, encName?: string]) {
+export function convertFile(
+ ...args: [password: string, encType: EncryptType, enc: 'enc' | 'dec', encPath: string, outPath?: string, encName?: string]
+) {
const statTime = Date.now()
if (args.length > 3) {
encryptFile(...args).then(() => {
- console.log('all file finish enc!!! time:', Date.now() - statTime)
+ logger.info('all file finish enc!!! time:', Date.now() - statTime)
process.exit(0)
})
} else {
- console.error('input error, example param:nodejs-linux passwd12345 rc4 enc ./myfolder /tmp/outPath encname ')
+ console.error('input error, example param:nodejs-linux passwd12345 rc4 enc ./myFolder /tmp/outPath encName ')
process.exit(1)
}
}
diff --git a/node-proxy/src/utils/crc6-8.js b/node-proxy/src/utils/crc6-8.js
deleted file mode 100644
index 7e712ba..0000000
--- a/node-proxy/src/utils/crc6-8.js
+++ /dev/null
@@ -1,104 +0,0 @@
-// "Class" for calculating CRC8 checksums...
-function CRCn(num, polynomial, initialValue = 0) {
- // constructor takes an optional polynomial type from CRC8.POLY
- polynomial = polynomial || CRCn.POLY8.CRC8_DALLAS_MAXIM
- if (num === 6) {
- this.table = CRCn.generateTable6()
- }
- if (num === 8) {
- this.table = CRCn.generateTable8MAXIM(polynomial)
- }
- this.initialValue = initialValue
-}
-
-// Returns the 8-bit checksum given an array of byte-sized numbers
-CRCn.prototype.checksum = function (byteArray) {
- let c = this.initialValue
- for (let i = 0; i < byteArray.length; i++) {
- c = this.table[(c ^ byteArray[i]) % 256]
- }
- return c
-}
-
-// returns a lookup table byte array given one of the values from CRC8.POLY
-CRCn.generateTable8 = function (polynomial) {
- const csTable = [] // 256 max len byte array
- for (let i = 0; i < 256; ++i) {
- let curr = i
- for (let j = 0; j < 8; ++j) {
- if ((curr & 0x80) !== 0) {
- curr = ((curr << 1) ^ polynomial) % 256
- } else {
- curr = (curr << 1) % 256
- }
- }
- csTable[i] = curr
- }
- return csTable
-}
-
-// CRC8_DALLAS_MAXIM,跟上面的比较的话,不需要输入和输出的反转
-CRCn.generateTable8MAXIM = function (polynomial) {
- const csTable = [] // 256 max len byte array
- for (let i = 0; i < 256; ++i) {
- let curr = i
- for (let j = 0; j < 8; ++j) {
- if ((curr & 0x01) !== 0) {
- // 0x31=>00110001 翻转 10001100=>0x8C
- curr = ((curr >> 1) ^ 0x8c) % 256
- } else {
- curr = (curr >> 1) % 256
- }
- }
- csTable[i] = curr
- }
- return csTable
-}
-
-// 已经完成了输入和输出的反转,所以输入和输出就不需要单独反转
-CRCn.generateTable6 = function () {
- const csTable = [] // 256 max len byte array
- for (let i = 0; i < 256; i++) {
- let curr = i
- for (let j = 0; j < 8; ++j) {
- if ((curr & 0x01) !== 0) {
- // 0x03(多项式:x6+x+1,00100011),最高位不需要异或,直接去掉 0000 0011
- // 0x30 = (reverse 0x03)>>(8-6) = 00110000
- curr = ((curr >> 1) ^ 0x30) % 256
- } else {
- curr = (curr >> 1) % 256
- }
- }
- csTable[i] = curr
- }
- return csTable
-}
-
-CRCn.generateTable6test = function () {
- const csTable = [] // 256 max len byte array
- for (let i = 0; i < 256; i++) {
- let curr = i
- for (let j = 0; j < 8; ++j) {
- if ((curr & 0x80) !== 0) {
- // 0x03(多项式:x6+x+1,00100011),最高位不需要异或,直接去掉
- // 0x30 = (reverse 0x03) >> (8-6)
- curr = ((curr << 1) ^ 0x03) % 256
- } else {
- curr = (curr << 1) % 256
- }
- }
- csTable[i] = curr >> 2
- }
- return csTable
-}
-
-// This "enum" can be used to indicate what kind of CRC8 checksum you will be calculating
-CRCn.POLY8 = {
- CRC8: 0xd5,
- CRC8_CCITT: 0x07,
- CRC8_DALLAS_MAXIM: 0x31,
- CRC8_SAE_J1850: 0x1d,
- CRC_8_WCDMA: 0x9b,
-}
-
-export default CRCn
diff --git a/node-proxy/src/utils/PRGAExcute.js b/node-proxy/src/utils/crypto/RPGA/PRGAExecute.js
similarity index 84%
rename from node-proxy/src/utils/PRGAExcute.js
rename to node-proxy/src/utils/crypto/RPGA/PRGAExecute.js
index 32632a5..ccb15e3 100644
--- a/node-proxy/src/utils/PRGAExcute.js
+++ b/node-proxy/src/utils/crypto/RPGA/PRGAExecute.js
@@ -1,4 +1,4 @@
-const PRGAExcute = function (data) {
+const PRGAExecute = function (data) {
let { sbox: S, i, j, position } = data
for (let k = 0; k < position * 1; k++) {
i = (i + 1) % 256
@@ -14,6 +14,6 @@ const PRGAExcute = function (data) {
process.on('message', function ({ msgId, workerData }) {
console.log('来自父进程的消息: ' + JSON.stringify(workerData))
- const resData = PRGAExcute(workerData)
+ const resData = PRGAExecute(workerData)
process.send({ msgId, resData })
})
diff --git a/node-proxy/src/utils/PRGAFork.js b/node-proxy/src/utils/crypto/RPGA/PRGAFork.js
similarity index 75%
rename from node-proxy/src/utils/PRGAFork.js
rename to node-proxy/src/utils/crypto/RPGA/PRGAFork.js
index 4fa122f..8069589 100644
--- a/node-proxy/src/utils/PRGAFork.js
+++ b/node-proxy/src/utils/crypto/RPGA/PRGAFork.js
@@ -1,18 +1,19 @@
+import os from 'os'
import { fork } from 'child_process'
import { randomUUID } from 'crypto'
-import os from 'os'
-let index = parseInt(os.cpus().length / 2)
+
+let index = os.cpus().length / 2
const childList = []
for (let i = index; i--; ) {
- const child = fork('./utils/PRGAExcute.js')
+ const child = fork('./utils/PRGAExecute.js')
childList[i] = child
child.once('message', ({ msgId, resData }) => {
child.emit(msgId, resData)
})
}
-const PRGAExcuteThread = function (workerData) {
+const PRGAExecuteThread = function (workerData) {
return new Promise((resolve, reject) => {
const child = childList[index++ % childList.length]
// 只监听一次,这样就可以重复监听
@@ -24,4 +25,4 @@ const PRGAExcuteThread = function (workerData) {
})
}
-export default PRGAExcuteThread
+export default PRGAExecuteThread
diff --git a/node-proxy/src/utils/PRGAThread.js b/node-proxy/src/utils/crypto/RPGA/PRGAThread.js
similarity index 88%
rename from node-proxy/src/utils/PRGAThread.js
rename to node-proxy/src/utils/crypto/RPGA/PRGAThread.js
index 8a12006..bb47488 100644
--- a/node-proxy/src/utils/PRGAThread.js
+++ b/node-proxy/src/utils/crypto/RPGA/PRGAThread.js
@@ -11,7 +11,7 @@ const pkgThreadPath = pkgDirPath + '/PRGAThreadCom.js'
// dev threadPath
const threadPath = require.resolve('./PRGAThread.js')
let index = 0
-let PRGAExcuteThread = null
+let PRGAExecuteThread = null
// 一定要加上这个,不然会产生递归,创建无数线程
if (isMainThread) {
// 避免消耗光资源,加一用于后续的预加载RC4的位置
@@ -36,7 +36,7 @@ if (isMainThread) {
})
}
- PRGAExcuteThread = function (data) {
+ PRGAExecuteThread = function (data) {
return new Promise((resolve, reject) => {
const worker = workerList[index++ % workerNum]
const msgId = randomUUID()
@@ -52,7 +52,7 @@ if (isMainThread) {
// 如果是线程执行了这个文件,就开始处理
if (!isMainThread) {
// 异步线程去计算这个位置
- const PRGAExcute = function (data) {
+ const PRGAExecute = function (data) {
let { sbox: S, i, j, position } = data
for (let k = 0; k < position; k++) {
i = (i + 1) % 256
@@ -68,10 +68,10 @@ if (!isMainThread) {
// workerData 由主线程发送过来的信息
parentPort.on('message', ({ msgId, data }) => {
const startTime = Date.now()
- const resData = PRGAExcute(data)
+ const resData = PRGAExecute(data)
parentPort.postMessage({ msgId, resData })
const time = Date.now() - startTime
- console.log('@@@PRGAExcute-end', data.position, Date.now(), '@time:' + time, workerData)
+ console.log('@@@PRGAExecute-end', data.position, Date.now(), '@time:' + time, workerData)
})
}
-module.exports = PRGAExcuteThread
+module.exports = PRGAExecuteThread
diff --git a/node-proxy/src/utils/PRGAThreadCom.js b/node-proxy/src/utils/crypto/RPGA/PRGAThreadCom.js
similarity index 79%
rename from node-proxy/src/utils/PRGAThreadCom.js
rename to node-proxy/src/utils/crypto/RPGA/PRGAThreadCom.js
index e9887ad..20d6550 100644
--- a/node-proxy/src/utils/PRGAThreadCom.js
+++ b/node-proxy/src/utils/crypto/RPGA/PRGAThreadCom.js
@@ -3,7 +3,7 @@ const { isMainThread, parentPort, workerData } = require('worker_threads')
// is not MainThread
if (!isMainThread) {
// Excute the posistion info
- const PRGAExcute = function (data) {
+ const PRGAExecute = function (data) {
let { sbox: S, i, j, position } = data
for (let k = 0; k < position; k++) {
i = (i + 1) % 256
@@ -19,9 +19,9 @@ if (!isMainThread) {
// workerData 由主线程发送过来的信息
parentPort.on('message', ({ msgId, data }) => {
const startTime = Date.now()
- const resData = PRGAExcute(data)
+ const resData = PRGAExecute(data)
parentPort.postMessage({ msgId, resData })
const time = Date.now() - startTime
- console.log('@@@PRGAExcute-end', data.position, Date.now(), '@time:' + time, workerData)
+ console.log('@@@PRGAExecute-end', data.position, Date.now(), '@time:' + time, workerData)
})
}
diff --git a/node-proxy/src/utils/aesCTR.js b/node-proxy/src/utils/crypto/aesCTR.ts
similarity index 60%
rename from node-proxy/src/utils/aesCTR.js
rename to node-proxy/src/utils/crypto/aesCTR.ts
index 91da4a2..133304e 100644
--- a/node-proxy/src/utils/aesCTR.js
+++ b/node-proxy/src/utils/crypto/aesCTR.ts
@@ -1,45 +1,78 @@
import crypto from 'crypto'
import { Transform } from 'stream'
-class AesCTR {
- constructor(password, sizeSalt) {
+class AesCTR implements EncryptFlow {
+ public password: string
+ public passwdOutward: string
+
+ private iv: Buffer
+ private readonly sourceIv: Buffer
+ private cipher: crypto.Cipher
+ private readonly key: Buffer
+
+ constructor(password: string, sizeSalt: string) {
+ if (!sizeSalt) {
+ throw new Error('salt is null')
+ }
+
this.password = password
- this.sizeSalt = sizeSalt + ''
// check base64
if (password.length !== 32) {
this.passwdOutward = crypto.pbkdf2Sync(this.password, 'AES-CTR', 1000, 16, 'sha256').toString('hex')
}
+
// create file aes-ctr key
- const passwdSalt = this.passwdOutward + sizeSalt
- this.key = crypto.createHash('md5').update(passwdSalt).digest()
- this.iv = crypto.createHash('md5').update(this.sizeSalt).digest()
- // copy to soureIv
- const ivBuffer = Buffer.alloc(this.iv.length)
- this.iv.copy(ivBuffer)
- this.soureIv = ivBuffer
- this.cipher = crypto.createCipheriv('aes-128-ctr', this.key, this.iv)
- }
+ this.key = crypto
+ .createHash('md5')
+ .update(this.passwdOutward + sizeSalt)
+ .digest()
+ this.iv = crypto.createHash('md5').update(sizeSalt).digest()
- encrypt(messageBytes) {
- return this.cipher.update(messageBytes)
+ // copy to sourceIv
+ this.sourceIv = Buffer.alloc(this.iv.length)
+ this.iv.copy(this.sourceIv)
+ this.cipher = crypto.createCipheriv('aes-128-ctr', this.key, this.iv)
}
- decrypt(messageBytes) {
- return this.cipher.update(messageBytes)
+ incrementIV(increment: number) {
+ const MAX_UINT32 = 0xffffffff
+ const incrementBig = ~~(increment / MAX_UINT32)
+ const incrementLittle = (increment % MAX_UINT32) - incrementBig
+ // split the 128bits IV in 4 numbers, 32bits each
+ let overflow = 0
+ for (let idx = 0; idx < 4; ++idx) {
+ let num = this.iv.readUInt32BE(12 - idx * 4)
+ let inc = overflow
+ if (idx === 0) inc += incrementLittle
+ if (idx === 1) inc += incrementBig
+ num += inc
+ const numBig = ~~(num / MAX_UINT32)
+ const numLittle = (num % MAX_UINT32) - numBig
+ overflow = numBig
+ this.iv.writeUInt32BE(numLittle, 12 - idx * 4)
+ }
}
// reset position
- async setPositionAsync(position) {
- const ivBuffer = Buffer.alloc(this.soureIv.length)
- this.soureIv.copy(ivBuffer)
+ async setPositionAsync(position: number) {
+ const ivBuffer = Buffer.alloc(this.sourceIv.length)
+ this.sourceIv.copy(ivBuffer)
this.iv = ivBuffer
- const increment = parseInt(position / 16)
- this.incrementIV(increment)
+ this.incrementIV(position / 16)
// create new Cipheriv
this.cipher = crypto.createCipheriv('aes-128-ctr', this.key, this.iv)
- const offset = position % 16
- const buffer = Buffer.alloc(offset)
+ const buffer = Buffer.alloc(position % 16)
this.encrypt(buffer)
+
+ return this
+ }
+
+ encrypt(plainBuffer: Buffer) {
+ return this.cipher.update(plainBuffer)
+ }
+
+ decrypt(encryptedBuffer: Buffer) {
+ return this.cipher.update(encryptedBuffer)
}
encryptTransform() {
@@ -58,25 +91,6 @@ class AesCTR {
},
})
}
-
- incrementIV(increment) {
- const MAX_UINT32 = 0xffffffff
- const incrementBig = ~~(increment / MAX_UINT32)
- const incrementLittle = (increment % MAX_UINT32) - incrementBig
- // split the 128bits IV in 4 numbers, 32bits each
- let overflow = 0
- for (let idx = 0; idx < 4; ++idx) {
- let num = this.iv.readUInt32BE(12 - idx * 4)
- let inc = overflow
- if (idx === 0) inc += incrementLittle
- if (idx === 1) inc += incrementBig
- num += inc
- const numBig = ~~(num / MAX_UINT32)
- const numLittle = (num % MAX_UINT32) - numBig
- overflow = numBig
- this.iv.writeUInt32BE(numLittle, 12 - idx * 4)
- }
- }
}
export default AesCTR
diff --git a/node-proxy/src/utils/crypto/crc6-8.ts b/node-proxy/src/utils/crypto/crc6-8.ts
new file mode 100644
index 0000000..8b5cf8b
--- /dev/null
+++ b/node-proxy/src/utils/crypto/crc6-8.ts
@@ -0,0 +1,64 @@
+// "Class" for calculating CRC8 checksums...
+class CRCn {
+ private readonly table: number[]
+ private readonly initialValue: number
+
+ constructor(num: number, initialValue = 0) {
+ // constructor takes an optional polynomial type from CRC8.POLY
+ if (num === 6) {
+ this.table = CRCn.generateTable6()
+ }
+ if (num === 8) {
+ this.table = CRCn.generateTable8MAXIM()
+ }
+ this.initialValue = initialValue
+ }
+
+ // CRC8_DALLAS_MAXIM,跟上面的比较的话,不需要输入和输出的反转
+ static generateTable8MAXIM(): number[] {
+ const csTable: number[] = [] // 256 max len byte array
+ for (let i = 0; i < 256; ++i) {
+ let curr = i
+ for (let j = 0; j < 8; ++j) {
+ if ((curr & 0x01) !== 0) {
+ // 0x31=>00110001 翻转 10001100=>0x8C
+ curr = ((curr >> 1) ^ 0x8c) % 256
+ } else {
+ curr = (curr >> 1) % 256
+ }
+ }
+ csTable[i] = curr
+ }
+ return csTable
+ }
+
+ // 已经完成了输入和输出的反转,所以输入和输出就不需要单独反转
+ static generateTable6(): number[] {
+ const csTable: number[] = [] // 256 max len byte array
+ for (let i = 0; i < 256; i++) {
+ let curr = i
+ for (let j = 0; j < 8; ++j) {
+ if ((curr & 0x01) !== 0) {
+ // 0x03(多项式:x6+x+1,00100011),最高位不需要异或,直接去掉 0000 0011
+ // 0x30 = (reverse 0x03)>>(8-6) = 00110000
+ curr = ((curr >> 1) ^ 0x30) % 256
+ } else {
+ curr = (curr >> 1) % 256
+ }
+ }
+ csTable[i] = curr
+ }
+ return csTable
+ }
+
+ // Returns the 8-bit checksum given an array of byte-sized numbers
+ checksum(byteArray: Buffer): number {
+ let c = this.initialValue
+ for (let i = 0; i < byteArray.length; i++) {
+ c = this.table[(c ^ byteArray[i]) % 256]
+ }
+ return c
+ }
+}
+
+export default CRCn
diff --git a/node-proxy/src/utils/crypto/mixBase64.ts b/node-proxy/src/utils/crypto/mixBase64.ts
new file mode 100644
index 0000000..0abd14b
--- /dev/null
+++ b/node-proxy/src/utils/crypto/mixBase64.ts
@@ -0,0 +1,126 @@
+import crypto from 'crypto'
+
+const source = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~+'
+
+// use sha1 str to init
+
+// 自定义的base64加密
+class MixBase64 implements EncryptMethod {
+ password: string
+ passwdOutward: string
+
+ private mapChars: Record = {}
+
+ private readonly chars: string[]
+
+ constructor(password: string, salt = 'mix64') {
+ const secret = password.length === 64 ? password : this.initKSA(password + salt)
+ this.chars = secret.split('')
+ this.chars.forEach((e, index) => {
+ this.mapChars[e] = index
+ })
+ // 编码加密
+ }
+
+ initKSA(password: string) {
+ const key = crypto.createHash('sha256').update(password).digest()
+ const K = []
+ // 对S表进行初始赋值
+ const sbox = []
+ const sourceKey = source.split('')
+ // 对S表进行初始赋值
+ for (let i = 0; i < source.length; i++) {
+ sbox[i] = i
+ }
+ // 用种子密钥对K表进行填充
+ for (let i = 0; i < source.length; i++) {
+ K[i] = key[i % key.length]
+ }
+ // 对S表进行置换
+ for (let i = 0, j = 0; i < source.length; i++) {
+ j = (j + sbox[i] + K[i]) % source.length
+ const temp = sbox[i]
+ sbox[i] = sbox[j]
+ sbox[j] = temp
+ }
+ let secret = ''
+ for (const i of sbox) {
+ secret += sourceKey[i]
+ }
+ return secret
+ }
+
+ static getSourceChar(index: number) {
+ // 不能使用 = 号,url穿参数不支持
+ return source.split('')[index]
+ }
+
+ encrypt(plainBuffer: Buffer) {
+ let result = ''
+ let arr: Buffer
+ let bt: Buffer
+ let char: string
+
+ for (let i = 0; i < plainBuffer.length; i += 3) {
+ if (i + 3 > plainBuffer.length) {
+ arr = plainBuffer.subarray(i, plainBuffer.length)
+ break
+ }
+
+ bt = plainBuffer.subarray(i, i + 3)
+ char =
+ this.chars[bt[0] >> 2] +
+ this.chars[((bt[0] & 3) << 4) | (bt[1] >> 4)] +
+ this.chars[((bt[1] & 15) << 2) | (bt[2] >> 6)] +
+ this.chars[bt[2] & 63]
+ result += char
+ }
+
+ if (plainBuffer.length % 3 === 1) {
+ char = this.chars[arr[0] >> 2] + this.chars[(arr[0] & 3) << 4] + this.chars[64] + this.chars[64]
+ result += char
+ } else if (plainBuffer.length % 3 === 2) {
+ char = this.chars[arr[0] >> 2] + this.chars[((arr[0] & 3) << 4) | (arr[1] >> 4)] + this.chars[(arr[1] & 15) << 2] + this.chars[64]
+ result += char
+ }
+
+ return Buffer.from(result)
+ }
+
+ // 编码解密
+ decrypt(encryptedBuffer: Buffer) {
+ const base64Str = encryptedBuffer.toString()
+
+ let size = (base64Str.length / 4) * 3
+ let j = 0
+
+ if (~base64Str.indexOf(this.chars[64] + '' + this.chars[64])) {
+ size -= 2
+ } else if (~base64Str.indexOf(this.chars[64])) {
+ size -= 1
+ }
+
+ const buffer = Buffer.alloc(size)
+ let enc1: number
+ let enc2: number
+ let enc3: number
+ let enc4: number
+ let i = 0
+ while (i < base64Str.length) {
+ enc1 = this.mapChars[base64Str.charAt(i++)]
+ enc2 = this.mapChars[base64Str.charAt(i++)]
+ enc3 = this.mapChars[base64Str.charAt(i++)]
+ enc4 = this.mapChars[base64Str.charAt(i++)]
+ buffer.writeUInt8((enc1 << 2) | (enc2 >> 4), j++)
+ if (enc3 !== 64) {
+ buffer.writeUInt8(((enc2 & 15) << 4) | (enc3 >> 2), j++)
+ }
+ if (enc4 !== 64) {
+ buffer.writeUInt8(((enc3 & 3) << 6) | enc4, j++)
+ }
+ }
+ return buffer
+ }
+}
+
+export default MixBase64
diff --git a/node-proxy/src/utils/mixEnc.js b/node-proxy/src/utils/crypto/mixEnc.ts
similarity index 53%
rename from node-proxy/src/utils/mixEnc.js
rename to node-proxy/src/utils/crypto/mixEnc.ts
index a66850a..3583ccc 100644
--- a/node-proxy/src/utils/mixEnc.js
+++ b/node-proxy/src/utils/crypto/mixEnc.ts
@@ -1,13 +1,18 @@
-'use strict'
-
import crypto from 'crypto'
import { Transform } from 'stream'
+import { logger } from '@/common/logger'
/**
* 混淆算法,加密强度低,容易因为文件特征被破解。可以提升encode长度来对抗
*/
-class MixEnc {
- constructor(password) {
+class MixEnc implements EncryptFlow {
+ public password: string
+ public passwdOutward: string
+
+ private readonly encode: Buffer
+ private readonly decode: Buffer
+
+ constructor(password: string) {
this.password = password
// 说明是输入encode的秘钥,用于找回文件加解密
this.passwdOutward = password
@@ -15,11 +20,12 @@ class MixEnc {
if (password.length !== 32) {
this.passwdOutward = crypto.pbkdf2Sync(this.password, 'MIX', 1000, 16, 'sha256').toString('hex')
}
- console.log('MixEnc.passwdOutward', this.passwdOutward)
+
const encode = crypto.createHash('sha256').update(this.passwdOutward).digest()
const decode = []
const length = encode.length
const decodeCheck = {}
+
for (let i = 0; i < length; i++) {
const enc = encode[i] ^ i
// 这里会产生冲突
@@ -37,20 +43,30 @@ class MixEnc {
}
}
}
+
this.encode = encode
this.decode = Buffer.from(decode)
- // console.log('@encode:', this.encode.toString('hex'))
- // console.log('@decode:', this.decode.toString('hex'))
}
- // MD5
- md5(content) {
- const md5 = crypto.createHash('md5')
- return md5.update(this.passwdOutward + content).digest('hex')
+ async setPositionAsync(position: number) {
+ logger.info('in the mix ', position)
+ return this
}
- async setPositionAsync() {
- console.log('in the mix ')
+ // 加密方法
+ encrypt(plainBuffer: Buffer) {
+ for (let i = plainBuffer.length; i--; ) {
+ plainBuffer[i] ^= this.encode[plainBuffer[i] % 32]
+ }
+ return plainBuffer
+ }
+
+ // 解密方法
+ decrypt(encryptedBuffer: Buffer) {
+ for (let i = encryptedBuffer.length; i--; ) {
+ encryptedBuffer[i] ^= this.decode[encryptedBuffer[i] % 32]
+ }
+ return encryptedBuffer
}
// 加密流转换
@@ -58,7 +74,7 @@ class MixEnc {
return new Transform({
// 匿名函数确保this是指向 FlowEnc
transform: (chunk, encoding, next) => {
- next(null, this.encodeData(chunk))
+ next(null, this.encrypt(chunk))
},
})
}
@@ -68,50 +84,10 @@ class MixEnc {
return new Transform({
transform: (chunk, encoding, next) => {
// this.push() 用push也可以
- next(null, this.decodeData(chunk))
+ next(null, this.decrypt(chunk))
},
})
}
-
- // 加密方法
- encodeData(data) {
- data = Buffer.from(data)
- for (let i = data.length; i--; ) {
- data[i] ^= this.encode[data[i] % 32]
- }
- return data
- }
-
- // 解密方法
- decodeData(data) {
- for (let i = data.length; i--; ) {
- data[i] ^= this.decode[data[i] % 32]
- }
- return data
- }
-}
-
-// 检查 encode 是否正确使用的
-MixEnc.checkEncode = function (_encode) {
- const encode = Buffer.from(_encode, 'hex')
- const length = encode.length
- const decodeCheck = {}
- for (let i = 0; i < encode.length; i++) {
- const enc = encode[i] ^ i
- // 这里会产生冲突
- if (!decodeCheck[enc % length]) {
- decodeCheck[enc % length] = encode[i]
- } else {
- return null
- }
- }
- return encode
}
-// const flowEnc = new MixEnc('abc1234', 1234)
-// const encode = flowEnc.encodeData('测试的明文加密1234¥%#')
-// const nwe = new MixEnc('5fc8482ac3a7b3fd9325566dfdd31673', 1234)
-// const decode = nwe.decodeData(encode)
-// console.log('@@@decode', encode, decode.toString())
-
export default MixEnc
diff --git a/node-proxy/src/utils/rc4Md5.js b/node-proxy/src/utils/crypto/rc4Md5.ts
similarity index 64%
rename from node-proxy/src/utils/rc4Md5.js
rename to node-proxy/src/utils/crypto/rc4Md5.ts
index 677a7c6..029cfc9 100644
--- a/node-proxy/src/utils/rc4Md5.js
+++ b/node-proxy/src/utils/crypto/rc4Md5.ts
@@ -1,8 +1,6 @@
-'use strict'
-
import crypto from 'crypto'
import { Transform } from 'stream'
-import PRGAExcuteThread from './PRGAThread'
+import PRGAExecuteThread from '@/utils/crypto/RPGA/PRGAThread'
/**
* RC4算法,安全性相对好很多
@@ -11,18 +9,23 @@ import PRGAExcuteThread from './PRGAThread'
// Reset sbox every 100W bytes
const segmentPosition = 100 * 10000
-class Rc4Md5 {
+class Rc4Md5 implements EncryptFlow {
+ public password: string
+ public passwdOutward: string
+
+ private i = 0
+ private j = 0
+ private position = 0
+ private sbox: number[] = []
+
+ private readonly fileHexKey: string
+
// password,salt: ensure that each file has a different password
- constructor(password, sizeSalt) {
+ constructor(password: string, sizeSalt: string) {
if (!sizeSalt) {
throw new Error('salt is null')
}
- this.position = 0
- this.i = 0
- this.j = 0
- this.sbox = []
this.password = password
- this.sizeSalt = sizeSalt
// share you folder passwdOutward safety
this.passwdOutward = password
// check base64,create passwdOutward
@@ -30,14 +33,16 @@ class Rc4Md5 {
this.passwdOutward = crypto.pbkdf2Sync(this.password, 'RC4', 1000, 16, 'sha256').toString('hex')
}
// add salt
- const passwdSalt = this.passwdOutward + sizeSalt
- // fileHexKey: file passwd,could be share
- this.fileHexKey = crypto.createHash('md5').update(passwdSalt).digest('hex')
+ this.fileHexKey = crypto
+ .createHash('md5')
+ .update(this.passwdOutward + sizeSalt)
+ .digest('hex')
+
this.resetKSA()
}
resetKSA() {
- const offset = parseInt(this.position / segmentPosition) * segmentPosition
+ const offset = (this.position / segmentPosition) * segmentPosition
const buf = Buffer.alloc(4)
buf.writeInt32BE(offset)
const rc4Key = Buffer.from(this.fileHexKey, 'hex')
@@ -48,38 +53,72 @@ class Rc4Md5 {
this.initKSA(rc4Key)
}
- // reset sbox,i,j
- setPosition(newPosition = 0) {
- newPosition *= 1
- this.position = newPosition
- this.resetKSA()
- // use PRGAExecPostion no change potision
- this.PRGAExecPostion(newPosition % segmentPosition)
- return this
+ initKSA(key: Buffer) {
+ const K = []
+ // init sbox
+ for (let i = 0; i < 256; i++) {
+ this.sbox[i] = i
+ }
+ // 用种子密钥对K表进行填充
+ for (let i = 0; i < 256; i++) {
+ K[i] = key[i % key.length]
+ }
+ // 对S表进行置换
+ for (let i = 0, j = 0; i < 256; i++) {
+ j = (j + this.sbox[i] + K[i]) % 256
+ const temp = this.sbox[i]
+ this.sbox[i] = this.sbox[j]
+ this.sbox[j] = temp
+ }
+ this.i = 0
+ this.j = 0
+ }
+
+ // 初始化长度,因为有一些文件下载 Range: bytes=3600-5000
+ PRGAExecute(plainBuffer: Buffer) {
+ let { sbox: S, i, j } = this
+ for (let k = 0; k < plainBuffer.length; k++) {
+ i = (i + 1) % 256
+ j = (j + S[i]) % 256
+ // swap
+ const temp = S[i]
+ S[i] = S[j]
+ S[j] = temp
+ plainBuffer[k] ^= S[(S[i] + S[j]) % 256]
+ if (++this.position % segmentPosition === 0) {
+ // reset sbox initKSA
+ this.resetKSA()
+ i = this.i
+ j = this.j
+ S = this.sbox
+ }
+ }
+ // save the i,j
+ this.i = i
+ this.j = j
+ return plainBuffer
}
// reset sbox,i,j, in other thread
async setPositionAsync(newPosition = 0) {
- // return this.setPosition(newPosition)
- newPosition *= 1
this.position = newPosition
this.resetKSA()
const { sbox, i, j } = this
- const data = await PRGAExcuteThread({ sbox, i, j, position: newPosition % segmentPosition })
+ const data = await PRGAExecuteThread({ sbox, i, j, position: newPosition % segmentPosition })
this.sbox = data.sbox
this.i = data.i
this.j = data.j
- return data
- }
- encryptText(plainTextLen) {
- const plainBuffer = Buffer.from(plainTextLen)
- return this.encrypt(plainBuffer)
+ return this
}
// 加解密都是同一个方法
- encrypt(plainBuffer) {
- return this.PRGAExcute(plainBuffer)
+ encrypt(plainBuffer: Buffer) {
+ return this.PRGAExecute(plainBuffer)
+ }
+
+ decrypt(encryptedBuffer: Buffer) {
+ return this.PRGAExecute(encryptedBuffer)
}
// 加密流转换
@@ -101,72 +140,6 @@ class Rc4Md5 {
},
})
}
-
- // 初始化长度,因为有一些文件下载 Range: bytes=3600-5000
- PRGAExcute(plainBuffer) {
- let { sbox: S, i, j } = this
- for (let k = 0; k < plainBuffer.length; k++) {
- i = (i + 1) % 256
- j = (j + S[i]) % 256
- // swap
- const temp = S[i]
- S[i] = S[j]
- S[j] = temp
- plainBuffer[k] ^= S[(S[i] + S[j]) % 256]
- if (++this.position % segmentPosition === 0) {
- // reset sbox initKSA
- this.resetKSA()
- i = this.i
- j = this.j
- S = this.sbox
- }
- }
- // save the i,j
- this.i = i
- this.j = j
- return plainBuffer
- }
-
- PRGAExecPostion(plainLen) {
- let { sbox: S, i, j } = this
- // k-- is inefficient
- for (let k = 0; k < plainLen; k++) {
- i = (i + 1) % 256
- j = (j + S[i]) % 256
- const temp = S[i]
- S[i] = S[j]
- S[j] = temp
- }
- this.i = i
- this.j = j
- }
-
- initKSA(key) {
- const K = []
- // init sbox
- for (let i = 0; i < 256; i++) {
- this.sbox[i] = i
- }
- // 用种子密钥对K表进行填充
- for (let i = 0; i < 256; i++) {
- K[i] = key[i % key.length]
- }
- // 对S表进行置换
- for (let i = 0, j = 0; i < 256; i++) {
- j = (j + this.sbox[i] + K[i]) % 256
- const temp = this.sbox[i]
- this.sbox[i] = this.sbox[j]
- this.sbox[j] = temp
- }
- this.i = 0
- this.j = 0
- }
}
-// const rc4 = new Rc4('123456')
-// const buffer = rc4.encryptText('abc')
-// // 要记得重置
-// const plainBy = rc4.resetSbox().encrypt(buffer)
-// console.log('@@@', buffer, Buffer.from(plainBy).toString('utf-8'))
-
export default Rc4Md5
diff --git a/node-proxy/src/utils/commonUtil.ts b/node-proxy/src/utils/cryptoUtil.ts
similarity index 70%
rename from node-proxy/src/utils/commonUtil.ts
rename to node-proxy/src/utils/cryptoUtil.ts
index 37af043..381ab29 100644
--- a/node-proxy/src/utils/commonUtil.ts
+++ b/node-proxy/src/utils/cryptoUtil.ts
@@ -1,28 +1,30 @@
-import { pathToRegexp } from 'path-to-regexp'
-import FlowEnc from './flowEnc'
import path from 'path'
-import MixBase64 from './mixBase64'
-import Crcn from './crc6-8'
+import { pathToRegexp } from 'path-to-regexp'
+
+import Crcn from './crypto/crc6-8'
+import MixBase64 from './crypto/mixBase64'
+import { getPassWdOutward } from './flowEnc'
+import { logger } from '@/common/logger'
const crc6 = new Crcn(6)
const origPrefix = 'orig_'
// check file name, return real name
-export function convertRealName(password: string, encType: string, pathText: string) {
+export function convertRealName(password: string, encType: EncryptType, pathText: string) {
const fileName = path.basename(pathText)
if (fileName.indexOf(origPrefix) === 0) {
return fileName.replace(origPrefix, '')
}
// try encode name, fileName don't need decodeURI,encodeUrl func can't encode that like '(' '!' in nodejs
const ext = path.extname(fileName)
+ logger.info(`获取原文件名: ${decodeURIComponent(fileName)} ${encType} ${password}`)
const encName = encodeName(password, encType, decodeURIComponent(fileName))
- console.log('@@decodeURI(fileName)', decodeURIComponent(fileName))
return encName + ext
}
-// if file name has encrypt, return show name
-export function convertShowName(password: string, encType: string, pathText: string) {
+// if file name has encrypted, return show name
+export function convertShowName(password: string, encType: EncryptType, pathText: string) {
const fileName = path.basename(decodeURIComponent(pathText))
const ext = path.extname(fileName)
const encName = fileName.replace(ext, '')
@@ -45,20 +47,20 @@ export function pathExec(encPath: string[], url: string) {
return null
}
-export function encodeName(password: string, encType: string, plainName: string) {
- const passwdOutward = FlowEnc.getPassWdOutward(password, encType)
+export function encodeName(password: string, encType: EncryptType, plainName: string) {
+ const passwdOutward = getPassWdOutward(password, encType)
// randomStr
const mix64 = new MixBase64(passwdOutward)
- let encodeName = mix64.encode(plainName)
+ let encodeName = mix64.encrypt(Buffer.from(plainName)).toString()
const crc6Bit = crc6.checksum(Buffer.from(encodeName + passwdOutward))
const crc6Check = MixBase64.getSourceChar(crc6Bit)
encodeName += crc6Check
return encodeName
}
-export function decodeName(password: string, encType: string, encodeName: string) {
+export function decodeName(password: string, encType: EncryptType, encodeName: string) {
const crc6Check = encodeName.substring(encodeName.length - 1)
- const passwdOutward = FlowEnc.getPassWdOutward(password, encType)
+ const passwdOutward = getPassWdOutward(password, encType)
const mix64 = new MixBase64(passwdOutward)
// start dec
const subEncName = encodeName.substring(0, encodeName.length - 1)
@@ -70,19 +72,19 @@ export function decodeName(password: string, encType: string, encodeName: string
// event pass crc6,it maybe decode error, like this name '68758PICxAd_1024-666 - 副本33.png'
let decodeStr = null
try {
- decodeStr = mix64.decode(subEncName).toString('utf8')
+ decodeStr = mix64.decrypt(Buffer.from(subEncName)).toString()
} catch (e) {
- console.log('@@mix64 decode error', subEncName)
+ logger.error('@@mix64 decode error', subEncName)
}
return decodeStr
}
-export function encodeFolderName(password: string, encType: string, folderPasswd: string, folderEncType: string) {
+export function encodeFolderName(password: string, encType: EncryptType, folderPasswd: string, folderEncType: string) {
const passwdInfo = folderEncType + '_' + folderPasswd
return encodeName(password, encType, passwdInfo)
}
-export function decodeFolderName(password: string, encType: string, encodeName: string) {
+export function decodeFolderName(password: string, encType: EncryptType, encodeName: string) {
const arr = encodeName.split('_')
if (arr.length < 2) {
return false
@@ -98,7 +100,13 @@ export function decodeFolderName(password: string, encType: string, encodeName:
}
// 检查
-export function pathFindPasswd(passwdList: PasswdInfo[], url: string) {
+export function pathFindPasswd(
+ passwdList: PasswdInfo[],
+ url: string
+): {
+ passwdInfo: PasswdInfo | null
+ pathInfo: RegExpExecArray | null
+} {
for (const passwdInfo of passwdList) {
for (const filePath of passwdInfo.encPath) {
const result = passwdInfo.enable ? pathToRegexp(new RegExp(filePath)).exec(url) : null
@@ -120,5 +128,5 @@ export function pathFindPasswd(passwdList: PasswdInfo[], url: string) {
}
}
}
- return {}
+ return { passwdInfo: null, pathInfo: null }
}
diff --git a/node-proxy/src/utils/flowEnc.js b/node-proxy/src/utils/flowEnc.js
deleted file mode 100644
index a2375e8..0000000
--- a/node-proxy/src/utils/flowEnc.js
+++ /dev/null
@@ -1,65 +0,0 @@
-'use strict'
-
-import MixEnc from './mixEnc'
-import Rc4Md5 from './rc4Md5'
-import AesCTR from './aesCTR'
-
-const cachePasswdOutward = {}
-
-class FlowEnc {
- constructor(password, encryptType = 'mix', fileSize = 0) {
- fileSize *= 1
- let encryptFlow = null
- if (encryptType === 'mix') {
- console.log('@@mix', encryptType)
- encryptFlow = new MixEnc(password, fileSize)
- this.passwdOutward = encryptFlow.passwdOutward
- }
- if (encryptType === 'rc4') {
- console.log('@@rc4', encryptType, fileSize)
- encryptFlow = new Rc4Md5(password, fileSize)
- this.passwdOutward = encryptFlow.passwdOutward
- }
- if (encryptType === 'aesctr') {
- console.log('@@AesCTR', encryptType, fileSize)
- encryptFlow = new AesCTR(password, fileSize)
- this.passwdOutward = encryptFlow.passwdOutward
- }
- if (encryptType === null) {
- throw new Error('FlowEnc error')
- }
- cachePasswdOutward[password + encryptType] = this.passwdOutward
- this.encryptFlow = encryptFlow
- this.encryptType = encryptType
- }
-
- async setPosition(position) {
- await this.encryptFlow.setPositionAsync(position)
- }
-
- // 加密流转换
- encryptTransform() {
- return this.encryptFlow.encryptTransform()
- }
-
- decryptTransform() {
- return this.encryptFlow.decryptTransform()
- }
-}
-
-FlowEnc.getPassWdOutward = function (password, encryptType) {
- const passwdOutward = cachePasswdOutward[password + encryptType]
- if (passwdOutward) {
- return passwdOutward
- }
- const flowEnc = new FlowEnc(password, encryptType, 1)
- return flowEnc.passwdOutward
-}
-
-// const flowEnc = new FlowEnc('abc1234')
-// const encode = flowEnc.encodeData('测试的明文加密1234¥%#')
-// const decode = flowEnc.decodeData(encode)
-// console.log('@@@decode', encode, decode.toString())
-// console.log(new FlowEnc('e10adc3949ba56abbe5be95ff90a8636'))
-
-export default FlowEnc
diff --git a/node-proxy/src/utils/flowEnc.ts b/node-proxy/src/utils/flowEnc.ts
new file mode 100644
index 0000000..733830b
--- /dev/null
+++ b/node-proxy/src/utils/flowEnc.ts
@@ -0,0 +1,64 @@
+import { Transform } from 'stream'
+
+import MixEnc from '@/utils/crypto/mixEnc'
+import Rc4Md5 from '@/utils/crypto/rc4Md5'
+import AesCTR from '@/utils/crypto/aesCTR'
+import { logger } from '@/common/logger'
+
+const cachePasswdOutward = {}
+
+class FlowEnc {
+ public passwdOutward: string
+ public encryptType: EncryptType
+ public encryptFlow: EncryptFlow
+
+ constructor(password: string, encryptType: EncryptType = 'mix', fileSize = 0) {
+ switch (encryptType) {
+ case 'mix':
+ logger.info('加解密算法: ', encryptType)
+ this.encryptFlow = new MixEnc(password)
+ break
+ case 'rc4':
+ logger.info('加解密算法: ', encryptType, fileSize)
+ this.encryptFlow = new Rc4Md5(password, fileSize.toString())
+ break
+ case 'aesctr':
+ logger.info('加解密算法: ', encryptType, fileSize)
+ this.encryptFlow = new AesCTR(password, fileSize.toString())
+ break
+ }
+
+ if (!this.encryptFlow) {
+ throw new Error('FlowEnc error')
+ }
+
+ this.encryptType = encryptType
+ this.passwdOutward = this.encryptFlow.passwdOutward
+ cachePasswdOutward[password + encryptType] = this.passwdOutward
+ }
+
+ async setPosition(position: number) {
+ await this.encryptFlow.setPositionAsync(position)
+ }
+
+ // 加密流转换
+ encryptTransform(): Transform {
+ return this.encryptFlow.encryptTransform()
+ }
+
+ decryptTransform(): Transform {
+ return this.encryptFlow.decryptTransform()
+ }
+}
+
+export const getPassWdOutward = function (password: string, encryptType: EncryptType) {
+ let passwdOutward = cachePasswdOutward[password + encryptType]
+ if (!passwdOutward) {
+ const flowEnc = new FlowEnc(password, encryptType, 1)
+ passwdOutward = flowEnc.passwdOutward
+ }
+
+ return passwdOutward
+}
+
+export default FlowEnc
diff --git a/node-proxy/src/utils/httpClient.js b/node-proxy/src/utils/httpClient.ts
similarity index 62%
rename from node-proxy/src/utils/httpClient.js
rename to node-proxy/src/utils/httpClient.ts
index e62b0fd..04c0655 100644
--- a/node-proxy/src/utils/httpClient.js
+++ b/node-proxy/src/utils/httpClient.ts
@@ -1,21 +1,101 @@
+import path from 'path'
import http from 'http'
import https from 'node:https'
+import { Transform } from 'stream'
import crypto, { randomUUID } from 'crypto'
-import levelDB from './levelDB'
-import path from 'path'
-import { decodeName } from './commonUtil'
-// import { pathExec } from './commonUtil'
-const Agent = http.Agent
-const Agents = https.Agent
+
+import { flat } from '@/utils/common'
+import levelDB from '@/dao/levelDB'
+import { decodeName } from '@/utils/cryptoUtil'
+import { logger } from '@/common/logger'
// 默认maxFreeSockets=256
-const httpsAgent = new Agents({ keepAlive: true })
-const httpAgent = new Agent({ keepAlive: true })
+const httpsAgent = new https.Agent({ keepAlive: true })
+const httpAgent = new http.Agent({ keepAlive: true })
+
+export async function httpClient({
+ urlAddr,
+ reqBody,
+ request,
+ response,
+}: {
+ urlAddr: string
+ reqBody?: string
+ request: http.IncomingMessage
+ response?: http.ServerResponse
+}): Promise {
+ const { method, headers, url } = request
+ logger.info('代理请求发起: ', method, urlAddr)
+ logger.trace('http请求头: ', headers)
+ // 创建请求
+ const httpRequest = ~urlAddr.indexOf('https') ? https : http
+ const options = {
+ method,
+ headers,
+ agent: ~urlAddr.indexOf('https') ? httpsAgent : httpAgent,
+ rejectUnauthorized: false,
+ }
+
+ return new Promise((resolve) => {
+ // 处理重定向的请求,让下载的流量经过代理服务器
+ const httpReq = httpRequest.request(urlAddr, options, async (httpResp) => {
+ logger.info('http响应接收: 状态码', httpResp.statusCode)
+ logger.trace('http响应头:', httpResp.headers)
+ if (response) {
+ response.statusCode = httpResp.statusCode
+ for (const key in httpResp.headers) {
+ response.setHeader(key, httpResp.headers[key])
+ }
+ }
+
+ let result = ''
+ httpResp
+ .on('data', (chunk) => {
+ result += chunk
+ })
+ .on('end', () => {
+ resolve(result)
+ logger.info('代理请求结束结束: ', url)
+ })
+ })
+
+ httpReq.on('error', (err) => {
+ logger.error('http请求出错: ', err)
+ })
+
+ // check request type
+ if (!reqBody) {
+ url ? request.pipe(httpReq) : httpReq.end()
+ return
+ }
+
+ httpReq.write(reqBody)
+ httpReq.end()
+ })
+}
-export async function httpProxy(request, response, encryptTransform, decryptTransform) {
- const { method, headers, urlAddr, passwdInfo, url, fileSize } = request
+export async function httpFlowClient({
+ urlAddr,
+ passwdInfo,
+ fileSize,
+ request,
+ response,
+ encryptTransform,
+ decryptTransform,
+}: {
+ urlAddr: string
+ passwdInfo: PasswdInfo
+ fileSize: number
+ request: http.IncomingMessage
+ response?: http.ServerResponse
+ encryptTransform?: Transform
+ decryptTransform?: Transform
+}) {
+ const { method, headers, url } = request
const reqId = randomUUID()
- console.log('@@request_info: ', reqId, method, urlAddr, headers, !!encryptTransform, !!decryptTransform)
+ logger.info(`代理请求(${reqId})发起: `, method, urlAddr)
+ logger.info(`httpFlow(${reqId})加解密: `, `流加密${!!encryptTransform}`, `流解密${!!decryptTransform}`)
+ logger.trace(`httpFlow(${reqId})请求头: `, headers)
// 创建请求
const options = {
method,
@@ -24,10 +104,11 @@ export async function httpProxy(request, response, encryptTransform, decryptTran
rejectUnauthorized: false,
}
const httpRequest = ~urlAddr.indexOf('https') ? https : http
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
// 处理重定向的请求,让下载的流量经过代理服务器
const httpReq = httpRequest.request(urlAddr, options, async (httpResp) => {
- console.log('@@statusCode', reqId, httpResp.statusCode, httpResp.headers)
+ logger.info(`httpFlow(${reqId})响应接收: 状态码`, httpResp.statusCode)
+ logger.trace(`httpFlow(${reqId})响应头: `, httpResp.headers)
response.statusCode = httpResp.statusCode
if (response.statusCode % 300 < 5) {
// 可能出现304,redirectUrl = undefined
@@ -35,12 +116,12 @@ export async function httpProxy(request, response, encryptTransform, decryptTran
// 百度云盘不是https,坑爹,因为天翼云会多次302,所以这里要保持,跳转后的路径保持跟上次一致,经过本服务器代理就可以解密
if (decryptTransform && passwdInfo.enable) {
const key = crypto.randomUUID()
- console.log()
+
await levelDB.setExpire(key, { redirectUrl, passwdInfo, fileSize }, 60 * 60 * 72) // 缓存起来,默认3天,足够下载和观看了
// 、Referer
httpResp.headers.location = `/redirect/${key}?decode=1&lastUrl=${encodeURIComponent(url)}`
}
- console.log('302 redirectUrl:', redirectUrl)
+ logger.info(`httpFlow(${reqId})重定向到:`, redirectUrl)
} else if (httpResp.headers['content-range'] && httpResp.statusCode === 200) {
response.statusCode = 206
}
@@ -52,10 +133,12 @@ export async function httpProxy(request, response, encryptTransform, decryptTran
if (method === 'GET' && response.statusCode === 200 && passwdInfo && passwdInfo.enable && passwdInfo.encName) {
let fileName = decodeURIComponent(path.basename(url))
fileName = decodeName(passwdInfo.password, passwdInfo.encType, fileName.replace(path.extname(fileName), ''))
+
if (fileName) {
let cd = response.getHeader('content-disposition')
- cd = cd ? cd.replace(/filename\*?=[^=;]*;?/g, '') : ''
- console.log('解密文件名...', reqId, fileName)
+ cd = flat(cd)
+ cd = cd ? cd.toString().replace(/filename\*?=[^=;]*;?/g, '') : ''
+ logger.info(`httpFlow(${reqId})解密后文件名:`, fileName)
response.setHeader('content-disposition', cd + `filename*=UTF-8''${encodeURIComponent(fileName)};`)
}
}
@@ -65,7 +148,7 @@ export async function httpProxy(request, response, encryptTransform, decryptTran
resolve()
})
.on('close', () => {
- console.log('响应关闭...', reqId, urlAddr)
+ logger.info(`代理请求(${reqId})结束:`, urlAddr)
// response.destroy()
if (decryptTransform) decryptTransform.destroy()
})
@@ -73,59 +156,14 @@ export async function httpProxy(request, response, encryptTransform, decryptTran
decryptTransform ? httpResp.pipe(decryptTransform).pipe(response) : httpResp.pipe(response)
})
httpReq.on('error', (err) => {
- console.log('@@httpProxy request error ', reqId, err, urlAddr, headers)
+ logger.error(`httpFlow(${reqId})请求出错:`, urlAddr, err)
})
// 是否需要加密
encryptTransform ? request.pipe(encryptTransform).pipe(httpReq) : request.pipe(httpReq)
// 重定向的请求 关闭时 关闭被重定向的请求
response.on('close', () => {
- console.log('响应关闭...', reqId, url)
+ logger.trace(`响应(${reqId})关闭: `, url)
httpReq.destroy()
})
})
}
-
-export async function httpClient(request, response) {
- const { method, headers, urlAddr, reqBody, url } = request
- console.log('@@request_client: ', method, urlAddr, headers)
- // 创建请求
- const options = {
- method,
- headers,
- agent: ~urlAddr.indexOf('https') ? httpsAgent : httpAgent,
- rejectUnauthorized: false,
- }
- const httpRequest = ~urlAddr.indexOf('https') ? https : http
- return new Promise((resolve, reject) => {
- // 处理重定向的请求,让下载的流量经过代理服务器
- const httpReq = httpRequest.request(urlAddr, options, async (httpResp) => {
- console.log('@@statusCode', httpResp.statusCode, httpResp.headers)
- if (response) {
- response.statusCode = httpResp.statusCode
- for (const key in httpResp.headers) {
- response.setHeader(key, httpResp.headers[key])
- }
- }
- let result = ''
- httpResp
- .on('data', (chunk) => {
- result += chunk
- })
- .on('end', () => {
- resolve(result)
- console.log('httpResp响应结束...', url)
- })
- })
- httpReq.on('error', (err) => {
- console.log('@@httpClient request error ', err)
- })
- // check request type
- if (!reqBody) {
- url ? request.pipe(httpReq) : httpReq.end()
- return
- }
- // 发送请求
- typeof reqBody === 'string' ? httpReq.write(reqBody) : httpReq.write(JSON.stringify(reqBody))
- httpReq.end()
- })
-}
diff --git a/node-proxy/src/utils/levelDB.js b/node-proxy/src/utils/levelDB.js
deleted file mode 100644
index baf8db0..0000000
--- a/node-proxy/src/utils/levelDB.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import Datastore from 'nedb-promises'
-
-// let datastore = Datastore.create('/path/to/db.db')
-/**
- * 封装新方法
- */
-class Nedb {
- constructor(dbFile) {
- this.datastore = Datastore.create(dbFile)
- }
-
- async load() {
- await this.datastore.load()
- }
-
- // 新增过期设置
- async setValue(key, value) {
- await this.datastore.removeMany({ key })
- console.log('@@setValue', key, value )
- await this.datastore.insert({ key, expire: -1, value })
- }
-
- async setExpire(key, value, second = 6 * 10) {
- await this.datastore.removeMany({ key })
- const expire = Date.now() + second * 1000
- await this.datastore.insert({ key, expire, value })
- }
-
- async getValue(key) {
- try {
- const { expire, value } = await this.datastore.findOne({ key })
- // 没有限制时间
- if (expire < 0) {
- return value
- }
- if (expire && expire > Date.now()) {
- return value
- }
- } catch (e) {
- return null
- }
- // 删除key
- this.datastore.remove(key)
- return null
- }
-}
-
-const nedb = new Nedb(process.cwd() + '/conf/nedb/datafile')
-
-// 定时清除过期的数据
-setInterval(async () => {
- const allData = await nedb.datastore.find({})
- for (const data of allData) {
- const { key, expire } = data
- if (expire && expire > 0 && expire < Date.now()) {
- console.log('@@expire:', key, expire, Date.now())
- nedb.datastore.remove({ key })
- }
- }
-}, 30 * 1000)
-
-export default nedb
diff --git a/node-proxy/src/utils/mixBase64.js b/node-proxy/src/utils/mixBase64.js
deleted file mode 100644
index 5a5a035..0000000
--- a/node-proxy/src/utils/mixBase64.js
+++ /dev/null
@@ -1,162 +0,0 @@
-'use strict'
-import crypto from 'crypto'
-
-const source = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~+'
-
-// use sha1 str to init
-function initKSA(passwd) {
- let key = passwd
- if (typeof passwd === 'string') {
- key = crypto.createHash('sha256').update(passwd).digest()
- }
- const K = []
- // 对S表进行初始赋值
- const sbox = []
- const sourceKey = source.split('')
- // 对S表进行初始赋值
- for (let i = 0; i < source.length; i++) {
- sbox[i] = i
- }
- // 用种子密钥对K表进行填充
- for (let i = 0; i < source.length; i++) {
- K[i] = key[i % key.length]
- }
- // 对S表进行置换
- for (let i = 0, j = 0; i < source.length; i++) {
- j = (j + sbox[i] + K[i]) % source.length
- const temp = sbox[i]
- sbox[i] = sbox[j]
- sbox[j] = temp
- }
- let secret = ''
- for (const i of sbox) {
- secret += sourceKey[i]
- }
- return secret
-}
-
-// 自定义的base64加密
-function MixBase64(passwd, salt = 'mix64') {
- // 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
- const secret = passwd.length === 64 ? passwd : initKSA(passwd + salt)
- const chars = secret.split('')
- const mapChars = {}
- chars.forEach((e, index) => {
- mapChars[e] = index
- })
- this.chars = chars
- // 编码加密
- this.encode = function (bufferOrStr, encoding = 'utf-8') {
- const buffer = bufferOrStr instanceof Buffer ? bufferOrStr : Buffer.from(bufferOrStr, encoding)
- let result = ''
- let arr = []
- let bt = []
- let char
- for (let i = 0; i < buffer.length; i += 3) {
- if (i + 3 > buffer.length) {
- arr = buffer.subarray(i, buffer.length)
- break
- }
- bt = buffer.subarray(i, i + 3)
- char = chars[bt[0] >> 2] + chars[((bt[0] & 3) << 4) | (bt[1] >> 4)] + chars[((bt[1] & 15) << 2) | (bt[2] >> 6)] + chars[bt[2] & 63]
- result += char
- }
- if (buffer.length % 3 === 1) {
- char = chars[arr[0] >> 2] + chars[(arr[0] & 3) << 4] + chars[64] + chars[64]
- result += char
- } else if (buffer.length % 3 === 2) {
- char = chars[arr[0] >> 2] + chars[((arr[0] & 3) << 4) | (arr[1] >> 4)] + chars[(arr[1] & 15) << 2] + chars[64]
- result += char
- }
- return result
- }
- // 编码解密
- this.decode = function (base64Str) {
- let size = (base64Str.length / 4) * 3
- let j = 0
- if (~base64Str.indexOf(chars[64] + '' + chars[64])) {
- size -= 2
- } else if (~base64Str.indexOf(chars[64])) {
- size -= 1
- }
- const buffer = Buffer.alloc(size)
- let enc1
- let enc2
- let enc3
- let enc4
- let i = 0
- while (i < base64Str.length) {
- enc1 = mapChars[base64Str.charAt(i++)]
- enc2 = mapChars[base64Str.charAt(i++)]
- enc3 = mapChars[base64Str.charAt(i++)]
- enc4 = mapChars[base64Str.charAt(i++)]
- buffer.writeUInt8((enc1 << 2) | (enc2 >> 4), j++)
- if (enc3 !== 64) {
- buffer.writeUInt8(((enc2 & 15) << 4) | (enc3 >> 2), j++)
- }
- if (enc4 !== 64) {
- buffer.writeUInt8(((enc3 & 3) << 6) | enc4, j++)
- }
- }
- return buffer
- }
-}
-
-MixBase64.sourceChars = source.split('')
-
-// plaintext check bit
-MixBase64.getCheckBit = function (text) {
- const bufferArr = Buffer.from(text)
- let count = 0
- for (const num of bufferArr) {
- count += num
- }
- count %= 64
- return MixBase64.sourceChars[count]
-}
-
-MixBase64.randomSecret = function () {
- // 不能使用 = 号,url穿参数不支持
- const source = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~+'
- const chars = source.split('')
- const newChars = []
- while (chars.length > 0) {
- const index = Math.floor(Math.random() * chars.length)
- newChars.push(chars[index])
- chars.splice(index, 1)
- }
- return newChars.join('')
-}
-
-MixBase64.randomStr = function (length) {
- // 不能使用 = 号,url穿参数不支持
- const chars = source.split('')
- const newChars = []
- while (length-- > 0) {
- const index = Math.floor(Math.random() * chars.length)
- newChars.push(chars[index])
- chars.splice(index, 1)
- }
- return newChars.join('')
-}
-
-MixBase64.getSourceChar = function (index) {
- // 不能使用 = 号,url穿参数不支持
- return source.split('')[index]
-}
-
-MixBase64.initKSA = initKSA
-
-export default MixBase64
-// Buffer.from(str, 'base64').toString('base64') === str
-
-// function test() {
-// const secret = '123456'
-// const mybase64 = new MixBase64(secret)
-// const test = 'test123456'
-// const encodeStr = mybase64.encode(test)
-// const bufferData = mybase64.decode(encodeStr)
-// const encodeFromBuffer = mybase64.encode(bufferData)
-// console.log(encodeStr, encodeFromBuffer, bufferData.toString())
-// }
-// test()
diff --git a/node-proxy/src/utils/webdavClient.js b/node-proxy/src/utils/webdavClient.js
deleted file mode 100644
index 4413c7b..0000000
--- a/node-proxy/src/utils/webdavClient.js
+++ /dev/null
@@ -1,30 +0,0 @@
-'use strict'
-
-import { httpClient } from './httpClient'
-import { XMLParser } from 'fast-xml-parser'
-
-// get file info from webdav
-export async function getWebdavFileInfo(urlAddr, authorization) {
- const request = {
- method: 'PROPFIND',
- headers: {
- depth: 1,
- authorization,
- },
- urlAddr,
- }
- const parser = new XMLParser({ removeNSPrefix: true })
- try {
- const XMLdata = await httpClient(request)
- const respBody = parser.parse(XMLdata)
- const res = respBody.multistatus.response
- const filePath = res.href
- const size = res.propstat.prop.getcontentlength || 0
- const name = res.propstat.prop.displayname
- const isDir = size === 0
- return { size, name, is_dir: isDir, path: filePath }
- } catch (e) {
- console.log('@@webdavFileInfo_error:', urlAddr)
- }
- return null
-}
diff --git a/node-proxy/src/utils/webdavClient.ts b/node-proxy/src/utils/webdavClient.ts
new file mode 100644
index 0000000..e34a34f
--- /dev/null
+++ b/node-proxy/src/utils/webdavClient.ts
@@ -0,0 +1,38 @@
+import http from 'http'
+
+import { XMLParser } from 'fast-xml-parser'
+
+import { httpClient } from '@/utils/httpClient'
+import { logger } from '@/common/logger'
+
+// get file info from webdav
+export async function getWebdavFileInfo(urlAddr: string, authorization: string): Promise {
+ //eslint-disable-next-line
+ //@ts-ignore
+ const request: http.IncomingMessage = {
+ method: 'PROPFIND',
+ headers: {
+ depth: '1',
+ authorization,
+ },
+ }
+
+ const parser = new XMLParser({ removeNSPrefix: true })
+
+ try {
+ const XMLData = await httpClient({
+ urlAddr,
+ request,
+ })
+ const respBody = parser.parse(XMLData)
+ const res = respBody.multistatus.response
+
+ const size = Number(res.propstat.prop.getcontentlength || 0)
+ const name = res.propstat.prop.displayname
+
+ return { size, name, is_dir: size === 0, path: res.href }
+ } catch (e) {
+ logger.error('@@webdavFileInfo_error:', urlAddr)
+ }
+ return null
+}
diff --git a/node-proxy/test/chachaTest.js b/node-proxy/test/chachaTest.js
deleted file mode 100644
index 95a6d76..0000000
--- a/node-proxy/test/chachaTest.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import crypto from 'crypto'
-import ChaCha20 from '.@/utils/chaCha20'
-import Rc4 from '.@/utils/rc4Md5'
-import ChaCha20Poly from '.@/utils/chaCha20Poly'
-import AesCTR from '.@/utils/aesCTR'
-
-let decrypted = null
-const rc4System = crypto.createCipheriv('rc4', 'MY SECRET KEY', '')
-const decipher = crypto.createDecipheriv('rc4', 'MY SECRET KEY', '')
-decrypted = rc4System.update(Buffer.from('test rc4 encrypt'), 'binary', 'hex')
-// decrypted += encipher.final('hex')
-console.log('@@@@', decrypted, decipher.update(decrypted, 'hex', 'utf8'))
-
-const iv = crypto.randomBytes(12)
-const keyenc = crypto.randomBytes(32)
-
-const keyaes = crypto.randomBytes(16)
-const ivaes = crypto.randomBytes(16)
-const chaCha20System = new ChaCha20Poly(keyenc, iv)
-const chaCha20Local = new ChaCha20(keyenc, iv)
-const rc4Local = new Rc4('1234', 33)
-const aseCTRSystem = new AesCTR(keyaes, ivaes)
-
-const textPlain = `test测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度
- 测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度
- 测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度
- 测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度
- 测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度
- 测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度
- 测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试性能速度-测试-`
-
-const textBuf = Buffer.from(textPlain)
-console.log('@textBuf.length', textBuf.length)
-
-const startDate = Date.now()
-for (let i = 0; i < 1; i++) {
- // chaCha20Local.encrypt(textBuf)
- // rc4Local.encrypt(textBuf)
- // chaCha20System.encChaPoly(textBuf)
- // aseCTRSystem.encrypt(textBuf)
-}
-const rc4BUf = Buffer.from(textPlain)
-rc4Local.setPosition(0)
-const encdata = rc4Local.encrypt(rc4BUf)
-rc4Local.setPosition(0)
-console.log('encd--------ata rc4BUf', rc4BUf.length)
-const position = 124
-rc4Local.setPosition(position)
-// rc4Local.encrypt(encdata.subarray(0, position))
-//
-
-const startPosition = Date.now()
-// chaCha20Local.setPosition(12345678)
-// rc4Local.setPosition(123456788)
-// console.log('startPosition time: ' + (Date.now() - startPosition))
-
-rc4Local.setPositionAsync(position).then((res) => {
- console.log('@@@encdata 33: ', rc4Local.encrypt(encdata.subarray(position, rc4BUf.length)).toString('utf-8'))
-
- // console.log('rc4 startPosition async time: ' + (Date.now() - startPosition))
-})
-
-console.log('test time: ' + (Date.now() - startDate))
diff --git a/node-proxy/tsconfig.json b/node-proxy/tsconfig.json
index 18c5377..248ce3b 100644
--- a/node-proxy/tsconfig.json
+++ b/node-proxy/tsconfig.json
@@ -1,16 +1,24 @@
{
"compilerOptions": {
"module": "NodeNext",
- "target": "ES5",
- "types": ["./src/@types"],
+ "target": "ESNext",
+ "types": [
+ "./src/@types"
+ ],
"baseUrl": ".",
- "paths": { "@/*": ["src/*"] },
- "allowSyntheticDefaultImports": true,
+ "paths": {
+ "@/*": [
+ "src/*"
+ ]
+ },
// "allowImportingTsExtensions": true,
- "allowJs": true,
+ "allowJs": false,
"outDir": "./dist"
},
- "exclude": ["node_modules", "**/node_modules/*", "dist"],
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.d.ts"
+ ],
"ts-node": {
"compilerOptions": {
"module": "NodeNext"
diff --git a/node-proxy/webdavTest.js b/node-proxy/webdavTest.js
deleted file mode 100644
index f1ade68..0000000
--- a/node-proxy/webdavTest.js
+++ /dev/null
@@ -1,6 +0,0 @@
-const url = 'http://192.168.8.21:5344/dav/aliyun'
-const client = {
- username: 'admin',
- password: 'YiuNH7ly',
-}
-console.log(url, client)
diff --git a/node-proxy/webpack.config.ts b/node-proxy/webpack.config.ts
index 88192a2..5b5932b 100644
--- a/node-proxy/webpack.config.ts
+++ b/node-proxy/webpack.config.ts
@@ -10,7 +10,7 @@ const output = {
}
export default () => {
return {
- entry: { index: path.resolve('./app.js'), PRGAThreadCom: path.resolve('./src/utils/PRGAThreadCom.js') },
+ entry: { index: path.resolve('./app.ts'), PRGAThreadCom: path.resolve('./src/utils/PRGAThreadCom.js') },
output,
module: {
rules: [