├── .editorconfig ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .prettierrc ├── CFr2_WebDav_n8n.json ├── README.md ├── output.py ├── package-lock.json ├── package.json ├── src ├── handlers │ ├── requestHandler.ts │ └── webdavHandler.ts ├── index.ts ├── types.ts └── utils │ ├── auth.ts │ ├── cors.ts │ ├── logger.ts │ ├── templates.ts │ └── webdavUtils.ts ├── tsconfig.json ├── wrangler.toml.template └── 免费一键部署Cloudflare R2 WebDAV服务,超简单拥有自己的私人网盘-封面.jpg /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | tab_width = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.yml] 13 | indent_style = space 14 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Cloudflare Workers 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 60 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Create wrangler.toml 16 | env: 17 | USERNAME: ${{ secrets.USERNAME || '_user' }} 18 | PASSWORD: ${{ secrets.PASSWORD || '_pass' }} 19 | BUCKET_NAME: ${{ secrets.BUCKET_NAME || 'bucket' }} 20 | run: | 21 | sed -e "s/\$USERNAME/$USERNAME/g" \ 22 | -e "s/\$PASSWORD/$PASSWORD/g" \ 23 | -e "s/\$BUCKET_NAME/$BUCKET_NAME/g" \ 24 | wrangler.toml.template > wrangler.toml 25 | cat wrangler.toml 26 | 27 | - name: Build & Deploy Worker 28 | uses: cloudflare/wrangler-action@v3 29 | with: 30 | apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | 3 | logs 4 | _.log 5 | npm-debug.log_ 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | .pnpm-debug.log* 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | 13 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 14 | 15 | # Runtime data 16 | 17 | pids 18 | _.pid 19 | _.seed 20 | \*.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | 28 | coverage 29 | \*.lcov 30 | 31 | # nyc test coverage 32 | 33 | .nyc_output 34 | 35 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 36 | 37 | .grunt 38 | 39 | # Bower dependency directory (https://bower.io/) 40 | 41 | bower_components 42 | 43 | # node-waf configuration 44 | 45 | .lock-wscript 46 | 47 | # Compiled binary addons (https://nodejs.org/api/addons.html) 48 | 49 | build/Release 50 | 51 | # Dependency directories 52 | 53 | node_modules/ 54 | jspm_packages/ 55 | 56 | # Snowpack dependency directory (https://snowpack.dev/) 57 | 58 | web_modules/ 59 | 60 | # TypeScript cache 61 | 62 | \*.tsbuildinfo 63 | 64 | # Optional npm cache directory 65 | 66 | .npm 67 | 68 | # Optional eslint cache 69 | 70 | .eslintcache 71 | 72 | # Optional stylelint cache 73 | 74 | .stylelintcache 75 | 76 | # Microbundle cache 77 | 78 | .rpt2_cache/ 79 | .rts2_cache_cjs/ 80 | .rts2_cache_es/ 81 | .rts2_cache_umd/ 82 | 83 | # Optional REPL history 84 | 85 | .node_repl_history 86 | 87 | # Output of 'npm pack' 88 | 89 | \*.tgz 90 | 91 | # Yarn Integrity file 92 | 93 | .yarn-integrity 94 | 95 | # dotenv environment variable files 96 | 97 | .env 98 | .env.development.local 99 | .env.test.local 100 | .env.production.local 101 | .env.local 102 | 103 | # parcel-bundler cache (https://parceljs.org/) 104 | 105 | .cache 106 | .parcel-cache 107 | 108 | # Next.js build output 109 | 110 | .next 111 | out 112 | 113 | # Nuxt.js build / generate output 114 | 115 | .nuxt 116 | dist 117 | 118 | # Gatsby files 119 | 120 | .cache/ 121 | 122 | # Comment in the public line in if your project uses Gatsby and not Next.js 123 | 124 | # https://nextjs.org/blog/next-9-1#public-directory-support 125 | 126 | # public 127 | 128 | # vuepress build output 129 | 130 | .vuepress/dist 131 | 132 | # vuepress v2.x temp and cache directory 133 | 134 | .temp 135 | .cache 136 | 137 | # Docusaurus cache and generated files 138 | 139 | .docusaurus 140 | 141 | # Serverless directories 142 | 143 | .serverless/ 144 | 145 | # FuseBox cache 146 | 147 | .fusebox/ 148 | 149 | # DynamoDB Local files 150 | 151 | .dynamodb/ 152 | 153 | # TernJS port file 154 | 155 | .tern-port 156 | 157 | # Stores VSCode versions used for testing VSCode extensions 158 | 159 | .vscode-test 160 | 161 | # yarn v2 162 | 163 | .yarn/cache 164 | .yarn/unplugged 165 | .yarn/build-state.yml 166 | .yarn/install-state.gz 167 | .pnp.\* 168 | 169 | # wrangler project 170 | 171 | .dev.vars 172 | .wrangler/ 173 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "semi": true, 5 | "useTabs": true 6 | } 7 | -------------------------------------------------------------------------------- /CFr2_WebDav_n8n.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CFr2 WebDav", 3 | "nodes": [ 4 | { 5 | "parameters": { 6 | "method": "=PROPFIND", 7 | "url": "={{ $json['CFr2 Webdav URL'] }}", 8 | "authentication": "genericCredentialType", 9 | "genericAuthType": "httpBasicAuth", 10 | "sendBody": true, 11 | "contentType": "raw", 12 | "rawContentType": "xml/text", 13 | "body": "=\n\n ", 14 | "options": {} 15 | }, 16 | "id": "5ae8b3cb-a521-4408-a13a-6de0fd66e9c9", 17 | "name": "List CFr2 webdav", 18 | "type": "n8n-nodes-base.httpRequest", 19 | "typeVersion": 4.1, 20 | "position": [ 21 | 660, 22 | 480 23 | ], 24 | "credentials": { 25 | "httpBasicAuth": { 26 | "id": "IGS9zbgMB0Sphjrq", 27 | "name": "Base_user_pass" 28 | } 29 | } 30 | }, 31 | { 32 | "parameters": { 33 | "options": {} 34 | }, 35 | "id": "62e8db9a-01dc-4b97-9b70-1585312157c7", 36 | "name": "XML", 37 | "type": "n8n-nodes-base.xml", 38 | "typeVersion": 1, 39 | "position": [ 40 | 840, 41 | 480 42 | ] 43 | }, 44 | { 45 | "parameters": { 46 | "values": { 47 | "string": [ 48 | { 49 | "name": "CFr2 Webdav URL", 50 | "value": "https://你的webdav地址/" 51 | }, 52 | { 53 | "name": "new file name", 54 | "value": "newfile2.txt" 55 | }, 56 | { 57 | "name": "filecontent", 58 | "value": "=This is the content of the new file.新的文件by N8N 是也!\nhello world" 59 | } 60 | ] 61 | }, 62 | "options": {} 63 | }, 64 | "id": "4c52160d-ea4b-426c-98ad-1aa696bc14ca", 65 | "name": "设置项", 66 | "type": "n8n-nodes-base.set", 67 | "typeVersion": 2, 68 | "position": [ 69 | 660, 70 | 260 71 | ] 72 | }, 73 | { 74 | "parameters": { 75 | "method": "PUT", 76 | "url": "={{ $json['CFr2 Webdav URL'] }}{{ $json['new file name'] }}", 77 | "authentication": "genericCredentialType", 78 | "genericAuthType": "httpBasicAuth", 79 | "sendBody": true, 80 | "contentType": "raw", 81 | "body": "={{ $json.filecontent }}", 82 | "options": {} 83 | }, 84 | "id": "f1b6c7a8-5ee2-4e0b-a8d1-927ec028718c", 85 | "name": "上传文件到webdav", 86 | "type": "n8n-nodes-base.httpRequest", 87 | "typeVersion": 4.1, 88 | "position": [ 89 | 840, 90 | 260 91 | ], 92 | "credentials": { 93 | "httpBasicAuth": { 94 | "id": "IGS9zbgMB0Sphjrq", 95 | "name": "Base_user_pass" 96 | } 97 | } 98 | }, 99 | { 100 | "parameters": { 101 | "operation": "convertToHtmlTable", 102 | "options": {} 103 | }, 104 | "id": "e6604851-51f3-490d-a54b-448d98859eaa", 105 | "name": "HTML", 106 | "type": "n8n-nodes-base.html", 107 | "typeVersion": 1, 108 | "position": [ 109 | 1380, 110 | 480 111 | ] 112 | }, 113 | { 114 | "parameters": { 115 | "fieldToSplitOut": "['D:multistatus']['D:response']", 116 | "options": {} 117 | }, 118 | "id": "e6732cd8-f683-41fa-bff4-e053d7400828", 119 | "name": "Item Lists", 120 | "type": "n8n-nodes-base.itemLists", 121 | "typeVersion": 3, 122 | "position": [ 123 | 1020, 124 | 480 125 | ] 126 | }, 127 | { 128 | "parameters": { 129 | "jsCode": "// 获取 WebDAV JSON 数据\nconst items = $('Item Lists').all(); // 获取所有输入的 JSON 项\n\n// 初始化一个空数组,用于存储所有的 \"D:href\" 值\nconst hrefList = [];\n\n// 遍历所有 JSON 项并提取 \"D:href\"\nitems.forEach((item) => {\n if (item.json && item.json[\"D:href\"]) {\n hrefList.push(item.json[\"D:href\"]);\n }\n});\n\n// 显示 \"D:href\" 的值列表\nconsole.log(\"D:href 列表:\", hrefList);\n\n// 返回一个包含所有 \"D:href\" 的数组,作为下一个节点的输入\nreturn hrefList.map(href => ({ json: { href } }));\n" 130 | }, 131 | "id": "cd85ade2-1d6c-47ed-be18-04c2631241ba", 132 | "name": "Code", 133 | "type": "n8n-nodes-base.code", 134 | "typeVersion": 2, 135 | "position": [ 136 | 1200, 137 | 480 138 | ] 139 | }, 140 | { 141 | "parameters": { 142 | "html": "\n\n\n\n \n My HTML document\n\n\n
\n

