├── .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 | 
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 | [](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 |
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
--------------------------------------------------------------------------------