diff --git a/functions/api/manage/batch/list.js b/functions/api/manage/batch/list.js index 18e7fabd..dbc9fdd4 100644 --- a/functions/api/manage/batch/list.js +++ b/functions/api/manage/batch/list.js @@ -6,6 +6,7 @@ */ import { getDatabase } from '../../../utils/databaseAdapter.js'; +import { sanitizeFileMetadata } from '../../../utils/metadataSecurity.js'; // CORS 跨域响应头 const corsHeaders = { @@ -124,7 +125,7 @@ export async function onRequestGet(context) { // 构建记录对象 const record = { id: item.name, - metadata: item.metadata, + metadata: sanitizeFileMetadata(item.metadata), }; // 如果需要包含 value 且是分块文件,读取 value diff --git a/functions/api/manage/cusConfig/files.js b/functions/api/manage/cusConfig/files.js index 9d3ad7bc..dbe4d6ff 100644 --- a/functions/api/manage/cusConfig/files.js +++ b/functions/api/manage/cusConfig/files.js @@ -1,4 +1,5 @@ import { readIndex } from "../../../utils/indexManager"; +import { sanitizeFileMetadata } from "../../../utils/metadataSecurity.js"; export async function onRequest(context) { const { request } = context; @@ -18,7 +19,12 @@ export async function onRequest(context) { count = Math.max(1, count); const allRecords = await readIndex(context, { count: -1, includeSubdirFiles: true }); - const files = allRecords.files.filter(item => item.metadata?.UploadIP === ip); + const files = allRecords.files + .filter(item => item.metadata?.UploadIP === ip) + .map(item => ({ + ...item, + metadata: sanitizeFileMetadata(item.metadata) + })); return new Response(JSON.stringify({ data: files.slice(start, start + count), diff --git a/functions/api/manage/delete/[[path]].js b/functions/api/manage/delete/[[path]].js index 7df6c7fb..cb1403e9 100644 --- a/functions/api/manage/delete/[[path]].js +++ b/functions/api/manage/delete/[[path]].js @@ -6,6 +6,11 @@ import { DiscordAPI } from '../../../utils/storage/discordAPI.js'; import { HuggingFaceAPI } from '../../../utils/storage/huggingfaceAPI.js'; import { WebDAVAPI } from '../../../utils/storage/webdavAPI.js'; import { resolveWebDAVConfig } from '../../../utils/webdavConfig.js'; +import { + resolveDiscordCredentials, + resolveHuggingFaceCredentials, + resolveS3Credentials, +} from '../../../utils/channelCredentials.js'; // CORS 跨域响应头 const corsHeaders = { @@ -145,17 +150,17 @@ async function deleteFile(env, fileId, cdnUrl, url) { // S3 渠道的图片,需要删除S3中对应的图片 if (img.metadata?.Channel === 'S3') { - await deleteS3File(img); + await deleteS3File(env, img); } // Discord 渠道的图片,需要删除 Discord 中对应的消息 if (img.metadata?.Channel === 'Discord') { - await deleteDiscordFile(img); + await deleteDiscordFile(env, img); } // HuggingFace 渠道的图片,需要删除 HuggingFace 中对应的文件 if (img.metadata?.Channel === 'HuggingFace') { - await deleteHuggingFaceFile(img); + await deleteHuggingFaceFile(env, img); } // WebDAV 渠道的图片,需要删除 WebDAV 中对应的文件 @@ -183,19 +188,21 @@ async function deleteFile(env, fileId, cdnUrl, url) { } // 删除 S3 渠道的图片 -async function deleteS3File(img) { +async function deleteS3File(env, img) { + const db = getDatabase(env); + const s3Credentials = await resolveS3Credentials(db, env, img.metadata); const s3Client = new S3Client({ - region: img.metadata?.S3Region || "auto", - endpoint: img.metadata?.S3Endpoint, + region: s3Credentials.region || "auto", + endpoint: s3Credentials.endpoint, credentials: { - accessKeyId: img.metadata?.S3AccessKeyId, - secretAccessKey: img.metadata?.S3SecretAccessKey + accessKeyId: s3Credentials.accessKeyId, + secretAccessKey: s3Credentials.secretAccessKey }, - forcePathStyle: img.metadata?.S3PathStyle || false // 是否启用路径风格 + forcePathStyle: s3Credentials.pathStyle || false // 是否启用路径风格 }); - const bucketName = img.metadata?.S3BucketName; - const key = img.metadata?.S3FileKey; + const bucketName = s3Credentials.bucketName; + const key = s3Credentials.key; try { await s3Client.send(new DeleteObjectCommand({ @@ -210,10 +217,12 @@ async function deleteS3File(img) { } // 删除 Discord 渠道的图片(删除 Discord 消息) -async function deleteDiscordFile(img) { - const botToken = img.metadata?.DiscordBotToken; - const channelId = img.metadata?.DiscordChannelId; - const messageId = img.metadata?.DiscordMessageId; +async function deleteDiscordFile(env, img) { + const db = getDatabase(env); + const discordCredentials = await resolveDiscordCredentials(db, env, img.metadata); + const botToken = discordCredentials.botToken; + const channelId = discordCredentials.channelId; + const messageId = discordCredentials.messageId; if (!botToken || !channelId || !messageId) { console.warn('Discord file missing required metadata for deletion'); @@ -235,11 +244,13 @@ async function deleteDiscordFile(img) { // 删除 HuggingFace 渠道的图片 -async function deleteHuggingFaceFile(img) { - const token = img.metadata?.HfToken; - const repo = img.metadata?.HfRepo; - const filePath = img.metadata?.HfFilePath; - const isPrivate = img.metadata?.HfIsPrivate || false; +async function deleteHuggingFaceFile(env, img) { + const db = getDatabase(env); + const hfCredentials = await resolveHuggingFaceCredentials(db, env, img.metadata); + const token = hfCredentials.token; + const repo = hfCredentials.repo; + const filePath = hfCredentials.filePath; + const isPrivate = hfCredentials.isPrivate || false; if (!token || !repo || !filePath) { console.warn('HuggingFace file missing required metadata for deletion'); diff --git a/functions/api/manage/list.js b/functions/api/manage/list.js index 242876c1..3e2aed36 100644 --- a/functions/api/manage/list.js +++ b/functions/api/manage/list.js @@ -3,6 +3,7 @@ import { getIndexInfo, getIndexStorageStats } from '../../utils/indexManager.js'; import { getDatabase } from '../../utils/databaseAdapter.js'; +import { sanitizeFileMetadata } from '../../utils/metadataSecurity.js'; // CORS 跨域响应头 const corsHeaders = { @@ -174,10 +175,7 @@ export async function onRequest(context) { } // 转换文件格式 - const compatibleFiles = result.files.map(file => ({ - name: file.id, - metadata: file.metadata - })); + const compatibleFiles = result.files.map(serializeFileRecordForManagement); return new Response(JSON.stringify({ files: compatibleFiles, @@ -237,7 +235,7 @@ async function getAllFileRecords(env, dir) { continue; } - allRecords.push(item); + allRecords.push(serializeFileRecordForManagement(item)); } if (!cursor) break; @@ -281,3 +279,10 @@ async function getAllFileRecords(env, dir) { }; } } + +export function serializeFileRecordForManagement(file) { + return { + name: file.id || file.name, + metadata: sanitizeFileMetadata(file.metadata) + }; +} diff --git a/functions/api/manage/metadata/[[path]].js b/functions/api/manage/metadata/[[path]].js index ca03308e..d4e63404 100644 --- a/functions/api/manage/metadata/[[path]].js +++ b/functions/api/manage/metadata/[[path]].js @@ -1,5 +1,6 @@ import { addFileToIndex } from '../../../utils/indexManager.js'; import { getDatabase } from '../../../utils/databaseAdapter.js'; +import { sanitizeFileMetadata } from '../../../utils/metadataSecurity.js'; // CORS 跨域响应头 const corsHeaders = { @@ -102,7 +103,7 @@ export async function onRequest(context) { return new Response(JSON.stringify({ success: true, - metadata: updatedMetadata, + metadata: sanitizeFileMetadata(updatedMetadata), }), { status: 200, headers: { 'Content-Type': 'application/json', ...corsHeaders }, diff --git a/functions/api/manage/move/[[path]].js b/functions/api/manage/move/[[path]].js index 2b8a0b58..2a8eeaa2 100644 --- a/functions/api/manage/move/[[path]].js +++ b/functions/api/manage/move/[[path]].js @@ -5,6 +5,13 @@ import { getDatabase } from '../../../utils/databaseAdapter.js'; import { sanitizeUploadFolder } from "../../../upload/uploadTools.js"; import { WebDAVAPI } from "../../../utils/storage/webdavAPI.js"; import { resolveWebDAVConfig } from "../../../utils/webdavConfig.js"; +import { + resolveDiscordCredentials, + resolveHuggingFaceCredentials, + resolveS3Credentials, + resolveTelegramCredentials, +} from "../../../utils/channelCredentials.js"; +import { stripSensitiveMetadata } from "../../../utils/metadataSecurity.js"; export async function onRequest(context) { const { request, env, params, waitUntil } = context; @@ -151,13 +158,16 @@ async function moveFile(env, fileId, newFileId, cdnUrl, url) { // S3 渠道的图片,需要移动S3中对应的图片 if (img.metadata?.Channel === 'S3') { - const { success, newKey, error } = await moveS3File(img, newFileId); + const { success, newKey, endpoint, bucketName, source, error } = await moveS3File(env, img, newFileId); if (success) { // 更新 metadata img.metadata.S3FileKey = newFileId; - const s3ServerDomain = img.metadata.S3Endpoint.replace(/https?:\/\//, ""); - img.metadata.S3Location = `https://${img.metadata.S3BucketName}.${s3ServerDomain}/${newKey}`; + const s3ServerDomain = endpoint.replace(/https?:\/\//, ""); + img.metadata.S3Location = `https://${bucketName}.${s3ServerDomain}/${newKey}`; + if (source === 'config') { + img.metadata = stripSensitiveMetadata(img.metadata); + } } else { // do nothing } @@ -188,6 +198,7 @@ async function moveFile(env, fileId, newFileId, cdnUrl, url) { // 更新文件夹信息,根目录为空,否则为 aaa/123/ 的格式 const DirectoryPath = newFileId.split('/').slice(0, -1).join('/') === '' ? '' : newFileId.split('/').slice(0, -1).join('/') + '/'; img.metadata.Directory = DirectoryPath; + img.metadata = await stripMetadataAfterConfigResolution(db, env, img.metadata); // 更新KV存储 await db.put(newFileId, img.value, { metadata: img.metadata }); @@ -209,20 +220,39 @@ async function moveFile(env, fileId, newFileId, cdnUrl, url) { } } +async function stripMetadataAfterConfigResolution(db, env, metadata) { + let credentials = null; + if (metadata?.Channel === 'TelegramNew') { + credentials = await resolveTelegramCredentials(db, env, metadata); + } else if (metadata?.Channel === 'Discord') { + credentials = await resolveDiscordCredentials(db, env, metadata); + } else if (metadata?.Channel === 'HuggingFace') { + credentials = await resolveHuggingFaceCredentials(db, env, metadata); + } else if (metadata?.Channel === 'WebDAV') { + credentials = await resolveWebDAVConfig(env, metadata); + } + + return credentials?.source === 'config' + ? stripSensitiveMetadata(metadata) + : metadata; +} + // 移动 S3 渠道的图片 -async function moveS3File(img, newFileId) { +async function moveS3File(env, img, newFileId) { + const db = getDatabase(env); + const s3Credentials = await resolveS3Credentials(db, env, img.metadata); const s3Client = new S3Client({ - region: img.metadata?.S3Region || "auto", - endpoint: img.metadata?.S3Endpoint, + region: s3Credentials.region || "auto", + endpoint: s3Credentials.endpoint, credentials: { - accessKeyId: img.metadata?.S3AccessKeyId, - secretAccessKey: img.metadata?.S3SecretAccessKey + accessKeyId: s3Credentials.accessKeyId, + secretAccessKey: s3Credentials.secretAccessKey }, - forcePathStyle: img.metadata?.S3PathStyle || false // 是否启用路径风格 + forcePathStyle: s3Credentials.pathStyle || false // 是否启用路径风格 }); - const bucketName = img.metadata?.S3BucketName; - const oldKey = img.metadata?.S3FileKey; + const bucketName = s3Credentials.bucketName; + const oldKey = s3Credentials.key; const newKey = newFileId; try { @@ -240,7 +270,13 @@ async function moveS3File(img, newFileId) { })); // 返回新的 S3 文件信息 - return { success: true, newKey }; + return { + success: true, + newKey, + endpoint: s3Credentials.endpoint, + bucketName, + source: s3Credentials.source + }; } catch (error) { console.error("S3 Move Failed:", error); return { success: false, error: error.message }; diff --git a/functions/api/manage/rename/[[path]].js b/functions/api/manage/rename/[[path]].js index 81488ba8..26c9cf48 100644 --- a/functions/api/manage/rename/[[path]].js +++ b/functions/api/manage/rename/[[path]].js @@ -5,6 +5,16 @@ import { getDatabase } from '../../../utils/databaseAdapter.js'; import { sanitizeUploadFolder } from "../../../upload/uploadTools.js"; import { WebDAVAPI } from "../../../utils/storage/webdavAPI.js"; import { resolveWebDAVConfig } from "../../../utils/webdavConfig.js"; +import { + resolveDiscordCredentials, + resolveHuggingFaceCredentials, + resolveS3Credentials, + resolveTelegramCredentials, +} from "../../../utils/channelCredentials.js"; +import { + sanitizeFileMetadata, + stripSensitiveMetadataInPlace, +} from "../../../utils/metadataSecurity.js"; // CORS 跨域响应头 const corsHeaders = { @@ -128,13 +138,16 @@ export async function onRequest(context) { // S3 渠道的图片,需要移动 S3 中对应的图片 if (metadata?.Channel === 'S3') { - const { success, newKey, error } = await moveS3File(fileData, newFileId); + const { success, newKey, endpoint, bucketName, source, error } = await moveS3File(env, fileData, newFileId); if (success) { // 更新 metadata metadata.S3FileKey = newFileId; - const s3ServerDomain = metadata.S3Endpoint.replace(/https?:\/\//, ""); - metadata.S3Location = `https://${metadata.S3BucketName}.${s3ServerDomain}/${newKey}`; + const s3ServerDomain = endpoint.replace(/https?:\/\//, ""); + metadata.S3Location = `https://${bucketName}.${s3ServerDomain}/${newKey}`; + if (source === 'config') { + stripSensitiveMetadataInPlace(metadata); + } } else { // do nothing } @@ -171,6 +184,7 @@ export async function onRequest(context) { // 更新文件夹信息,根目录为空,否则为 aaa/123/ 的格式 const DirectoryPath = newFileId.split('/').slice(0, -1).join('/') === '' ? '' : newFileId.split('/').slice(0, -1).join('/') + '/'; metadata.Directory = DirectoryPath; + await stripMetadataInPlaceAfterConfigResolution(db, env, metadata); // 更新 KV 存储 await db.put(newFileId, fileData.value, { metadata }); @@ -192,7 +206,7 @@ export async function onRequest(context) { return new Response(JSON.stringify({ success: true, newFileId, - metadata, + metadata: sanitizeFileMetadata(metadata), }), { status: 200, headers: { 'Content-Type': 'application/json', ...corsHeaders }, @@ -210,20 +224,41 @@ export async function onRequest(context) { } } +async function stripMetadataInPlaceAfterConfigResolution(db, env, metadata) { + let credentials = null; + if (metadata?.Channel === 'TelegramNew') { + credentials = await resolveTelegramCredentials(db, env, metadata); + } else if (metadata?.Channel === 'Discord') { + credentials = await resolveDiscordCredentials(db, env, metadata); + } else if (metadata?.Channel === 'HuggingFace') { + credentials = await resolveHuggingFaceCredentials(db, env, metadata); + } else if (metadata?.Channel === 'WebDAV') { + credentials = await resolveWebDAVConfig(env, metadata); + } + + if (credentials?.source !== 'config') { + return; + } + + stripSensitiveMetadataInPlace(metadata); +} + // 移动 S3 渠道的图片 -async function moveS3File(img, newFileId) { +async function moveS3File(env, img, newFileId) { + const db = getDatabase(env); + const s3Credentials = await resolveS3Credentials(db, env, img.metadata); const s3Client = new S3Client({ - region: img.metadata?.S3Region || "auto", - endpoint: img.metadata?.S3Endpoint, + region: s3Credentials.region || "auto", + endpoint: s3Credentials.endpoint, credentials: { - accessKeyId: img.metadata?.S3AccessKeyId, - secretAccessKey: img.metadata?.S3SecretAccessKey + accessKeyId: s3Credentials.accessKeyId, + secretAccessKey: s3Credentials.secretAccessKey }, - forcePathStyle: img.metadata?.S3PathStyle || false + forcePathStyle: s3Credentials.pathStyle || false }); - const bucketName = img.metadata?.S3BucketName; - const oldKey = img.metadata?.S3FileKey; + const bucketName = s3Credentials.bucketName; + const oldKey = s3Credentials.key; const newKey = newFileId; try { @@ -241,7 +276,13 @@ async function moveS3File(img, newFileId) { })); // 返回新的 S3 文件信息 - return { success: true, newKey }; + return { + success: true, + newKey, + endpoint: s3Credentials.endpoint, + bucketName, + source: s3Credentials.source + }; } catch (error) { console.error("S3 Move Failed:", error); return { success: false, error: error.message }; diff --git a/functions/file/[[path]].js b/functions/file/[[path]].js index 9bfa0529..bd4531c1 100644 --- a/functions/file/[[path]].js +++ b/functions/file/[[path]].js @@ -11,6 +11,12 @@ import { } from './fileTools'; import { getDatabase } from '../utils/databaseAdapter.js'; import { authenticate, AUTH_SCOPE } from '../utils/auth/authCore.js'; +import { + resolveDiscordCredentials, + resolveHuggingFaceCredentials, + resolveS3Credentials, + resolveTelegramCredentials, +} from '../utils/channelCredentials.js'; export async function onRequest(context) { // Contents of context object @@ -130,8 +136,9 @@ export async function onRequest(context) { // Contents of context object } // 获取TG图片真实地址(支持代理域名) - const TgBotToken = imgRecord.metadata?.TgBotToken || env.TG_BOT_TOKEN; - const TgProxyUrl = imgRecord.metadata?.TgProxyUrl || ''; + const tgCredentials = await resolveTelegramCredentials(db, env, imgRecord.metadata); + const TgBotToken = tgCredentials.botToken; + const TgProxyUrl = tgCredentials.proxyUrl || ''; const tgApi = new TelegramAPI(TgBotToken, TgProxyUrl); const filePath = await tgApi.getFilePath(TgFileID); if (filePath === null) { @@ -205,8 +212,10 @@ async function handleTelegramChunkedFile(context, imgRecord, encodedFileName, fi const { env, request, url, Referer } = context; const metadata = imgRecord.metadata; - const TgBotToken = metadata.TgBotToken || env.TG_BOT_TOKEN; - const TgProxyUrl = metadata.TgProxyUrl || ''; + const db = getDatabase(env); + const tgCredentials = await resolveTelegramCredentials(db, env, metadata); + const TgBotToken = tgCredentials.botToken; + const TgProxyUrl = tgCredentials.proxyUrl || ''; // 从KV的value中读取分片信息 let chunks = []; @@ -393,11 +402,14 @@ async function fetchTelegramChunkWithRetry(botToken, chunk, proxyUrl = '', maxRe // 处理 Discord 渠道分片文件读取 async function handleDiscordChunkedFile(context, imgRecord, encodedFileName, fileType) { - const { request, url, Referer } = context; + const { env, request, url, Referer } = context; const metadata = imgRecord.metadata; - const botToken = metadata.DiscordBotToken; - const proxyUrl = metadata.DiscordProxyUrl; + const db = getDatabase(env); + const discordCredentials = await resolveDiscordCredentials(db, env, metadata); + const botToken = discordCredentials.botToken; + const channelId = discordCredentials.channelId; + const proxyUrl = discordCredentials.proxyUrl; // 从KV的value中读取分片信息 let chunks = []; @@ -495,7 +507,7 @@ async function handleDiscordChunkedFile(context, imgRecord, encodedFileName, fil } // 获取分片数据(每次通过 API 获取新的附件 URL) - const chunkData = await fetchDiscordChunkWithRetry(botToken, metadata.DiscordChannelId, chunk, proxyUrl, 3); + const chunkData = await fetchDiscordChunkWithRetry(botToken, channelId, chunk, proxyUrl, 3); if (!chunkData) { throw new Error(`Failed to fetch Discord chunk ${chunk.index} after retries`); } @@ -735,20 +747,22 @@ async function handleS3File(context, metadata, encodedFileName, fileType) { // 通过 S3 API 读取文件 async function handleS3FileViaAPI(context, metadata, encodedFileName, fileType) { - const { Referer, url, request } = context; + const { Referer, url, request, env } = context; + const db = getDatabase(env); + const s3Credentials = await resolveS3Credentials(db, env, metadata); const s3Client = new S3Client({ - region: metadata?.S3Region || "auto", - endpoint: metadata?.S3Endpoint, + region: s3Credentials.region || "auto", + endpoint: s3Credentials.endpoint, credentials: { - accessKeyId: metadata?.S3AccessKeyId, - secretAccessKey: metadata?.S3SecretAccessKey + accessKeyId: s3Credentials.accessKeyId, + secretAccessKey: s3Credentials.secretAccessKey }, - forcePathStyle: metadata?.S3PathStyle || false + forcePathStyle: s3Credentials.pathStyle || false }); - const bucketName = metadata?.S3BucketName; - const key = metadata?.S3FileKey; + const bucketName = s3Credentials.bucketName; + const key = s3Credentials.key; try { // 检查Range请求头 @@ -802,11 +816,13 @@ async function handleDiscordFile(context, metadata, encodedFileName, fileType) { const { env, request, url, Referer } = context; try { + const db = getDatabase(env); + const discordCredentials = await resolveDiscordCredentials(db, env, metadata); // 每次读取都通过 API 获取新的附件 URL(因为 Discord 附件 URL 会在约24小时后过期) let fileUrl = null; - if (metadata.DiscordMessageId && metadata.DiscordChannelId && metadata.DiscordBotToken) { - const discordAPI = new DiscordAPI(metadata.DiscordBotToken); - fileUrl = await discordAPI.getFileURL(metadata.DiscordChannelId, metadata.DiscordMessageId); + if (discordCredentials.messageId && discordCredentials.channelId && discordCredentials.botToken) { + const discordAPI = new DiscordAPI(discordCredentials.botToken); + fileUrl = await discordAPI.getFileURL(discordCredentials.channelId, discordCredentials.messageId); } if (!fileUrl) { @@ -814,8 +830,8 @@ async function handleDiscordFile(context, metadata, encodedFileName, fileType) { } // 如果配置了代理 URL,替换 Discord CDN 域名 - if (metadata.DiscordProxyUrl) { - fileUrl = fileUrl.replace('https://cdn.discordapp.com', `https://${metadata.DiscordProxyUrl}`); + if (discordCredentials.proxyUrl) { + fileUrl = fileUrl.replace('https://cdn.discordapp.com', `https://${discordCredentials.proxyUrl}`); } // 处理 HEAD 请求 @@ -866,20 +882,22 @@ async function handleDiscordFile(context, metadata, encodedFileName, fileType) { // 处理 HuggingFace 文件读取 async function handleHuggingFaceFile(context, metadata, encodedFileName, fileType) { - const { request, url, Referer } = context; + const { env, request, url, Referer } = context; try { - const hfRepo = metadata.HfRepo; - const hfFilePath = metadata.HfFilePath; - const hfToken = metadata.HfToken; - const hfIsPrivate = metadata.HfIsPrivate || false; + const db = getDatabase(env); + const hfCredentials = await resolveHuggingFaceCredentials(db, env, metadata); + const hfRepo = hfCredentials.repo; + const hfFilePath = hfCredentials.filePath; + const hfToken = hfCredentials.token; + const hfIsPrivate = hfCredentials.isPrivate || false; if (!hfRepo || !hfFilePath) { return new Response('Error: HuggingFace file info not found', { status: 500 }); } // 构建文件 URL - const fileUrl = metadata.HfFileUrl || `https://huggingface.co/datasets/${hfRepo}/resolve/main/${hfFilePath}`; + const fileUrl = hfCredentials.fileUrl || `https://huggingface.co/datasets/${hfRepo}/resolve/main/${hfFilePath}`; const fileSize = HuggingFaceAPI.getMetadataFileSize(metadata); // 处理 HEAD 请求 diff --git a/functions/upload/chunkMerge.js b/functions/upload/chunkMerge.js index e5693c32..45e42862 100644 --- a/functions/upload/chunkMerge.js +++ b/functions/upload/chunkMerge.js @@ -386,8 +386,6 @@ async function mergeS3ChunksInfo(context, uploadId, completedChunks, metadata) { } metadata.S3Endpoint = endpoint; metadata.S3PathStyle = pathStyle; - metadata.S3AccessKeyId = accessKeyId; - metadata.S3SecretAccessKey = secretAccessKey; metadata.S3Region = region || "auto"; metadata.S3BucketName = bucketName; metadata.S3FileKey = finalFileId; @@ -464,7 +462,6 @@ async function mergeTelegramChunksInfo(context, uploadId, completedChunks, metad metadata.Channel = "TelegramNew"; metadata.ChannelName = tgChannel.name; metadata.TgChatId = tgChatId; - metadata.TgBotToken = tgBotToken; metadata.TgProxyUrl = tgChannel.proxyUrl || ''; metadata.IsChunked = true; metadata.TotalChunks = completedChunks.length; @@ -544,7 +541,6 @@ async function mergeDiscordChunksInfo(context, uploadId, completedChunks, metada metadata.Channel = "Discord"; metadata.ChannelName = discordChannel.name; metadata.DiscordChannelId = channelId; - metadata.DiscordBotToken = botToken; metadata.DiscordProxyUrl = discordChannel.proxyUrl || ''; metadata.IsChunked = true; metadata.TotalChunks = completedChunks.length; diff --git a/functions/upload/chunkUpload.js b/functions/upload/chunkUpload.js index eeb1975d..591fab77 100644 --- a/functions/upload/chunkUpload.js +++ b/functions/upload/chunkUpload.js @@ -1253,7 +1253,6 @@ export async function uploadLargeFileToTelegram(context, file, fullId, metadata, metadata.Channel = "TelegramNew"; metadata.ChannelName = tgChannel.name; metadata.TgChatId = tgChatId; - metadata.TgBotToken = tgBotToken; metadata.TgProxyUrl = tgChannel.proxyUrl || ''; metadata.IsChunked = true; metadata.TotalChunks = totalChunks; diff --git a/functions/upload/huggingface/commitUpload.js b/functions/upload/huggingface/commitUpload.js index e088032b..02e1e2b5 100644 --- a/functions/upload/huggingface/commitUpload.js +++ b/functions/upload/huggingface/commitUpload.js @@ -107,7 +107,6 @@ export async function onRequestPost(context) { ListType: "None", HfRepo: hfChannel.repo, HfFilePath: filePath, - HfToken: hfChannel.token, HfIsPrivate: hfChannel.isPrivate || false, HfFileUrl: fileUrl, TimeStamp: Date.now(), diff --git a/functions/upload/index.js b/functions/upload/index.js index f4bb14c5..e4fd93bf 100644 --- a/functions/upload/index.js +++ b/functions/upload/index.js @@ -401,8 +401,6 @@ async function uploadFileToS3(context, fullId, metadata, returnLink) { } metadata.S3Endpoint = endpoint; metadata.S3PathStyle = pathStyle; - metadata.S3AccessKeyId = accessKeyId; - metadata.S3SecretAccessKey = secretAccessKey; metadata.S3Region = region || "auto"; metadata.S3BucketName = bucketName; metadata.S3FileKey = s3FileName; @@ -556,7 +554,6 @@ async function uploadFileToTelegram(context, fullId, metadata, fileExt, fileName metadata.TgFileId = id; metadata.TgChatId = tgChatId; - metadata.TgBotToken = tgBotToken; // 保存代理域名配置 if (tgProxyUrl) { metadata.TgProxyUrl = tgProxyUrl; @@ -675,7 +672,6 @@ async function uploadFileToDiscord(context, fullId, metadata, returnLink) { metadata.FileSize = (fileInfo.file_size / 1024 / 1024).toFixed(2); metadata.DiscordMessageId = fileInfo.message_id; metadata.DiscordChannelId = discordChannel.channelId; - metadata.DiscordBotToken = discordChannel.botToken; // 注意:不存储 DiscordAttachmentUrl,因为 Discord 附件 URL 会在约24小时后过期 // 读取时会通过 API 获取新的 URL @@ -789,7 +785,6 @@ async function uploadFileToHuggingFace(context, fullId, metadata, returnLink) { metadata.ChannelName = hfChannel.name || "HuggingFace_env"; metadata.HfRepo = hfChannel.repo; metadata.HfFilePath = hfFilePath; - metadata.HfToken = hfChannel.token; metadata.HfIsPrivate = hfChannel.isPrivate || false; metadata.HfFileUrl = result.fileUrl; diff --git a/functions/utils/channelCredentials.js b/functions/utils/channelCredentials.js new file mode 100644 index 00000000..6a570226 --- /dev/null +++ b/functions/utils/channelCredentials.js @@ -0,0 +1,108 @@ +import { getUploadConfig } from '../api/manage/sysConfig/upload.js'; + +export async function resolveS3Credentials(db, env, metadata = {}) { + const channel = await findChannel(db, env, 's3', metadata.ChannelName); + if (channel) { + return { + source: 'config', + endpoint: channel.endpoint, + region: channel.region || 'auto', + bucketName: channel.bucketName, + pathStyle: channel.pathStyle || false, + accessKeyId: channel.accessKeyId, + secretAccessKey: channel.secretAccessKey, + cdnDomain: channel.cdnDomain || '', + key: metadata.S3FileKey, + }; + } + + return { + source: 'metadata', + endpoint: metadata.S3Endpoint, + region: metadata.S3Region || 'auto', + bucketName: metadata.S3BucketName, + pathStyle: metadata.S3PathStyle || false, + accessKeyId: metadata.S3AccessKeyId, + secretAccessKey: metadata.S3SecretAccessKey, + cdnDomain: metadata.S3CdnDomain || '', + key: metadata.S3FileKey, + }; +} + +export async function resolveTelegramCredentials(db, env, metadata = {}) { + const channel = await findChannel(db, env, 'telegram', metadata.ChannelName); + if (channel) { + return { + source: 'config', + botToken: channel.botToken, + chatId: channel.chatId, + proxyUrl: channel.proxyUrl || '', + fileId: metadata.TgFileId, + }; + } + + return { + source: 'metadata', + botToken: metadata.TgBotToken || env.TG_BOT_TOKEN, + chatId: metadata.TgChatId || env.TG_CHAT_ID, + proxyUrl: metadata.TgProxyUrl || '', + fileId: metadata.TgFileId, + }; +} + +export async function resolveDiscordCredentials(db, env, metadata = {}) { + const channel = await findChannel(db, env, 'discord', metadata.ChannelName); + if (channel) { + return { + source: 'config', + botToken: channel.botToken, + channelId: channel.channelId, + proxyUrl: channel.proxyUrl || '', + messageId: metadata.DiscordMessageId, + }; + } + + return { + source: 'metadata', + botToken: metadata.DiscordBotToken, + channelId: metadata.DiscordChannelId, + proxyUrl: metadata.DiscordProxyUrl || '', + messageId: metadata.DiscordMessageId, + }; +} + +export async function resolveHuggingFaceCredentials(db, env, metadata = {}) { + const channel = await findChannel(db, env, 'huggingface', metadata.ChannelName); + if (channel) { + return { + source: 'config', + token: channel.token, + repo: channel.repo, + isPrivate: channel.isPrivate || false, + filePath: metadata.HfFilePath, + fileUrl: metadata.HfFileUrl, + }; + } + + return { + source: 'metadata', + token: metadata.HfToken, + repo: metadata.HfRepo, + isPrivate: metadata.HfIsPrivate || false, + filePath: metadata.HfFilePath, + fileUrl: metadata.HfFileUrl, + }; +} + +async function findChannel(db, env, groupName, channelName) { + if (!channelName) return null; + + try { + const uploadConfig = await getUploadConfig(db, env); + const channels = uploadConfig[groupName]?.channels || []; + return channels.find((channel) => channel.name === channelName) || null; + } catch (error) { + console.error(`Failed to resolve ${groupName} channel credentials:`, error); + return null; + } +} diff --git a/functions/utils/metadataSecurity.js b/functions/utils/metadataSecurity.js new file mode 100644 index 00000000..d56b65f2 --- /dev/null +++ b/functions/utils/metadataSecurity.js @@ -0,0 +1,59 @@ +const SENSITIVE_METADATA_KEYS = [ + 'S3AccessKeyId', + 'S3SecretAccessKey', + 'TgBotToken', + 'DiscordBotToken', + 'HfToken', + 'WebDAVUsername', + 'WebDAVPassword', + 'WebDAVHeaders', +]; + +export function sanitizeFileMetadata(metadata = {}) { + if (!metadata || typeof metadata !== 'object') { + return metadata; + } + + return stripSensitiveMetadata(metadata); +} + +export function stripSensitiveMetadata(metadata = {}) { + if (!metadata || typeof metadata !== 'object') { + return metadata; + } + + const stripped = { ...metadata }; + return stripSensitiveMetadataInPlace(stripped); +} + +export function stripSensitiveMetadataInPlace(metadata = {}) { + if (!metadata || typeof metadata !== 'object') { + return metadata; + } + + for (const key of SENSITIVE_METADATA_KEYS) { + delete metadata[key]; + } + + if (metadata.WebDAVBaseUrl) { + const safeBaseUrl = stripUrlUserinfo(metadata.WebDAVBaseUrl); + if (safeBaseUrl) { + metadata.WebDAVBaseUrl = safeBaseUrl; + } else { + delete metadata.WebDAVBaseUrl; + } + } + + return metadata; +} + +function stripUrlUserinfo(value) { + try { + const url = new URL(value); + url.username = ''; + url.password = ''; + return url.toString(); + } catch { + return ''; + } +} diff --git a/functions/utils/webdavConfig.js b/functions/utils/webdavConfig.js index 8c8710ee..64ed4e12 100644 --- a/functions/utils/webdavConfig.js +++ b/functions/utils/webdavConfig.js @@ -15,13 +15,14 @@ export async function resolveWebDAVConfig(env, metadata = {}) { || channels.find((item) => getWebDAVBaseUrl(item) === metadataBaseUrl); if (channel) { - return normalizeWebDAVConfig(channel); + const config = normalizeWebDAVConfig(channel); + return config ? { ...config, source: 'config' } : null; } } catch (error) { console.error('Failed to resolve WebDAV channel config:', error); } - return normalizeWebDAVConfig({ + const config = normalizeWebDAVConfig({ baseUrl: metadataBaseUrl, username: metadata.WebDAVUsername || '', password: metadata.WebDAVPassword || '', @@ -29,6 +30,7 @@ export async function resolveWebDAVConfig(env, metadata = {}) { createDirectory: metadata.WebDAVCreateDirectory !== false, publicUrl: metadata.WebDAVPublicBaseUrl || '', }); + return config ? { ...config, source: 'metadata' } : null; } function normalizeWebDAVConfig(config = {}) { diff --git a/package-lock.json b/package-lock.json index 576657c9..017eeb12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { - "name": "CloudFlare-ImgBed", + "name": "cloudflare-imgbed", + "version": "2.7.3", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "cloudflare-imgbed", + "version": "2.7.3", "dependencies": { "@aws-sdk/client-s3": "3.726.1", "@cloudflare/pages-plugin-sentry": "^1.1.3", - "@hono/node-server": "^1.13.8", "@sentry/tracing": "^7.114.0", - "better-sqlite3": "^11.9.1", - "hono": "^4.7.4", "miniflare": "^3.20240718.0" }, "devDependencies": { @@ -18,6 +18,11 @@ "mocha": "^10.6.0", "wait-on": "^7.2.0", "wrangler": "^4.24.0" + }, + "optionalDependencies": { + "@hono/node-server": "^1.13.8", + "better-sqlite3": "^11.9.1", + "hono": "^4.7.4" } }, "node_modules/@aws-crypto/crc32": { @@ -1900,6 +1905,7 @@ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.10.tgz", "integrity": "sha512-hZ7nOssGqRgyV3FVVQdfi+U4q02uB23bpnYpdvNXkYTRRyWx84b7yf1ans+dnJ/7h41sGL3CeQTfO+ZGxuO+Iw==", "license": "MIT", + "optional": true, "engines": { "node": ">=18.14.1" }, @@ -3431,7 +3437,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/better-sqlite3": { "version": "11.10.0", @@ -3439,6 +3446,7 @@ "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==", "hasInstallScript": true, "license": "MIT", + "optional": true, "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" @@ -3462,6 +3470,7 @@ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "license": "MIT", + "optional": true, "dependencies": { "file-uri-to-path": "1.0.0" } @@ -3471,6 +3480,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "license": "MIT", + "optional": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -3539,6 +3549,7 @@ } ], "license": "MIT", + "optional": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -3630,7 +3641,8 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/cliui": { "version": "8.0.1", @@ -3776,6 +3788,7 @@ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "license": "MIT", + "optional": true, "dependencies": { "mimic-response": "^3.1.0" }, @@ -3791,6 +3804,7 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "license": "MIT", + "optional": true, "engines": { "node": ">=4.0.0" } @@ -3809,6 +3823,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -3851,6 +3866,7 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", + "optional": true, "dependencies": { "once": "^1.4.0" } @@ -3996,6 +4012,7 @@ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", "license": "(MIT OR WTFPL)", + "optional": true, "engines": { "node": ">=6" } @@ -4035,7 +4052,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fill-range": { "version": "7.1.1", @@ -4119,7 +4137,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -4216,7 +4235,8 @@ "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/glob": { "version": "8.1.0", @@ -4338,6 +4358,7 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.4.tgz", "integrity": "sha512-ooiZW1Xy8rQ4oELQ++otI2T9DsKpV0M6c6cO6JGx4RTfav9poFFLlet9UMXHZnoM1yG0HWGlQLswBGX3RZmHtg==", "license": "MIT", + "optional": true, "engines": { "node": ">=16.9.0" } @@ -4360,7 +4381,8 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "optional": true }, "node_modules/inflight": { "version": "1.0.6", @@ -4378,13 +4400,15 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "devOptional": true, "license": "ISC" }, "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -4580,6 +4604,7 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "license": "MIT", + "optional": true, "engines": { "node": ">=10" }, @@ -4629,6 +4654,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "devOptional": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4638,7 +4664,8 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/mocha": { "version": "10.8.2", @@ -4727,13 +4754,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/node-abi": { "version": "3.87.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", "license": "MIT", + "optional": true, "dependencies": { "semver": "^7.3.5" }, @@ -4755,6 +4784,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "devOptional": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -4835,6 +4865,7 @@ "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", "license": "MIT", + "optional": true, "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", @@ -4874,6 +4905,7 @@ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "license": "MIT", + "optional": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -4894,6 +4926,7 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -4909,6 +4942,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } @@ -4918,6 +4952,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", + "optional": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -4964,6 +4999,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "devOptional": true, "funding": [ { "type": "github", @@ -4984,6 +5020,7 @@ "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -5078,7 +5115,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/simple-get": { "version": "4.0.1", @@ -5099,6 +5137,7 @@ } ], "license": "MIT", + "optional": true, "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", @@ -5145,6 +5184,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", + "optional": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -5223,6 +5263,7 @@ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "license": "MIT", + "optional": true, "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -5235,6 +5276,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "license": "MIT", + "optional": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -5280,6 +5322,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "license": "Apache-2.0", + "optional": true, "dependencies": { "safe-buffer": "^5.0.1" }, @@ -5313,7 +5356,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/wait-on": { "version": "7.2.0", @@ -5600,6 +5644,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "devOptional": true, "license": "ISC" }, "node_modules/ws": {