列出所有文件

\n

{{ $('HTML').item.json.table }}

\n
\n\n\n\n\n" 143 | }, 144 | "id": "f3402453-9a79-4d9f-b705-42eaf8629220", 145 | "name": "HTML1", 146 | "type": "n8n-nodes-base.html", 147 | "typeVersion": 1, 148 | "position": [ 149 | 1560, 150 | 480 151 | ] 152 | }, 153 | { 154 | "parameters": {}, 155 | "id": "c12613f1-f466-4d30-af86-28a6cdd46a2f", 156 | "name": "请先设置好", 157 | "type": "n8n-nodes-base.manualTrigger", 158 | "typeVersion": 1, 159 | "position": [ 160 | 480, 161 | 260 162 | ] 163 | } 164 | ], 165 | "pinData": {}, 166 | "connections": { 167 | "List CFr2 webdav": { 168 | "main": [ 169 | [ 170 | { 171 | "node": "XML", 172 | "type": "main", 173 | "index": 0 174 | } 175 | ] 176 | ] 177 | }, 178 | "设置项": { 179 | "main": [ 180 | [ 181 | { 182 | "node": "上传文件到webdav", 183 | "type": "main", 184 | "index": 0 185 | }, 186 | { 187 | "node": "List CFr2 webdav", 188 | "type": "main", 189 | "index": 0 190 | } 191 | ] 192 | ] 193 | }, 194 | "XML": { 195 | "main": [ 196 | [ 197 | { 198 | "node": "Item Lists", 199 | "type": "main", 200 | "index": 0 201 | } 202 | ] 203 | ] 204 | }, 205 | "Item Lists": { 206 | "main": [ 207 | [ 208 | { 209 | "node": "Code", 210 | "type": "main", 211 | "index": 0 212 | } 213 | ] 214 | ] 215 | }, 216 | "Code": { 217 | "main": [ 218 | [ 219 | { 220 | "node": "HTML", 221 | "type": "main", 222 | "index": 0 223 | } 224 | ] 225 | ] 226 | }, 227 | "HTML": { 228 | "main": [ 229 | [ 230 | { 231 | "node": "HTML1", 232 | "type": "main", 233 | "index": 0 234 | } 235 | ] 236 | ] 237 | }, 238 | "请先设置好": { 239 | "main": [ 240 | [ 241 | { 242 | "node": "设置项", 243 | "type": "main", 244 | "index": 0 245 | } 246 | ] 247 | ] 248 | } 249 | }, 250 | "active": false, 251 | "settings": { 252 | "executionOrder": "v1" 253 | }, 254 | "versionId": "98177e24-18a0-42bf-8d36-35ec275b4a86", 255 | "meta": { 256 | "templateCredsSetupCompleted": true, 257 | "instanceId": "a21814256a0abe8e07427ccc82d5f3f7dae9c34dbccadc9dbb556bfae5ce2ec8" 258 | }, 259 | "id": "m6YHCqDPucnkkByR", 260 | "tags": [] 261 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloudflare R2 WebDAV Server 2 | 3 | 这个项目实现了一个基于 Cloudflare Workers 和 R2 存储的 WebDAV 服务器。它允许用户通过 WebDAV 协议访问和管理存储在 Cloudflare R2 中的文件和目录。 4 | 5 | [R2免费额度](https://developers.cloudflare.com/r2/pricing/) [视频教程](https://www.bilibili.com/video/BV1mh4peNECe/) 6 | 7 | ![部署Cloudflare R2 WebDAV服务,超简单拥有自己的私人网盘](https://raw.githubusercontent.com/aigem/CFr2-webdav/main/%E5%85%8D%E8%B4%B9%E4%B8%80%E9%94%AE%E9%83%A8%E7%BD%B2Cloudflare%20R2%20WebDAV%E6%9C%8D%E5%8A%A1%EF%BC%8C%E8%B6%85%E7%AE%80%E5%8D%95%E6%8B%A5%E6%9C%89%E8%87%AA%E5%B7%B1%E7%9A%84%E7%A7%81%E4%BA%BA%E7%BD%91%E7%9B%98-%E5%B0%81%E9%9D%A2.jpg) 8 | 9 | 10 | ## 特性 11 | 12 | - 完全兼容 WebDAV 协议 13 | - 基于 Cloudflare Workers,无需管理服务器 14 | - 使用 Cloudflare R2 作为存储后端(免费额度慷慨) 15 | - 支持基本的身份验证 16 | - 支持文件上传、下载、删除、移动和复制操作 17 | - 支持目录创建和列表 18 | 19 | ## 一键部署到 Cloudflare Workers 20 | 21 | 点击下面的按钮,一键将此项目部署到您的Cloudflare Workers账户: 22 | 23 | [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/aigem/CFr2-webdav) 24 | 25 | 注需要有Cloudflare账户才能使用此功能。如果您还没有账户,可以在[Cloudflare官www.cloudflare.com)注册。 26 | 27 | ## 手动部署步骤 [Githut Actions] 28 | 29 | 如果您需要自定义配置或想要深入了解部署流程,请按以下步骤操作: 30 | 31 | ### 前提条件 32 | 33 | - Cloudflare 账户 34 | - 已创建的 R2 存储桶 35 | - GitHub 账户 36 | 37 | ### 步骤 1: 配置 Cloudflare 38 | 39 | 1. 【获取API令牌】在 Cloudflare 仪表板中,创建一个新的 API 令牌,确保它有足够的权限来管理编辑Workers(和 R2)。 40 | 2. 【获取桶名称】创建的 R2 存储桶 41 | 42 | ### 步骤 2: 准备仓库 43 | 44 | Fork 这个仓库到您的 GitHub 账户。 45 | ``` 46 | https://github.com/aigem/CFr2-webdav 47 | ``` 48 | 49 | ### 步骤 3: 配置 GitHub Secrets 50 | 51 | 在您的 GitHub 仓库中,转到 Settings -> Secrets and variables -> Actions,添加以下 secrets: 52 | 53 | - `CLOUDFLARE_API_TOKEN`: 步骤1的 Cloudflare API 令牌 (必须) 54 | - `USERNAME`: WebDAV 服务器的用户名 (可选,默认为 _user) 55 | - `PASSWORD`: WebDAV 服务器的密码 (可选,默认为 _pass) 56 | - `BUCKET_NAME`: 的 R2 存储桶名称 (可选,默认为 bucket 如果与你实际的bucket不符,则GithubAction部署会失败) 57 | 58 | ### 步骤 4: 配置 GitHub Actions 59 | 60 | 1. 在您的 GitHub 仓库设置中,启用 GitHub Actions。 61 | 2. workflow 文件已经存在,请选择: .github/workflow/main.yml 62 | 63 | ### 步骤 6: 触发部署 64 | 65 | 按上面操作完成后就会自动进行部署到CF Worker中,或将任何更改推送到 GitHub 仓库的 `main` 分支,或者手动运行 GitHub Actions 工作流。GitHub Actions 将自动触发部署流程。 66 | 67 | 您可以在 GitHub 仓库的 Actions 标签页中查看部署进度。部署成功后,您可以在 Cloudflare Workers 仪表板中找到您的 Worker URL。 68 | 69 | ## 使用方法 70 | 71 | 使用任何支持 WebDAV 协议的客到您的 Worker URL,使用配置的用户名和密码进行身份验证。 72 | 73 | 74 | ## 本地开发(可选) 75 | 76 | 如果您需要在本地进行开发和测试,请按以下步骤操作: 77 | 78 | 0. 同上面步骤1 :配置 Cloudflare 79 | 80 | 1. 克隆仓库到本地: 81 | ```bash 82 | git clone https://github.com/aigem/CFr2-webdav.git 83 | cd cf-r2-webdav 84 | ``` 85 | 86 | 2. 安装依赖: 87 | ```bash 88 | npm install 89 | ``` 90 | 91 | 3. 修改wrangler.toml.template为wrangler.toml文件,并修改为你的实际参数: 92 | 93 | 4. 使用 Wrangler 进行本地开发: 94 | ```bash 95 | npx wrangler dev --local 96 | ``` 97 | 98 | 注意:本地开发可能无法完全模拟 Cloudflare Workers 环境,特别是 R2 存储的操作。 99 | 100 | ## 注意事项 101 | 102 | - 确保妥善保管您的 API 令牌和其他敏感信息。 103 | - 定期更新您的依赖以确保安全性。 104 | - 遵守 Cloudflare 的使用政策和条款。 105 | 106 | ## 贡献 107 | 108 | 欢迎提交 Pull Requests 或创建 Issues 来改进这个项目。 109 | 110 | ## 许可证 111 | 112 | 本项目采用 MIT 许可证。 113 | -------------------------------------------------------------------------------- /output.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def read_file(file_path): 4 | try: 5 | with open(file_path, 'r', encoding='utf-8') as file: 6 | return file.read() 7 | except FileNotFoundError: 8 | return f"文件不存在: {file_path}" 9 | except Exception as e: 10 | return f"读取文件时出错: {file_path}\n错误: {str(e)}" 11 | 12 | def export_files(files, output_file): 13 | with open(output_file, 'w', encoding='utf-8') as out: 14 | for file_path in files: 15 | out.write(f"文件名:{file_path}\n") 16 | out.write(read_file(file_path)) 17 | out.write("\n\n") 18 | print(f"文件内容已成功导出到 {output_file}") 19 | 20 | files_to_export = [ 21 | # "tsconfig.json", 22 | # "src/utils/templates.ts", 23 | # "package.json", 24 | "src/index.ts", 25 | "src/types.ts", 26 | # "src/handlers/requestHandler.ts", 27 | "src/handlers/webdavHandler.ts", 28 | "src/utils/auth.ts", 29 | "src/utils/cors.ts", 30 | "src/utils/logger.ts", 31 | "src/utils/webdavUtils.ts" 32 | ] 33 | 34 | output_file = "output.txt" 35 | export_files(files_to_export, output_file) -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cf-r2-webdav", 3 | "version": "0.3.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "cf-r2-webdav", 9 | "version": "0.3.0", 10 | "devDependencies": { 11 | "@cloudflare/workers-types": "^4.20231121.0", 12 | "prettier": "3.1.1", 13 | "typescript": "^5.0.4", 14 | "wrangler": "^3.0.0" 15 | } 16 | }, 17 | "node_modules/@cloudflare/kv-asset-handler": { 18 | "version": "0.3.1", 19 | "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.1.tgz", 20 | "integrity": "sha512-lKN2XCfKCmpKb86a1tl4GIwsJYDy9TGuwjhDELLmpKygQhw8X2xR4dusgpC5Tg7q1pB96Eb0rBo81kxSILQMwA==", 21 | "dev": true, 22 | "dependencies": { 23 | "mime": "^3.0.0" 24 | } 25 | }, 26 | "node_modules/@cloudflare/workerd-darwin-64": { 27 | "version": "1.20240223.1", 28 | "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240223.1.tgz", 29 | "integrity": "sha512-GgHnvkazLFZ7bmR96+dTX0+WS13a+5CHOOP3qNUSR9oEnR4hHzpNIO75MuZsm9RPAXrvtT7nSJmYwiGCZXh6og==", 30 | "cpu": [ 31 | "x64" 32 | ], 33 | "dev": true, 34 | "optional": true, 35 | "os": [ 36 | "darwin" 37 | ], 38 | "engines": { 39 | "node": ">=16" 40 | } 41 | }, 42 | "node_modules/@cloudflare/workerd-darwin-arm64": { 43 | "version": "1.20240223.1", 44 | "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240223.1.tgz", 45 | "integrity": "sha512-ZF98vUmVlC0EVEd3RRuhMq4HYWFcqmPtMIMPUN2+ivEHR92TE+6E/AvdeE6wcE7fKHQ+fk3dH+ZgB0GcfptfnA==", 46 | "cpu": [ 47 | "arm64" 48 | ], 49 | "dev": true, 50 | "optional": true, 51 | "os": [ 52 | "darwin" 53 | ], 54 | "engines": { 55 | "node": ">=16" 56 | } 57 | }, 58 | "node_modules/@cloudflare/workerd-linux-64": { 59 | "version": "1.20240223.1", 60 | "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240223.1.tgz", 61 | "integrity": "sha512-1kH41ewNTGMmAk2zUX0Xj9VSfidl26GQ0ZrWMdi5kwf6gAHd3oVWNigJN078Jx56SgQxNcqVGX1LunqF949asw==", 62 | "cpu": [ 63 | "x64" 64 | ], 65 | "dev": true, 66 | "optional": true, 67 | "os": [ 68 | "linux" 69 | ], 70 | "engines": { 71 | "node": ">=16" 72 | } 73 | }, 74 | "node_modules/@cloudflare/workerd-linux-arm64": { 75 | "version": "1.20240223.1", 76 | "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240223.1.tgz", 77 | "integrity": "sha512-Ro8Og5C4evh890JrRm0B8sHyumRtgL+mUqPeNcEsyG45jAQy5xHpapHnmJAMJV6ah+zDc1cZtQq+en39SojXvQ==", 78 | "cpu": [ 79 | "arm64" 80 | ], 81 | "dev": true, 82 | "optional": true, 83 | "os": [ 84 | "linux" 85 | ], 86 | "engines": { 87 | "node": ">=16" 88 | } 89 | }, 90 | "node_modules/@cloudflare/workerd-windows-64": { 91 | "version": "1.20240223.1", 92 | "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240223.1.tgz", 93 | "integrity": "sha512-eNP5sfaP6WL07DaoigYou5ASPF7jHsFiNzzD2vGOI7yFd5sPlb7sJ4SpIy+BCX0LdqFnjmlUo5Xr+/I6qJ2Nww==", 94 | "cpu": [ 95 | "x64" 96 | ], 97 | "dev": true, 98 | "optional": true, 99 | "os": [ 100 | "win32" 101 | ], 102 | "engines": { 103 | "node": ">=16" 104 | } 105 | }, 106 | "node_modules/@cloudflare/workers-types": { 107 | "version": "4.20240222.0", 108 | "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240222.0.tgz", 109 | "integrity": "sha512-luO0BdK3rLlCv3B240+cTrfqm+XSbHtpk+88aJtGwzyVK9QF/Xz8lBgE/oZZLN8nCTmOvxAZnszyxUuZ8GP8Cg==", 110 | "dev": true 111 | }, 112 | "node_modules/@cspotcode/source-map-support": { 113 | "version": "0.8.1", 114 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 115 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 116 | "dev": true, 117 | "dependencies": { 118 | "@jridgewell/trace-mapping": "0.3.9" 119 | }, 120 | "engines": { 121 | "node": ">=12" 122 | } 123 | }, 124 | "node_modules/@esbuild-plugins/node-globals-polyfill": { 125 | "version": "0.2.3", 126 | "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz", 127 | "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==", 128 | "dev": true, 129 | "peerDependencies": { 130 | "esbuild": "*" 131 | } 132 | }, 133 | "node_modules/@esbuild-plugins/node-modules-polyfill": { 134 | "version": "0.2.2", 135 | "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz", 136 | "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==", 137 | "dev": true, 138 | "dependencies": { 139 | "escape-string-regexp": "^4.0.0", 140 | "rollup-plugin-node-polyfills": "^0.2.1" 141 | }, 142 | "peerDependencies": { 143 | "esbuild": "*" 144 | } 145 | }, 146 | "node_modules/@esbuild/android-arm": { 147 | "version": "0.17.19", 148 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", 149 | "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", 150 | "cpu": [ 151 | "arm" 152 | ], 153 | "dev": true, 154 | "optional": true, 155 | "os": [ 156 | "android" 157 | ], 158 | "engines": { 159 | "node": ">=12" 160 | } 161 | }, 162 | "node_modules/@esbuild/android-arm64": { 163 | "version": "0.17.19", 164 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", 165 | "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", 166 | "cpu": [ 167 | "arm64" 168 | ], 169 | "dev": true, 170 | "optional": true, 171 | "os": [ 172 | "android" 173 | ], 174 | "engines": { 175 | "node": ">=12" 176 | } 177 | }, 178 | "node_modules/@esbuild/android-x64": { 179 | "version": "0.17.19", 180 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", 181 | "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", 182 | "cpu": [ 183 | "x64" 184 | ], 185 | "dev": true, 186 | "optional": true, 187 | "os": [ 188 | "android" 189 | ], 190 | "engines": { 191 | "node": ">=12" 192 | } 193 | }, 194 | "node_modules/@esbuild/darwin-arm64": { 195 | "version": "0.17.19", 196 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", 197 | "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", 198 | "cpu": [ 199 | "arm64" 200 | ], 201 | "dev": true, 202 | "optional": true, 203 | "os": [ 204 | "darwin" 205 | ], 206 | "engines": { 207 | "node": ">=12" 208 | } 209 | }, 210 | "node_modules/@esbuild/darwin-x64": { 211 | "version": "0.17.19", 212 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", 213 | "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", 214 | "cpu": [ 215 | "x64" 216 | ], 217 | "dev": true, 218 | "optional": true, 219 | "os": [ 220 | "darwin" 221 | ], 222 | "engines": { 223 | "node": ">=12" 224 | } 225 | }, 226 | "node_modules/@esbuild/freebsd-arm64": { 227 | "version": "0.17.19", 228 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", 229 | "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", 230 | "cpu": [ 231 | "arm64" 232 | ], 233 | "dev": true, 234 | "optional": true, 235 | "os": [ 236 | "freebsd" 237 | ], 238 | "engines": { 239 | "node": ">=12" 240 | } 241 | }, 242 | "node_modules/@esbuild/freebsd-x64": { 243 | "version": "0.17.19", 244 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", 245 | "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", 246 | "cpu": [ 247 | "x64" 248 | ], 249 | "dev": true, 250 | "optional": true, 251 | "os": [ 252 | "freebsd" 253 | ], 254 | "engines": { 255 | "node": ">=12" 256 | } 257 | }, 258 | "node_modules/@esbuild/linux-arm": { 259 | "version": "0.17.19", 260 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", 261 | "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", 262 | "cpu": [ 263 | "arm" 264 | ], 265 | "dev": true, 266 | "optional": true, 267 | "os": [ 268 | "linux" 269 | ], 270 | "engines": { 271 | "node": ">=12" 272 | } 273 | }, 274 | "node_modules/@esbuild/linux-arm64": { 275 | "version": "0.17.19", 276 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", 277 | "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", 278 | "cpu": [ 279 | "arm64" 280 | ], 281 | "dev": true, 282 | "optional": true, 283 | "os": [ 284 | "linux" 285 | ], 286 | "engines": { 287 | "node": ">=12" 288 | } 289 | }, 290 | "node_modules/@esbuild/linux-ia32": { 291 | "version": "0.17.19", 292 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", 293 | "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", 294 | "cpu": [ 295 | "ia32" 296 | ], 297 | "dev": true, 298 | "optional": true, 299 | "os": [ 300 | "linux" 301 | ], 302 | "engines": { 303 | "node": ">=12" 304 | } 305 | }, 306 | "node_modules/@esbuild/linux-loong64": { 307 | "version": "0.17.19", 308 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", 309 | "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", 310 | "cpu": [ 311 | "loong64" 312 | ], 313 | "dev": true, 314 | "optional": true, 315 | "os": [ 316 | "linux" 317 | ], 318 | "engines": { 319 | "node": ">=12" 320 | } 321 | }, 322 | "node_modules/@esbuild/linux-mips64el": { 323 | "version": "0.17.19", 324 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", 325 | "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", 326 | "cpu": [ 327 | "mips64el" 328 | ], 329 | "dev": true, 330 | "optional": true, 331 | "os": [ 332 | "linux" 333 | ], 334 | "engines": { 335 | "node": ">=12" 336 | } 337 | }, 338 | "node_modules/@esbuild/linux-ppc64": { 339 | "version": "0.17.19", 340 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", 341 | "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", 342 | "cpu": [ 343 | "ppc64" 344 | ], 345 | "dev": true, 346 | "optional": true, 347 | "os": [ 348 | "linux" 349 | ], 350 | "engines": { 351 | "node": ">=12" 352 | } 353 | }, 354 | "node_modules/@esbuild/linux-riscv64": { 355 | "version": "0.17.19", 356 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", 357 | "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", 358 | "cpu": [ 359 | "riscv64" 360 | ], 361 | "dev": true, 362 | "optional": true, 363 | "os": [ 364 | "linux" 365 | ], 366 | "engines": { 367 | "node": ">=12" 368 | } 369 | }, 370 | "node_modules/@esbuild/linux-s390x": { 371 | "version": "0.17.19", 372 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", 373 | "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", 374 | "cpu": [ 375 | "s390x" 376 | ], 377 | "dev": true, 378 | "optional": true, 379 | "os": [ 380 | "linux" 381 | ], 382 | "engines": { 383 | "node": ">=12" 384 | } 385 | }, 386 | "node_modules/@esbuild/linux-x64": { 387 | "version": "0.17.19", 388 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", 389 | "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", 390 | "cpu": [ 391 | "x64" 392 | ], 393 | "dev": true, 394 | "optional": true, 395 | "os": [ 396 | "linux" 397 | ], 398 | "engines": { 399 | "node": ">=12" 400 | } 401 | }, 402 | "node_modules/@esbuild/netbsd-x64": { 403 | "version": "0.17.19", 404 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", 405 | "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", 406 | "cpu": [ 407 | "x64" 408 | ], 409 | "dev": true, 410 | "optional": true, 411 | "os": [ 412 | "netbsd" 413 | ], 414 | "engines": { 415 | "node": ">=12" 416 | } 417 | }, 418 | "node_modules/@esbuild/openbsd-x64": { 419 | "version": "0.17.19", 420 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", 421 | "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", 422 | "cpu": [ 423 | "x64" 424 | ], 425 | "dev": true, 426 | "optional": true, 427 | "os": [ 428 | "openbsd" 429 | ], 430 | "engines": { 431 | "node": ">=12" 432 | } 433 | }, 434 | "node_modules/@esbuild/sunos-x64": { 435 | "version": "0.17.19", 436 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", 437 | "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", 438 | "cpu": [ 439 | "x64" 440 | ], 441 | "dev": true, 442 | "optional": true, 443 | "os": [ 444 | "sunos" 445 | ], 446 | "engines": { 447 | "node": ">=12" 448 | } 449 | }, 450 | "node_modules/@esbuild/win32-arm64": { 451 | "version": "0.17.19", 452 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", 453 | "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", 454 | "cpu": [ 455 | "arm64" 456 | ], 457 | "dev": true, 458 | "optional": true, 459 | "os": [ 460 | "win32" 461 | ], 462 | "engines": { 463 | "node": ">=12" 464 | } 465 | }, 466 | "node_modules/@esbuild/win32-ia32": { 467 | "version": "0.17.19", 468 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", 469 | "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", 470 | "cpu": [ 471 | "ia32" 472 | ], 473 | "dev": true, 474 | "optional": true, 475 | "os": [ 476 | "win32" 477 | ], 478 | "engines": { 479 | "node": ">=12" 480 | } 481 | }, 482 | "node_modules/@esbuild/win32-x64": { 483 | "version": "0.17.19", 484 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", 485 | "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", 486 | "cpu": [ 487 | "x64" 488 | ], 489 | "dev": true, 490 | "optional": true, 491 | "os": [ 492 | "win32" 493 | ], 494 | "engines": { 495 | "node": ">=12" 496 | } 497 | }, 498 | "node_modules/@fastify/busboy": { 499 | "version": "2.1.1", 500 | "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", 501 | "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", 502 | "dev": true, 503 | "engines": { 504 | "node": ">=14" 505 | } 506 | }, 507 | "node_modules/@jridgewell/resolve-uri": { 508 | "version": "3.1.2", 509 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 510 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 511 | "dev": true, 512 | "engines": { 513 | "node": ">=6.0.0" 514 | } 515 | }, 516 | "node_modules/@jridgewell/sourcemap-codec": { 517 | "version": "1.4.15", 518 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 519 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 520 | "dev": true 521 | }, 522 | "node_modules/@jridgewell/trace-mapping": { 523 | "version": "0.3.9", 524 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 525 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 526 | "dev": true, 527 | "dependencies": { 528 | "@jridgewell/resolve-uri": "^3.0.3", 529 | "@jridgewell/sourcemap-codec": "^1.4.10" 530 | } 531 | }, 532 | "node_modules/@types/node": { 533 | "version": "20.11.24", 534 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz", 535 | "integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==", 536 | "dev": true, 537 | "dependencies": { 538 | "undici-types": "~5.26.4" 539 | } 540 | }, 541 | "node_modules/@types/node-forge": { 542 | "version": "1.3.11", 543 | "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", 544 | "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", 545 | "dev": true, 546 | "dependencies": { 547 | "@types/node": "*" 548 | } 549 | }, 550 | "node_modules/acorn": { 551 | "version": "8.11.3", 552 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", 553 | "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", 554 | "dev": true, 555 | "bin": { 556 | "acorn": "bin/acorn" 557 | }, 558 | "engines": { 559 | "node": ">=0.4.0" 560 | } 561 | }, 562 | "node_modules/acorn-walk": { 563 | "version": "8.3.2", 564 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", 565 | "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", 566 | "dev": true, 567 | "engines": { 568 | "node": ">=0.4.0" 569 | } 570 | }, 571 | "node_modules/anymatch": { 572 | "version": "3.1.3", 573 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 574 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 575 | "dev": true, 576 | "dependencies": { 577 | "normalize-path": "^3.0.0", 578 | "picomatch": "^2.0.4" 579 | }, 580 | "engines": { 581 | "node": ">= 8" 582 | } 583 | }, 584 | "node_modules/as-table": { 585 | "version": "1.0.55", 586 | "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", 587 | "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", 588 | "dev": true, 589 | "dependencies": { 590 | "printable-characters": "^1.0.42" 591 | } 592 | }, 593 | "node_modules/binary-extensions": { 594 | "version": "2.2.0", 595 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 596 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 597 | "dev": true, 598 | "engines": { 599 | "node": ">=8" 600 | } 601 | }, 602 | "node_modules/blake3-wasm": { 603 | "version": "2.1.5", 604 | "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", 605 | "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", 606 | "dev": true 607 | }, 608 | "node_modules/braces": { 609 | "version": "3.0.3", 610 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 611 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 612 | "dev": true, 613 | "dependencies": { 614 | "fill-range": "^7.1.1" 615 | }, 616 | "engines": { 617 | "node": ">=8" 618 | } 619 | }, 620 | "node_modules/capnp-ts": { 621 | "version": "0.7.0", 622 | "resolved": "https://registry.npmjs.org/capnp-ts/-/capnp-ts-0.7.0.tgz", 623 | "integrity": "sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==", 624 | "dev": true, 625 | "dependencies": { 626 | "debug": "^4.3.1", 627 | "tslib": "^2.2.0" 628 | } 629 | }, 630 | "node_modules/chokidar": { 631 | "version": "3.6.0", 632 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", 633 | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", 634 | "dev": true, 635 | "dependencies": { 636 | "anymatch": "~3.1.2", 637 | "braces": "~3.0.2", 638 | "glob-parent": "~5.1.2", 639 | "is-binary-path": "~2.1.0", 640 | "is-glob": "~4.0.1", 641 | "normalize-path": "~3.0.0", 642 | "readdirp": "~3.6.0" 643 | }, 644 | "engines": { 645 | "node": ">= 8.10.0" 646 | }, 647 | "funding": { 648 | "url": "https://paulmillr.com/funding/" 649 | }, 650 | "optionalDependencies": { 651 | "fsevents": "~2.3.2" 652 | } 653 | }, 654 | "node_modules/cookie": { 655 | "version": "0.5.0", 656 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", 657 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", 658 | "dev": true, 659 | "engines": { 660 | "node": ">= 0.6" 661 | } 662 | }, 663 | "node_modules/data-uri-to-buffer": { 664 | "version": "2.0.2", 665 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", 666 | "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", 667 | "dev": true 668 | }, 669 | "node_modules/debug": { 670 | "version": "4.3.4", 671 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 672 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 673 | "dev": true, 674 | "dependencies": { 675 | "ms": "2.1.2" 676 | }, 677 | "engines": { 678 | "node": ">=6.0" 679 | }, 680 | "peerDependenciesMeta": { 681 | "supports-color": { 682 | "optional": true 683 | } 684 | } 685 | }, 686 | "node_modules/esbuild": { 687 | "version": "0.17.19", 688 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", 689 | "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", 690 | "dev": true, 691 | "hasInstallScript": true, 692 | "bin": { 693 | "esbuild": "bin/esbuild" 694 | }, 695 | "engines": { 696 | "node": ">=12" 697 | }, 698 | "optionalDependencies": { 699 | "@esbuild/android-arm": "0.17.19", 700 | "@esbuild/android-arm64": "0.17.19", 701 | "@esbuild/android-x64": "0.17.19", 702 | "@esbuild/darwin-arm64": "0.17.19", 703 | "@esbuild/darwin-x64": "0.17.19", 704 | "@esbuild/freebsd-arm64": "0.17.19", 705 | "@esbuild/freebsd-x64": "0.17.19", 706 | "@esbuild/linux-arm": "0.17.19", 707 | "@esbuild/linux-arm64": "0.17.19", 708 | "@esbuild/linux-ia32": "0.17.19", 709 | "@esbuild/linux-loong64": "0.17.19", 710 | "@esbuild/linux-mips64el": "0.17.19", 711 | "@esbuild/linux-ppc64": "0.17.19", 712 | "@esbuild/linux-riscv64": "0.17.19", 713 | "@esbuild/linux-s390x": "0.17.19", 714 | "@esbuild/linux-x64": "0.17.19", 715 | "@esbuild/netbsd-x64": "0.17.19", 716 | "@esbuild/openbsd-x64": "0.17.19", 717 | "@esbuild/sunos-x64": "0.17.19", 718 | "@esbuild/win32-arm64": "0.17.19", 719 | "@esbuild/win32-ia32": "0.17.19", 720 | "@esbuild/win32-x64": "0.17.19" 721 | } 722 | }, 723 | "node_modules/escape-string-regexp": { 724 | "version": "4.0.0", 725 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 726 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 727 | "dev": true, 728 | "engines": { 729 | "node": ">=10" 730 | }, 731 | "funding": { 732 | "url": "https://github.com/sponsors/sindresorhus" 733 | } 734 | }, 735 | "node_modules/estree-walker": { 736 | "version": "0.6.1", 737 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", 738 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", 739 | "dev": true 740 | }, 741 | "node_modules/exit-hook": { 742 | "version": "2.2.1", 743 | "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", 744 | "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", 745 | "dev": true, 746 | "engines": { 747 | "node": ">=6" 748 | }, 749 | "funding": { 750 | "url": "https://github.com/sponsors/sindresorhus" 751 | } 752 | }, 753 | "node_modules/fill-range": { 754 | "version": "7.1.1", 755 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 756 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 757 | "dev": true, 758 | "dependencies": { 759 | "to-regex-range": "^5.0.1" 760 | }, 761 | "engines": { 762 | "node": ">=8" 763 | } 764 | }, 765 | "node_modules/fsevents": { 766 | "version": "2.3.3", 767 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 768 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 769 | "dev": true, 770 | "hasInstallScript": true, 771 | "optional": true, 772 | "os": [ 773 | "darwin" 774 | ], 775 | "engines": { 776 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 777 | } 778 | }, 779 | "node_modules/function-bind": { 780 | "version": "1.1.2", 781 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 782 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 783 | "dev": true, 784 | "funding": { 785 | "url": "https://github.com/sponsors/ljharb" 786 | } 787 | }, 788 | "node_modules/get-source": { 789 | "version": "2.0.12", 790 | "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", 791 | "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", 792 | "dev": true, 793 | "dependencies": { 794 | "data-uri-to-buffer": "^2.0.0", 795 | "source-map": "^0.6.1" 796 | } 797 | }, 798 | "node_modules/glob-parent": { 799 | "version": "5.1.2", 800 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 801 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 802 | "dev": true, 803 | "dependencies": { 804 | "is-glob": "^4.0.1" 805 | }, 806 | "engines": { 807 | "node": ">= 6" 808 | } 809 | }, 810 | "node_modules/glob-to-regexp": { 811 | "version": "0.4.1", 812 | "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", 813 | "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", 814 | "dev": true 815 | }, 816 | "node_modules/hasown": { 817 | "version": "2.0.1", 818 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", 819 | "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", 820 | "dev": true, 821 | "dependencies": { 822 | "function-bind": "^1.1.2" 823 | }, 824 | "engines": { 825 | "node": ">= 0.4" 826 | } 827 | }, 828 | "node_modules/is-binary-path": { 829 | "version": "2.1.0", 830 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 831 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 832 | "dev": true, 833 | "dependencies": { 834 | "binary-extensions": "^2.0.0" 835 | }, 836 | "engines": { 837 | "node": ">=8" 838 | } 839 | }, 840 | "node_modules/is-core-module": { 841 | "version": "2.13.1", 842 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", 843 | "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", 844 | "dev": true, 845 | "dependencies": { 846 | "hasown": "^2.0.0" 847 | }, 848 | "funding": { 849 | "url": "https://github.com/sponsors/ljharb" 850 | } 851 | }, 852 | "node_modules/is-extglob": { 853 | "version": "2.1.1", 854 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 855 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 856 | "dev": true, 857 | "engines": { 858 | "node": ">=0.10.0" 859 | } 860 | }, 861 | "node_modules/is-glob": { 862 | "version": "4.0.3", 863 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 864 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 865 | "dev": true, 866 | "dependencies": { 867 | "is-extglob": "^2.1.1" 868 | }, 869 | "engines": { 870 | "node": ">=0.10.0" 871 | } 872 | }, 873 | "node_modules/is-number": { 874 | "version": "7.0.0", 875 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 876 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 877 | "dev": true, 878 | "engines": { 879 | "node": ">=0.12.0" 880 | } 881 | }, 882 | "node_modules/magic-string": { 883 | "version": "0.25.9", 884 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", 885 | "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", 886 | "dev": true, 887 | "dependencies": { 888 | "sourcemap-codec": "^1.4.8" 889 | } 890 | }, 891 | "node_modules/mime": { 892 | "version": "3.0.0", 893 | "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", 894 | "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", 895 | "dev": true, 896 | "bin": { 897 | "mime": "cli.js" 898 | }, 899 | "engines": { 900 | "node": ">=10.0.0" 901 | } 902 | }, 903 | "node_modules/miniflare": { 904 | "version": "3.20240223.0", 905 | "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240223.0.tgz", 906 | "integrity": "sha512-8T/36FEfvsL4aMF7SLZ28v+PQL0jsUlVw/u114GYcdobkyPax9E6Ahn0XePOHEqLxQSndwPee+eS1phHANFePA==", 907 | "dev": true, 908 | "dependencies": { 909 | "@cspotcode/source-map-support": "0.8.1", 910 | "acorn": "^8.8.0", 911 | "acorn-walk": "^8.2.0", 912 | "capnp-ts": "^0.7.0", 913 | "exit-hook": "^2.2.1", 914 | "glob-to-regexp": "^0.4.1", 915 | "stoppable": "^1.1.0", 916 | "undici": "^5.28.2", 917 | "workerd": "1.20240223.1", 918 | "ws": "^8.11.0", 919 | "youch": "^3.2.2", 920 | "zod": "^3.20.6" 921 | }, 922 | "bin": { 923 | "miniflare": "bootstrap.js" 924 | }, 925 | "engines": { 926 | "node": ">=16.13" 927 | } 928 | }, 929 | "node_modules/ms": { 930 | "version": "2.1.2", 931 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 932 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 933 | "dev": true 934 | }, 935 | "node_modules/mustache": { 936 | "version": "4.2.0", 937 | "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", 938 | "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", 939 | "dev": true, 940 | "bin": { 941 | "mustache": "bin/mustache" 942 | } 943 | }, 944 | "node_modules/nanoid": { 945 | "version": "3.3.7", 946 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", 947 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", 948 | "dev": true, 949 | "funding": [ 950 | { 951 | "type": "github", 952 | "url": "https://github.com/sponsors/ai" 953 | } 954 | ], 955 | "bin": { 956 | "nanoid": "bin/nanoid.cjs" 957 | }, 958 | "engines": { 959 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 960 | } 961 | }, 962 | "node_modules/node-forge": { 963 | "version": "1.3.1", 964 | "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", 965 | "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", 966 | "dev": true, 967 | "engines": { 968 | "node": ">= 6.13.0" 969 | } 970 | }, 971 | "node_modules/normalize-path": { 972 | "version": "3.0.0", 973 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 974 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 975 | "dev": true, 976 | "engines": { 977 | "node": ">=0.10.0" 978 | } 979 | }, 980 | "node_modules/path-parse": { 981 | "version": "1.0.7", 982 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 983 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 984 | "dev": true 985 | }, 986 | "node_modules/path-to-regexp": { 987 | "version": "6.2.1", 988 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", 989 | "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", 990 | "dev": true 991 | }, 992 | "node_modules/picomatch": { 993 | "version": "2.3.1", 994 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 995 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 996 | "dev": true, 997 | "engines": { 998 | "node": ">=8.6" 999 | }, 1000 | "funding": { 1001 | "url": "https://github.com/sponsors/jonschlinkert" 1002 | } 1003 | }, 1004 | "node_modules/prettier": { 1005 | "version": "3.1.1", 1006 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", 1007 | "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", 1008 | "dev": true, 1009 | "bin": { 1010 | "prettier": "bin/prettier.cjs" 1011 | }, 1012 | "engines": { 1013 | "node": ">=14" 1014 | }, 1015 | "funding": { 1016 | "url": "https://github.com/prettier/prettier?sponsor=1" 1017 | } 1018 | }, 1019 | "node_modules/printable-characters": { 1020 | "version": "1.0.42", 1021 | "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", 1022 | "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", 1023 | "dev": true 1024 | }, 1025 | "node_modules/readdirp": { 1026 | "version": "3.6.0", 1027 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1028 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1029 | "dev": true, 1030 | "dependencies": { 1031 | "picomatch": "^2.2.1" 1032 | }, 1033 | "engines": { 1034 | "node": ">=8.10.0" 1035 | } 1036 | }, 1037 | "node_modules/resolve": { 1038 | "version": "1.22.8", 1039 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", 1040 | "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", 1041 | "dev": true, 1042 | "dependencies": { 1043 | "is-core-module": "^2.13.0", 1044 | "path-parse": "^1.0.7", 1045 | "supports-preserve-symlinks-flag": "^1.0.0" 1046 | }, 1047 | "bin": { 1048 | "resolve": "bin/resolve" 1049 | }, 1050 | "funding": { 1051 | "url": "https://github.com/sponsors/ljharb" 1052 | } 1053 | }, 1054 | "node_modules/resolve.exports": { 1055 | "version": "2.0.2", 1056 | "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", 1057 | "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", 1058 | "dev": true, 1059 | "engines": { 1060 | "node": ">=10" 1061 | } 1062 | }, 1063 | "node_modules/rollup-plugin-inject": { 1064 | "version": "3.0.2", 1065 | "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", 1066 | "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", 1067 | "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.", 1068 | "dev": true, 1069 | "dependencies": { 1070 | "estree-walker": "^0.6.1", 1071 | "magic-string": "^0.25.3", 1072 | "rollup-pluginutils": "^2.8.1" 1073 | } 1074 | }, 1075 | "node_modules/rollup-plugin-node-polyfills": { 1076 | "version": "0.2.1", 1077 | "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", 1078 | "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", 1079 | "dev": true, 1080 | "dependencies": { 1081 | "rollup-plugin-inject": "^3.0.0" 1082 | } 1083 | }, 1084 | "node_modules/rollup-pluginutils": { 1085 | "version": "2.8.2", 1086 | "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", 1087 | "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", 1088 | "dev": true, 1089 | "dependencies": { 1090 | "estree-walker": "^0.6.1" 1091 | } 1092 | }, 1093 | "node_modules/selfsigned": { 1094 | "version": "2.4.1", 1095 | "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", 1096 | "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", 1097 | "dev": true, 1098 | "dependencies": { 1099 | "@types/node-forge": "^1.3.0", 1100 | "node-forge": "^1" 1101 | }, 1102 | "engines": { 1103 | "node": ">=10" 1104 | } 1105 | }, 1106 | "node_modules/source-map": { 1107 | "version": "0.6.1", 1108 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 1109 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 1110 | "dev": true, 1111 | "engines": { 1112 | "node": ">=0.10.0" 1113 | } 1114 | }, 1115 | "node_modules/sourcemap-codec": { 1116 | "version": "1.4.8", 1117 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", 1118 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", 1119 | "deprecated": "Please use @jridgewell/sourcemap-codec instead", 1120 | "dev": true 1121 | }, 1122 | "node_modules/stacktracey": { 1123 | "version": "2.1.8", 1124 | "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", 1125 | "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", 1126 | "dev": true, 1127 | "dependencies": { 1128 | "as-table": "^1.0.36", 1129 | "get-source": "^2.0.12" 1130 | } 1131 | }, 1132 | "node_modules/stoppable": { 1133 | "version": "1.1.0", 1134 | "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", 1135 | "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", 1136 | "dev": true, 1137 | "engines": { 1138 | "node": ">=4", 1139 | "npm": ">=6" 1140 | } 1141 | }, 1142 | "node_modules/supports-preserve-symlinks-flag": { 1143 | "version": "1.0.0", 1144 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 1145 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 1146 | "dev": true, 1147 | "engines": { 1148 | "node": ">= 0.4" 1149 | }, 1150 | "funding": { 1151 | "url": "https://github.com/sponsors/ljharb" 1152 | } 1153 | }, 1154 | "node_modules/to-regex-range": { 1155 | "version": "5.0.1", 1156 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1157 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1158 | "dev": true, 1159 | "dependencies": { 1160 | "is-number": "^7.0.0" 1161 | }, 1162 | "engines": { 1163 | "node": ">=8.0" 1164 | } 1165 | }, 1166 | "node_modules/tslib": { 1167 | "version": "2.6.2", 1168 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", 1169 | "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", 1170 | "dev": true 1171 | }, 1172 | "node_modules/typescript": { 1173 | "version": "5.3.3", 1174 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", 1175 | "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", 1176 | "dev": true, 1177 | "bin": { 1178 | "tsc": "bin/tsc", 1179 | "tsserver": "bin/tsserver" 1180 | }, 1181 | "engines": { 1182 | "node": ">=14.17" 1183 | } 1184 | }, 1185 | "node_modules/undici": { 1186 | "version": "5.28.4", 1187 | "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", 1188 | "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", 1189 | "dev": true, 1190 | "dependencies": { 1191 | "@fastify/busboy": "^2.0.0" 1192 | }, 1193 | "engines": { 1194 | "node": ">=14.0" 1195 | } 1196 | }, 1197 | "node_modules/undici-types": { 1198 | "version": "5.26.5", 1199 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 1200 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 1201 | "dev": true 1202 | }, 1203 | "node_modules/workerd": { 1204 | "version": "1.20240223.1", 1205 | "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20240223.1.tgz", 1206 | "integrity": "sha512-Mo1fwdp6DLva4/fWdL09ZdYllkO45I4YpWG5PbF/YUGFlu2aMk24fmU6Pd6fo5/cWek4F+n3LmYEKKHfqjiJIA==", 1207 | "dev": true, 1208 | "hasInstallScript": true, 1209 | "bin": { 1210 | "workerd": "bin/workerd" 1211 | }, 1212 | "engines": { 1213 | "node": ">=16" 1214 | }, 1215 | "optionalDependencies": { 1216 | "@cloudflare/workerd-darwin-64": "1.20240223.1", 1217 | "@cloudflare/workerd-darwin-arm64": "1.20240223.1", 1218 | "@cloudflare/workerd-linux-64": "1.20240223.1", 1219 | "@cloudflare/workerd-linux-arm64": "1.20240223.1", 1220 | "@cloudflare/workerd-windows-64": "1.20240223.1" 1221 | } 1222 | }, 1223 | "node_modules/wrangler": { 1224 | "version": "3.30.1", 1225 | "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.30.1.tgz", 1226 | "integrity": "sha512-cT6Ezx8h2v5QiI0HWhnHVy32ng4omdMVdhaMQLuMnyMIHmyDoRg7pmrbhtZfj0663gExLdVtE4ucK//yncVTwg==", 1227 | "dev": true, 1228 | "dependencies": { 1229 | "@cloudflare/kv-asset-handler": "0.3.1", 1230 | "@esbuild-plugins/node-globals-polyfill": "^0.2.3", 1231 | "@esbuild-plugins/node-modules-polyfill": "^0.2.2", 1232 | "blake3-wasm": "^2.1.5", 1233 | "chokidar": "^3.5.3", 1234 | "esbuild": "0.17.19", 1235 | "miniflare": "3.20240223.0", 1236 | "nanoid": "^3.3.3", 1237 | "path-to-regexp": "^6.2.0", 1238 | "resolve": "^1.22.8", 1239 | "resolve.exports": "^2.0.2", 1240 | "selfsigned": "^2.0.1", 1241 | "source-map": "0.6.1", 1242 | "xxhash-wasm": "^1.0.1" 1243 | }, 1244 | "bin": { 1245 | "wrangler": "bin/wrangler.js", 1246 | "wrangler2": "bin/wrangler.js" 1247 | }, 1248 | "engines": { 1249 | "node": ">=16.17.0" 1250 | }, 1251 | "optionalDependencies": { 1252 | "fsevents": "~2.3.2" 1253 | }, 1254 | "peerDependencies": { 1255 | "@cloudflare/workers-types": "^4.20230914.0" 1256 | }, 1257 | "peerDependenciesMeta": { 1258 | "@cloudflare/workers-types": { 1259 | "optional": true 1260 | } 1261 | } 1262 | }, 1263 | "node_modules/ws": { 1264 | "version": "8.17.1", 1265 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", 1266 | "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", 1267 | "dev": true, 1268 | "engines": { 1269 | "node": ">=10.0.0" 1270 | }, 1271 | "peerDependencies": { 1272 | "bufferutil": "^4.0.1", 1273 | "utf-8-validate": ">=5.0.2" 1274 | }, 1275 | "peerDependenciesMeta": { 1276 | "bufferutil": { 1277 | "optional": true 1278 | }, 1279 | "utf-8-validate": { 1280 | "optional": true 1281 | } 1282 | } 1283 | }, 1284 | "node_modules/xxhash-wasm": { 1285 | "version": "1.0.2", 1286 | "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz", 1287 | "integrity": "sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==", 1288 | "dev": true 1289 | }, 1290 | "node_modules/youch": { 1291 | "version": "3.3.3", 1292 | "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.3.tgz", 1293 | "integrity": "sha512-qSFXUk3UZBLfggAW3dJKg0BMblG5biqSF8M34E06o5CSsZtH92u9Hqmj2RzGiHDi64fhe83+4tENFP2DB6t6ZA==", 1294 | "dev": true, 1295 | "dependencies": { 1296 | "cookie": "^0.5.0", 1297 | "mustache": "^4.2.0", 1298 | "stacktracey": "^2.1.8" 1299 | } 1300 | }, 1301 | "node_modules/zod": { 1302 | "version": "3.22.4", 1303 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", 1304 | "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", 1305 | "dev": true, 1306 | "funding": { 1307 | "url": "https://github.com/sponsors/colinhacks" 1308 | } 1309 | } 1310 | } 1311 | } 1312 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cf-r2-webdav", 3 | "version": "0.3.0", 4 | "private": true, 5 | "scripts": { 6 | "deploy": "wrangler deploy", 7 | "dev": "wrangler dev", 8 | "start": "wrangler dev", 9 | "lint": "prettier . --write", 10 | "check": "prettier . --check" 11 | }, 12 | "devDependencies": { 13 | "@cloudflare/workers-types": "^4.20231121.0", 14 | "prettier": "3.1.1", 15 | "typescript": "^5.0.4", 16 | "wrangler": "^3.0.0" 17 | }, 18 | "type": "module" 19 | } -------------------------------------------------------------------------------- /src/handlers/requestHandler.ts: -------------------------------------------------------------------------------- 1 | import { Env } from '../types'; 2 | import { handleWebDAV } from './webdavHandler'; 3 | import { authenticate } from '../utils/auth'; 4 | import { setCORSHeaders } from '../utils/cors'; 5 | import { logger } from '../utils/logger'; 6 | 7 | export async function handleRequest(request: Request, env: Env, ctx: ExecutionContext): Promise { 8 | try { 9 | if (request.method !== "OPTIONS" && !authenticate(request, env)) { 10 | return new Response("Unauthorized", { 11 | status: 401, 12 | headers: { 13 | "WWW-Authenticate": 'Basic realm="WebDAV"' 14 | } 15 | }); 16 | } 17 | 18 | // 直接传递整个 env 对象给 handleWebDAV 19 | const response = await handleWebDAV(request, env); 20 | 21 | setCORSHeaders(response, request); 22 | return response; 23 | } catch (error) { 24 | logger.error("Error in request handling:", error); 25 | return new Response("Internal Server Error", { status: 500 }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/handlers/webdavHandler.ts: -------------------------------------------------------------------------------- 1 | // 文件名:src/handlers/webdavHandler.ts 2 | import { listAll, fromR2Object, make_resource_path, generatePropfindResponse } from '../utils/webdavUtils'; 3 | import { logger } from '../utils/logger'; 4 | import { generateHTML, generateErrorHTML } from '../utils/templates'; 5 | import { WebDAVProps, Env } from '../types'; 6 | import { authenticate } from '../utils/auth'; 7 | 8 | const SUPPORT_METHODS = ["OPTIONS", "PROPFIND", "MKCOL", "GET", "HEAD", "PUT", "COPY", "MOVE", "DELETE"]; 9 | const DAV_CLASS = "1, 2"; 10 | 11 | export async function handleWebDAV(request: Request, env: Env): Promise { 12 | const { BUCKET, BUCKET_NAME } = env; // 从 env 中获取 BUCKET 和 BUCKET_NAME 13 | 14 | try { 15 | switch (request.method) { 16 | // 原来的处理逻辑不变 17 | case "OPTIONS": 18 | return handleOptions(); 19 | case "HEAD": 20 | return await handleHead(request, BUCKET); 21 | case "GET": 22 | return await handleGet(request, BUCKET, BUCKET_NAME); 23 | case "PUT": 24 | return await handlePut(request, BUCKET); 25 | case "DELETE": 26 | return await handleDelete(request, BUCKET); 27 | case "MKCOL": 28 | return await handleMkcol(request, BUCKET); 29 | case "PROPFIND": 30 | return await handlePropfind(request, BUCKET, BUCKET_NAME); 31 | case "COPY": 32 | return await handleCopy(request, BUCKET); 33 | case "MOVE": 34 | return await handleMove(request, BUCKET); 35 | default: 36 | return new Response("Method Not Allowed", { 37 | status: 405, 38 | headers: { 39 | Allow: SUPPORT_METHODS.join(", "), 40 | DAV: DAV_CLASS 41 | } 42 | }); 43 | } 44 | } catch (error) { 45 | const err = error as Error; 46 | logger.error("Error in WebDAV handling:", err.message); 47 | return new Response(generateErrorHTML("Internal Server Error", err.message), { 48 | status: 500, 49 | headers: { "Content-Type": "text/html; charset=utf-8" } 50 | }); 51 | } 52 | } 53 | 54 | function handleOptions(): Response { 55 | return new Response(null, { 56 | status: 200, 57 | headers: { 58 | Allow: SUPPORT_METHODS.join(", "), 59 | DAV: DAV_CLASS, 60 | "Access-Control-Allow-Methods": SUPPORT_METHODS.join(", "), 61 | "Access-Control-Allow-Origin": "*", 62 | "Access-Control-Allow-Headers": "Authorization, Content-Type, Depth, Overwrite, Destination, Range", 63 | "Access-Control-Expose-Headers": "Content-Type, Content-Length, DAV, ETag, Last-Modified, Location, Date, Content-Range", 64 | "Access-Control-Allow-Credentials": "true", 65 | "Access-Control-Max-Age": "86400" 66 | } 67 | }); 68 | } 69 | 70 | async function handleHead(request: Request, bucket: R2Bucket): Promise { 71 | const resource_path = make_resource_path(request); 72 | const object = await bucket.head(resource_path); 73 | 74 | if (!object) { 75 | return new Response(null, { status: 404 }); 76 | } 77 | 78 | return new Response(null, { 79 | status: 200, 80 | headers: { 81 | "Content-Type": object.httpMetadata?.contentType ?? "application/octet-stream", 82 | "Content-Length": object.size.toString(), 83 | "ETag": object.etag, 84 | "Last-Modified": object.uploaded.toUTCString() 85 | } 86 | }); 87 | } 88 | 89 | async function handleGet(request: Request, bucket: R2Bucket, bucketName: string): Promise { 90 | const resource_path = make_resource_path(request); 91 | 92 | if (request.url.endsWith("/")) { 93 | return await handleDirectory(bucket, resource_path, bucketName); 94 | } else { 95 | return await handleFile(bucket, resource_path); 96 | } 97 | } 98 | 99 | async function handleDirectory(bucket: R2Bucket, resource_path: string, bucketName: string): Promise { 100 | let items = []; 101 | 102 | if (resource_path !== "") { 103 | items.push({ name: "📁 ..", href: "../" }); 104 | } 105 | 106 | try { 107 | for await (const object of listAll(bucket, resource_path)) { 108 | if (object.key === resource_path) continue; 109 | const isDirectory = object.customMetadata?.resourcetype === "collection"; 110 | const displayName = object.key.split('/').pop() || object.key; 111 | const href = `/${object.key}${isDirectory ? "/" : ""}`; 112 | items.push({ name: `${isDirectory ? '📁 ' : '📄 '}${displayName}`, href }); 113 | } 114 | } catch (error) { 115 | const err = error as Error; 116 | logger.error("Error listing objects:", err.message); 117 | return new Response(generateErrorHTML("Error listing directory contents", err.message), { 118 | status: 500, 119 | headers: { "Content-Type": "text/html; charset=utf-8" } 120 | }); 121 | } 122 | 123 | const page = generateHTML("WebDAV File Browser", items); 124 | return new Response(page, { 125 | status: 200, 126 | headers: { "Content-Type": "text/html; charset=utf-8" } 127 | }); 128 | } 129 | 130 | async function handleFile(bucket: R2Bucket, resource_path: string): Promise { 131 | try { 132 | const object = await bucket.get(resource_path); 133 | if (!object) { 134 | return new Response("Not Found", { status: 404 }); 135 | } 136 | return new Response(object.body, { 137 | status: 200, 138 | headers: { 139 | "Content-Type": object.httpMetadata?.contentType ?? "application/octet-stream", 140 | "Content-Length": object.size.toString(), 141 | "ETag": object.etag, 142 | "Last-Modified": object.uploaded.toUTCString() 143 | } 144 | }); 145 | } catch (error) { 146 | const err = error as Error; 147 | logger.error("Error getting object:", err.message); 148 | return new Response(generateErrorHTML("Error retrieving file", err.message), { 149 | status: 500, 150 | headers: { "Content-Type": "text/html; charset=utf-8" } 151 | }); 152 | } 153 | } 154 | 155 | async function handlePut(request: Request, bucket: R2Bucket): Promise { 156 | const resource_path = make_resource_path(request); 157 | 158 | try { 159 | const body = await request.arrayBuffer(); 160 | await bucket.put(resource_path, body, { 161 | httpMetadata: { 162 | contentType: request.headers.get("Content-Type") || "application/octet-stream", 163 | }, 164 | }); 165 | return new Response("Created", { status: 201 }); 166 | } catch (error) { 167 | const err = error as Error; 168 | logger.error("Error uploading file:", err.message); 169 | return new Response(generateErrorHTML("Error uploading file", err.message), { 170 | status: 500, 171 | headers: { "Content-Type": "text/html; charset=utf-8" } 172 | }); 173 | } 174 | } 175 | 176 | async function handleDelete(request: Request, bucket: R2Bucket): Promise { 177 | const resource_path = make_resource_path(request); 178 | 179 | try { 180 | await bucket.delete(resource_path); 181 | return new Response("No Content", { status: 204 }); 182 | } catch (error) { 183 | const err = error as Error; 184 | logger.error("Error deleting object:", err.message); 185 | return new Response(generateErrorHTML("Error deleting file", err.message), { 186 | status: 500, 187 | headers: { "Content-Type": "text/html; charset=utf-8" } 188 | }); 189 | } 190 | } 191 | 192 | async function handleMkcol(request: Request, bucket: R2Bucket): Promise { 193 | const resource_path = make_resource_path(request); 194 | 195 | if (resource_path === "") { 196 | return new Response("Method Not Allowed", { status: 405 }); 197 | } 198 | 199 | try { 200 | await bucket.put(resource_path + "/", new Uint8Array(), { 201 | customMetadata: { resourcetype: "collection" } 202 | }); 203 | return new Response("Created", { status: 201 }); 204 | } catch (error) { 205 | const err = error as Error; 206 | logger.error("Error creating collection:", err.message); 207 | return new Response(generateErrorHTML("Error creating collection", err.message), { 208 | status: 500, 209 | headers: { "Content-Type": "text/html; charset=utf-8" } 210 | }); 211 | } 212 | } 213 | 214 | async function handlePropfind(request: Request, bucket: R2Bucket, bucketName: string): Promise { 215 | const resource_path = make_resource_path(request); 216 | const depth = request.headers.get("Depth") || "infinity"; 217 | 218 | try { 219 | const props: WebDAVProps[] = []; 220 | if (depth !== "0") { 221 | for await (const object of listAll(bucket, resource_path)) { 222 | props.push(fromR2Object(object)); 223 | } 224 | } else { 225 | const object = await bucket.head(resource_path); 226 | if (object) { 227 | props.push(fromR2Object(object)); 228 | } else { 229 | return new Response("Not Found", { status: 404 }); 230 | } 231 | } 232 | 233 | const xml = generatePropfindResponse(bucketName, resource_path, props); 234 | logger.info("Generated XML for PROPFIND:", xml); 235 | return new Response(xml, { 236 | status: 207, 237 | headers: { "Content-Type": "application/xml; charset=utf-8" } 238 | }); 239 | } catch (error) { 240 | const err = error as Error; 241 | logger.error("Error in PROPFIND:", err.message); 242 | return new Response(generateErrorHTML("Error in PROPFIND", err.message), { 243 | status: 500, 244 | headers: { "Content-Type": "application/xml; charset=utf-8" } 245 | }); 246 | } 247 | } 248 | 249 | async function handleCopy(request: Request, bucket: R2Bucket): Promise { 250 | const sourcePath = make_resource_path(request); 251 | const destinationHeader = request.headers.get("Destination"); 252 | if (!destinationHeader) { 253 | return new Response("Bad Request: Destination header is missing", { status: 400 }); 254 | } 255 | const destinationUrl = new URL(destinationHeader); 256 | const destinationPath = make_resource_path(new Request(destinationUrl)); 257 | 258 | try { 259 | const sourceObject = await bucket.get(sourcePath); 260 | if (!sourceObject) { 261 | return new Response("Not Found", { status: 404 }); 262 | } 263 | 264 | await bucket.put(destinationPath, sourceObject.body, { 265 | httpMetadata: sourceObject.httpMetadata, 266 | customMetadata: sourceObject.customMetadata 267 | }); 268 | 269 | return new Response("Created", { status: 201 }); 270 | } catch (error) { 271 | const err = error as Error; 272 | logger.error("Error copying object:", err.message); 273 | return new Response(generateErrorHTML("Error copying file", err.message), { 274 | status: 500, 275 | headers: { "Content-Type": "text/html; charset=utf-8" } 276 | }); 277 | } 278 | } 279 | 280 | async function handleMove(request: Request, bucket: R2Bucket): Promise { 281 | const sourcePath = make_resource_path(request); 282 | const destinationHeader = request.headers.get("Destination"); 283 | if (!destinationHeader) { 284 | return new Response("Bad Request: Destination header is missing", { status: 400 }); 285 | } 286 | const destinationUrl = new URL(destinationHeader); 287 | const destinationPath = make_resource_path(new Request(destinationUrl)); 288 | 289 | try { 290 | const sourceObject = await bucket.get(sourcePath); 291 | if (!sourceObject) { 292 | return new Response("Not Found", { status: 404 }); 293 | } 294 | 295 | await bucket.put(destinationPath, sourceObject.body, { 296 | httpMetadata: sourceObject.httpMetadata, 297 | customMetadata: sourceObject.customMetadata 298 | }); 299 | 300 | await bucket.delete(sourcePath); 301 | return new Response("No Content", { status: 204 }); 302 | } catch (error) { 303 | const err = error as Error; 304 | logger.error("Error moving object:", err.message); 305 | return new Response(generateErrorHTML("Error moving file", err.message), { 306 | status: 500, 307 | headers: { "Content-Type": "text/html; charset=utf-8" } 308 | }); 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // 文件名:src/index.ts 2 | import { handleRequest } from './handlers/requestHandler'; 3 | import { Env } from './types'; 4 | 5 | export default { 6 | async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { 7 | return handleRequest(request, env, ctx); 8 | } 9 | }; -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | // 文件名:src/types.ts 2 | export interface Env { 3 | BUCKET: R2Bucket; 4 | USERNAME: string; 5 | PASSWORD: string; 6 | BUCKET_NAME: string; 7 | } 8 | 9 | export interface CacheableResponse { 10 | response: Response; 11 | expiry: number; 12 | } 13 | 14 | export interface WebDAVProps { 15 | creationdate: string; 16 | displayname: string | undefined; 17 | getcontentlanguage: string | undefined; 18 | getcontentlength: string; 19 | getcontenttype: string | undefined; 20 | getetag: string | undefined; 21 | getlastmodified: string; 22 | resourcetype: string; 23 | } -------------------------------------------------------------------------------- /src/utils/auth.ts: -------------------------------------------------------------------------------- 1 | // 文件名:src/utils/auth.ts 2 | import { Env } from '../types'; 3 | 4 | export function authenticate(request: Request, env: Env): boolean { 5 | const authHeader = request.headers.get("Authorization"); 6 | if (!authHeader) { 7 | return false; 8 | } 9 | 10 | const [authType, authValue] = authHeader.split(' '); 11 | if (authType.toLowerCase() !== 'basic') { 12 | return false; 13 | } 14 | 15 | const [username, password] = atob(authValue).split(':'); 16 | return username === env.USERNAME && password === env.PASSWORD; 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/cors.ts: -------------------------------------------------------------------------------- 1 | // 文件名:src/utils/cors.ts 2 | export function setCORSHeaders(response: Response, request: Request): void { 3 | const origin = request.headers.get("Origin"); 4 | if (origin) { 5 | response.headers.set("Access-Control-Allow-Origin", origin); 6 | } 7 | 8 | response.headers.set("Access-Control-Allow-Methods", "OPTIONS, PROPFIND, MKCOL, GET, HEAD, PUT, COPY, MOVE, DELETE"); 9 | response.headers.set("Access-Control-Allow-Headers", "Authorization, Content-Type, Depth, Overwrite, Destination, Range"); 10 | response.headers.set("Access-Control-Expose-Headers", "Content-Type, Content-Length, DAV, ETag, Last-Modified, Location, Date, Content-Range"); 11 | response.headers.set("Access-Control-Allow-Credentials", "true"); 12 | response.headers.set("Access-Control-Max-Age", "86400"); 13 | } -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | // 文件名:src/utils/logger.ts 2 | export const logger = { 3 | info: (message: string, ...args: any[]) => console.log(`[INFO] ${new Date().toISOString()} - ${message}`, ...args), 4 | error: (message: string, ...args: any[]) => console.error(`[ERROR] ${new Date().toISOString()} - ${message}`, ...args), 5 | warn: (message: string, ...args: any[]) => console.warn(`[WARN] ${new Date().toISOString()} - ${message}`, ...args), 6 | debug: (message: string, ...args: any[]) => { 7 | console.debug(`[DEBUG] ${new Date().toISOString()} - ${message}`, ...args); 8 | } 9 | }; -------------------------------------------------------------------------------- /src/utils/templates.ts: -------------------------------------------------------------------------------- 1 | export function generateHTML(title: string, items: { name: string, href: string }[]): string { 2 | return ` 3 | 4 | 5 | 6 | 7 | 8 | ${title} 9 | 41 | 42 | 43 |

${title}

44 |
45 | ${items.map(item => ` 46 |
47 | ${item.name} 48 |
49 | `).join('')} 50 |
51 | 52 | 53 | `; 54 | } 55 | 56 | export function generateErrorHTML(title: string, message: string): string { 57 | return ` 58 | 59 | 60 | 61 | 62 | 63 | ${title} 64 | 83 | 84 | 85 |

${title}

86 |
87 | ${message} 88 |
89 | 90 | 91 | `; 92 | } -------------------------------------------------------------------------------- /src/utils/webdavUtils.ts: -------------------------------------------------------------------------------- 1 | // 文件名:src/utils/webdavUtils.ts 2 | import { R2Object } from '@cloudflare/workers-types'; 3 | import { WebDAVProps } from '../types'; 4 | 5 | export function make_resource_path(request: Request): string { 6 | const url = new URL(request.url); 7 | return decodeURIComponent(url.pathname.slice(1)); 8 | } 9 | 10 | export async function* listAll(bucket: R2Bucket, prefix: string) { 11 | const options = { prefix, delimiter: "/" }; 12 | let result = await bucket.list(options); 13 | 14 | while (result.objects.length > 0) { 15 | for (const object of result.objects) { 16 | yield object; 17 | } 18 | 19 | if (result.truncated && result.cursor) { 20 | result = await bucket.list({ ...options, cursor: result.cursor }); 21 | } else { 22 | break; 23 | } 24 | } 25 | } 26 | 27 | export function fromR2Object(object: R2Object | null): WebDAVProps { 28 | if (!object) { 29 | return { 30 | creationdate: new Date().toUTCString(), 31 | displayname: undefined, 32 | getcontentlanguage: undefined, 33 | getcontentlength: "0", 34 | getcontenttype: undefined, 35 | getetag: undefined, 36 | getlastmodified: new Date().toUTCString(), 37 | resourcetype: "collection" 38 | }; 39 | } 40 | return { 41 | creationdate: object.uploaded.toUTCString(), 42 | displayname: object.key.split('/').pop(), 43 | getcontentlanguage: object.httpMetadata?.contentLanguage, 44 | getcontentlength: object.size.toString(), 45 | getcontenttype: object.httpMetadata?.contentType, 46 | getetag: object.etag, 47 | getlastmodified: object.uploaded.toUTCString(), 48 | resourcetype: object.customMetadata?.resourcetype || "" 49 | }; 50 | } 51 | 52 | export function generatePropfindResponse(bucketName: string, basePath: string, props: WebDAVProps[]): string { 53 | const responses = props.map(prop => generatePropResponse(bucketName, basePath, prop)).join("\n"); 54 | return ` 55 | 56 | ${responses} 57 | `; 58 | } 59 | 60 | function generatePropResponse(bucketName: string, basePath: string, prop: WebDAVProps): string { 61 | const resourcePath = `/${bucketName}/${basePath}${prop.displayname ? '/' + prop.displayname : ''}`; 62 | return ` 63 | ${resourcePath} 64 | 65 | 66 | ${prop.creationdate} 67 | ${prop.getcontentlength} 68 | ${prop.getcontenttype || ''} 69 | ${prop.getetag || ''} 70 | ${prop.getlastmodified} 71 | ${prop.resourcetype ? '' : ''} 72 | 73 | HTTP/1.1 200 OK 74 | 75 | `; 76 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2021", 4 | "module": "CommonJS", 5 | "lib": ["ES2021"], 6 | "types": ["@cloudflare/workers-types"], 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "baseUrl": ".", 12 | "paths": { 13 | "@/*": ["src/*"] 14 | } 15 | }, 16 | "include": ["src/**/*.ts"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /wrangler.toml.template: -------------------------------------------------------------------------------- 1 | name = "cf-r2-webdav" 2 | main = "src/index.ts" 3 | compatibility_date = "2023-01-01" 4 | 5 | [vars] 6 | USERNAME = "$USERNAME" 7 | PASSWORD = "$PASSWORD" 8 | BUCKET_NAME = "$BUCKET_NAME" 9 | 10 | [[r2_buckets]] 11 | binding = "BUCKET" 12 | bucket_name = "$BUCKET_NAME" -------------------------------------------------------------------------------- /免费一键部署Cloudflare R2 WebDAV服务,超简单拥有自己的私人网盘-封面.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aigem/CFr2-webdav/226f150c98dcb8e692c3e23157937cf1d92c5255/免费一键部署Cloudflare R2 WebDAV服务,超简单拥有自己的私人网盘-封面.jpg --------------------------------------------------------------------------------