From 5643d2224215a5f92a8275daf209e26168de4641 Mon Sep 17 00:00:00 2001 From: lhc <2+lhc@noreply.localhost> Date: Mon, 5 Oct 2026 23:41:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E8=93=9D=E9=B8=9F?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=BF=AB=E9=80=92=E6=9F=9C=E6=BA=90=E7=A0=81?= =?UTF-8?q?=E4=B8=8E=E9=83=A8=E7=BD=B2=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 27 + Makefile | 17 + README.md | 112 + THIRD_PARTY_NOTICES.txt | 9475 +++++++++++++++++++++++++++++++ cmd/bluebird/main.go | 171 + cmd/bluebird/main_test.go | 38 + deploy/Caddyfile.example | 16 + deploy/bluebird.service | 35 + deploy/config.example.toml | 46 + deploy/install.sh | 174 + deploy/upgrade.sh | 92 + docs/api.md | 56 + docs/deployment.md | 140 + docs/operations.md | 35 + docs/testing.md | 75 + go.mod | 25 + go.sum | 64 + internal/app/access.go | 172 + internal/app/access_config.go | 78 + internal/app/access_test.go | 227 + internal/app/admin.go | 247 + internal/app/admin_list.go | 216 + internal/app/admin_list_test.go | 175 + internal/app/app.go | 471 ++ internal/app/app_test.go | 790 +++ internal/app/backup.go | 211 + internal/app/config.go | 157 + internal/app/config_test.go | 58 + internal/app/download.go | 381 ++ internal/app/integrity.go | 71 + internal/app/maintenance.go | 337 ++ internal/app/runtime.go | 143 + internal/app/runtime_test.go | 177 + internal/app/security.go | 254 + internal/app/upload.go | 678 +++ internal/web/dist/.gitkeep | 0 internal/web/embed.go | 12 + scripts/build.sh | 33 + scripts/icons.mjs | 15 + scripts/install-smoke.sh | 72 + scripts/notices.mjs | 11 + scripts/smoke.py | 109 + scripts/upgrade-smoke.py | 103 + web/index.html | 1 + web/package-lock.json | 2222 ++++++++ web/package.json | 1 + web/playwright.config.ts | 8 + web/public/apple-touch-icon.png | Bin 0 -> 5813 bytes web/public/favicon.ico | Bin 0 -> 2836 bytes web/public/favicon.svg | 9 + web/public/icon-192.png | Bin 0 -> 6277 bytes web/public/icon-512.png | Bin 0 -> 21105 bytes web/public/logo.svg | 9 + web/src/Admin.vue | 139 + web/src/AdminPager.vue | 17 + web/src/App.vue | 10 + web/src/Mine.vue | 16 + web/src/Pickup.vue | 12 + web/src/Send.vue | 24 + web/src/ShareDialog.vue | 14 + web/src/api.ts | 23 + web/src/clipboard.ts | 27 + web/src/main.ts | 7 + web/src/style.css | 26 + web/tests/access.spec.ts | 80 + web/tests/admin.spec.ts | 92 + web/tests/cabinet.spec.ts | 34 + web/tests/clipboard.spec.ts | 98 + web/tsconfig.json | 1 + web/vite.config.ts | 3 + 70 files changed, 18669 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 THIRD_PARTY_NOTICES.txt create mode 100644 cmd/bluebird/main.go create mode 100644 cmd/bluebird/main_test.go create mode 100644 deploy/Caddyfile.example create mode 100644 deploy/bluebird.service create mode 100644 deploy/config.example.toml create mode 100755 deploy/install.sh create mode 100755 deploy/upgrade.sh create mode 100644 docs/api.md create mode 100644 docs/deployment.md create mode 100644 docs/operations.md create mode 100644 docs/testing.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/app/access.go create mode 100644 internal/app/access_config.go create mode 100644 internal/app/access_test.go create mode 100644 internal/app/admin.go create mode 100644 internal/app/admin_list.go create mode 100644 internal/app/admin_list_test.go create mode 100644 internal/app/app.go create mode 100644 internal/app/app_test.go create mode 100644 internal/app/backup.go create mode 100644 internal/app/config.go create mode 100644 internal/app/config_test.go create mode 100644 internal/app/download.go create mode 100644 internal/app/integrity.go create mode 100644 internal/app/maintenance.go create mode 100644 internal/app/runtime.go create mode 100644 internal/app/runtime_test.go create mode 100644 internal/app/security.go create mode 100644 internal/app/upload.go create mode 100644 internal/web/dist/.gitkeep create mode 100644 internal/web/embed.go create mode 100755 scripts/build.sh create mode 100644 scripts/icons.mjs create mode 100755 scripts/install-smoke.sh create mode 100644 scripts/notices.mjs create mode 100644 scripts/smoke.py create mode 100644 scripts/upgrade-smoke.py create mode 100644 web/index.html create mode 100644 web/package-lock.json create mode 100644 web/package.json create mode 100644 web/playwright.config.ts create mode 100644 web/public/apple-touch-icon.png create mode 100644 web/public/favicon.ico create mode 100644 web/public/favicon.svg create mode 100644 web/public/icon-192.png create mode 100644 web/public/icon-512.png create mode 100644 web/public/logo.svg create mode 100644 web/src/Admin.vue create mode 100644 web/src/AdminPager.vue create mode 100644 web/src/App.vue create mode 100644 web/src/Mine.vue create mode 100644 web/src/Pickup.vue create mode 100644 web/src/Send.vue create mode 100644 web/src/ShareDialog.vue create mode 100644 web/src/api.ts create mode 100644 web/src/clipboard.ts create mode 100644 web/src/main.ts create mode 100644 web/src/style.css create mode 100644 web/tests/access.spec.ts create mode 100644 web/tests/admin.spec.ts create mode 100644 web/tests/cabinet.spec.ts create mode 100644 web/tests/clipboard.spec.ts create mode 100644 web/tsconfig.json create mode 100644 web/vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad32dd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +/build/ +/var/ +/config.toml +web/node_modules/ +web/test-results/ +web/playwright-report/ +internal/web/dist/* +!internal/web/dist/.gitkeep + +.bluebird-smoke-*/ +/demo/ + +# Local credentials, backups and interpreter/editor caches +.env +.env.* +!.env.example +*.key +*.pem +*.db +*.db-wal +*.db-shm +*.log +__pycache__/ +*.py[cod] +.DS_Store +*.swp +*.swo diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5d71f9e --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +.PHONY: build web test check dev release release-amd64 +web: + ./scripts/build.sh web +build: + ./scripts/build.sh build +test: + go test -race ./... +check: + go vet ./... + npm --prefix web run build +dev: + go run ./cmd/bluebird serve --config config.toml +release: + ./scripts/build.sh release + +release-amd64: + ./scripts/build.sh release amd64 diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae9d2a4 --- /dev/null +++ b/README.md @@ -0,0 +1,112 @@ +# 蓝鸟文件快递柜 + +匿名寄件、凭码取件,管理员独立登录。Go + Vue 3 + SQLite,本机文件存储。相同内容完整上传校验后共用一个对象,分享权限、次数和自动续期按每个文件独立计算。支持取件密码和可选 age 落盘加密。 + +## 从源码构建并运行 + +需要 Go(见 go.mod)和 Node.js 仅用于构建,构建脚本无需 make。运行发布包不需要它们,也不需要外部数据库。 + +```sh +./scripts/build.sh build +cp deploy/config.example.toml config.toml +./build/bluebird init --config config.toml +./build/bluebird serve --config config.toml +``` + +打开 `http://localhost:8080`。首次初始化在终端设置管理员密码,无默认密码。自动化可通过 `BLUEBIRD_ADMIN_PASSWORD` 环境变量传入,避免命令行参数泄露密码。默认只监听回环地址;可通过 `access_urls` 增加公网HTTP、HTTPS和独立内网IP入口。浏览器cookie按入口协议自动处理,主入口的 `secure_cookies` 与其协议一致。内网HTTP需显式开启 `allow_lan_http`,并绑定可访问的内网地址。 + +## 直接运行发布包 + +解压对应架构的发布包,进入解压目录后运行,无需安装Go、Node.js或数据库服务: + +```sh +./bluebird init --config config.toml +./bluebird serve --config config.toml +``` + +默认地址为 `http://localhost:8080`。其他访问入口按部署指南配置。 + +## Linux 服务器安装 + +Linux amd64发布包包含首次安装脚本,默认将二进制、配置及数据安装到 `/opt/bluebird`,使用独立bluebird账号和systemd运行,不覆盖已有安装。运行时无需Go、Node.js或外部数据库。 + +```sh +mkdir bluebird-release +tar -xzf bluebird-linux-amd64.tar.gz -C bluebird-release +cd bluebird-release +sudo ./install.sh --url https://files.example.com \ + --access-url http://files.example.com \ + --lan-url http://192.168.1.10:8080 --encrypt --no-start +``` + +将域名替换成自己的,按提示设置管理员密码。配置好已有HTTPS代理后启动: + +```sh +sudo systemctl daemon-reload +sudo systemctl enable --now bluebird.service +``` + +完整步骤、内网HTTP选项、目录权限、备份及升级见 [部署指南](docs/deployment.md)。脚本不修改代理、DNS或防火墙。管理员通过 `https://你的域名/#/admin` 登录。 + +匿名会话7天、默认每天每 IP 最多20批及1GiB、默认单文件100MiB/每批10个/总500MiB。cookie用于寄件历史和所有权,不能替代 IP 限额;NAT 多人会共享 IP 限额,IPv6 按 /64 归并。管理策略可调整这些值。系统不试图证明一个匿名会话对应一个自然人。可在“我的寄件”导出管理凭据,换浏览器后通过同页导入管理。 + +`renewal_mode="add"` 默认成功下载延长3天,总保留期最多30天,剩余次数不恢复。初始保留最多7天与总保留期上限是两个控制项。下载后仅该文件续期;最后一次下载不再续期,因为此时已无下载权限。 + +## 存储、加密和配额 + +对象 ID 随机,原文件名不作为服务器路径;上传内容不执行、不解压、不预览。下载统一附件类型。设置 `encryption=true` 后,新对象直接流式加密落盘;文件/密钥同时泄露时不能提供额外保密,也不是端到端加密。备份 `var/secrets`,不要删掉既有密钥;缺失加密私钥时服务拒绝启动。 + +默认跨包裹去重,相同内容、相同存储加密格式/密钥只存一份。服务端完整接收并计算哈希,不接受客户端“已有哈希免上传”。这能节省长期实体占用,不能省网络流量,也不能省上传期间的临时预留。剩余空间不足时,即使文件可能重复,也可能先被拒绝;不要依赖去重保证磁盘装得下。 + +文件预算原子预留、临时文件计入预算,独立管理元数据和隔离对象。管理员可在“容量与保留”调整文件预算、数据总预算、卷最少保留空间及审计的天数、条数、内容预算;保存后立即生效并持久化,重启不会被TOML初始值覆盖。应用预算是保守接收控制,严格整站磁盘硬上限请设置项目配额/定容卷,见 [运维说明](docs/operations.md)。运行日志默认进入 journald,由主机单独限制其容量。 + +## 备份、恢复和升级 + +先停止 systemd 服务;维护 CLI 与服务使用同一个独占进程锁。 + +```sh +sudo systemctl stop bluebird +sudo -u bluebird /opt/bluebird/bluebird backup --config /opt/bluebird/config.toml --output /备份目标/bluebird.tar.gz +sudo systemctl start bluebird +``` + +备份输出须位于数据目录之外、目录需对运行账号可写。归档带数据库、对象、密钥和配置,所有内容均为敏感数据;备份可由外部备份系统进一步加密。恢复配置指向一个空数据目录: + +```sh +./bluebird restore --config /新安装目录/config.toml --input /备份目标/bluebird.tar.gz +./bluebird check --config /新安装目录/config.toml +``` + +原配置副本存为数据目录中的 `restored-config.toml`,供管理员核对,不自动覆盖新主机配置。恢复后所有旧取件码、匿名管理凭据及会话失效,包裹显示“恢复待审核”。管理员可显式重新发布并设置新分享授权,避免回滚快照重开已撤销分享。需重置管理员密码时停止服务后执行 `reset-password`。 + +新版发布包包含 `upgrade.sh`,自动停服、一致性备份、保留配置与数据、替换程序并检查后启动。可用 `--access-url` 和 `--lan-url` 添加入口,具体命令见部署指南。当前数据库结构版本1;不支持结构降级,后续不兼容迁移需恢复配套备份。 + +## 开发与检查 + +以下命令在源码目录中运行。 + +```sh +go test -race ./... # Go race + 业务/故障集成测试 +go vet ./... +npm --prefix web run build # Vue 类型检查及构建 +./scripts/build.sh release amd64 # Linux amd64部署包、安装脚本、校验和 +# ./scripts/build.sh release # 同时生成amd64/arm64包 +``` + +前端开发:`npm --prefix web run dev`,代理本机8080后端。此时 Origin 仍需匹配配置的 `public_url` 或 `access_urls`,如把 public_url 设为 `http://localhost:5173`。 + +测试步骤见 [测试说明](docs/testing.md)。API 见 [接口文档](docs/api.md),备份、容量与清理规则见 [运维说明](docs/operations.md)。 + +首版不包含断点续传、服务端自动压缩、在线预览、端到端加密或病毒扫描。系统限制内容执行及资源使用,不能保证收件人下载的任意文件无病毒。 + +## 页面、身份与审计 + +产品显示名称为“蓝鸟”,主色为 #1677ff,使用蓝底白鸟SVG标志及多尺寸favicon。公共页面不展示管理入口;管理员通过 `/#/admin` 登录独立控制台,寄件/取件通过公共页面匿名使用。 + +“开放寄件服务”和“开放取件服务”是全站开关,关闭不会出现注册流程,管理员身份也不绕过匿名服务开关。 + +浏览器识别cookie从首次访问起固定有效7天,访问不会自动续期;“我的寄件”显示实际到期时间。原始管理凭据暂存在当前标签页的sessionStorage,关闭标签页会丢失,可事先导出JSON到其他设备导入。管理凭据只证明对应寄件所有权,不能延长文件保留期或恢复到期/删除文件;记录尚在时显示历史状态,记录已清理则提示凭据失效。 + +审计默认保留30天、最多100000条、32MiB估算内容预算,任一上限先达到时清理最旧记录。写入时执行条数和内容上限,后台每分钟清理到期记录。估算包括UTF-8字段与每条开销,不等于整个SQLite文件大小;删除后数据库复用空闲页,严格物理上限由文件系统配额约束。 + +标志源文件位于 `web/public/logo.svg`,网站图标位于同目录。修改SVG后可运行 `node scripts/icons.mjs` 生成ICO、PNG及手机图标,再构建前端。 diff --git a/THIRD_PARTY_NOTICES.txt b/THIRD_PARTY_NOTICES.txt new file mode 100644 index 0000000..7e41bea --- /dev/null +++ b/THIRD_PARTY_NOTICES.txt @@ -0,0 +1,9475 @@ +蓝鸟第三方组件许可声明 +Generated from pinned dependency sources. Some development/test dependencies are included. + +======================================================================== +filippo.io/age@v1.3.2 / LICENSE +Copyright 2019 The age Authors +Copyright 2019 Google LLC +Copyright 2022 Filippo Valsorda + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of the age project nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +filippo.io/hpke@v0.4.0 / LICENSE +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +github.com/dustin/go-humanize@v1.0.1 / LICENSE +Copyright (c) 2005-2008 Dustin Sallings + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + + + +======================================================================== +github.com/google/pprof@v0.0.0-20260802141513-ef3492d7dac3 / LICENSE + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +github.com/google/uuid@v1.6.0 / LICENSE +Copyright (c) 2009,2014 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +github.com/hashicorp/golang-lru/v2@v2.0.7 / LICENSE +Copyright (c) 2014 HashiCorp, Inc. + +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. "Contributor" + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. "Contributor Version" + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms of + a Secondary License. + +1.6. "Executable Form" + + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. + +1.8. "License" + + means this document. + +1.9. "Licensable" + + means having the right to grant, to the maximum extent possible, whether + at the time of the initial grant or subsequently, any and all of the + rights conveyed by this License. + +1.10. "Modifications" + + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor + + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" + + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, + judicial order, or regulation then You must: (a) comply with the terms of + this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing + basis, if such Contributor fails to notify You of the non-compliance by + some reasonable means prior to 60 days after You have come back into + compliance. Moreover, Your grants from a particular Contributor are + reinstated on an ongoing basis if such Contributor notifies You of the + non-compliance by some reasonable means, this is the first time You have + received notice of non-compliance with this License from such + Contributor, and You become compliant prior to 30 days after Your receipt + of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an "as is" basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is free + of defects, merchantable, fit for a particular purpose or non-infringing. + The entire risk as to the quality and performance of the Covered Software + is with You. Should any Covered Software prove defective in any respect, + You (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an essential + part of this License. No use of any Covered Software is authorized under + this License except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from + such party's negligence to the extent applicable law prohibits such + limitation. Some jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and limitation may + not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party's ability to bring cross-claims or + counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall not + be used to construe this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this License must be + attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + + This Source Code Form is "Incompatible + With Secondary Licenses", as defined by + the Mozilla Public License, v. 2.0. + + +======================================================================== +github.com/mattn/go-isatty@v0.0.24 / LICENSE +Copyright (c) Yasuhiro MATSUMOTO + +MIT License (Expat) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +github.com/ncruces/go-strftime@v1.0.0 / LICENSE +MIT License + +Copyright (c) 2022 Nuno Cruces + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +github.com/pelletier/go-toml/v2@v2.4.3 / LICENSE +The MIT License (MIT) + +go-toml v2 +Copyright (c) 2021 - 2023 Thomas Pelletier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +github.com/remyoudompheng/bigfft@v0.0.0-20230129092748-24d4a6f8daec / LICENSE +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +golang.org/x/crypto@v0.57.0 / LICENSE +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +golang.org/x/mod@v0.41.0 / LICENSE +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +golang.org/x/sync@v0.23.0 / LICENSE +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +golang.org/x/sys@v0.48.0 / LICENSE +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +golang.org/x/term@v0.46.0 / LICENSE +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +golang.org/x/time@v0.16.0 / LICENSE +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +golang.org/x/tools@v0.50.0 / LICENSE +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +modernc.org/cc/v4@v4.29.7 / LICENSE +Copyright (c) 2014 The CC Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the names of the authors nor the names of the +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +modernc.org/ccgo/v4@v4.36.1 / LICENSE +Copyright (c) 2017 The CCGO Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the names of the authors nor the names of the +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +modernc.org/fileutil@v1.4.0 / LICENSE +Copyright (c) 2014 The fileutil Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the names of the authors nor the names of the +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +modernc.org/gc/v2@v2.6.5 / LICENSE +------------------------------------------------------------------------------- +Copyright (c) 2016 The GC Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the names of the authors nor the names of the +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +------------------------------------------------------------------------------- + +Code in this repository contains or may contain + + - copied original code from + - code based on original code from + - code inspired by original code in + + "The Go Programming Language" project at https://go.googlesource.com/go/. + +Copy of the license of the original code follows below + +------------------------------------------------------------------------------- +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +------------------------------------------------------------------------------- + + +======================================================================== +modernc.org/gc/v3@v3.1.5 / LICENSE +------------------------------------------------------------------------------- +Copyright (c) 2016 The GC Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the names of the authors nor the names of the +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +------------------------------------------------------------------------------- + +Code in this repository contains or may contain + + - copied original code from + - code based on original code from + - code inspired by original code in + + "The Go Programming Language" project at https://go.googlesource.com/go/. + +Copy of the license of the original code follows below + +------------------------------------------------------------------------------- +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +------------------------------------------------------------------------------- + + +======================================================================== +modernc.org/goabi0@v0.2.0 / LICENSE +Copyright (c) 2025 The goabi0 Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +modernc.org/libc@v1.77.1 / LICENSE +Copyright (c) 2017 The Libc Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the names of the authors nor the names of the +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +modernc.org/libc@v1.77.1 / LICENSE-3RD-PARTY.md +# Third-Party Software Notices + +This repository contains code and assets acquired from third-party sources. +While the main project is licensed under the BSD-3 License, the components +listed below are subject to their own specific license terms and copyright +notices. + +The following is a list of third-party software included in this repository, +their locations, and their respective licenses. + + +---- + +## Go + +* **URL:** https://github.com/golang/go +---- + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---- + +## musl libc + +* **URL:** https://musl.libc.org/ + +---- + +musl as a whole is licensed under the following standard MIT license: + +---------------------------------------------------------------------- +Copyright © 2005-2020 Rich Felker, et al. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------------------------------------------------------------- + +Authors/contributors include: + +A. Wilcox +Ada Worcester +Alex Dowad +Alex Suykov +Alexander Monakov +Andre McCurdy +Andrew Kelley +Anthony G. Basile +Aric Belsito +Arvid Picciani +Bartosz Brachaczek +Benjamin Peterson +Bobby Bingham +Boris Brezillon +Brent Cook +Chris Spiegel +Clément Vasseur +Daniel Micay +Daniel Sabogal +Daurnimator +David Carlier +David Edelsohn +Denys Vlasenko +Dmitry Ivanov +Dmitry V. Levin +Drew DeVault +Emil Renner Berthing +Fangrui Song +Felix Fietkau +Felix Janda +Gianluca Anzolin +Hauke Mehrtens +He X +Hiltjo Posthuma +Isaac Dunham +Jaydeep Patil +Jens Gustedt +Jeremy Huntwork +Jo-Philipp Wich +Joakim Sindholt +John Spencer +Julien Ramseier +Justin Cormack +Kaarle Ritvanen +Khem Raj +Kylie McClain +Leah Neukirchen +Luca Barbato +Luka Perkov +M Farkas-Dyck (Strake) +Mahesh Bodapati +Markus Wichmann +Masanori Ogino +Michael Clark +Michael Forney +Mikhail Kremnyov +Natanael Copa +Nicholas J. Kain +orc +Pascal Cuoq +Patrick Oppenlander +Petr Hosek +Petr Skocik +Pierre Carrier +Reini Urban +Rich Felker +Richard Pennington +Ryan Fairfax +Samuel Holland +Segev Finer +Shiz +sin +Solar Designer +Stefan Kristiansson +Stefan O'Rear +Szabolcs Nagy +Timo Teräs +Trutz Behn +Valentin Ochs +Will Dietz +William Haddon +William Pitcock + +Portions of this software are derived from third-party works licensed +under terms compatible with the above MIT license: + +The TRE regular expression implementation (src/regex/reg* and +src/regex/tre*) is Copyright © 2001-2008 Ville Laurikari and licensed +under a 2-clause BSD license (license text in the source files). The +included version has been heavily modified by Rich Felker in 2012, in +the interests of size, simplicity, and namespace cleanliness. + +Much of the math library code (src/math/* and src/complex/*) is +Copyright © 1993,2004 Sun Microsystems or +Copyright © 2003-2011 David Schultz or +Copyright © 2003-2009 Steven G. Kargl or +Copyright © 2003-2009 Bruce D. Evans or +Copyright © 2008 Stephen L. Moshier or +Copyright © 2017-2018 Arm Limited +and labelled as such in comments in the individual source files. All +have been licensed under extremely permissive terms. + +The ARM memcpy code (src/string/arm/memcpy.S) is Copyright © 2008 +The Android Open Source Project and is licensed under a two-clause BSD +license. It was taken from Bionic libc, used on Android. + +The AArch64 memcpy and memset code (src/string/aarch64/*) are +Copyright © 1999-2019, Arm Limited. + +The implementation of DES for crypt (src/crypt/crypt_des.c) is +Copyright © 1994 David Burren. It is licensed under a BSD license. + +The implementation of blowfish crypt (src/crypt/crypt_blowfish.c) was +originally written by Solar Designer and placed into the public +domain. The code also comes with a fallback permissive license for use +in jurisdictions that may not recognize the public domain. + +The smoothsort implementation (src/stdlib/qsort.c) is Copyright © 2011 +Valentin Ochs and is licensed under an MIT-style license. + +The x86_64 port was written by Nicholas J. Kain and is licensed under +the standard MIT terms. + +The mips and microblaze ports were originally written by Richard +Pennington for use in the ellcc project. The original code was adapted +by Rich Felker for build system and code conventions during upstream +integration. It is licensed under the standard MIT terms. + +The mips64 port was contributed by Imagination Technologies and is +licensed under the standard MIT terms. + +The powerpc port was also originally written by Richard Pennington, +and later supplemented and integrated by John Spencer. It is licensed +under the standard MIT terms. + +All other files which have no copyright comments are original works +produced specifically for use as part of this library, written either +by Rich Felker, the main author of the library, or by one or more +contibutors listed above. Details on authorship of individual files +can be found in the git version control history of the project. The +omission of copyright and license comments in each file is in the +interest of source tree size. + +In addition, permission is hereby granted for all public header files +(include/* and arch/*/bits/*) and crt files intended to be linked into +applications (crt/*, ldso/dlstart.c, and arch/*/crt_arch.h) to omit +the copyright notice and permission notice otherwise required by the +license, and to use these files without any requirement of +attribution. These files include substantial contributions from: + +Bobby Bingham +John Spencer +Nicholas J. Kain +Rich Felker +Richard Pennington +Stefan Kristiansson +Szabolcs Nagy + +all of whom have explicitly granted such permission. + +This file previously contained text expressing a belief that most of +the files covered by the above exception were sufficiently trivial not +to be subject to copyright, resulting in confusion over whether it +negated the permissions granted in the license. In the spirit of +permissive licensing, and of not having licensing issues being an +obstacle to adoption, that text has been removed. + +---- + +## go-netdb + +* **URL:** https://github.com/dominikh/go-netdb + +---- + +Copyright (c) 2012 Dominik Honnef + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +## NixOS/nixpkgs + +* **URL:** https://github.com/NixOS/nixpkgs + +---- + +Copyright (c) 2003-2025 Eelco Dolstra and the Nixpkgs/NixOS contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +modernc.org/mathutil@v1.7.1 / LICENSE +Copyright (c) 2014 The mathutil Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the names of the authors nor the names of the +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +modernc.org/memory@v1.12.1 / LICENSE +Copyright (c) 2017 The Memory Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the names of the authors nor the names of the +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +modernc.org/memory@v1.12.1 / LICENSE-GO +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +modernc.org/memory@v1.12.1 / LICENSE-LOGO +https://commons.wikimedia.org/wiki/File:Memory_infra_logo.png + + +======================================================================== +modernc.org/memory@v1.12.1 / LICENSE-MMAP-GO +Copyright (c) 2011, Evan Shaw +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +======================================================================== +modernc.org/opt@v0.2.0 / LICENSE +Copyright (c) 2019 The Opt Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the names of the authors nor the names of the +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +modernc.org/sortutil@v1.2.1 / LICENSE +Copyright (c) 2014 The sortutil Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the names of the authors nor the names of the +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +modernc.org/sqlite@v1.60.1 / LICENSE +Copyright (c) 2017 The Sqlite Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +modernc.org/sqlite@v1.60.1 / LICENSE-3RD-PARTY.md +# Third-Party Licenses + +`modernc.org/sqlite` is BSD-3-Clause; that license is in [LICENSE](LICENSE) and it is +not repeated here. This file accounts for everything else -- every other body of code, +of any origin, that reaches you through this repository. + +The list is **transitively flattened**. It is not the direct dependencies of +`go.mod`: it is the whole module graph, `go list -m all`, plus the third-party C that +was transpiled into Go and committed here, plus the upstream notices that dependencies +carry in turn -- musl libc, for one, reaches you through `modernc.org/libc` and appears +below on its own account. Each component is classified by what it actually does to a +program that imports this driver, because "a dependency" and "code in your binary" +are not the same question: + +- [1. Components linked into your program](#1-components-linked-into-your-program) +- [2. Components used only by the test suite](#2-components-used-only-by-the-test-suite) +- [3. Components in the module graph, not compiled into anything](#3-components-in-the-module-graph-not-compiled-into-anything) +- [4. Third-party material in the repository, outside any build](#4-third-party-material-in-the-repository-outside-any-build) +- [Appendix A: license texts](#appendix-a-license-texts) +- [Appendix B: inherited third-party notices](#appendix-b-inherited-third-party-notices) + +Every license text referenced by the tables is reproduced in full in Appendix A. Where +several components share one text, it appears once, with every copyright notice that +came with it merged in place -- nothing is summarized, truncated or linked away. + + + +## 1. Components linked into your program + +Everything below ends up in a binary that imports `modernc.org/sqlite`. This is the +section that matters when you redistribute software built on this driver: the notices +and license texts it points at are the ones you have to carry with you. + +| Component | Version | License | Reached via | Text | +| --- | --- | --- | --- | --- | +| [github.com/dustin/go-humanize](https://pkg.go.dev/github.com/dustin/go-humanize) | v1.0.1 | MIT | modernc.org/libc, `go.mod` | [L1](#L1) | +| [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) | v1.6.0 | BSD-3-Clause | modernc.org/libc, `go.mod` | [L2](#L2) | +| [github.com/remyoudompheng/bigfft](https://pkg.go.dev/github.com/remyoudompheng/bigfft) | v0.0.0-20230129092748-24d4a6f8daec | BSD-3-Clause | modernc.org/fileutil, modernc.org/libc, modernc.org/mathutil, modernc.org/memory, `go.mod` | [L2](#L2) | +| [Go](https://github.com/golang/go) | -- | BSD-3-Clause | modernc.org/libc | [N1](#N1) | +| [go-netdb](https://github.com/dominikh/go-netdb) | -- | MIT | modernc.org/libc | [N1](#N1) | +| [golang.org/x/sys](https://pkg.go.dev/golang.org/x/sys) | v0.48.0 | BSD-3-Clause | github.com/google/pprof, github.com/mattn/go-isatty, modernc.org/libc, modernc.org/memory, `go.mod` | [L3](#L3) | +| [modernc.org/libc](https://pkg.go.dev/modernc.org/libc) | v1.77.1 | BSD-3-Clause | `go.mod` | [L4](#L4) | +| [modernc.org/mathutil](https://pkg.go.dev/modernc.org/mathutil) | v1.7.1 | BSD-3-Clause | modernc.org/fileutil, modernc.org/libc, modernc.org/memory, `go.mod` | [L4](#L4) | +| [modernc.org/memory](https://pkg.go.dev/modernc.org/memory) | v1.12.1 | BSD-3-Clause, attribution reference | modernc.org/libc, `go.mod` | [L4](#L4), [L2](#L2), [L5](#L5), [L6](#L6) | +| [musl libc](https://musl.libc.org/) | -- | MIT | modernc.org/libc | [N1](#N1) | +| [NixOS/nixpkgs](https://github.com/NixOS/nixpkgs) | -- | MIT | modernc.org/libc | [N1](#N1) | +| [SQLite](https://sqlite.org/) | 3.53.4 | public domain | transpiled into `lib/` | [L7](#L7) | +| [sqlite-vec](https://github.com/asg017/sqlite-vec) | v0.1.9 | MIT | transpiled into `vec/` | [L8](#L8) | +| [SQLite `test_demovfs.c`](https://www.sqlite.org/src/doc/trunk/src/test_demovfs.c) | -- | public domain | `vfs/c/vfs.c`, transpiled into `vfs/` | [L7](#L7) | + +- **SQLite** -- The SQLite C amalgamation, translated to Go by `modernc.org/ccgo`. Local copy of the upstream dedication: `LICENSE-SQLITE`. +- **sqlite-vec** -- Vector-search extension, linked only when `modernc.org/sqlite/vec` is imported. Local copy: `LICENSE-SQLITE_VEC`. +- **SQLite `test_demovfs.c`** -- Starting point of the Go-fs VFS bridge, linked only when `modernc.org/sqlite/vfs` is imported. Carries SQLite's own copyright disclaimer. + +## 2. Components used only by the test suite + +Compiled into this repository's own tests only. It is not part of the library and does +not reach a program that imports `modernc.org/sqlite`. + +| Component | Version | License | Reached via | Text | +| --- | --- | --- | --- | --- | +| [github.com/google/pprof](https://pkg.go.dev/github.com/google/pprof) | v0.0.0-20260802141513-ef3492d7dac3 | Apache-2.0, BSD-3-Clause | `go.mod` | [L9](#L9), [L10](#L10) | +| [modernc.org/fileutil](https://pkg.go.dev/modernc.org/fileutil) | v1.4.0 | BSD-3-Clause | modernc.org/libc, `go.mod` | [L4](#L4), [L11](#L11) | + +## 3. Components in the module graph, not compiled into anything + +Reachable in the module graph -- `go list -m all` reports them, and the Go module proxy +will fetch them -- but no package of theirs is compiled into the library or into its +tests. They are listed for completeness, because tools that read `go.sum` or an SBOM +generated from the module graph will report them. Note in particular that the only +copyleft-adjacent license anywhere in this project, MPL-2.0, appears here and only here. +`modernc.org/ccgo` and `modernc.org/cc` are in this section because `modernc.org/libc` +requires them; they are separately the transpilers that produced the Go code in `lib/` +and `vec/`, but that happens in the sibling `libsqlite3` repository, not in any build of +this one. + +| Component | Version | License | Reached via | Text | +| --- | --- | --- | --- | --- | +| [github.com/chzyer/readline](https://pkg.go.dev/github.com/chzyer/readline) | v1.5.1 | MIT | github.com/google/pprof | [L12](#L12) | +| [github.com/hashicorp/golang-lru/v2](https://pkg.go.dev/github.com/hashicorp/golang-lru/v2) | v2.0.7 | MPL-2.0, BSD-3-Clause | modernc.org/libc | [L13](#L13), [L14](#L14) | +| [github.com/ianlancetaylor/demangle](https://pkg.go.dev/github.com/ianlancetaylor/demangle) | v0.0.0-20250417193237-f615e6bd150b | BSD-3-Clause | github.com/google/pprof | [L2](#L2) | +| [github.com/mattn/go-isatty](https://pkg.go.dev/github.com/mattn/go-isatty) | v0.0.24 | MIT | modernc.org/libc, `go.mod` | [L15](#L15) | +| [github.com/ncruces/go-strftime](https://pkg.go.dev/github.com/ncruces/go-strftime) | v1.0.0 | MIT | modernc.org/libc, `go.mod` | [L8](#L8) | +| [golang.org/x/mod](https://pkg.go.dev/golang.org/x/mod) | v0.41.0 | BSD-3-Clause | modernc.org/libc | [L3](#L3) | +| [golang.org/x/sync](https://pkg.go.dev/golang.org/x/sync) | v0.23.0 | BSD-3-Clause | modernc.org/libc | [L3](#L3) | +| [golang.org/x/tools](https://pkg.go.dev/golang.org/x/tools) | v0.50.0 | BSD-3-Clause | modernc.org/libc | [L3](#L3) | +| [modernc.org/cc/v4](https://pkg.go.dev/modernc.org/cc/v4) | v4.29.7 | BSD-3-Clause | modernc.org/libc | [L4](#L4) | +| [modernc.org/ccgo/v4](https://pkg.go.dev/modernc.org/ccgo/v4) | v4.36.1 | BSD-3-Clause | modernc.org/libc | [L4](#L4) | +| [modernc.org/gc/v2](https://pkg.go.dev/modernc.org/gc/v2) | v2.6.5 | BSD-3-Clause | modernc.org/libc | [L16](#L16) | +| [modernc.org/gc/v3](https://pkg.go.dev/modernc.org/gc/v3) | v3.1.5 | BSD-3-Clause | modernc.org/libc | [L16](#L16) | +| [modernc.org/goabi0](https://pkg.go.dev/modernc.org/goabi0) | v0.2.0 | BSD-3-Clause | modernc.org/libc | [L17](#L17) | +| [modernc.org/opt](https://pkg.go.dev/modernc.org/opt) | v0.2.0 | BSD-3-Clause | modernc.org/libc | [L4](#L4) | +| [modernc.org/sortutil](https://pkg.go.dev/modernc.org/sortutil) | v1.2.1 | BSD-3-Clause | modernc.org/libc | [L4](#L4) | +| [modernc.org/strutil](https://pkg.go.dev/modernc.org/strutil) | v1.2.1 | BSD-3-Clause | modernc.org/libc | [L4](#L4) | +| [modernc.org/token](https://pkg.go.dev/modernc.org/token) | v1.1.0 | BSD-3-Clause | modernc.org/libc | [L2](#L2) | + +## 4. Third-party material in the repository, outside any build + +Present in a clone or a source tarball of this repository, compiled into nothing. Listed +so that a redistribution of the source tree is covered as well as a redistribution of a +binary. + +| Material | Location | Origin | License | Text | +| --- | --- | --- | --- | --- | +| SQLite TCL test suite | `testdata/tcl/`, `testdata/overlay/`, `testdata/mptest.c` | [SQLite](https://sqlite.org/) | public domain | [L7](#L7) | +| Sponsor logos and word marks | `sponsors/` | the respective sponsors | -- | -- | + +The sponsor logos and word marks in `sponsors/` are the trademarks of their owners. They +are reproduced to acknowledge sponsorship, are not licensed under this project's +license, and confer no right to use those marks. + +`vendor_libs/` is a second, self-contained Go module holding the regeneration tool +invoked by `make vendor`. Its own dependencies are a subset of the modules already +listed above. + +## Appendix A: license texts + +Verbatim. The only editing is the merging of copyright notices where one text is shared +by several components; where a component's text is unique, it is byte-for-byte the file +shipped upstream. + + + +### L1: MIT + +Applies to: + +- github.com/dustin/go-humanize -- Copyright (c) 2005-2008 Dustin Sallings + +```text +Copyright (c) 2005-2008 Dustin Sallings + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +``` + + + +### L2: BSD-3-Clause + +Applies to: + +- github.com/google/uuid -- Copyright (c) 2009,2014 Google Inc. All rights reserved. +- github.com/ianlancetaylor/demangle -- Copyright (c) 2015 The Go Authors. All rights reserved. +- github.com/remyoudompheng/bigfft -- Copyright (c) 2012 The Go Authors. All rights reserved. +- modernc.org/memory (`LICENSE-GO`) -- Copyright (c) 2009 The Go Authors. All rights reserved. +- modernc.org/token -- Copyright (c) 2009 The Go Authors. All rights reserved. + +```text +Copyright (c) 2009,2014 Google Inc. All rights reserved. +Copyright (c) 2015 The Go Authors. All rights reserved. +Copyright (c) 2012 The Go Authors. All rights reserved. +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + + + +### L3: BSD-3-Clause + +Applies to: + +- golang.org/x/mod -- Copyright 2009 The Go Authors. +- golang.org/x/sync -- Copyright 2009 The Go Authors. +- golang.org/x/sys -- Copyright 2009 The Go Authors. +- golang.org/x/tools -- Copyright 2009 The Go Authors. + +```text +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + + + +### L4: BSD-3-Clause + +Applies to: + +- modernc.org/cc/v4 -- Copyright (c) 2014 The CC Authors. All rights reserved. +- modernc.org/ccgo/v4 -- Copyright (c) 2017 The CCGO Authors. All rights reserved. +- modernc.org/fileutil -- Copyright (c) 2014 The fileutil Authors. All rights reserved. +- modernc.org/libc -- Copyright (c) 2017 The Libc Authors. All rights reserved. +- modernc.org/mathutil -- Copyright (c) 2014 The mathutil Authors. All rights reserved. +- modernc.org/mathutil (`mersenne/LICENSE`) -- Copyright (c) 2014 The mersenne Authors. All rights reserved. +- modernc.org/memory -- Copyright (c) 2017 The Memory Authors. All rights reserved. +- modernc.org/opt -- Copyright (c) 2019 The Opt Authors. All rights reserved. +- modernc.org/sortutil -- Copyright (c) 2014 The sortutil Authors. All rights reserved. +- modernc.org/strutil -- Copyright (c) 2014 The strutil Authors. All rights reserved. + +```text +Copyright (c) 2014 The CC Authors. All rights reserved. +Copyright (c) 2017 The CCGO Authors. All rights reserved. +Copyright (c) 2014 The fileutil Authors. All rights reserved. +Copyright (c) 2017 The Libc Authors. All rights reserved. +Copyright (c) 2014 The mathutil Authors. All rights reserved. +Copyright (c) 2014 The mersenne Authors. All rights reserved. +Copyright (c) 2017 The Memory Authors. All rights reserved. +Copyright (c) 2019 The Opt Authors. All rights reserved. +Copyright (c) 2014 The sortutil Authors. All rights reserved. +Copyright (c) 2014 The strutil Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the names of the authors nor the names of the +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + + + +### L5: attribution reference + +Applies to: + +- modernc.org/memory (`LICENSE-LOGO`) + +```text +https://commons.wikimedia.org/wiki/File:Memory_infra_logo.png +``` + + + +### L6: BSD-3-Clause + +Applies to: + +- modernc.org/memory (`LICENSE-MMAP-GO`) -- Copyright (c) 2011, Evan Shaw All rights reserved. + +```text +Copyright (c) 2011, Evan Shaw +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + + + +### L7: public domain + +Applies to: + +- SQLite 3.53.4 (`LICENSE-SQLITE`) +- SQLite `test_demovfs.c` +- SQLite test suite (`testdata/`) + +```text +SQLite Is Public Domain + +All of the code and documentation in SQLite has been dedicated to the public +domain by the authors. All code authors, and representatives of the companies +they work for, have signed affidavits dedicating their contributions to the +public domain and originals of those signed affidavits are stored in a firesafe +at the main offices of Hwaci. Anyone is free to copy, modify, publish, use, +compile, sell, or distribute the original SQLite code, either in source code +form or as a compiled binary, for any purpose, commercial or non-commercial, +and by any means. + +The previous paragraph applies to the deliverable code and documentation in +SQLite - those parts of the SQLite library that you actually bundle and ship +with a larger application. Some scripts used as part of the build process (for +example the "configure" scripts generated by autoconf) might fall under other +open-source licenses. Nothing from these build scripts ever reaches the final +deliverable SQLite library, however, and so the licenses associated with those +scripts should not be a factor in assessing your rights to copy and use the +SQLite library. + +All of the deliverable code in SQLite has been written from scratch. No code +has been taken from other projects or from the open internet. Every line of +code can be traced back to its original author, and all of those authors have +public domain dedications on file. So the SQLite code base is clean and is +uncontaminated with licensed code from other projects. +``` + + + +### L8: MIT + +Applies to: + +- github.com/ncruces/go-strftime -- Copyright (c) 2022 Nuno Cruces +- sqlite-vec v0.1.9 (`LICENSE-SQLITE_VEC`) -- Copyright (c) 2024 Alex Garcia + +```text +MIT License + +Copyright (c) 2022 Nuno Cruces +Copyright (c) 2024 Alex Garcia + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + + + +### L9: Apache-2.0 + +Applies to: + +- github.com/google/pprof -- Copyright [yyyy] [name of copyright owner] + +```text + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +``` + + + +### L10: BSD-3-Clause + +Applies to: + +- github.com/google/pprof (`third_party/svgpan/LICENSE`) -- Copyright 2009-2017 Andrea Leofreddi . All rights reserved. + +```text +Copyright 2009-2017 Andrea Leofreddi . All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the +authors and should not be interpreted as representing official policies, either expressed +or implied, of Andrea Leofreddi. +``` + + + +### L11: BSD-3-Clause + +Applies to: + +- modernc.org/fileutil (`falloc/LICENSE`) -- Copyright (c) 2011 CZ.NIC z.s.p.o. All rights reserved. +- modernc.org/fileutil (`hdb/LICENSE`) -- Copyright (c) 2011 CZ.NIC z.s.p.o. All rights reserved. +- modernc.org/fileutil (`storage/LICENSE`) -- Copyright (c) 2011 CZ.NIC z.s.p.o. All rights reserved. + +```text +Copyright (c) 2011 CZ.NIC z.s.p.o. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of CZ.NIC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + + + +### L12: MIT + +Applies to: + +- github.com/chzyer/readline -- Copyright (c) 2015 Chzyer + +```text +The MIT License (MIT) + +Copyright (c) 2015 Chzyer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + + + +### L13: MPL-2.0 + +Applies to: + +- github.com/hashicorp/golang-lru/v2 -- Copyright (c) 2014 HashiCorp, Inc. + +```text +Copyright (c) 2014 HashiCorp, Inc. + +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. "Contributor" + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. "Contributor Version" + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms of + a Secondary License. + +1.6. "Executable Form" + + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. + +1.8. "License" + + means this document. + +1.9. "Licensable" + + means having the right to grant, to the maximum extent possible, whether + at the time of the initial grant or subsequently, any and all of the + rights conveyed by this License. + +1.10. "Modifications" + + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor + + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" + + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, + judicial order, or regulation then You must: (a) comply with the terms of + this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing + basis, if such Contributor fails to notify You of the non-compliance by + some reasonable means prior to 60 days after You have come back into + compliance. Moreover, Your grants from a particular Contributor are + reinstated on an ongoing basis if such Contributor notifies You of the + non-compliance by some reasonable means, this is the first time You have + received notice of non-compliance with this License from such + Contributor, and You become compliant prior to 30 days after Your receipt + of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an "as is" basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is free + of defects, merchantable, fit for a particular purpose or non-infringing. + The entire risk as to the quality and performance of the Covered Software + is with You. Should any Covered Software prove defective in any respect, + You (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an essential + part of this License. No use of any Covered Software is authorized under + this License except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from + such party's negligence to the extent applicable law prohibits such + limitation. Some jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and limitation may + not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party's ability to bring cross-claims or + counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall not + be used to construe this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this License must be + attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + + This Source Code Form is "Incompatible + With Secondary Licenses", as defined by + the Mozilla Public License, v. 2.0. +``` + + + +### L14: BSD-3-Clause + +Applies to: + +- github.com/hashicorp/golang-lru/v2 (`simplelru/LICENSE_list`) -- Copyright (c) 2009 The Go Authors. All rights reserved. + +```text +This license applies to simplelru/list.go + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + + + +### L15: MIT + +Applies to: + +- github.com/mattn/go-isatty -- Copyright (c) Yasuhiro MATSUMOTO + +```text +Copyright (c) Yasuhiro MATSUMOTO + +MIT License (Expat) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` + + + +### L16: BSD-3-Clause + +Applies to: + +- modernc.org/gc/v2 -- Copyright (c) 2016 The GC Authors. All rights reserved. +- modernc.org/gc/v3 -- Copyright (c) 2016 The GC Authors. All rights reserved. + +```text +------------------------------------------------------------------------------- +Copyright (c) 2016 The GC Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the names of the authors nor the names of the +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +------------------------------------------------------------------------------- + +Code in this repository contains or may contain + + - copied original code from + - code based on original code from + - code inspired by original code in + + "The Go Programming Language" project at https://go.googlesource.com/go/. + +Copy of the license of the original code follows below + +------------------------------------------------------------------------------- +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +------------------------------------------------------------------------------- +``` + + + +### L17: BSD-3-Clause + +Applies to: + +- modernc.org/goabi0 -- Copyright (c) 2025 The goabi0 Authors. All rights reserved. + +```text +Copyright (c) 2025 The goabi0 Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +## Appendix B: inherited third-party notices + +These documents are the transitive tail: notices that a dependency carries for code of +its own that came from somewhere else. They are reproduced verbatim, with only the +Markdown heading levels shifted so they nest inside this file. + + + +### N1: `LICENSE-3RD-PARTY.md` from modernc.org/libc + +Covers: Go (BSD-3-Clause), musl libc (MIT), go-netdb (MIT), NixOS/nixpkgs (MIT). + +--- + +#### Third-Party Software Notices + +This repository contains code and assets acquired from third-party sources. +While the main project is licensed under the BSD-3 License, the components +listed below are subject to their own specific license terms and copyright +notices. + +The following is a list of third-party software included in this repository, +their locations, and their respective licenses. + + +---- + +##### Go + +* **URL:** https://github.com/golang/go +---- + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---- + +##### musl libc + +* **URL:** https://musl.libc.org/ + +---- + +musl as a whole is licensed under the following standard MIT license: + +---------------------------------------------------------------------- +Copyright © 2005-2020 Rich Felker, et al. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------------------------------------------------------------- + +Authors/contributors include: + +A. Wilcox +Ada Worcester +Alex Dowad +Alex Suykov +Alexander Monakov +Andre McCurdy +Andrew Kelley +Anthony G. Basile +Aric Belsito +Arvid Picciani +Bartosz Brachaczek +Benjamin Peterson +Bobby Bingham +Boris Brezillon +Brent Cook +Chris Spiegel +Clément Vasseur +Daniel Micay +Daniel Sabogal +Daurnimator +David Carlier +David Edelsohn +Denys Vlasenko +Dmitry Ivanov +Dmitry V. Levin +Drew DeVault +Emil Renner Berthing +Fangrui Song +Felix Fietkau +Felix Janda +Gianluca Anzolin +Hauke Mehrtens +He X +Hiltjo Posthuma +Isaac Dunham +Jaydeep Patil +Jens Gustedt +Jeremy Huntwork +Jo-Philipp Wich +Joakim Sindholt +John Spencer +Julien Ramseier +Justin Cormack +Kaarle Ritvanen +Khem Raj +Kylie McClain +Leah Neukirchen +Luca Barbato +Luka Perkov +M Farkas-Dyck (Strake) +Mahesh Bodapati +Markus Wichmann +Masanori Ogino +Michael Clark +Michael Forney +Mikhail Kremnyov +Natanael Copa +Nicholas J. Kain +orc +Pascal Cuoq +Patrick Oppenlander +Petr Hosek +Petr Skocik +Pierre Carrier +Reini Urban +Rich Felker +Richard Pennington +Ryan Fairfax +Samuel Holland +Segev Finer +Shiz +sin +Solar Designer +Stefan Kristiansson +Stefan O'Rear +Szabolcs Nagy +Timo Teräs +Trutz Behn +Valentin Ochs +Will Dietz +William Haddon +William Pitcock + +Portions of this software are derived from third-party works licensed +under terms compatible with the above MIT license: + +The TRE regular expression implementation (src/regex/reg* and +src/regex/tre*) is Copyright © 2001-2008 Ville Laurikari and licensed +under a 2-clause BSD license (license text in the source files). The +included version has been heavily modified by Rich Felker in 2012, in +the interests of size, simplicity, and namespace cleanliness. + +Much of the math library code (src/math/* and src/complex/*) is +Copyright © 1993,2004 Sun Microsystems or +Copyright © 2003-2011 David Schultz or +Copyright © 2003-2009 Steven G. Kargl or +Copyright © 2003-2009 Bruce D. Evans or +Copyright © 2008 Stephen L. Moshier or +Copyright © 2017-2018 Arm Limited +and labelled as such in comments in the individual source files. All +have been licensed under extremely permissive terms. + +The ARM memcpy code (src/string/arm/memcpy.S) is Copyright © 2008 +The Android Open Source Project and is licensed under a two-clause BSD +license. It was taken from Bionic libc, used on Android. + +The AArch64 memcpy and memset code (src/string/aarch64/*) are +Copyright © 1999-2019, Arm Limited. + +The implementation of DES for crypt (src/crypt/crypt_des.c) is +Copyright © 1994 David Burren. It is licensed under a BSD license. + +The implementation of blowfish crypt (src/crypt/crypt_blowfish.c) was +originally written by Solar Designer and placed into the public +domain. The code also comes with a fallback permissive license for use +in jurisdictions that may not recognize the public domain. + +The smoothsort implementation (src/stdlib/qsort.c) is Copyright © 2011 +Valentin Ochs and is licensed under an MIT-style license. + +The x86_64 port was written by Nicholas J. Kain and is licensed under +the standard MIT terms. + +The mips and microblaze ports were originally written by Richard +Pennington for use in the ellcc project. The original code was adapted +by Rich Felker for build system and code conventions during upstream +integration. It is licensed under the standard MIT terms. + +The mips64 port was contributed by Imagination Technologies and is +licensed under the standard MIT terms. + +The powerpc port was also originally written by Richard Pennington, +and later supplemented and integrated by John Spencer. It is licensed +under the standard MIT terms. + +All other files which have no copyright comments are original works +produced specifically for use as part of this library, written either +by Rich Felker, the main author of the library, or by one or more +contibutors listed above. Details on authorship of individual files +can be found in the git version control history of the project. The +omission of copyright and license comments in each file is in the +interest of source tree size. + +In addition, permission is hereby granted for all public header files +(include/* and arch/*/bits/*) and crt files intended to be linked into +applications (crt/*, ldso/dlstart.c, and arch/*/crt_arch.h) to omit +the copyright notice and permission notice otherwise required by the +license, and to use these files without any requirement of +attribution. These files include substantial contributions from: + +Bobby Bingham +John Spencer +Nicholas J. Kain +Rich Felker +Richard Pennington +Stefan Kristiansson +Szabolcs Nagy + +all of whom have explicitly granted such permission. + +This file previously contained text expressing a belief that most of +the files covered by the above exception were sufficiently trivial not +to be subject to copyright, resulting in confusion over whether it +negated the permissions granted in the license. In the spirit of +permissive licensing, and of not having licensing issues being an +obstacle to adoption, that text has been removed. + +---- + +##### go-netdb + +* **URL:** https://github.com/dominikh/go-netdb + +---- + +Copyright (c) 2012 Dominik Honnef + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +---- + +##### NixOS/nixpkgs + +* **URL:** https://github.com/NixOS/nixpkgs + +---- + +Copyright (c) 2003-2025 Eelco Dolstra and the Nixpkgs/NixOS contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- + + +======================================================================== +modernc.org/sqlite@v1.60.1 / LICENSE-SQLITE +SQLite Is Public Domain + +All of the code and documentation in SQLite has been dedicated to the public +domain by the authors. All code authors, and representatives of the companies +they work for, have signed affidavits dedicating their contributions to the +public domain and originals of those signed affidavits are stored in a firesafe +at the main offices of Hwaci. Anyone is free to copy, modify, publish, use, +compile, sell, or distribute the original SQLite code, either in source code +form or as a compiled binary, for any purpose, commercial or non-commercial, +and by any means. + +The previous paragraph applies to the deliverable code and documentation in +SQLite - those parts of the SQLite library that you actually bundle and ship +with a larger application. Some scripts used as part of the build process (for +example the "configure" scripts generated by autoconf) might fall under other +open-source licenses. Nothing from these build scripts ever reaches the final +deliverable SQLite library, however, and so the licenses associated with those +scripts should not be a factor in assessing your rights to copy and use the +SQLite library. + +All of the deliverable code in SQLite has been written from scratch. No code +has been taken from other projects or from the open internet. Every line of +code can be traced back to its original author, and all of those authors have +public domain dedications on file. So the SQLite code base is clean and is +uncontaminated with licensed code from other projects. + + +======================================================================== +modernc.org/sqlite@v1.60.1 / LICENSE-SQLITE_VEC +MIT License + +Copyright (c) 2024 Alex Garcia + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +modernc.org/strutil@v1.2.1 / LICENSE +Copyright (c) 2014 The strutil Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the names of the authors nor the names of the +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +modernc.org/token@v1.1.0 / LICENSE +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +@babel/helper-string-parser@7.29.7 / LICENSE +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +@babel/helper-validator-identifier@7.29.7 / LICENSE +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +@babel/parser@7.29.9 / LICENSE +Copyright (C) 2012-2014 by various contributors (see AUTHORS) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +@babel/types@7.29.8 / LICENSE +MIT License + +Copyright (c) 2014-present Sebastian McKenzie and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +@emotion/hash@0.8.0 / LICENSE +MIT License + +Copyright (c) Emotion team and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +@jridgewell/sourcemap-codec@1.6.0 / LICENSE +Copyright 2024 Justin Ridgewell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +@juggle/resize-observer@3.4.0 / LICENSE + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 JUGGLE LTD + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +@playwright/test@1.63.0 / LICENSE + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Portions Copyright (c) Microsoft Corporation. + Portions Copyright 2017 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +@rolldown/pluginutils@1.0.1 / LICENSE +MIT License + +Copyright (c) 2026-present, rolldown/plugins repository contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +@types/estree@1.0.9 / LICENSE + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +======================================================================== +@types/lodash@4.17.25 / LICENSE + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +======================================================================== +@types/lodash-es@4.17.12 / LICENSE + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +======================================================================== +@types/node@24.19.1 / LICENSE + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +======================================================================== +@types/qrcode@1.5.6 / LICENSE + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +======================================================================== +@vitejs/plugin-vue@6.0.9 / LICENSE +MIT License + +Copyright (c) 2019-present, Yuxi (Evan) You and Vite contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +@volar/language-core@2.4.28 / LICENSE +MIT License + +Copyright (c) 2021-present Johnson Chu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +@volar/source-map@2.4.28 / LICENSE +MIT License + +Copyright (c) 2021-present Johnson Chu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +@volar/typescript@2.4.28 / LICENSE +MIT License + +Copyright (c) 2021-present Johnson Chu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +@vue/compiler-core@3.5.43 / LICENSE +The MIT License (MIT) + +Copyright (c) 2018-present, Yuxi (Evan) You + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +@vue/compiler-dom@3.5.43 / LICENSE +The MIT License (MIT) + +Copyright (c) 2018-present, Yuxi (Evan) You + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +@vue/compiler-sfc@3.5.43 / LICENSE +The MIT License (MIT) + +Copyright (c) 2018-present, Yuxi (Evan) You + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +@vue/compiler-ssr@3.5.43 / LICENSE +The MIT License (MIT) + +Copyright (c) 2018-present, Yuxi (Evan) You + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +@vue/devtools-api@7.7.10 / LICENSE +MIT License + +Copyright (c) 2023 webfansplz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +@vue/devtools-kit@7.7.10 / LICENSE +MIT License + +Copyright (c) 2023 webfansplz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +@vue/devtools-shared@7.7.10 / LICENSE +MIT License + +Copyright (c) 2023 webfansplz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +@vue/language-core@3.3.12 / LICENSE +MIT License + +Copyright (c) 2021-present Johnson Chu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +@vue/reactivity@3.5.43 / LICENSE +The MIT License (MIT) + +Copyright (c) 2018-present, Yuxi (Evan) You + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +@vue/runtime-core@3.5.43 / LICENSE +The MIT License (MIT) + +Copyright (c) 2018-present, Yuxi (Evan) You + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +@vue/runtime-dom@3.5.43 / LICENSE +The MIT License (MIT) + +Copyright (c) 2018-present, Yuxi (Evan) You + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +@vue/server-renderer@3.5.43 / LICENSE +The MIT License (MIT) + +Copyright (c) 2018-present, Yuxi (Evan) You + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +@vue/shared@3.5.43 / LICENSE +The MIT License (MIT) + +Copyright (c) 2018-present, Yuxi (Evan) You + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +alien-signals@3.2.1 / LICENSE +MIT License + +Copyright (c) 2024-present Johnson Chu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +ansi-regex@5.0.1 / license +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +ansi-styles@4.3.0 / license +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +async-validator@4.2.5 / LICENSE.md +The MIT License (MIT) + +Copyright (c) 2014-present yiminghe + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +birpc@2.9.0 / LICENSE +MIT License + +Copyright (c) 2021 Anthony Fu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +camelcase@5.3.1 / license +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +cliui@6.0.0 / LICENSE.txt +Copyright (c) 2015, Contributors + +Permission to use, copy, modify, and/or distribute this software +for any purpose with or without fee is hereby granted, provided +that the above copyright notice and this permission notice +appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE +LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +======================================================================== +color-convert@2.0.1 / LICENSE +Copyright (c) 2011-2016 Heather Arthur + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + +======================================================================== +color-name@1.1.4 / LICENSE +The MIT License (MIT) +Copyright (c) 2015 Dmitry Ivanov + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== +copy-anything@4.1.5 / LICENSE +MIT License + +Copyright (c) 2018 Luca Ban - Mesqueeb + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +css-render/node_modules/csstype@3.0.11 / LICENSE +Copyright (c) 2017-2018 Fredrik Nicol + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +csstype@3.2.3 / LICENSE +Copyright (c) 2017-2018 Fredrik Nicol + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +date-fns@4.4.0 / LICENSE.md +MIT License + +Copyright (c) 2021 Sasha Koss and Lesha Koss https://kossnocorp.mit-license.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +date-fns-tz@3.2.0 / LICENSE.md +The MIT License (MIT) + +Copyright © 2018 Marnus Weststrate + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +======================================================================== +decamelize@1.2.0 / license +The MIT License (MIT) + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +dijkstrajs@1.0.3 / LICENSE.md +``` +Dijkstra path-finding functions. Adapted from the Dijkstar Python project. + +Copyright (C) 2008 + Wyatt Baldwin + All rights reserved + +Licensed under the MIT license. + + http://www.opensource.org/licenses/mit-license.php + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +``` + + +======================================================================== +emoji-regex@8.0.0 / LICENSE-MIT.txt +Copyright Mathias Bynens + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +entities@7.0.1 / LICENSE +Copyright (c) Felix Böhm +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +esbuild@0.28.2 / LICENSE.md +MIT License + +Copyright (c) 2020 Evan Wallace + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +estree-walker@2.0.2 / LICENSE +Copyright (c) 2015-20 [these people](https://github.com/Rich-Harris/estree-walker/graphs/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== +evtd@0.2.4 / LICENSE +MIT License + +Copyright (c) 2020 07akioni + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +fdir@6.5.0 / LICENSE +Copyright 2023 Abdullah Atta + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +find-up@4.1.0 / license +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +get-caller-file@2.0.5 / LICENSE.md +ISC License (ISC) +Copyright 2018 Stefan Penner + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +======================================================================== +highlight.js@11.12.0 / LICENSE +BSD 3-Clause License + +Copyright (c) 2006, Ivan Sagalaev. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +hookable@5.5.3 / LICENSE.md +The MIT License (MIT) + +Copyright (c) Pooya Parsa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +is-fullwidth-code-point@3.0.0 / license +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +locate-path@5.0.0 / license +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +lodash@4.18.1 / LICENSE +Copyright OpenJS Foundation and other contributors + +Based on Underscore.js, copyright Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/lodash/lodash + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules and vendor directories are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. + + +======================================================================== +lodash-es@4.18.1 / LICENSE +Copyright OpenJS Foundation and other contributors + +Based on Underscore.js, copyright Jeremy Ashkenas, +DocumentCloud and Investigative Reporters & Editors + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/lodash/lodash + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code displayed within the prose of the +documentation. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +Files located in the node_modules and vendor directories are externally +maintained libraries used by this software which have their own +licenses; we recommend you read them, as their terms may differ from the +terms above. + + +======================================================================== +magic-string@0.30.21 / LICENSE +Copyright 2018 Rich Harris + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +mitt@3.0.1 / LICENSE +MIT License + +Copyright (c) 2021 Jason Miller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +muggle-string@0.4.1 / LICENSE +MIT License + +Copyright (c) 2022-present Johnson Chu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +naive-ui@2.45.3 / LICENSE +MIT License + +Copyright (c) 2021 TuSimple + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +nanoid@3.3.19 / LICENSE +The MIT License (MIT) + +Copyright 2017 Andrey Sitnik + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +p-limit@2.3.0 / license +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +p-locate@4.1.0 / license +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +p-try@2.2.0 / license +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +path-browserify@1.0.1 / LICENSE +MIT License + +Copyright (c) 2013 James Halliday + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +path-exists@4.0.0 / license +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +perfect-debounce@1.0.0 / LICENSE +MIT License + +Copyright (c) Pooya Parsa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +picocolors@1.1.1 / LICENSE +ISC License + +Copyright (c) 2021-2024 Oleksii Raspopov, Kostiantyn Denysov, Anton Verinov + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +======================================================================== +picomatch@4.0.7 / LICENSE +The MIT License (MIT) + +Copyright (c) 2017-present, Jon Schlinkert. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +pinia@3.0.4 / LICENSE +The MIT License (MIT) + +Copyright (c) 2019-present Eduardo San Martin Morote + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +playwright@1.63.0 / LICENSE + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Portions Copyright (c) Microsoft Corporation. + Portions Copyright 2017 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +playwright-core@1.63.0 / LICENSE + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Portions Copyright (c) Microsoft Corporation. + Portions Copyright 2017 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +======================================================================== +pngjs@5.0.0 / LICENSE +pngjs2 original work Copyright (c) 2015 Luke Page & Original Contributors +pngjs derived work Copyright (c) 2012 Kuba Niegowski + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +postcss@8.5.28 / LICENSE +The MIT License (MIT) + +Copyright 2013 Andrey Sitnik + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +qrcode@1.5.4 / license +The MIT License (MIT) + +Copyright (c) 2012 Ryan Day + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + +======================================================================== +require-directory@2.1.1 / LICENSE +The MIT License (MIT) + +Copyright (c) 2011 Troy Goode + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +require-main-filename@2.0.0 / LICENSE.txt +Copyright (c) 2016, Contributors + +Permission to use, copy, modify, and/or distribute this software +for any purpose with or without fee is hereby granted, provided +that the above copyright notice and this permission notice +appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE +LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +======================================================================== +rfdc@1.4.1 / LICENSE +Copyright 2019 "David Mark Clements " + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and +to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. + + +======================================================================== +rollup@4.64.0 / LICENSE.md +# Rollup core license +Rollup is released under the MIT license: + +The MIT License (MIT) + +Copyright (c) 2017 [these people](https://github.com/rollup/rollup/graphs/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# Licenses of bundled dependencies +The published Rollup artifact additionally contains code with the following licenses: +MIT, ISC, 0BSD + +# Bundled dependencies: +## @jridgewell/sourcemap-codec +License: MIT +By: Justin Ridgewell +Repository: git+https://github.com/jridgewell/sourcemaps.git + +> Copyright 2024 Justin Ridgewell +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## @rollup/pluginutils +License: MIT +By: Rich Harris +Repository: rollup/plugins + +> The MIT License (MIT) +> +> Copyright (c) 2019 RollupJS Plugin Contributors (https://github.com/rollup/plugins/graphs/contributors) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## anymatch +License: ISC +By: Elan Shanker +Repository: https://github.com/micromatch/anymatch + +> The ISC License +> +> Copyright (c) 2019 Elan Shanker, Paul Miller (https://paulmillr.com) +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## binary-extensions +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/binary-extensions + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> Copyright (c) Paul Miller (https://paulmillr.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## braces +License: MIT +By: Jon Schlinkert, Brian Woodward, Elan Shanker, Eugene Sharygin, hemanth.hm +Repository: micromatch/braces + +> The MIT License (MIT) +> +> Copyright (c) 2014-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## builtin-modules +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/builtin-modules + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## chokidar +License: MIT +By: Paul Miller, Elan Shanker +Repository: git+https://github.com/paulmillr/chokidar.git + +> The MIT License (MIT) +> +> Copyright (c) 2012-2019 Paul Miller (https://paulmillr.com), Elan Shanker +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the “Software”), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## date-time +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/date-time + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## fill-range +License: MIT +By: Jon Schlinkert, Edo Rivai, Paul Miller, Rouven Weßling +Repository: jonschlinkert/fill-range + +> The MIT License (MIT) +> +> Copyright (c) 2014-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## flru +License: MIT +By: Luke Edwards +Repository: lukeed/flru + +> MIT License +> +> Copyright (c) Luke Edwards (lukeed.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## glob-parent +License: ISC +By: Gulp Team, Elan Shanker, Blaine Bublitz +Repository: gulpjs/glob-parent + +> The ISC License +> +> Copyright (c) 2015, 2019 Elan Shanker +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## is-binary-path +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/is-binary-path + +> MIT License +> +> Copyright (c) 2019 Sindre Sorhus (https://sindresorhus.com), Paul Miller (https://paulmillr.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## is-extglob +License: MIT +By: Jon Schlinkert +Repository: jonschlinkert/is-extglob + +> The MIT License (MIT) +> +> Copyright (c) 2014-2016, Jon Schlinkert +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## is-glob +License: MIT +By: Jon Schlinkert, Brian Woodward, Daniel Perez +Repository: micromatch/is-glob + +> The MIT License (MIT) +> +> Copyright (c) 2014-2017, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## is-number +License: MIT +By: Jon Schlinkert, Olsten Larck, Rouven Weßling +Repository: jonschlinkert/is-number + +> The MIT License (MIT) +> +> Copyright (c) 2014-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## is-reference +License: MIT +By: Rich Harris +Repository: git+https://github.com/Rich-Harris/is-reference.git + +--------------------------------------- + +## locate-character +License: MIT +By: Rich Harris +Repository: git+https://gitlab.com/Rich-Harris/locate-character.git + +--------------------------------------- + +## magic-string +License: MIT +By: Rich Harris +Repository: git+https://github.com/Rich-Harris/magic-string.git + +> Copyright 2018 Rich Harris +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## normalize-path +License: MIT +By: Jon Schlinkert, Blaine Bublitz +Repository: jonschlinkert/normalize-path + +> The MIT License (MIT) +> +> Copyright (c) 2014-2018, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## parse-ms +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/parse-ms + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## picocolors +License: ISC +By: Alexey Raspopov +Repository: alexeyraspopov/picocolors + +> ISC License +> +> Copyright (c) 2021-2024 Oleksii Raspopov, Kostiantyn Denysov, Anton Verinov +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +> OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## picomatch +License: MIT +By: Jon Schlinkert +Repository: micromatch/picomatch + +> The MIT License (MIT) +> +> Copyright (c) 2017-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## pretty-bytes +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/pretty-bytes + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## pretty-ms +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/pretty-ms + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## readdirp +License: MIT +By: Thorsten Lorenz, Paul Miller +Repository: git://github.com/paulmillr/readdirp.git + +> MIT License +> +> Copyright (c) 2012-2019 Thorsten Lorenz, Paul Miller (https://paulmillr.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## signal-exit +License: ISC +By: Ben Coe +Repository: https://github.com/tapjs/signal-exit.git + +> The ISC License +> +> Copyright (c) 2015-2023 Benjamin Coe, Isaac Z. Schlueter, and Contributors +> +> Permission to use, copy, modify, and/or distribute this software +> for any purpose with or without fee is hereby granted, provided +> that the above copyright notice and this permission notice +> appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +> OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE +> LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES +> OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +> WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +> ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## time-zone +License: MIT +By: Sindre Sorhus +Repository: sindresorhus/time-zone + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## to-regex-range +License: MIT +By: Jon Schlinkert, Rouven Weßling +Repository: micromatch/to-regex-range + +> The MIT License (MIT) +> +> Copyright (c) 2015-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## tslib +License: 0BSD +By: Microsoft Corp. +Repository: https://github.com/Microsoft/tslib.git + +> Copyright (c) Microsoft Corporation. +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +> REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +> AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +> INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +> LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +> OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +> PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## yargs-parser +License: ISC +By: Ben Coe +Repository: https://github.com/yargs/yargs-parser.git + +> Copyright (c) 2016, Contributors +> +> Permission to use, copy, modify, and/or distribute this software +> for any purpose with or without fee is hereby granted, provided +> that the above copyright notice and this permission notice +> appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +> OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE +> LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES +> OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +> WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +> ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +======================================================================== +seemly@0.3.10 / LICENSE +MIT License + +Copyright (c) 2024 07akioni + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +set-blocking@2.0.0 / LICENSE.txt +Copyright (c) 2016, Contributors + +Permission to use, copy, modify, and/or distribute this software +for any purpose with or without fee is hereby granted, provided +that the above copyright notice and this permission notice +appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE +LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +======================================================================== +source-map-js@1.2.2 / LICENSE + +Copyright (c) 2009-2011, Mozilla Foundation and contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the names of the Mozilla Foundation nor the names of project + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +speakingurl@14.0.1 / LICENSE +The BSD 3-Clause License (BSD3) + +Copyright (c) 2013-2017 Sascha Droste +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +======================================================================== +string-width@4.2.3 / license +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +strip-ansi@6.0.1 / license +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +superjson@2.2.6 / LICENSE +MIT License + +Copyright (c) 2020 Simon Knott and superjson contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +tinyglobby@0.2.17 / LICENSE +MIT License + +Copyright (c) 2024 Madeline Gurriarán + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +treemate@0.3.11 / LICENSE +MIT License + +Copyright (c) 2020 07akioni + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +typescript@5.9.3 / LICENSE.txt +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and + +You must cause any modified files to carry prominent notices stating that You changed the files; and + +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + + +======================================================================== +undici-types@7.24.6 / LICENSE +MIT License + +Copyright (c) Matteo Collina and Undici contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +vite@7.3.6 / LICENSE.md +# Vite core license +Vite is released under the MIT license: + +MIT License + +Copyright (c) 2019-present, VoidZero Inc. and Vite contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +# Licenses of bundled dependencies +The published Vite artifact additionally contains code with the following licenses: +BSD-2-Clause, CC0-1.0, ISC, MIT + +# Bundled dependencies: +## @jridgewell/gen-mapping, @jridgewell/remapping, @jridgewell/sourcemap-codec, @jridgewell/trace-mapping +License: MIT +By: Justin Ridgewell +Repositories: https://github.com/jridgewell/sourcemaps, https://github.com/jridgewell/sourcemaps, https://github.com/jridgewell/sourcemaps, https://github.com/jridgewell/sourcemaps + +> Copyright 2024 Justin Ridgewell +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## @jridgewell/resolve-uri +License: MIT +By: Justin Ridgewell +Repository: https://github.com/jridgewell/resolve-uri + +> Copyright 2019 Justin Ridgewell +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## @polka/compression +License: MIT +Repository: https://github.com/lukeed/polka + +--------------------------------------- + +## @polka/url +License: MIT +By: Luke Edwards +Repository: https://github.com/lukeed/polka + +--------------------------------------- + +## @rolldown/pluginutils +License: MIT +Repository: https://github.com/rolldown/rolldown + +> MIT License +> +> Copyright (c) 2024-present VoidZero Inc. & Contributors +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. +> +> end of terms and conditions +> +> The licenses of externally maintained libraries from which parts of the Software is derived are listed [here](https://github.com/rolldown/rolldown/blob/main/THIRD-PARTY-LICENSE). + +--------------------------------------- + +## @rollup/plugin-alias, @rollup/plugin-commonjs, @rollup/plugin-dynamic-import-vars, @rollup/pluginutils +License: MIT +By: Johannes Stein +Repository: https://github.com/rollup/plugins + +License: MIT +By: Rich Harris +Repository: https://github.com/rollup/plugins + +License: MIT +By: LarsDenBakker +Repository: https://github.com/rollup/plugins + +License: MIT +By: Rich Harris +Repository: https://github.com/rollup/plugins + +> The MIT License (MIT) +> +> Copyright (c) 2019 RollupJS Plugin Contributors (https://github.com/rollup/plugins/graphs/contributors) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## anymatch +License: ISC +By: Elan Shanker +Repository: https://github.com/micromatch/anymatch + +> The ISC License +> +> Copyright (c) 2019 Elan Shanker, Paul Miller (https://paulmillr.com) +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## artichokie +License: MIT +By: sapphi-red, Evan You +Repository: https://github.com/sapphi-red/artichokie + +> MIT License +> +> Copyright (c) 2020-present, Yuxi (Evan) You +> Copyright (c) 2023-present, sapphi-red +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## binary-extensions +License: MIT +By: Sindre Sorhus +Repository: https://github.com/sindresorhus/binary-extensions + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> Copyright (c) Paul Miller (https://paulmillr.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## braces, fill-range, is-number +License: MIT +By: Jon Schlinkert, Brian Woodward, Elan Shanker, Eugene Sharygin, hemanth.hm +Repository: https://github.com/micromatch/braces + +License: MIT +By: Jon Schlinkert, Edo Rivai, Paul Miller, Rouven Weßling +Repository: https://github.com/jonschlinkert/fill-range + +License: MIT +By: Jon Schlinkert, Olsten Larck, Rouven Weßling +Repository: https://github.com/jonschlinkert/is-number + +> The MIT License (MIT) +> +> Copyright (c) 2014-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## bundle-name, default-browser, default-browser-id, define-lazy-prop, is-docker, is-inside-container, is-wsl, open, run-applescript, wsl-utils +License: MIT +By: Sindre Sorhus +Repositories: https://github.com/sindresorhus/bundle-name, https://github.com/sindresorhus/default-browser, https://github.com/sindresorhus/default-browser-id, https://github.com/sindresorhus/define-lazy-prop, https://github.com/sindresorhus/is-docker, https://github.com/sindresorhus/is-inside-container, https://github.com/sindresorhus/is-wsl, https://github.com/sindresorhus/open, https://github.com/sindresorhus/run-applescript, https://github.com/sindresorhus/wsl-utils + +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## cac +License: MIT +By: egoist +Repository: https://github.com/egoist/cac + +> The MIT License (MIT) +> +> Copyright (c) EGOIST <0x142857@gmail.com> (https://github.com/egoist) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## chokidar +License: MIT +By: Paul Miller, Elan Shanker +Repository: https://github.com/paulmillr/chokidar + +> The MIT License (MIT) +> +> Copyright (c) 2012-2019 Paul Miller (https://paulmillr.com), Elan Shanker +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the “Software”), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## commondir, shell-quote +License: MIT +By: James Halliday +Repositories: http://github.com/substack/node-commondir, http://github.com/ljharb/shell-quote + +> The MIT License +> +> Copyright (c) 2013 James Halliday (mail@substack.net) +> +> Permission is hereby granted, free of charge, +> to any person obtaining a copy of this software and +> associated documentation files (the "Software"), to +> deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, +> merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom +> the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice +> shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +> ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## connect +License: MIT +By: TJ Holowaychuk, Douglas Christopher Wilson, Jonathan Ong, Tim Caswell +Repository: https://github.com/senchalabs/connect + +> (The MIT License) +> +> Copyright (c) 2010 Sencha Inc. +> Copyright (c) 2011 LearnBoost +> Copyright (c) 2011-2014 TJ Holowaychuk +> Copyright (c) 2015 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## convert-source-map +License: MIT +By: Thorsten Lorenz +Repository: https://github.com/thlorenz/convert-source-map + +> Copyright 2013 Thorsten Lorenz. +> All rights reserved. +> +> Permission is hereby granted, free of charge, to any person +> obtaining a copy of this software and associated documentation +> files (the "Software"), to deal in the Software without +> restriction, including without limitation the rights to use, +> copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following +> conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +> OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## cors +License: MIT +By: Troy Goode +Repository: https://github.com/expressjs/cors + +> (The MIT License) +> +> Copyright (c) 2013 Troy Goode +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## cross-spawn +License: MIT +By: André Cruz +Repository: https://github.com/moxystudio/node-cross-spawn + +> The MIT License (MIT) +> +> Copyright (c) 2018 Made With MOXY Lda +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## cssesc +License: MIT +By: Mathias Bynens +Repository: https://github.com/mathiasbynens/cssesc + +> Copyright Mathias Bynens +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## dotenv +License: BSD-2-Clause +Repository: https://github.com/motdotla/dotenv + +> Copyright (c) 2015, Scott Motte +> All rights reserved. +> +> Redistribution and use in source and binary forms, with or without +> modification, are permitted provided that the following conditions are met: +> +> * Redistributions of source code must retain the above copyright notice, this +> list of conditions and the following disclaimer. +> +> * Redistributions in binary form must reproduce the above copyright notice, +> this list of conditions and the following disclaimer in the documentation +> and/or other materials provided with the distribution. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +> DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +> FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +> DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +> SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +> OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +> OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------- + +## dotenv-expand +License: BSD-2-Clause +By: motdotla +Repository: https://github.com/motdotla/dotenv-expand + +> Copyright (c) 2016, Scott Motte +> All rights reserved. +> +> Redistribution and use in source and binary forms, with or without +> modification, are permitted provided that the following conditions are met: +> +> * Redistributions of source code must retain the above copyright notice, this +> list of conditions and the following disclaimer. +> +> * Redistributions in binary form must reproduce the above copyright notice, +> this list of conditions and the following disclaimer in the documentation +> and/or other materials provided with the distribution. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +> DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +> FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +> DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +> SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +> OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +> OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------- + +## ee-first +License: MIT +By: Jonathan Ong, Douglas Christopher Wilson +Repository: https://github.com/jonathanong/ee-first + +> The MIT License (MIT) +> +> Copyright (c) 2014 Jonathan Ong me@jongleberry.com +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## encodeurl +License: MIT +By: Douglas Christopher Wilson +Repository: https://github.com/pillarjs/encodeurl + +> (The MIT License) +> +> Copyright (c) 2016 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## entities +License: BSD-2-Clause +By: Felix Boehm +Repository: https://github.com/fb55/entities + +> Copyright (c) Felix Böhm +> All rights reserved. +> +> Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +> +> Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +> +> Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +> +> THIS IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS, +> EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------- + +## es-module-lexer +License: MIT +By: Guy Bedford +Repository: https://github.com/guybedford/es-module-lexer + +> MIT License +> ----------- +> +> Copyright (C) 2018-2022 Guy Bedford +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## escape-html +License: MIT +Repository: https://github.com/component/escape-html + +> (The MIT License) +> +> Copyright (c) 2012-2013 TJ Holowaychuk +> Copyright (c) 2015 Andreas Lubbe +> Copyright (c) 2015 Tiancheng "Timothy" Gu +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## estree-walker +License: MIT +By: Rich Harris +Repository: https://github.com/Rich-Harris/estree-walker + +> Copyright (c) 2015-20 [these people](https://github.com/Rich-Harris/estree-walker/graphs/contributors) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## etag +License: MIT +By: Douglas Christopher Wilson, David Björklund +Repository: https://github.com/jshttp/etag + +> (The MIT License) +> +> Copyright (c) 2014-2016 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## finalhandler +License: MIT +By: Douglas Christopher Wilson +Repository: https://github.com/pillarjs/finalhandler + +> (The MIT License) +> +> Copyright (c) 2014-2017 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## follow-redirects +License: MIT +By: Ruben Verborgh, Olivier Lalonde, James Talmage +Repository: https://github.com/follow-redirects/follow-redirects + +> Copyright 2014–present Olivier Lalonde , James Talmage , Ruben Verborgh +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +> of the Software, and to permit persons to whom the Software is furnished to do +> so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +> IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## generic-names +License: MIT +By: Alexey Litvinov +Repository: https://github.com/css-modules/generic-names + +> The MIT License (MIT) +> +> Copyright (c) 2015 Alexey Litvinov +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## glob-parent +License: ISC +By: Gulp Team, Elan Shanker, Blaine Bublitz +Repository: https://github.com/gulpjs/glob-parent + +> The ISC License +> +> Copyright (c) 2015, 2019 Elan Shanker +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## host-validation-middleware +License: MIT +By: sapphi-red +Repository: https://github.com/sapphi-red/host-validation-middleware + +> MIT License +> +> Copyright (c) 2025 sapphi-red +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## http-proxy-3 +License: MIT +By: William Stein, Charlie Robbins, Jimb Esser, jcrugzz +Repository: https://github.com/sagemathinc/http-proxy-3 + +> node-http-3 +> +> Copyright (c) 2010-2025 William Stein, Charlie Robbins, Jarrett Cruger & the Contributors. +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## icss-utils +License: ISC +By: Glen Maddern +Repository: https://github.com/css-modules/icss-utils + +> ISC License (ISC) +> Copyright 2018 Glen Maddern +> +> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## is-binary-path +License: MIT +By: Sindre Sorhus +Repository: https://github.com/sindresorhus/is-binary-path + +> MIT License +> +> Copyright (c) 2019 Sindre Sorhus (https://sindresorhus.com), Paul Miller (https://paulmillr.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## is-extglob +License: MIT +By: Jon Schlinkert +Repository: https://github.com/jonschlinkert/is-extglob + +> The MIT License (MIT) +> +> Copyright (c) 2014-2016, Jon Schlinkert +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## is-glob +License: MIT +By: Jon Schlinkert, Brian Woodward, Daniel Perez +Repository: https://github.com/micromatch/is-glob + +> The MIT License (MIT) +> +> Copyright (c) 2014-2017, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## is-reference +License: MIT +By: Rich Harris +Repository: https://github.com/Rich-Harris/is-reference + +--------------------------------------- + +## isexe, which +License: ISC +By: Isaac Z. Schlueter +Repositories: https://github.com/isaacs/isexe, https://github.com/isaacs/node-which + +> The ISC License +> +> Copyright (c) Isaac Z. Schlueter and Contributors +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +> IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## js-tokens +License: MIT +By: Simon Lydell +Repository: https://github.com/lydell/js-tokens + +> The MIT License (MIT) +> +> Copyright (c) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024 Simon Lydell +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## launch-editor, launch-editor-middleware +License: MIT +By: Evan You +Repositories: https://github.com/vitejs/launch-editor, https://github.com/vitejs/launch-editor + +> The MIT License (MIT) +> +> Copyright (c) 2017-present, Yuxi (Evan) You +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## lilconfig +License: MIT +By: antonk52 +Repository: https://github.com/antonk52/lilconfig + +> MIT License +> +> Copyright (c) 2022 Anton Kastritskiy +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## loader-utils +License: MIT +By: Tobias Koppers @sokra +Repository: https://github.com/webpack/loader-utils + +> Copyright JS Foundation and other contributors +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## lodash.camelcase +License: MIT +By: John-David Dalton, Blaine Bublitz, Mathias Bynens +Repository: https://github.com/lodash/lodash + +> Copyright jQuery Foundation and other contributors +> +> Based on Underscore.js, copyright Jeremy Ashkenas, +> DocumentCloud and Investigative Reporters & Editors +> +> This software consists of voluntary contributions made by many +> individuals. For exact contribution history, see the revision history +> available at https://github.com/lodash/lodash +> +> The following license applies to all parts of this software except as +> documented below: +> +> ==== +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> "Software"), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +> +> ==== +> +> Copyright and related rights for sample code are waived via CC0. Sample +> code is defined as all source code displayed within the prose of the +> documentation. +> +> CC0: http://creativecommons.org/publicdomain/zero/1.0/ +> +> ==== +> +> Files located in the node_modules and vendor directories are externally +> maintained libraries used by this software which have their own +> licenses; we recommend you read them, as their terms may differ from the +> terms above. + +--------------------------------------- + +## magic-string +License: MIT +By: Rich Harris +Repository: https://github.com/Rich-Harris/magic-string + +> Copyright 2018 Rich Harris +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## mlly, ufo +License: MIT +Repositories: https://github.com/unjs/mlly, https://github.com/unjs/ufo + +> MIT License +> +> Copyright (c) Pooya Parsa +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## mrmime +License: MIT +By: Luke Edwards +Repository: https://github.com/lukeed/mrmime + +> The MIT License (MIT) +> +> Copyright (c) Luke Edwards (https://lukeed.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## normalize-path +License: MIT +By: Jon Schlinkert, Blaine Bublitz +Repository: https://github.com/jonschlinkert/normalize-path + +> The MIT License (MIT) +> +> Copyright (c) 2014-2018, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## object-assign +License: MIT +By: Sindre Sorhus +Repository: https://github.com/sindresorhus/object-assign + +> The MIT License (MIT) +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## obug +License: MIT +By: Kevin Deng +Repository: https://github.com/sxzz/obug + +> The MIT License (MIT) +> +> Copyright © 2025-PRESENT Kevin Deng (https://github.com/sxzz) +> Copyright (c) 2014-2017 TJ Holowaychuk +> Copyright (c) 2018-2021 Josh Junon +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## on-finished +License: MIT +By: Douglas Christopher Wilson, Jonathan Ong +Repository: https://github.com/jshttp/on-finished + +> (The MIT License) +> +> Copyright (c) 2013 Jonathan Ong +> Copyright (c) 2014 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## parse5 +License: MIT +By: Ivan Nikulin, https://github.com/inikulin/parse5/graphs/contributors +Repository: https://github.com/inikulin/parse5 + +> Copyright (c) 2013-2019 Ivan Nikulin (ifaaan@gmail.com, https://github.com/inikulin) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## parseurl +License: MIT +By: Douglas Christopher Wilson, Jonathan Ong +Repository: https://github.com/pillarjs/parseurl + +> (The MIT License) +> +> Copyright (c) 2014 Jonathan Ong +> Copyright (c) 2014-2017 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## path-key, shebang-regex +License: MIT +By: Sindre Sorhus +Repositories: https://github.com/sindresorhus/path-key, https://github.com/sindresorhus/shebang-regex + +> MIT License +> +> Copyright (c) Sindre Sorhus (sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## periscopic +License: MIT +Repository: https://github.com/Rich-Harris/periscopic + +> Copyright (c) 2019 Rich Harris +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## picocolors +License: ISC +By: Alexey Raspopov +Repository: https://github.com/alexeyraspopov/picocolors + +> ISC License +> +> Copyright (c) 2021-2024 Oleksii Raspopov, Kostiantyn Denysov, Anton Verinov +> +> Permission to use, copy, modify, and/or distribute this software for any +> purpose with or without fee is hereby granted, provided that the above +> copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +> MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +> ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +> WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +> ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +> OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## postcss-import +License: MIT +By: Maxime Thirouin +Repository: https://github.com/postcss/postcss-import + +> The MIT License (MIT) +> +> Copyright (c) 2014 Maxime Thirouin, Jason Campbell & Kevin Mårtensson +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## postcss-load-config +License: MIT +By: Michael Ciniawky, Ryan Dunckel, Mateusz Derks, Dalton Santos, Patrick Gilday, François Wouts +Repository: https://github.com/postcss/postcss-load-config + +> The MIT License (MIT) +> +> Copyright Michael Ciniawsky +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## postcss-modules +License: MIT +By: Alexander Madyankin +Repository: https://github.com/css-modules/postcss-modules + +> The MIT License (MIT) +> +> Copyright 2015-present Alexander Madyankin +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## postcss-modules-extract-imports +License: ISC +By: Glen Maddern +Repository: https://github.com/css-modules/postcss-modules-extract-imports + +> Copyright 2015 Glen Maddern +> +> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## postcss-modules-local-by-default +License: MIT +By: Mark Dalgleish +Repository: https://github.com/css-modules/postcss-modules-local-by-default + +> The MIT License (MIT) +> +> Copyright 2015 Mark Dalgleish +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## postcss-modules-scope +License: ISC +By: Glen Maddern +Repository: https://github.com/css-modules/postcss-modules-scope + +> ISC License (ISC) +> +> Copyright (c) 2015, Glen Maddern +> +> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## postcss-modules-values +License: ISC +By: Glen Maddern +Repository: https://github.com/css-modules/postcss-modules-values + +> ISC License (ISC) +> +> Copyright (c) 2015, Glen Maddern +> +> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. +> +> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +--------------------------------------- + +## postcss-selector-parser +License: MIT +By: Ben Briggs, Chris Eppstein +Repository: https://github.com/postcss/postcss-selector-parser + +> Copyright (c) Ben Briggs (http://beneb.info) +> +> Permission is hereby granted, free of charge, to any person +> obtaining a copy of this software and associated documentation +> files (the "Software"), to deal in the Software without +> restriction, including without limitation the rights to use, +> copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following +> conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +> OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## postcss-value-parser +License: MIT +By: Bogdan Chadkin +Repository: https://github.com/TrySound/postcss-value-parser + +> Copyright (c) Bogdan Chadkin +> +> Permission is hereby granted, free of charge, to any person +> obtaining a copy of this software and associated documentation +> files (the "Software"), to deal in the Software without +> restriction, including without limitation the rights to use, +> copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following +> conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +> OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## readdirp +License: MIT +By: Thorsten Lorenz, Paul Miller +Repository: https://github.com/paulmillr/readdirp + +> MIT License +> +> Copyright (c) 2012-2019 Thorsten Lorenz, Paul Miller (https://paulmillr.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## resolve.exports, totalist +License: MIT +By: Luke Edwards +Repositories: https://github.com/lukeed/resolve.exports, https://github.com/lukeed/totalist + +> The MIT License (MIT) +> +> Copyright (c) Luke Edwards (lukeed.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## shebang-command +License: MIT +By: Kevin Mårtensson +Repository: https://github.com/kevva/shebang-command + +> MIT License +> +> Copyright (c) Kevin Mårtensson (github.com/kevva) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## sirv +License: MIT +By: Luke Edwards +Repository: https://github.com/lukeed/sirv + +--------------------------------------- + +## statuses +License: MIT +By: Douglas Christopher Wilson, Jonathan Ong +Repository: https://github.com/jshttp/statuses + +> The MIT License (MIT) +> +> Copyright (c) 2014 Jonathan Ong +> Copyright (c) 2016 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## string-hash +License: CC0-1.0 +By: The Dark Sky Company +Repository: https://github.com/darkskyapp/string-hash + +--------------------------------------- + +## strip-literal +License: MIT +By: Anthony Fu +Repository: https://github.com/antfu/strip-literal + +> MIT License +> +> Copyright (c) 2022 Anthony Fu +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## to-regex-range +License: MIT +By: Jon Schlinkert, Rouven Weßling +Repository: https://github.com/micromatch/to-regex-range + +> The MIT License (MIT) +> +> Copyright (c) 2015-present, Jon Schlinkert. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in +> all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. + +--------------------------------------- + +## tsconfck +License: MIT +By: dominikg +Repository: https://github.com/dominikg/tsconfck + +> MIT License +> +> Copyright (c) 2021-present dominikg and [contributors](https://github.com/dominikg/tsconfck/graphs/contributors) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. +> +> -- Licenses for 3rd-party code included in tsconfck -- +> +> # strip-bom and strip-json-comments +> MIT License +> +> Copyright (c) Sindre Sorhus (https://sindresorhus.com) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +--------------------------------------- + +## unpipe +License: MIT +By: Douglas Christopher Wilson +Repository: https://github.com/stream-utils/unpipe + +> (The MIT License) +> +> Copyright (c) 2015 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## util-deprecate +License: MIT +By: Nathan Rajlich +Repository: https://github.com/TooTallNate/util-deprecate + +> (The MIT License) +> +> Copyright (c) 2014 Nathan Rajlich +> +> Permission is hereby granted, free of charge, to any person +> obtaining a copy of this software and associated documentation +> files (the "Software"), to deal in the Software without +> restriction, including without limitation the rights to use, +> copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following +> conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +> HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +> WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +> OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## utils-merge +License: MIT +By: Jared Hanson +Repository: https://github.com/jaredhanson/utils-merge + +> The MIT License (MIT) +> +> Copyright (c) 2013-2017 Jared Hanson +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## vary +License: MIT +By: Douglas Christopher Wilson +Repository: https://github.com/jshttp/vary + +> (The MIT License) +> +> Copyright (c) 2014-2017 Douglas Christopher Wilson +> +> Permission is hereby granted, free of charge, to any person obtaining +> a copy of this software and associated documentation files (the +> 'Software'), to deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, merge, publish, +> distribute, sublicense, and/or sell copies of the Software, and to +> permit persons to whom the Software is furnished to do so, subject to +> the following conditions: +> +> The above copyright notice and this permission notice shall be +> included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------- + +## ws +License: MIT +By: Einar Otto Stangvik +Repository: https://github.com/websockets/ws + +> Copyright (c) 2011 Einar Otto Stangvik +> Copyright (c) 2013 Arnout Kazemier and contributors +> Copyright (c) 2016 Luigi Pinca and contributors +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of +> this software and associated documentation files (the "Software"), to deal in +> the Software without restriction, including without limitation the rights to +> use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +> the Software, and to permit persons to whom the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +> FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +> COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +> IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +> CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +vooks@0.2.12 / LICENSE +MIT License + +Copyright (c) 2020 07akioni + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +vscode-uri@3.2.0 / LICENSE.md +The MIT License (MIT) + +Copyright (c) Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +======================================================================== +vue@3.5.43 / LICENSE +The MIT License (MIT) + +Copyright (c) 2018-present, Yuxi (Evan) You + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +vue-router@4.6.4 / LICENSE +The MIT License (MIT) + +Copyright (c) 2019-present Eduardo San Martin Morote + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +vue-tsc@3.3.12 / LICENSE +MIT License + +Copyright (c) 2021-present Johnson Chu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +vueuc@0.4.66 / LICENSE +MIT License + +Copyright (c) 2020 07akioni + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +======================================================================== +which-module@2.0.1 / LICENSE +Copyright (c) 2016, Contributors + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + +======================================================================== +wrap-ansi@6.2.0 / license +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +======================================================================== +y18n@4.0.3 / LICENSE +Copyright (c) 2015, Contributors + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + +======================================================================== +yargs@15.4.1 / LICENSE +MIT License + +Copyright 2010 James Halliday (mail@substack.net); Modified work Copyright 2014 Contributors (ben@npmjs.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +======================================================================== +yargs-parser@18.1.3 / LICENSE.txt +Copyright (c) 2016, Contributors + +Permission to use, copy, modify, and/or distribute this software +for any purpose with or without fee is hereby granted, provided +that the above copyright notice and this permission notice +appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE +LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/cmd/bluebird/main.go b/cmd/bluebird/main.go new file mode 100644 index 0000000..52bb552 --- /dev/null +++ b/cmd/bluebird/main.go @@ -0,0 +1,171 @@ +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "strings" + "syscall" + "time" + + "bluebird/internal/app" + "bluebird/internal/web" + "golang.org/x/term" +) + +type accessURLFlags []string + +func (v *accessURLFlags) String() string { return strings.Join(*v, ",") } +func (v *accessURLFlags) Set(raw string) error { *v = append(*v, raw); return nil } + +func main() { + if e := run(); e != nil { + log.Print(e) + os.Exit(1) + } +} +func run() error { + if len(os.Args) < 2 { + return errors.New("用法: bluebird init|serve|validate-config|configure-access|check|cleanup|backup|restore|reset-password --config config.toml") + } + command := os.Args[1] + flags := flag.NewFlagSet(command, flag.ContinueOnError) + config := flags.String("config", "config.toml", "配置文件") + username := flags.String("username", "admin", "管理员名称") + output := flags.String("output", "", "备份输出路径") + input := flags.String("input", "", "备份输入路径") + var accessURLs accessURLFlags + flags.Var(&accessURLs, "access-url", "增加访问入口,可重复") + listen := flags.String("listen", "", "访问监听地址") + lanURL := flags.String("lan-url", "", "内网直连入口") + allowLAN := flags.Bool("allow-lan-http", false, "允许明确配置的内网HTTP入口") + if e := flags.Parse(os.Args[2:]); e != nil { + return e + } + if command == "configure-access" { + if *lanURL != "" { + address, base, err := app.LANListen(*lanURL) + if err != nil { + return err + } + if *listen == "" { + *listen = address + } + accessURLs = append(accessURLs, base) + *allowLAN = true + } + if e := app.UpdateAccessConfig(*config, *listen, accessURLs, *allowLAN); e != nil { + return e + } + fmt.Println("访问入口配置已更新") + return nil + } + c, e := app.LoadConfig(*config) + if e != nil { + return e + } + if command == "validate-config" { + fmt.Println("配置检查通过") + return nil + } + if command == "restore" { + if *input == "" { + return errors.New("需指定 --input") + } + return app.Restore(c, *input) + } + a, e := app.Open(c) + if e != nil { + return e + } + defer a.Close() + switch command { + case "init", "reset-password": + if command == "init" && a.Initialized() { + return errors.New("已初始化;重置请使用 reset-password") + } + password := os.Getenv("BLUEBIRD_ADMIN_PASSWORD") + if password == "" { + if !term.IsTerminal(int(os.Stdin.Fd())) { + return errors.New("需使用交互终端或 BLUEBIRD_ADMIN_PASSWORD 环境变量") + } + fmt.Print("管理员密码(至少8字节): ") + b, e := term.ReadPassword(int(os.Stdin.Fd())) + fmt.Println() + if e != nil { + return e + } + password = string(b) + fmt.Print("再次输入: ") + again, e := term.ReadPassword(int(os.Stdin.Fd())) + fmt.Println() + if e != nil { + return e + } + if password != string(again) { + return errors.New("两次密码不同") + } + } + if e = a.SetAdmin(*username, password); e != nil { + return e + } + fmt.Println("管理员配置完成") + return nil + case "check": + if e = a.Check(); e != nil { + return e + } + fmt.Println("数据库与实体内容完整性检查通过") + return nil + case "cleanup": + return a.Cleanup() + case "backup": + if *output == "" { + return errors.New("需指定 --output") + } + return a.Backup(*config, *output) + case "serve": + if !a.Initialized() { + return errors.New("请先执行 init") + } + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT) + defer stop() + maintenanceStop := make(chan struct{}) + maintenanceDone := make(chan struct{}) + go func() { defer close(maintenanceDone); a.RunMaintenance(maintenanceStop) }() + server := &http.Server{Addr: c.Listen, Handler: a.Handler(web.Handler()), ReadHeaderTimeout: 10 * time.Second, IdleTimeout: 60 * time.Second, MaxHeaderBytes: 32 << 10} + result := make(chan error, 1) + go func() { + if c.TLSCert != "" { + result <- server.ListenAndServeTLS(c.TLSCert, c.TLSKey) + } else { + result <- server.ListenAndServe() + } + }() + log.Printf("蓝鸟正在监听 %s (%s)", c.Listen, strings.TrimRight(c.PublicURL, "/")) + select { + case e = <-result: + case <-ctx.Done(): + shutdown, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + e = server.Shutdown(shutdown) + if e != nil { + _ = server.Close() + } + } + close(maintenanceStop) + <-maintenanceDone + a.WaitRequests() + if errors.Is(e, http.ErrServerClosed) { + return nil + } + return e + default: + return errors.New("未知命令") + } +} diff --git a/cmd/bluebird/main_test.go b/cmd/bluebird/main_test.go new file mode 100644 index 0000000..4b9ee09 --- /dev/null +++ b/cmd/bluebird/main_test.go @@ -0,0 +1,38 @@ +package main + +import ( + "os" + "path/filepath" + "testing" + + "bluebird/internal/app" + "github.com/pelletier/go-toml/v2" +) + +func TestValidateConfigDoesNotCreateRuntimeData(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "config.toml") + args := os.Args + t.Cleanup(func() { os.Args = args }) + os.Args = []string{"bluebird", "validate-config", "--config", path} + cfg := app.DefaultConfig() + for _, invalid := range []bool{false, true} { + if invalid { + cfg.PublicURL = "https://files.example.com" + } + encoded, err := toml.Marshal(cfg) + if err != nil { + t.Fatal(err) + } + if err = os.WriteFile(path, encoded, 0600); err != nil { + t.Fatal(err) + } + err = run() + if (err != nil) != invalid { + t.Fatalf("validation error=%v invalid=%v", err, invalid) + } + if _, err = os.Stat(filepath.Join(dir, "var")); !os.IsNotExist(err) { + t.Fatal("configuration validation initialized runtime data") + } + } +} diff --git a/deploy/Caddyfile.example b/deploy/Caddyfile.example new file mode 100644 index 0000000..91853b1 --- /dev/null +++ b/deploy/Caddyfile.example @@ -0,0 +1,16 @@ +# 两个明确入口:HTTP直接提供服务,HTTPS使用自动证书。 +# 代理异机时改为蓝鸟实际内网IP,并将代理IP加入trusted_proxies。 +{ + auto_https disable_redirects +} +http://share.example.com { + reverse_proxy 192.168.1.10:8080 { + flush_interval -1 + # 不配置request_buffers/response_buffers;流式传输,避免额外磁盘副本 + } +} +https://share.example.com { + reverse_proxy 192.168.1.10:8080 { + flush_interval -1 + } +} diff --git a/deploy/bluebird.service b/deploy/bluebird.service new file mode 100644 index 0000000..990bb51 --- /dev/null +++ b/deploy/bluebird.service @@ -0,0 +1,35 @@ +[Unit] +Description=Bluebird anonymous file cabinet +After=network.target + +[Service] +Type=simple +User=bluebird +Group=bluebird +WorkingDirectory=/opt/bluebird +ExecStart=/opt/bluebird/bluebird serve --config /opt/bluebird/config.toml +Restart=on-failure +RestartSec=5 +TimeoutStopSec=90 +UMask=0077 +NoNewPrivileges=true +PrivateTmp=true +PrivateDevices=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/opt/bluebird/var +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectControlGroups=true +RestrictSUIDSGID=true +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +CapabilityBoundingSet= +LockPersonality=true +MemoryMax=768M +TasksMax=256 +LimitNOFILE=4096 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target diff --git a/deploy/config.example.toml b/deploy/config.example.toml new file mode 100644 index 0000000..647cd94 --- /dev/null +++ b/deploy/config.example.toml @@ -0,0 +1,46 @@ +listen = "127.0.0.1:8080" +public_url = "http://localhost:8080" # 主入口,支持HTTP或HTTPS +access_urls = [] # 额外入口,如 ["http://files.example.com", "http://192.168.1.10:8080"] +secure_cookies = false # 主入口HTTPS时为true;各入口按自身协议设置cookie +allow_lan_http = false # 私有IP HTTP入口需显式开启;纯内网模式需绑定具体私有IP +# 相对路径相对于此配置文件,而非进程当前目录 +data_dir = "var" +encryption = false +deduplication = true +trusted_proxies = [] # 仅填实际反向代理 CIDR,如 127.0.0.1/32 +# 二进制字节单位,文件预算须小于数据预算至少64MiB +data_budget = 10737418240 +file_budget = 9663676416 +min_free_bytes = 1073741824 +# 以下容量和审计值仅用于首次初始化;之后在管理员后台管理 +audit_retention_days = 30 +audit_max_records = 100000 +audit_max_bytes = 33554432 +max_uploads = 8 +max_downloads = 16 +max_ip_uploads = 2 +max_ip_downloads = 4 +max_records = 200000 +max_transfer_seconds = 3600 +transfer_idle_seconds = 30 +# 内置 HTTPS 可选,否则由已有代理终止 TLS +# tls_cert = "cert.pem" +# tls_key = "key.pem" + +[policy] +anonymous_upload = true +anonymous_download = true +max_file_bytes = 104857600 +max_batch_bytes = 524288000 +max_files = 10 +default_days = 1 +max_initial_days = 7 +max_lifetime_days = 30 +default_downloads = 5 +max_downloads = 20 +renewal_days = 3 +renewal_mode = "add" # add:现到期+3天;sliding:至少从下载完成保留3天 +ip_daily_bytes = 1073741824 +ip_daily_uploads = 20 +session_daily_uploads = 20 +require_password = false diff --git a/deploy/install.sh b/deploy/install.sh new file mode 100755 index 0000000..c768b4f --- /dev/null +++ b/deploy/install.sh @@ -0,0 +1,174 @@ +#!/bin/sh +# Fresh installation; existing services, configuration and data are never replaced. +set -eu +PATH=/usr/sbin:/usr/bin:/sbin:/bin +export PATH +umask 077 + +usage() { + cat <<'HELP' +用法:sudo ./install.sh --url 站点地址 [选项] + + --url URL 完整站点地址,如 https://files.example.com + --prefix DIR 安装目录,默认 /opt/bluebird + --listen ADDR 后端监听地址,HTTPS代理默认 127.0.0.1:8080 + --lan-http 允许绑定具体私有IP提供内网HTTP服务 + --access-url URL 增加其他HTTP/HTTPS入口,可重复 + --lan-url URL 增加内网IP入口,并默认监听该IP和端口 + --encrypt 启用新文件的服务端落盘加密 + --no-start 安装并初始化,但暂不启用/启动systemd服务 + -h, --help 显示帮助 + +首次安装会在终端询问管理员密码,账号为 admin。 +自动化可通过 BLUEBIRD_ADMIN_PASSWORD 环境变量传入;密码不写入配置文件。 +系统需使用systemd,并提供getent、useradd、groupadd、runuser和coreutils。 +HTTPS证书由你已有的反向代理管理;脚本不安装代理或修改防火墙。 +已有安装请按 docs/operations.md 升级,脚本不会覆盖它。 +HELP +} +die() { printf '%s\n' "安装失败:$*" >&2; exit 1; } +require_value() { [ "$#" -ge 2 ] && [ -n "$2" ] || die "$1 缺少参数"; } +prefix=/opt/bluebird +public_url= +listen= +lan_http=false +extra_urls= +lan_url= +encryption=false +start_service=true +while [ "$#" -gt 0 ]; do + case "$1" in + --url) require_value "$@"; public_url=$2; shift 2 ;; + --prefix) require_value "$@"; prefix=$2; shift 2 ;; + --listen) require_value "$@"; listen=$2; shift 2 ;; + --lan-http) lan_http=true; shift ;; + --access-url|--lan-url) + require_value "$@" + case "$2" in *[[:space:]]*|*'"'*|*'\'*) die '额外入口不能包含空白、引号或反斜杠' ;; esac + extra_urls="$extra_urls\"$2\"," + if [ "$1" = --lan-url ]; then lan_url=$2; lan_http=true; fi + shift 2 ;; + --encrypt) encryption=true; shift ;; + --no-start) start_service=false; shift ;; + -h|--help) usage; exit 0 ;; + *) die "未知选项:$1" ;; + esac +done +[ -n "$public_url" ] || { usage; exit 1; } +[ "$(id -u)" = 0 ] || die '请使用sudo或root运行' +[ "$(uname -s)" = Linux ] || die '仅支持Linux' +# Keep TOML and systemd paths literal; reject newline, quote and expansion syntax. +case "$public_url" in *[[:space:]]*|*'"'*|*'\'*) die '站点地址不能包含空白、引号或反斜杠' ;; esac +case "$listen" in *[[:space:]]*|*'"'*|*'\'*) die '监听地址格式无效' ;; esac +printf '%s\n' "$prefix" | grep -Eq '^/[A-Za-z0-9._/-]+$' || die '安装目录须为不含空格的绝对路径' +prefix=$(realpath -m -- "$prefix") +[ "$prefix" != / ] || die '不能安装到根目录' +case "$prefix" in /home/*|/root/*) die 'systemd保护home目录,请安装到/opt或/srv等目录' ;; esac +[ ! -e "$prefix" ] && [ ! -L "$prefix" ] || die "$prefix 已存在,未修改已有安装" +unit=/etc/systemd/system/bluebird.service +[ ! -e "$unit" ] && [ ! -L "$unit" ] || die "$unit 已存在,未修改已有服务" +package_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +[ -x "$package_dir/bluebird" ] && [ -r "$package_dir/config.toml" ] && [ -r "$package_dir/ARCH" ] || die '请从解压后的发布包运行install.sh' +case "$(cat "$package_dir/ARCH")" in + amd64) [ "$(uname -m)" = x86_64 ] || die '需要Linux amd64/x86_64主机' ;; + arm64) [ "$(uname -m)" = aarch64 ] || die '需要Linux arm64/aarch64主机' ;; + *) die '发布包架构信息无效' ;; +esac +for command in getent useradd groupadd runuser install mktemp realpath awk sed; do + command -v "$command" >/dev/null 2>&1 || die "缺少系统命令:$command" +done +if [ "$start_service" = true ]; then + command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ] || die 'systemd未运行;离线安装请使用--no-start' +fi +explicit_listen=$listen +public_url=${public_url%/} +case "$public_url" in + https://*) secure_cookies=true; trusted_proxies='["127.0.0.1/32", "::1/128"]'; [ -n "$listen" ] || listen=127.0.0.1:8080 ;; + http://*) secure_cookies=false; trusted_proxies='[]' + authority=${public_url#http://} + if [ -z "$listen" ]; then + case "$authority" in + localhost:*|127.*:*|10.*:*|192.168.*:*|172.*:*|\[*\]:*) listen=$authority ;; + localhost|127.*|10.*|192.168.*|172.*|\[*\]) die '内网HTTP地址须包含端口,如http://192.168.1.10:8080' ;; + *) listen=127.0.0.1:8080 ;; + esac + case "$listen" in localhost:*) listen="127.0.0.1:${listen#localhost:}" ;; esac + fi ;; + *) die '站点地址须以http://或https://开头' ;; +esac +if [ -n "$lan_url" ] && [ -z "$explicit_listen" ]; then + case "$lan_url" in http://*) listen=${lan_url#http://}; listen=${listen%/} ;; *) die '--lan-url须使用http://内网IP:端口' ;; esac +fi +port=${listen##*:} +case "$port" in ''|*[!0-9]*) die '监听地址须包含数字端口,如127.0.0.1:8080' ;; esac +[ "$port" -ge 1024 ] && [ "$port" -le 65535 ] || die '独立服务账号使用1024至65535端口;80/443交给反向代理' +parent=$(dirname -- "$prefix") +[ -d "$parent" ] || install -d -m 0755 -- "$parent" +stage=$(mktemp -d "$parent/.bluebird-install-XXXXXX") +cleanup() { [ -z "$stage" ] || rm -rf -- "$stage"; } +trap cleanup EXIT +trap 'exit 130' INT +trap 'exit 143' TERM HUP +awk -v address="$listen" -v url="$public_url" -v secure="$secure_cookies" -v lan="$lan_http" -v encryption="$encryption" -v proxies="$trusted_proxies" -v extras="[$extra_urls]" ' + /^listen = / {print "listen = \"" address "\"";next} + /^public_url = / {print "public_url = \"" url "\"";next} + /^access_urls = / {print "access_urls = " extras;next} + /^secure_cookies = / {print "secure_cookies = " secure;next} + /^allow_lan_http = / {print "allow_lan_http = " lan;next} + /^encryption = / {print "encryption = " encryption;next} + /^trusted_proxies = / {print "trusted_proxies = " proxies;next} + {print} +' "$package_dir/config.toml" > "$stage/config.toml" +# Validate before creating an account, database or final installation directory. +if [ -n "$lan_url" ]; then + "$package_dir/bluebird" configure-access --config "$stage/config.toml" --listen "$listen" --lan-url "$lan_url" +fi +"$package_dir/bluebird" validate-config --config "$stage/config.toml" +if ! getent group bluebird >/dev/null; then groupadd --system bluebird; fi +[ "$(getent group bluebird | cut -d: -f3)" != 0 ] || die 'bluebird组不能使用GID 0' +if ! getent passwd bluebird >/dev/null; then + nologin=/usr/sbin/nologin + [ -x "$nologin" ] || nologin=/sbin/nologin + [ -x "$nologin" ] || nologin=/bin/false + useradd --system --no-create-home --gid bluebird --home-dir "$prefix" --shell "$nologin" bluebird +fi +[ "$(id -u bluebird)" != 0 ] || die 'bluebird账号不能使用UID 0' +chown root:bluebird "$stage" +chmod 0750 "$stage" +install -o root -g bluebird -m 0750 "$package_dir/bluebird" "$stage/bluebird" +chown root:bluebird "$stage/config.toml" +chmod 0640 "$stage/config.toml" +install -d -o bluebird -g bluebird -m 0700 "$stage/var" +if [ -f "$package_dir/THIRD_PARTY_NOTICES.txt" ]; then + install -o root -g bluebird -m 0644 "$package_dir/THIRD_PARTY_NOTICES.txt" "$stage/THIRD_PARTY_NOTICES.txt" +fi +# runuser preserves the terminal and optional bootstrap environment variable. +runuser --preserve-environment -u bluebird -- "$stage/bluebird" init --config "$stage/config.toml" +unset BLUEBIRD_ADMIN_PASSWORD +sed "s|/opt/bluebird|$prefix|g" "$package_dir/deploy/bluebird.service" > "$stage/bluebird.service" +chown root:bluebird "$stage/bluebird.service" +chmod 0640 "$stage/bluebird.service" +# Stage and target share a filesystem; publish the initialized installation once. +[ ! -e "$prefix" ] && [ ! -L "$prefix" ] || die "$prefix 已出现,未覆盖它" +[ ! -e "$unit" ] && [ ! -L "$unit" ] || die "$unit 已出现,未覆盖它" +mv -T -- "$stage" "$prefix" +stage= +[ -d /etc/systemd/system ] || install -d -m 0755 /etc/systemd/system +install -m 0644 "$prefix/bluebird.service" "$unit" +if [ "$start_service" = true ]; then + systemctl daemon-reload + if ! systemctl enable --now bluebird.service; then + printf '%s\n' '文件和数据已保留;请执行 journalctl -u bluebird -n 50 查看启动错误。' >&2 + exit 1 + fi +fi +printf '%s\n' '' "蓝鸟已安装:$prefix" "配置:$prefix/config.toml" "数据、数据库和密钥:$prefix/var" "后端监听:$listen" "站点:$public_url" "管理地址:$public_url/#/admin(账号admin)" +if [ "$secure_cookies" = true ]; then + printf '%s\n' 'HTTPS入口通过反向代理访问;已配置的HTTP或内网入口也可使用,详见docs/deployment.md。' +fi +if [ -n "$extra_urls" ]; then printf '%s\n' "额外入口:[$extra_urls]"; fi +if [ "$start_service" = false ]; then + printf '%s\n' '服务尚未启动。配置好代理后执行:' ' systemctl daemon-reload' ' systemctl enable --now bluebird.service' +else + printf '%s\n' '已启用开机启动;查看状态:systemctl status bluebird' +fi diff --git a/deploy/upgrade.sh b/deploy/upgrade.sh new file mode 100755 index 0000000..fb13e06 --- /dev/null +++ b/deploy/upgrade.sh @@ -0,0 +1,92 @@ +#!/bin/sh +# Upgrade the existing installation, retaining its data and administrator account. +set -eu +PATH=/usr/sbin:/usr/bin:/sbin:/bin +export PATH +umask 077 +usage() { + cat <<'HELP' +用法:sudo ./upgrade.sh [--prefix /opt/bluebird] [--access-url URL ...] [--lan-url URL] [--listen IP:端口] [--no-start] + +保留当前配置参数、文件、密钥和管理员密码;新增入口与指定监听地址会合并到配置中。 +默认停止服务、一致性备份、更新程序及配置、检查后启动。失败时恢复旧程序和配置。 +--no-start用于已手动停服的离线升级,升级后暂不启动服务。 +HELP +} +die(){ printf '%s\n' "升级失败:$*" >&2;exit 1; } +value(){ [ "$#" -ge 2 ] && [ -n "$2" ] || die "$1缺少参数"; } +prefix=/opt/bluebird +additions= +lan_url= +listen= +no_start=false +while [ "$#" -gt 0 ];do + case "$1" in + --prefix) value "$@";prefix=$2;shift 2 ;; + --access-url) value "$@";case "$2" in *[[:space:]]*) die '访问地址不能含空白' ;;esac;additions="$additions $2";shift 2 ;; + --lan-url) value "$@";lan_url=$2;shift 2 ;; + --listen) value "$@";listen=$2;shift 2 ;; + --no-start) no_start=true;shift ;; + -h|--help) usage;exit 0 ;; + *) die "未知选项:$1" ;; + esac +done +[ "$(id -u)" = 0 ] || die '请使用sudo或root运行' +[ "$(uname -s)" = Linux ] || die '仅支持Linux' +prefix=$(realpath -m -- "$prefix") +[ -x "$prefix/bluebird" ] && [ -f "$prefix/config.toml" ] || die '未找到已有安装,请使用install.sh' +exec 9>"$prefix/.upgrade.lock" +flock -n 9 || die '另一升级进程正在运行' +package_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +[ -x "$package_dir/bluebird" ] && [ -r "$package_dir/ARCH" ] || die '请在新版发布包目录运行' +case "$(cat "$package_dir/ARCH"):$(uname -m)" in amd64:x86_64|arm64:aarch64) ;;*) die '发布包架构与主机不匹配' ;;esac +if [ "$no_start" = false ];then [ -d /run/systemd/system ] && command -v systemctl >/dev/null || die 'systemd未运行,先手动停服再使用--no-start';fi +candidate=$(mktemp "$prefix/.upgrade-config-XXXXXX.toml") +backup= +changed=false +was_active=false +success=false +cleanup(){ + rm -f -- "$candidate" "$prefix/bluebird.new" + if [ "$success" = false ];then + if [ "$changed" = true ];then + if [ "$no_start" = false ];then systemctl stop bluebird.service || :;fi + install -o root -g bluebird -m 0750 "$backup/bluebird" "$prefix/bluebird.rollback" + mv -f -- "$prefix/bluebird.rollback" "$prefix/bluebird" + cp -p -- "$backup/config.toml" "$prefix/config.toml" + printf '%s\n' "已恢复旧程序和配置;备份保留在$backup" >&2 + fi + if [ "$was_active" = true ] && [ "$no_start" = false ];then systemctl start bluebird.service || :;fi + fi +} +trap cleanup EXIT +trap 'exit 130' INT +trap 'exit 143' TERM HUP +cp -p -- "$prefix/config.toml" "$candidate" +set -- "$package_dir/bluebird" configure-access --config "$candidate" +[ -z "$listen" ] || set -- "$@" --listen "$listen" +[ -z "$lan_url" ] || set -- "$@" --lan-url "$lan_url" +# URLs are separate argv values, never shell code. Globbing is disabled. +set -f +for item in $additions;do set -- "$@" --access-url "$item";done +"$@" +"$package_dir/bluebird" validate-config --config "$candidate" +if [ "$no_start" = false ];then + if systemctl is-active --quiet bluebird.service;then was_active=true;fi + systemctl stop bluebird.service +fi +backup="$prefix/backups/upgrade-$(date -u +%Y%m%dT%H%M%SZ)-$$" +install -d -o bluebird -g bluebird -m 0700 "$backup" +cp -p -- "$prefix/config.toml" "$backup/config.toml" +install -o root -g bluebird -m 0750 "$prefix/bluebird" "$backup/bluebird" +runuser -u bluebird -- "$prefix/bluebird" backup --config "$prefix/config.toml" --output "$backup/data.tar.gz" +# No configuration/data reinitialization; keep all existing administrator hashes. +install -o root -g bluebird -m 0750 "$package_dir/bluebird" "$prefix/bluebird.new" +changed=true +mv -f -- "$prefix/bluebird.new" "$prefix/bluebird" +mv -f -- "$candidate" "$prefix/config.toml" +runuser -u bluebird -- "$prefix/bluebird" check --config "$prefix/config.toml" +if [ "$no_start" = false ];then systemctl start bluebird.service;systemctl is-active --quiet bluebird.service;fi +success=true +printf '%s\n' "升级完成:$prefix" "旧版本、配置及一致性数据备份:$backup" +if [ "$no_start" = true ];then printf '%s\n' '服务尚未启动,请执行:systemctl start bluebird.service';fi diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..e1833c7 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,56 @@ +# HTTP API(v0.1) + +所有路径以 `/api/v1` 开始。JSON 错误为 `{"error":"说明"}`。修改请求带 `Origin`,应匹配 `public_url` 或 `access_urls` 中的完整站点地址。前端请求另携带 `X-Bluebird-Origin: location.origin`,该值也必须匹配入口且与Origin一致,便于反向代理转发后仍正确识别协议。浏览器使用同源 HttpOnly cookie,不开放 CORS。无 cookie 的脚本可使用匿名管理 Bearer 凭据;不要在 URL 中传凭据。 + +| 方法/路径 | 用途 | +| --- | --- | +| GET `/health` | 存活/初始化状态 | +| GET `/policy` | 公开有效策略、当前入口及可用入口 URL、初始化 7 天匿名会话 | +| POST `/uploads` | 清单声明、完整容量预留、生成上传会话与管理凭据 | +| PUT `/uploads/{id}/files/{file}` | 原始文件字节流,需同会话 cookie 或管理 Bearer | +| GET `/uploads/{id}` | 查询上传状态,需所有权凭据 | +| POST `/uploads/{id}/publish` | 所有文件就绪后发布,仅首次返回取件码 | +| GET `/mine` | 当前匿名会话最近100个包裹 | +| POST `/parcels/{id}/revoke` | 提前撤销 | +| POST `/parcels/{id}/code` | 重生成取件码,旧码/授权失效 | +| POST `/pickup/resolve` | `{code,password}`,校验后设置10分钟取件 cookie | +| GET `/pickup` | 当前授权包裹的文件清单 | +| GET `/pickup/files/{file}/download` | 附件下载;忽略 Range,HEAD 不扣次数 | +| POST `/admin/login` | `{username,password}`,设置8小时管理员 cookie | +| POST `/admin/logout` | 删除管理员会话 | +| GET `/admin/status` | 登录状态 | +| GET `/admin/stats` | 实体、逻辑、预留、预算及并发统计 | +| GET `/admin/parcels?page=1&page_size=10` | 分页包裹及文件;支持 `q`(文件名/包裹ID片段)、`state` | +| POST `/admin/parcels/{id}/delete` | 撤销包裹,后台回收 | +| POST `/admin/files/{id}/delete` | 删除单份文件引用,后台回收 | +| POST `/admin/parcels/{id}/reissue` | 显式重发恢复待审核包裹,使用当前默认期限/次数 | +| GET/PUT `/admin/policy` | 读取/修改完整业务策略 | +| GET/PUT `/admin/runtime` | 文件/数据预算、磁盘余量及审计天数/条数/内容预算 | +| GET `/admin/audit?page=1&page_size=20` | 分页审计;支持 `action`、`source`、`target`、`from`、`to` | +| POST `/admin/cleanup` | 触发清理 | + +上传清单示例: + +```json +{"files":[{"name":"example.zip","size":12345}],"days":7,"downloads":5,"password":"optional-password","renew":true} +``` + +响应包含 `id`、`manage_token` 和带服务生成文件 ID 的 `files`。凭据只保存到受控位置,不转发给收件人。实际文件长度必须与清单相符。会话最长1小时,进程重启会撤销尚未发布的会话。已就绪的文件重发 PUT 幂等返回成功;失败文件可重新完整上传。每次失败重试的传入内容计入日上传流量。 + +下载成功是服务器输出完整响应体,无法证明用户已保存。请求开始即扣次数,中断保守计一次;开始前确定没有发送内容则返还。仅成功传输且仍有剩余次数才续期;默认 `到期时间 = min(max(旧到期,完成时刻)+3天,发布时间+30天)`。另一模式为 `min(max(旧到期,完成时刻+3天),总期限上限)`。取件授权到期需重新输入取件码。 + +`GET /policy` 另返回 `visitor_expires`(当前浏览器身份到期Unix时间)。管理凭据用于所有权,过期/删除记录不开放重新取件,已清理记录返回404。 + +## 管理列表分页与搜索 + +包裹和审计列表统一返回 `{items,total,page,page_size,pages}`,其中 `total` 是符合当前筛选条件的总条数,`pages` 是总页数。空结果仍返回 `items:[]`、`page:1`、`pages:1`。页码从1开始;默认包裹每页10个、审计每页20条,`page_size` 支持1至100。超出最后一页时自动回到最后一页;非法页码、条数或筛选值返回400。分页仅加载当前页,数据库保留条数与页面每页条数相互独立。 + +包裹 `q` 匹配文件名或包裹ID片段,`state` 为精确状态。审计 `action` 匹配完整操作代码,`source` 和 `target` 匹配字面片段(百分号和下划线无通配作用);`from`、`to` 为Unix秒数,两端均包含,可以仅设置一端,开始时间不能晚于结束时间。多个条件同时满足才匹配。审计按事件ID倒序,包裹按寄出时间、ID倒序,保证同一秒内多条记录的排序明确。 + +示例:`/admin/audit?action=admin.delete&source=192.168.1.&from=1791158400&to=1791244800&page=1&page_size=20`。界面时间输入按管理员设备本地时区转换成Unix秒数;搜索和每页条数改变后回到第一页。 + +## 多入口与分享链接 + +`GET /policy` 的 `public_url` 返回当前访问入口,`access_urls` 返回所有配置入口。发布、重新生成取件码、管理员重新发布返回的 `url` 使用当前入口,`urls` 为可选分享入口列表 `[{label,url}]`;同一取件码在各入口下均有效,下载次数和期限共享。二维码随分享入口切换。 + +HTTP与HTTPS并存时,HTTP cookie使用 `_http` 后缀,HTTPS沿用原名称并设置Secure,防止同一域名的两种协议互相覆盖。不同入口的浏览器身份、登录状态及管理凭据存储可能独立;这不会改变文件及取件码。未知Origin、相互不一致的来源头和跨站请求仍被拒绝,不开放任意CORS。 diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..3de4847 --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,140 @@ +# Linux amd64 部署 + +前端与SQLite已包含在二进制中,运行无需Go、Node.js、PostgreSQL或Valkey。脚本用于有systemd的Linux amd64/x86_64主机,需root或sudo,以及shadow、util-linux和coreutils(useradd、groupadd、getent、runuser、flock、install等)。 + +## 已有安装:添加公网HTTP和独立内网入口 + +本节以蓝鸟服务器192.168.1.10、独立反向代理和主入口 `https://files.example.com` 为例。将新版发布包与SHA256SUMS上传至工作目录,解压到一个新的空目录: + +```sh +sha256sum -c SHA256SUMS +mkdir bluebird-release-new +tar -xzf bluebird-linux-amd64.tar.gz -C bluebird-release-new +cd bluebird-release-new +sudo ./upgrade.sh --lan-url http://192.168.1.10:8080 \ + --access-url http://files.example.com +``` + +脚本先校验候选配置,随后自动停服、备份、更新程序和配置、执行完整性检查、启动服务。保留原管理员密码、数据库、文件、密钥、取件码、后台策略和其他配置;不会重新初始化。旧程序、原配置和一致性数据归档保留在 `/opt/bluebird/backups/upgrade-时间-进程号/`。备份含敏感信息,应复制到独立备份位置并按自己的容量策略清理。更新失败会恢复旧程序及配置;当前数据库结构版本仍为1。 + +升级后以下入口同时可用: + +- 内网:`http://192.168.1.10:8080`,无需解析公网域名。 +- 公网HTTP:`http://files.example.com`,由反向代理提供80端口。 +- 公网HTTPS:`https://files.example.com`,由反向代理提供443端口和有效证书。 +- 各入口的管理后台:在对应地址后添加 `/#/admin`。 + +内网入口直接连接蓝鸟。公网入口仍需域名正确解析到代理、代理转发到 `192.168.1.10:8080`;程序不能绕过公网DNS故障。主机网络/防火墙需允许内网客户端和代理连接8080。HTTP传输不加密;它可以正常上传、取件和管理登录。 + +## 首次安装 + +已有安装使用上面的upgrade.sh;install.sh不会覆盖已有目录或同名服务。首次部署在发布包解压目录运行: + +```sh +sudo ./install.sh --url https://files.example.com \ + --access-url http://files.example.com \ + --lan-url http://192.168.1.10:8080 --encrypt --no-start +sudo systemctl daemon-reload +sudo systemctl enable --now bluebird.service +``` + +将域名、IP换成实际值。脚本提示输入两次管理员密码,账号admin,没有默认密码。`--encrypt`启用新上传文件的服务端落盘加密;`--no-start`安装并初始化,但暂不启动。也支持单独的公网HTTP域名:`--url http://files.example.com`,无需在域名后写8080,后端默认监听127.0.0.1:8080。纯内网部署可用 `--url http://192.168.1.10:8080 --lan-http`。 + +默认目录: + +```text +/opt/bluebird/ +├── bluebird # 二进制,前端内嵌 +├── config.toml # root:bluebird / 0640 +├── bluebird.service # 服务定义副本 +├── THIRD_PARTY_NOTICES.txt +├── backups/ # 升级时生成的备份 +└── var/ # bluebird:bluebird / 0700,数据库、对象、密钥 +``` + +程序与配置由root管理,服务以bluebird账号运行,仅写var;systemd定义安装在 `/etc/systemd/system/bluebird.service`。不修改代理、DNS、防火墙。自动化初始化可用 `BLUEBIRD_ADMIN_PASSWORD` 环境变量,密码不写入配置或服务定义。 + +## 多入口配置 + +上述升级命令得到的相关配置为: + +```toml +listen = "192.168.1.10:8080" +public_url = "https://files.example.com" +access_urls = ["http://192.168.1.10:8080", "http://files.example.com"] +secure_cookies = true +allow_lan_http = true +# 将下例替换成反向代理机器的实际IP,保留原有需要信任的代理: +# trusted_proxies = ["192.168.1.20/32"] +``` + +`public_url`是默认入口,`access_urls`增加精确允许的入口,最多16个,地址只包含协议、域名/IP和可选端口,不带路径。`secure_cookies`与默认入口协议一致;其他入口按实际协议自动设置cookie。HTTP与HTTPS同时使用同一域名时,HTTP cookie采用独立名称,避免覆盖Secure cookie。不同入口的浏览器会话和登录可能独立,文件和取件码在所有入口共用,下载额度与期限不会翻倍。 + +生成分享时默认采用当前访问入口,可在弹窗中选择其他入口;链接及二维码一起更新,取件码保持相同。内网寄件无需访问公网域名,即使公网DNS不可用也可完成全过程。 + +异机代理的真实IP/CIDR需要加入 `trusted_proxies`,使公网访客IP限额与审计准确。脚本不会猜测代理IP,升级保留旧值;不要设置为整个互联网或任意来源。配置修改后重启服务。程序拒绝未知Origin及相互矛盾的来源头,不使用无限制CORS。 + +## 代理同时提供HTTP和HTTPS + +在反向代理机器上使用下例(域名与蓝鸟配置一致): + +```caddyfile +{ + auto_https disable_redirects +} +http://files.example.com { + reverse_proxy 192.168.1.10:8080 { + flush_interval -1 + } +} +https://files.example.com { + reverse_proxy 192.168.1.10:8080 { + flush_interval -1 + } +} +``` + +两个明确的站点块分别提供HTTP与HTTPS;不要仅使用自动将HTTP跳转到HTTPS的配置。Nginx等代理同样需将80和443的请求转发至这个后端,并转发Host、X-Forwarded-Host、X-Forwarded-Proto、X-Forwarded-For及前端的X-Bluebird-Origin。应避免上传磁盘缓冲、下载内容缓存,并配置匹配文件限制的请求大小及超时。不要将var配置成代理静态目录。 + +证书与公网DNS在代理侧管理;代理必须能连接192.168.1.10的8080端口。若使用其他软件管理代理,在该软件中为HTTP和HTTPS各开启对应入口。 + +## 检查与日常操作 + +```sh +sudo systemctl status bluebird --no-pager +sudo ss -lntp | grep ':8080' +sudo journalctl -u bluebird -n 50 --no-pager +``` + +应看到监听192.168.1.10:8080。没有curl也可直接在浏览器打开 `http://192.168.1.10:8080/api/v1/health`,预期 `{"ready":true}`,然后访问首页测试寄件和取件。检查公网HTTP和HTTPS时分别使用两个协议,不能依赖代理自动跳转;管理页均在 `/#/admin`。 + +默认文件预算9GiB、数据预算10GiB、卷最少保留1GiB;单文件100MiB、每批10个、总500MiB。管理员在“服务策略”“容量与保留”修改额度、期限、下载次数和审计规则。初始化后的后台设置持久化于SQLite,编辑TOML的初始值不会覆盖。 + +`--prefix`可指定其他已有安装目录;默认 `/opt/bluebird`。`--listen`覆盖监听地址,`--lan-url`未指定listen时自动绑定其IP和端口;独立账号监听1024至65535端口,公网80/443由代理负责。`upgrade.sh --no-start`用于手动停服后的离线升级,完成后暂不启动;必须先确认旧服务已停止,数据锁会拒绝并行维护。首次安装路径不能包含空格或位于/home、/root,systemd保护这些目录。 + +## 备份和密码重置 + +全部维护CLI需先停服,与服务共用独占数据锁: + +```sh +sudo install -d -o bluebird -g bluebird -m 0700 /opt/bluebird/backups +sudo systemctl stop bluebird +sudo runuser -u bluebird -- /opt/bluebird/bluebird backup \ + --config /opt/bluebird/config.toml \ + --output /opt/bluebird/backups/bluebird-backup.tar.gz +sudo systemctl start bluebird +``` + +备份包含数据库、文件、配置和密钥,不能只复制运行中的SQLite主文件或只备份对象。升级保留旧取件码;执行restore恢复历史快照时旧分享及会话失效,管理员需逐项审核重新发布,规则见 [operations.md](operations.md)。 + +```sh +sudo systemctl stop bluebird +sudo runuser -u bluebird -- /opt/bluebird/bluebird reset-password --config /opt/bluebird/config.toml +sudo systemctl start bluebird +``` + +只校验配置、不打开数据库: + +```sh +sudo /opt/bluebird/bluebird validate-config --config /opt/bluebird/config.toml +``` diff --git a/docs/operations.md b/docs/operations.md new file mode 100644 index 0000000..75acafe --- /dev/null +++ b/docs/operations.md @@ -0,0 +1,35 @@ +# 运维与容量 + +首次安装与systemd部署步骤见 [deployment.md](deployment.md)。 + +## 两层限制 + +`file_budget` 限制实体对象与尚未写入的预留之和,重复分享共用一个实体;声明清单仍按每个文件完整预留。`data_budget` 另考虑 SQLite/WAL、隔离对象及余量;`min_free_bytes` 防止侵占主机最后的可用空间。配额按文件系统分配块保守估计,不等同于原始文件大小。到期但尚未删除、删除失败或仍有下载引用的对象继续占用额度。 + +Btrfs等文件系统可能以inode总数0表示不提供统计,此时不根据free_inodes=0拒绝上传;记录上限及文件系统硬配额仍生效。 + +应用控制无法为 SQLite、文件系统目录元数据、系统日志及管理员外部写入提供字节级绝对上限。严格模式需为 `/opt/bluebird/var` 设置独立文件系统项目配额或定容卷;应用预算必须低于硬上限,建议另留至少1GiB,并限制 inode。先用主机实际文件系统支持的管理方式配置配额,再验证 `quota`/`xfs_quota` 等工具显示的限制;软件不自动格式化或修改磁盘。 + +如现有主机不支持项目配额,可将数据目录挂载到专用定容卷。数据库、objects和tmp放在同一可靠本机文件系统,tmp与objects必须支持原子rename。不要把SQLite WAL放到NFS/SMB。 + +journald 不在 `/opt/bluebird` 下,主机需设置 `SystemMaxUse` 等日志上限。外部反向代理、备份、TLS证书与发布包有各自容量;严格整机预算须一起计入。 + +## 清理与故障 + +文件传输默认最长1小时、读写空闲30秒,配置可调整。 + +默认每分钟回收失效引用,删除最后引用后才删除实体。正在传输的对象延后删除。进程重启撤销尚未发布的上传,避免部分文件可见;未能确认实际输入量的中断尝试按完整预留流量保守计费。日额度按UTC日期计算。孤儿实体移入不可访问的 quarantine,24小时后清理。文件或密钥异常时不通过生成替代对象恢复权限。 + +已失效包裹在其文件全部清理、且距记录寄出时间超过30天后删除元数据;审计默认30天/10万条/32MiB估算内容,可在后台“容量与保留”修改;最大文件/访客/授权数受 `max_records` 限制。达到容量或记录上限后拒绝新增,不偷偷删除尚有效的分享。 + +停止服务后可执行 `check`(SQLite完整性、全部对象解密/哈希及启动对账)和 `cleanup`。启动恢复会取消未发布会话,故维护命令需安排窗口;不是无副作用的在线巡检。 + +## 安全部署 + +二进制及配置归 root,服务以专用账号仅写 var。按示例 systemd 收紧权限。支持配置中的HTTP与HTTPS入口;HTTP传输不加密,HTTPS入口使用Secure cookie。只信任实际代理IP/CIDR。上传对象不可通过代理 alias/root 直接获取。 + +加密 private identity 和 HMAC 密钥都有恢复意义。密钥轮换目前仅支持开关混存和保留同一既有 identity,不提供后台轮换旧私钥;不要手工覆盖 age.key,否则既有文件无法读取。 + +静止备份会短暂中断服务。不要在运行中只复制主数据库文件,不要把不同时间的数据库和对象目录拼接。升级或恢复前保存配置及密钥副本。恢复得到的是历史快照,所以旧分享/会话全部失效,需要管理员逐项审核重新发布。 + +后台容量与审计设置保存在SQLite runtime_settings中,TOML的相应值仅首次初始化使用。降低文件预算会停止超出预算的新上传,不删除有效文件;降低审计预算会清理最旧事件。审计计数及内容量由SQLite触发器维护,避免每次日志写入扫描所有记录。 diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000..bd7da8e --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,75 @@ +# 构建与测试 + +源码构建需要go.mod指定的Go版本、Node.js及npm。生产服务器只需要发布包,不需要这些构建工具。脚本先构建Vue前端,再将其嵌入Go二进制;internal/web/dist为生成目录,不提交到源码仓库。 + +## 构建 + +```sh +./scripts/build.sh build +# Linux amd64发布包及SHA256SUMS +./scripts/build.sh release amd64 +# 同时生成Linux amd64和arm64发布包 +./scripts/build.sh release +``` + +输出位于build,包含前端内嵌的程序、配置模板、安装/升级脚本、systemd单元、运行文档及第三方许可证。node_modules、build和测试结果不纳入Git。 + +## 后端检查 + +先构建前端,保证Go嵌入的静态资源已生成: + +```sh +./scripts/build.sh web +go test -race ./... +go vet ./... +``` + +后端用例覆盖匿名所有权、密码取件、加密和去重、空间预留、下载计数与续期、并发传输、分页搜索、审计清理、来源校验、多入口cookie、备份恢复及故障回收。可单独运行较大数据量的并发测试: + +```sh +BLUEBIRD_STRESS_MIB=64 go test ./internal/app -run TestConcurrentStreamingTransfers -v -count=1 +``` + +## 浏览器测试 + +```sh +./scripts/build.sh build +cd web +npx playwright install chromium +npm run test:e2e +``` + +仅针对隔离测试实例运行,不能指向生产数据。默认实例监听127.0.0.1:18981;多入口用例另使用19081、19082,启动本机HTTPS代理和临时自签名证书,需要openssl。测试覆盖寄件、取件、管理凭据、剪贴板、手机布局、分页筛选,以及同一域名的HTTP/HTTPS和独立IP入口。 + +执行前确保这些端口未被占用。浏览器依赖、失败追踪保存在本机缓存和web/test-results,不能提交到仓库。Playwright主实例的临时数据目录由测试配置创建,可在测试结束后按实际路径清理;多入口用例自动回收其进程、证书及数据。手机布局采用Chromium模拟,不等同于Safari或手机实机验证。 + +## 原生发布包测试 + +Python脚本需要Python 3.12或更新版本: + +```sh +./scripts/build.sh release amd64 +python3 scripts/smoke.py +``` + +脚本在项目中的隔离临时目录验证发布包的初始化、内嵌前端、加密去重、下载、管理员接口、完整性检查及备份恢复,结束后自动回收进程和数据。 + +首次安装测试需要rootless Podman,每次使用一次性Debian容器: + +```sh +podman run --rm --security-opt label=disable \ + -e BLUEBIRD_INSTALL_SMOKE=1 \ + -v "$PWD/build/linux-amd64:/pkg:ro" \ + -v "$PWD/scripts/install-smoke.sh:/test.sh:ro" \ + docker.io/library/debian:bookworm-slim sh /test.sh https +``` + +将末尾https换成http或lan可检查单独的公网HTTP与内网HTTP配置。label=disable仅用于此一次性容器读取本机测试文件,不修改主机SELinux配置。该脚本禁止直接在普通主机运行,不注册主机systemd服务。 + +已有安装升级测试需要保留一份旧版amd64部署包: + +```sh +python3 scripts/upgrade-smoke.py /path/to/previous-bluebird-linux-amd64.tar.gz +``` + +此用例在一次性容器中验证旧密码、加密文件、密钥和取件码保留,新增入口下载、配置权限、一致性备份、非法配置保护和检查失败回滚。容器使用手动停服后的--no-start模式;目标服务器的systemd启停、DNS、代理、证书及防火墙需要在实际部署环境验收。 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2ad4e8d --- /dev/null +++ b/go.mod @@ -0,0 +1,25 @@ +module bluebird + +go 1.27.1 + +require ( + filippo.io/age v1.3.2 + github.com/pelletier/go-toml/v2 v2.4.3 + golang.org/x/crypto v0.57.0 + golang.org/x/sys v0.48.0 + golang.org/x/term v0.46.0 + golang.org/x/time v0.16.0 + modernc.org/sqlite v1.60.1 +) + +require ( + filippo.io/hpke v0.4.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + modernc.org/libc v1.77.1 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.12.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1c255cd --- /dev/null +++ b/go.sum @@ -0,0 +1,64 @@ +c2sp.org/CCTV/age v0.0.0-20260829155415-4448f2097b2d h1:Blprhc2SbChNZtWcU+BLTM4YdoqYAS9V7cJgOwJKyAs= +c2sp.org/CCTV/age v0.0.0-20260829155415-4448f2097b2d/go.mod h1:SrHC2C7r5GkDk8R+NFVzYy/sdj0Ypg9htaPXQq5Cqeo= +filippo.io/age v1.3.2 h1:r6RSZLFSMm6rzKepZ7ZAYkKCu14f3/Me8c7uKYh7C8c= +filippo.io/age v1.3.2/go.mod h1:TH/Yr2sSRhCKbaH4XPxpUV0Us8Gv6txYUpiZQWz8Evk= +filippo.io/hpke v0.4.0 h1:p575VVQ6ted4pL+it6M00V/f2qTZITO0zgmdKCkd5+A= +filippo.io/hpke v0.4.0/go.mod h1:EmAN849/P3qdeK+PCMkDpDm83vRHM5cDipBJ8xbQLVY= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= +github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= +golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= +golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE= +golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s= +golang.org/x/tools v0.50.0 h1:c2ifzfcuY7L90lZ2aKd8S4K2NpASF08SZx9ZuJkHmSU= +golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0= +modernc.org/cc/v4 v4.29.7 h1:q+NXGJ0bK3b4TXFYQQVr9pYETGnmwFWkrUzJnMya/Tg= +modernc.org/cc/v4 v4.29.7/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.36.1 h1:ZNIUZAryN0UgnJwtyxrdEzcFc3yD4Cu4AzjfPXsLsIE= +modernc.org/ccgo/v4 v4.36.1/go.mod h1:rrtGc2QkS239nYb/mQNuBMyjq3/y3ZXWbBjPoV3wqzA= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.5 h1:21ldfPfRYE31Tb7B3mwAK8gy1AxP4+dKjrOQPfqakoc= +modernc.org/gc/v3 v3.1.5/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.77.1 h1:Ct8j47QtiZ1Enj2DtFXQtUqrPCAjdCmPjtCuvrYQ0Hs= +modernc.org/libc v1.77.1/go.mod h1:87/pZ4L6nD1zqW4nItuS12YO7hN1igAah34xjnQo/W0= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g= +modernc.org/memory v1.12.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= +modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.60.1 h1:/blz53O951KWFOso4QQvEs/Fq6cDBKLtMVrYNSeJVKw= +modernc.org/sqlite v1.60.1/go.mod h1:1dIoEagfDE72QytD5scH1lxARtaUgKgHC/NuApA27r0= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/internal/app/access.go b/internal/app/access.go new file mode 100644 index 0000000..2e717b0 --- /dev/null +++ b/internal/app/access.go @@ -0,0 +1,172 @@ +package app + +import ( + "context" + "errors" + "net" + "net/http" + "net/url" + "strconv" + "strings" +) + +func normalizeAccessURL(raw string) (string, error) { + u, err := url.Parse(raw) + if err != nil || u.Host == "" || u.Hostname() == "" || u.User != nil || u.RawQuery != "" || u.Fragment != "" || (u.Path != "" && u.Path != "/") || (u.Scheme != "http" && u.Scheme != "https") { + return "", errors.New("访问地址须为无路径的http/https站点地址") + } + host := strings.ToLower(u.Hostname()) + port := u.Port() + if port != "" { + n, e := strconv.Atoi(port) + if e != nil || n < 1 || n > 65535 { + return "", errors.New("访问端口须为1至65535") + } + } + if port == "80" && u.Scheme == "http" || port == "443" && u.Scheme == "https" { + port = "" + } + if port != "" { + host = net.JoinHostPort(host, port) + } else if strings.Contains(host, ":") { + host = "[" + host + "]" + } + return u.Scheme + "://" + host, nil +} +func (c Config) accessOrigins() []string { + out := []string{} + for _, raw := range append([]string{c.PublicURL}, c.AccessURLs...) { + base, err := normalizeAccessURL(raw) + if err != nil { + continue + } + found := false + for _, v := range out { + if v == base { + found = true + break + } + } + if !found { + out = append(out, base) + } + } + return out +} + +type requestAccessKey struct{} +type requestAccess struct { + base string + httpCookie bool +} + +func (a *App) accessFor(r *http.Request) requestAccess { + if state, ok := r.Context().Value(requestAccessKey{}).(requestAccess); ok { + return state + } + return requestAccess{base: strings.TrimRight(a.cfg.PublicURL, "/")} +} +func (a *App) selectAccess(r *http.Request) (requestAccess, error) { + origins := a.cfg.accessOrigins() + allowed := func(raw string) string { + normalized, err := normalizeAccessURL(raw) + if err != nil { + return "" + } + for _, v := range origins { + if v == normalized { + return v + } + } + return "" + } + origin, hint := r.Header.Get("Origin"), r.Header.Get("X-Bluebird-Origin") + selected := "" + if origin != "" { + selected = allowed(origin) + if selected == "" { + return requestAccess{}, errors.New("请求来源不允许") + } + } + if hint != "" { + base := allowed(hint) + if base == "" || selected != "" && base != selected { + return requestAccess{}, errors.New("访问入口不允许") + } + selected = base + } + if selected == "" { + host, scheme := r.Host, "" + if r.TLS != nil { + scheme = "https" + } + peer, _, err := net.SplitHostPort(r.RemoteAddr) + ip := net.ParseIP(peer) + trusted := false + if err == nil { + for _, network := range a.proxies { + if network.Contains(ip) { + trusted = true + break + } + } + } + if trusted { + if forwarded := r.Header.Get("X-Forwarded-Host"); forwarded != "" && !strings.Contains(forwarded, ",") { + host = forwarded + } + if proto := r.Header.Get("X-Forwarded-Proto"); proto == "http" || proto == "https" { + scheme = proto + } + } + for _, base := range origins { + u, _ := url.Parse(base) + if strings.EqualFold(u.Host, host) && (scheme == "" || scheme == u.Scheme) { + selected = base + break + } + } + } + if selected == "" { + selected = origins[0] + } + if r.TLS != nil && strings.HasPrefix(selected, "http://") { + return requestAccess{}, errors.New("访问入口协议与连接不一致") + } + hasHTTPS := false + for _, base := range origins { + if strings.HasPrefix(base, "https://") { + hasHTTPS = true + break + } + } + return requestAccess{base: selected, httpCookie: hasHTTPS && strings.HasPrefix(selected, "http://")}, nil +} +func (a *App) withAccess(r *http.Request) (*http.Request, error) { + state, err := a.selectAccess(r) + if err != nil { + return nil, err + } + return r.WithContext(context.WithValue(r.Context(), requestAccessKey{}, state)), nil +} +func cookieNames(r *http.Request, name string) []string { + state, _ := r.Context().Value(requestAccessKey{}).(requestAccess) + if state.httpCookie && (r.Header.Get("Origin") != "" || r.Header.Get("X-Bluebird-Origin") != "") { + return []string{name + "_http", name} + } + return []string{name, name + "_http"} +} + +// LANListen accepts only a concrete private or loopback HTTP address. +func LANListen(raw string) (string, string, error) { + base, err := normalizeAccessURL(raw) + if err != nil { + return "", "", err + } + u, _ := url.Parse(base) + ip := net.ParseIP(u.Hostname()) + if u.Scheme != "http" || ip == nil || !ip.IsPrivate() && !ip.IsLoopback() || u.Port() == "" { + return "", "", errors.New("内网入口须为http://具体私有IP:端口") + } + return u.Host, base, nil +} diff --git a/internal/app/access_config.go b/internal/app/access_config.go new file mode 100644 index 0000000..048e6f0 --- /dev/null +++ b/internal/app/access_config.go @@ -0,0 +1,78 @@ +package app + +import ( + "bytes" + "os" + "path/filepath" + "syscall" + + "github.com/pelletier/go-toml/v2" +) + +// UpdateAccessConfig preserves all existing settings, ownership and permissions. +// It never opens the data directory; callers can prepare a candidate while serving. +func UpdateAccessConfig(path, listen string, additions []string, allowLAN bool) error { + data, err := os.ReadFile(path) + if err != nil { + return err + } + info, err := os.Stat(path) + if err != nil { + return err + } + cfg := DefaultConfig() + if err = toml.NewDecoder(bytes.NewReader(data)).DisallowUnknownFields().Decode(&cfg); err != nil { + return err + } + if listen != "" { + cfg.Listen = listen + } + cfg.AllowLANHTTP = cfg.AllowLANHTTP || allowLAN + for _, raw := range additions { + base, e := normalizeAccessURL(raw) + if e != nil { + return e + } + found := false + for _, existing := range cfg.accessOrigins() { + if base == existing { + found = true + break + } + } + if !found { + cfg.AccessURLs = append(cfg.AccessURLs, base) + } + } + if err = cfg.Validate(); err != nil { + return err + } + encoded, err := toml.Marshal(cfg) + if err != nil { + return err + } + out, err := os.CreateTemp(filepath.Dir(path), ".access-config-*.toml") + if err != nil { + return err + } + defer os.Remove(out.Name()) + defer out.Close() + if stat, ok := info.Sys().(*syscall.Stat_t); ok { + if err = out.Chown(int(stat.Uid), int(stat.Gid)); err != nil { + return err + } + } + if err = out.Chmod(info.Mode().Perm()); err != nil { + return err + } + if _, err = out.Write(encoded); err != nil { + return err + } + if err = out.Sync(); err != nil { + return err + } + if err = out.Close(); err != nil { + return err + } + return os.Rename(out.Name(), path) +} diff --git a/internal/app/access_test.go b/internal/app/access_test.go new file mode 100644 index 0000000..be1a879 --- /dev/null +++ b/internal/app/access_test.go @@ -0,0 +1,227 @@ +package app + +import ( + "bytes" + "encoding/json" + "net/http/cookiejar" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/pelletier/go-toml/v2" +) + +func TestMultipleAccessOriginsAndProtocolCookies(t *testing.T) { + c := fixture(t, true) + c.a.cfg.PublicURL = "https://files.example.com" + c.a.cfg.AccessURLs = []string{"http://files.example.com", "http://192.168.1.210:8080"} + c.a.cfg.SecureCookies = true + c.a.cfg.AllowLANHTTP = true + c.a.cfg.Listen = "192.168.1.210:8080" + if err := c.a.cfg.Validate(); err != nil { + t.Fatal(err) + } + jar, _ := cookiejar.New(nil) + request := func(base, method, path string, body any, hint bool) *httptest.ResponseRecorder { + var data []byte + if body != nil { + switch v := body.(type) { + case []byte: + data = v + default: + data, _ = json.Marshal(v) + } + } + r := httptest.NewRequest(method, base+"/api/v1"+path, bytes.NewReader(data)) + r.TLS = nil // Real HTTPS is terminated on a separate proxy, which rewrites Host. + r.Host = "192.168.1.210:8080" + if base == "https://files.example.com" { + r.RemoteAddr = "192.168.1.60:1000" + } else if base == "http://files.example.com" { + r.RemoteAddr = "192.168.1.61:1000" + } else { + r.RemoteAddr = "192.168.1.62:1000" + } + if method != "GET" { + r.Header.Set("Origin", base) + } + if hint { + r.Header.Set("X-Bluebird-Origin", base) + } + u, _ := url.Parse(base + "/api/v1" + path) + for _, cookie := range jar.Cookies(u) { + r.AddCookie(cookie) + } + w := httptest.NewRecorder() + c.h.ServeHTTP(w, r) + jar.SetCookies(u, w.Result().Cookies()) + return w + } + for _, base := range c.a.cfg.accessOrigins() { + w := request(base, "GET", "/policy", nil, true) + expect(t, w, 200) + var policy map[string]any + json.Unmarshal(w.Body.Bytes(), &policy) + if policy["public_url"] != base { + t.Fatal("policy forced a different origin") + } + cookies := w.Result().Cookies() + if len(cookies) != 1 { + t.Fatal("visitor cookie not returned") + } + https := strings.HasPrefix(base, "https:") + if cookies[0].Secure != https { + t.Fatalf("incorrect Secure flag for %s", base) + } + want := "bb_visitor" + if !https { + want += "_http" + } + if cookies[0].Name != want { + t.Fatal("HTTP can overwrite HTTPS cookie") + } + w = request(base, "POST", "/admin/login", map[string]string{"username": "admin", "password": "test-password"}, true) + expect(t, w, 200) + if w.Result().Cookies()[0].Secure != https { + t.Fatal("admin cookie protocol incorrect") + } + expect(t, request(base, "GET", "/admin/stats", nil, true), 200) + } + // HTTPS and HTTP on the same public hostname retain independent sessions. + u, _ := url.Parse("http://files.example.com/api/v1/admin/stats") + for _, cookie := range jar.Cookies(u) { + if cookie.Name == "bb_admin" { + t.Fatal("HTTPS credential sent over HTTP") + } + } + payload := []byte("available with failed public DNS") + w := request("http://192.168.1.210:8080", "POST", "/uploads", Manifest{Files: []ManifestFile{{Name: "offline.txt", Size: int64(len(payload))}}, Days: 7, Downloads: 8, Renew: true}, true) + expect(t, w, 201) + var parcel struct { + ID string + Files []struct{ ID string } + } + json.Unmarshal(w.Body.Bytes(), &parcel) + expect(t, request("http://192.168.1.210:8080", "PUT", "/uploads/"+parcel.ID+"/files/"+parcel.Files[0].ID, payload, true), 200) + w = request("http://192.168.1.210:8080", "POST", "/uploads/"+parcel.ID+"/publish", map[string]any{}, true) + expect(t, w, 200) + var share struct { + Code, URL string + URLs []map[string]string + } + json.Unmarshal(w.Body.Bytes(), &share) + if !strings.HasPrefix(share.URL, "http://192.168.1.210:8080/") || len(share.URLs) != 3 { + t.Fatal("share did not offer independent LAN/public URLs") + } + for _, base := range c.a.cfg.accessOrigins() { + expect(t, request(base, "POST", "/pickup/resolve", map[string]string{"code": share.Code}, true), 200) + // Navigation downloads have neither Origin nor the frontend access hint. + w = request(base, "GET", "/pickup/files/"+parcel.Files[0].ID+"/download", nil, false) + expect(t, w, 200) + if !bytes.Equal(w.Body.Bytes(), payload) { + t.Fatal("cross-entry download failed") + } + } + // The same hostname can hold different pickup grants on HTTP and HTTPS. + other := []byte("a different public HTTP parcel") + w = request("http://files.example.com", "POST", "/uploads", Manifest{Files: []ManifestFile{{Name: "other.txt", Size: int64(len(other))}}, Days: 7, Downloads: 4}, true) + expect(t, w, 201) + var second struct { + ID string + Files []struct{ ID string } + } + json.Unmarshal(w.Body.Bytes(), &second) + expect(t, request("http://files.example.com", "PUT", "/uploads/"+second.ID+"/files/"+second.Files[0].ID, other, true), 200) + w = request("http://files.example.com", "POST", "/uploads/"+second.ID+"/publish", map[string]any{}, true) + expect(t, w, 200) + var otherShare struct{ Code string } + json.Unmarshal(w.Body.Bytes(), &otherShare) + expect(t, request("https://files.example.com", "POST", "/pickup/resolve", map[string]string{"code": share.Code}, true), 200) + expect(t, request("http://files.example.com", "POST", "/pickup/resolve", map[string]string{"code": otherShare.Code}, true), 200) + w = request("https://files.example.com", "GET", "/pickup/files/"+parcel.Files[0].ID+"/download", nil, false) + expect(t, w, 200) + if !bytes.Equal(w.Body.Bytes(), payload) { + t.Fatal("HTTP grant shadowed the HTTPS grant") + } + w = request("http://files.example.com", "GET", "/pickup/files/"+second.Files[0].ID+"/download", nil, false) + expect(t, w, 200) + if !bytes.Equal(w.Body.Bytes(), other) { + t.Fatal("HTTPS grant shadowed the HTTP grant") + } + for _, header := range []struct{ origin, hint string }{{"https://evil.example", ""}, {"", "http://attacker.example"}, {"https://files.example.com", "http://files.example.com"}, {"http://192.168.1.211:8080", ""}} { + r := httptest.NewRequest("POST", "http://192.168.1.210:8080/api/v1/admin/login", strings.NewReader(`{"username":"admin","password":"test-password"}`)) + r.Header.Set("Origin", header.origin) + r.Header.Set("X-Bluebird-Origin", header.hint) + out := httptest.NewRecorder() + c.h.ServeHTTP(out, r) + expect(t, out, 403) + } + // Neither Host nor forwarded headers can inject a foreign share URL. + r := httptest.NewRequest("GET", "http://attacker.example/api/v1/policy", nil) + r.RemoteAddr = "203.0.113.4:4000" + r.Header.Set("X-Forwarded-Host", "evil.example") + r.Header.Set("X-Forwarded-Proto", "http") + out := httptest.NewRecorder() + c.h.ServeHTTP(out, r) + expect(t, out, 200) + if strings.Contains(out.Body.String(), "evil.example") || strings.Contains(out.Body.String(), "attacker.example") { + t.Fatal("untrusted forwarding/Host used as site URL") + } +} +func TestAccessConfigurationValidationAndAtomicUpdate(t *testing.T) { + cfg := DefaultConfig() + cfg.PublicURL = "https://files.example.com" + cfg.SecureCookies = true + cfg.AccessURLs = []string{"http://files.example.com", "http://192.168.1.210:8080"} + if cfg.Validate() == nil { + t.Fatal("private HTTP accepted without explicit opt-in") + } + cfg.AllowLANHTTP = true + if err := cfg.Validate(); err != nil { + t.Fatal(err) + } + for _, bad := range []string{"null", "https://", "https://:443", "http://user:pass@files.example.com", "http://files.example.com/path", "http://files.example.com?x=1", "ftp://files.example.com", "https://files.example.com:70000"} { + cfg.AccessURLs = []string{bad} + if cfg.Validate() == nil { + t.Fatalf("accepted bad origin %q", bad) + } + } + cfg.AccessURLs = nil + directory := t.TempDir() + path := filepath.Join(directory, "config.toml") + original, _ := toml.Marshal(cfg) + if err := os.WriteFile(path, original, 0640); err != nil { + t.Fatal(err) + } + if err := UpdateAccessConfig(path, "192.168.1.210:8080", []string{"http://files.example.com", "http://192.168.1.210:8080"}, true); err != nil { + t.Fatal(err) + } + saved, err := LoadConfig(path) + if err != nil { + t.Fatal(err) + } + if len(saved.AccessURLs) != 2 || saved.PublicURL != cfg.PublicURL || saved.Encryption != cfg.Encryption || saved.FileBudget != cfg.FileBudget || saved.Listen != "192.168.1.210:8080" { + t.Fatal("access update reset existing settings") + } + if _, err = os.Stat(filepath.Join(directory, "var")); !os.IsNotExist(err) { + t.Fatal("access configuration opened runtime data") + } + updated, _ := os.ReadFile(path) + if err = UpdateAccessConfig(path, "", []string{"http://evil.example/path"}, false); err == nil { + t.Fatal("invalid access update accepted") + } + intact, _ := os.ReadFile(path) + if !bytes.Equal(updated, intact) { + t.Fatal("failed update changed live configuration") + } + info, _ := os.Stat(path) + if info.Mode().Perm() != 0640 { + t.Fatal("configuration permissions changed") + } + if _, _, err = LANListen("http://files.example.com:8080"); err == nil { + t.Fatal("LAN flag accepted DNS-dependent domain") + } +} diff --git a/internal/app/admin.go b/internal/app/admin.go new file mode 100644 index 0000000..8398417 --- /dev/null +++ b/internal/app/admin.go @@ -0,0 +1,247 @@ +package app + +import ( + "encoding/json" + "golang.org/x/sys/unix" + "net/http" +) + +func (a *App) requireAdmin(w http.ResponseWriter, r *http.Request) bool { + if !a.isAdmin(r) { + fail(w, 401, "请先登录管理员") + return false + } + return true +} +func (a *App) adminLogin(w http.ResponseWriter, r *http.Request) { + if !a.allow(r, "login", 5) { + fail(w, 429, "登录尝试过于频繁") + return + } + var in struct { + Username string `json:"username"` + Password string `json:"password"` + } + if !decode(w, r, &in) { + return + } + select { + case a.passwordSlots <- struct{}{}: + default: + fail(w, 429, "登录繁忙") + return + } + defer func() { <-a.passwordSlots }() + var username, hash string + e := a.db.QueryRow("SELECT username,password_hash FROM admins WHERE id=1").Scan(&username, &hash) + valid := e == nil && verifyPassword(in.Password, hash) && in.Username == username + if !valid { + a.audit("admin.login_failed", "", a.remoteIP(r)) + fail(w, 401, "账号或密码错误") + return + } + a.mu.Lock() + defer a.mu.Unlock() + if _, e = a.db.Exec("DELETE FROM admin_sessions WHERE expires<=? OR token_hash IN(SELECT token_hash FROM admin_sessions ORDER BY expires DESC LIMIT -1 OFFSET 49)", a.now().Unix()); e != nil { + a.serverError(w, e) + return + } + token := randomToken() + _, e = a.db.Exec("INSERT INTO admin_sessions VALUES(?,?)", tokenHash(token), a.now().Unix()+8*3600) + if e != nil { + a.serverError(w, e) + return + } + a.cookie(w, r, "bb_admin", token, "/api/v1/admin", 8*3600) + a.audit("admin.login", "", a.remoteIP(r)) + respond(w, 200, map[string]bool{"authenticated": true}) +} +func (a *App) adminLogout(w http.ResponseWriter, r *http.Request) { + _, e := a.db.Exec("DELETE FROM admin_sessions WHERE token_hash=?", tokenHash(cookieValue(r, "bb_admin"))) + if e != nil { + a.serverError(w, e) + return + } + a.cookie(w, r, "bb_admin", "", "/api/v1/admin", -1) + respond(w, 200, map[string]bool{"authenticated": false}) +} +func (a *App) adminStatus(w http.ResponseWriter, r *http.Request) { + respond(w, 200, map[string]bool{"authenticated": a.isAdmin(r)}) +} +func (a *App) adminStats(w http.ResponseWriter, r *http.Request) { + if !a.requireAdmin(w, r) { + return + } + a.mu.Lock() + defer a.mu.Unlock() + used, reserved, meta, e := a.capacity() + if e != nil { + a.serverError(w, e) + return + } + var raw int64 + var files, objects, parcels int + queries := []struct { + sql string + dst any + }{{"SELECT COALESCE(sum(size),0) FROM files WHERE object_id IS NOT NULL", &raw}, {"SELECT count(*) FROM files", &files}, {"SELECT count(*) FROM objects", &objects}, {"SELECT count(*) FROM parcels WHERE state='ready'", &parcels}} + for _, q := range queries { + if e = a.db.QueryRow(q.sql).Scan(q.dst); e != nil { + a.serverError(w, e) + return + } + } + limits, e := a.runtimeSettings() + if e != nil { + a.serverError(w, e) + return + } + var disk unix.Statfs_t + if e = unix.Statfs(a.cfg.DataDir, &disk); e != nil { + a.serverError(w, e) + return + } + var auditCount, auditBytes int64 + if e = a.db.QueryRow("SELECT records,content_bytes FROM audit_usage WHERE id=1").Scan(&auditCount, &auditBytes); e != nil { + a.serverError(w, e) + return + } + a.transferMu.Lock() + up, down := a.transfers["upload"], a.transfers["download"] + a.transferMu.Unlock() + respond(w, 200, map[string]any{"object_bytes": used, "reserved_bytes": reserved, "metadata_bytes": meta, "logical_bytes": raw, "objects": objects, "files": files, "active_parcels": parcels, "data_budget": limits.DataBudget, "file_budget": limits.FileBudget, "min_free_bytes": limits.MinFreeBytes, "disk_total_bytes": int64(disk.Blocks) * int64(disk.Bsize), "disk_free_bytes": int64(disk.Bavail) * int64(disk.Bsize), "audit_records": auditCount, "audit_content_bytes": auditBytes, "active_uploads": up, "active_downloads": down, "encryption": a.cfg.Encryption, "deduplication": a.cfg.Deduplication}) +} +func (a *App) adminDelete(w http.ResponseWriter, r *http.Request) { + if !a.requireAdmin(w, r) { + return + } + a.mu.Lock() + defer a.mu.Unlock() + id := r.PathValue("parcel") + res, e := a.db.Exec("UPDATE parcels SET state='revoked',version=version+1 WHERE id=?", id) + if e != nil { + a.serverError(w, e) + return + } + n, _ := res.RowsAffected() + if n == 0 { + fail(w, 404, "包裹不存在") + return + } + a.audit("admin.delete", id, a.remoteIP(r)) + respond(w, 200, map[string]any{"revoked": true, "message": "已禁止新下载,实体文件由清理任务回收"}) +} +func (a *App) adminPolicy(w http.ResponseWriter, r *http.Request) { + if !a.requireAdmin(w, r) { + return + } + if r.Method == "GET" { + p, e := a.policy() + if e != nil { + a.serverError(w, e) + return + } + respond(w, 200, p) + return + } + var p Policy + if !decode(w, r, &p) { + return + } + if e := p.Validate(); e != nil { + fail(w, 400, e.Error()) + return + } + a.mu.Lock() + defer a.mu.Unlock() + b, e := json.Marshal(p) + if e == nil { + _, e = a.db.Exec("UPDATE settings SET json=? WHERE id=1", string(b)) + } + if e != nil { + a.serverError(w, e) + return + } + a.audit("policy.update", "", a.remoteIP(r)) + respond(w, 200, p) +} +func (a *App) adminCleanup(w http.ResponseWriter, r *http.Request) { + if !a.requireAdmin(w, r) { + return + } + if e := a.Cleanup(); e != nil { + a.serverError(w, e) + return + } + a.audit("admin.cleanup", "", a.remoteIP(r)) + respond(w, 200, map[string]bool{"cleaned": true}) +} + +func (a *App) adminDeleteFile(w http.ResponseWriter, r *http.Request) { + if !a.requireAdmin(w, r) { + return + } + a.mu.Lock() + defer a.mu.Unlock() + fid := r.PathValue("file") + res, e := a.db.Exec("UPDATE files SET status='deleting' WHERE id=?", fid) + if e != nil { + a.serverError(w, e) + return + } + n, _ := res.RowsAffected() + if n == 0 { + fail(w, 404, "文件不存在") + return + } + a.audit("admin.file_delete", fid, a.remoteIP(r)) + respond(w, 200, map[string]bool{"deleted": true}) +} +func (a *App) adminReissue(w http.ResponseWriter, r *http.Request) { + if !a.requireAdmin(w, r) { + return + } + a.mu.Lock() + defer a.mu.Unlock() + id := r.PathValue("parcel") + var state string + if e := a.db.QueryRow("SELECT state FROM parcels WHERE id=?", id).Scan(&state); e != nil || state != "restored" { + fail(w, 409, "仅允许重新发布恢复待审核包裹") + return + } + p, e := a.policy() + if e != nil { + a.serverError(w, e) + return + } + now := a.now().Unix() + expiry := now + int64(p.DefaultDays)*86400 + hard := now + int64(p.MaxLifetimeDays)*86400 + code := pickupCode() + tx, e := a.db.Begin() + if e != nil { + a.serverError(w, e) + return + } + defer tx.Rollback() + res, e := tx.Exec("UPDATE files SET status='ready',remaining=?,expires=?,hard_expires=? WHERE parcel=? AND object_id IS NOT NULL AND status NOT IN('missing','deleted','deleting')", p.DefaultDownloads, expiry, hard, id) + if e != nil { + a.serverError(w, e) + return + } + n, _ := res.RowsAffected() + if n == 0 { + fail(w, 409, "没有可重新发布的文件") + return + } + _, e = tx.Exec("UPDATE parcels SET state='ready',created=?,expires=?,hard_expires=?,code_hash=?,version=version+1,renewal_days=?,renewal_mode=? WHERE id=?", now, expiry, hard, a.keyed(code), p.RenewalDays, p.RenewalMode, id) + if e == nil { + e = tx.Commit() + } + if e != nil { + a.serverError(w, e) + return + } + a.audit("admin.reissue", id, a.remoteIP(r)) + respond(w, 200, a.shareResult(r, code, expiry)) +} diff --git a/internal/app/admin_list.go b/internal/app/admin_list.go new file mode 100644 index 0000000..5a84222 --- /dev/null +++ b/internal/app/admin_list.go @@ -0,0 +1,216 @@ +package app + +import ( + "database/sql" + "errors" + "net/http" + "net/url" + "strconv" + "strings" +) + +type adminListResult struct { + Items []map[string]any `json:"items"` + Total int `json:"total"` + Page int `json:"page"` + PageSize int `json:"page_size"` + Pages int `json:"pages"` +} + +func adminPaging(q url.Values, defaultSize int) (adminListResult, error) { + p := adminListResult{Items: []map[string]any{}, Page: 1, PageSize: defaultSize, Pages: 1} + for key, dest := range map[string]*int{"page": &p.Page, "page_size": &p.PageSize} { + if raw := q.Get(key); raw != "" { + n, err := strconv.Atoi(raw) + if err != nil || n < 1 || (key == "page_size" && n > 100) { + return p, errors.New("页码须为正整数,每页条数须为1至100") + } + *dest = n + } + } + return p, nil +} +func (p *adminListResult) setTotal(total int) { + p.Total = total + p.Pages = max(1, (total+p.PageSize-1)/p.PageSize) + p.Page = min(p.Page, p.Pages) +} +func adminFilter(q url.Values, key string, limit int) (string, error) { + v := strings.TrimSpace(q.Get(key)) + if len(v) > limit { + return "", errors.New("搜索条件过长") + } + return v, nil +} + +func (a *App) adminParcels(w http.ResponseWriter, r *http.Request) { + if !a.requireAdmin(w, r) { + return + } + q := r.URL.Query() + result, err := adminPaging(q, 10) + if err != nil { + fail(w, 400, err.Error()) + return + } + keyword, err := adminFilter(q, "q", 256) + if err != nil { + fail(w, 400, err.Error()) + return + } + state, err := adminFilter(q, "state", 32) + if err != nil { + fail(w, 400, err.Error()) + return + } + if state != "" && state != "uploading" && state != "ready" && state != "revoked" && state != "expired" && state != "exhausted" && state != "restored" { + fail(w, 400, "包裹状态无效") + return + } + where := " WHERE 1=1" + args := []any{} + if keyword != "" { + where += " AND (instr(lower(p.id),lower(?))>0 OR EXISTS(SELECT 1 FROM files f WHERE f.parcel=p.id AND instr(lower(f.name),lower(?))>0))" + args = append(args, keyword, keyword) + } + if state != "" { + where += " AND p.state=?" + args = append(args, state) + } + // File details use the same connection after the rows are closed. Prevent + // cleanup/publication from changing the selected parcels between those reads. + a.mu.Lock() + defer a.mu.Unlock() + var total int + if err = a.db.QueryRowContext(r.Context(), "SELECT count(*) FROM parcels p"+where, args...).Scan(&total); err != nil { + a.serverError(w, err) + return + } + result.setTotal(total) + listArgs := append(append([]any{}, args...), result.PageSize, (result.Page-1)*result.PageSize) + rows, err := a.db.QueryContext(r.Context(), "SELECT p.id,p.state,p.ip,p.created,p.expires,p.renewal_days,p.renewal_mode,(SELECT count(*) FROM files WHERE parcel=p.id),(SELECT COALESCE(sum(size),0) FROM files WHERE parcel=p.id) FROM parcels p"+where+" ORDER BY p.created DESC,p.id DESC LIMIT ? OFFSET ?", listArgs...) + if err != nil { + a.serverError(w, err) + return + } + for rows.Next() { + var id, state, ip, mode string + var created, expiry, size int64 + var renewal, count int + if err = rows.Scan(&id, &state, &ip, &created, &expiry, &renewal, &mode, &count, &size); err != nil { + rows.Close() + a.serverError(w, err) + return + } + result.Items = append(result.Items, map[string]any{"id": id, "state": state, "ip": ip, "created": created, "expires": expiry, "renewal_days": renewal, "renewal_mode": mode, "count": count, "size": size}) + } + err = rows.Err() + rows.Close() + if err != nil { + a.serverError(w, err) + return + } + for _, p := range result.Items { + files, err := a.fileList(p["id"].(string)) + if err != nil { + a.serverError(w, err) + return + } + p["files"] = files + } + respond(w, 200, result) +} + +func (a *App) adminAudit(w http.ResponseWriter, r *http.Request) { + if !a.requireAdmin(w, r) { + return + } + q := r.URL.Query() + result, err := adminPaging(q, 20) + if err != nil { + fail(w, 400, err.Error()) + return + } + where := " WHERE 1=1" + args := []any{} + for _, f := range []struct { + key string + limit int + }{{"action", 64}, {"source", 128}, {"target", 256}} { + value, err := adminFilter(q, f.key, f.limit) + if err != nil { + fail(w, 400, err.Error()) + return + } + if value == "" { + continue + } + if f.key == "action" { + where += " AND action=?" + } else { + where += " AND instr(lower(" + f.key + "),lower(?))>0" + } + args = append(args, value) + } + var from, to int64 + for _, bound := range []struct { + key, op string + dest *int64 + }{{"from", ">=", &from}, {"to", "<=", &to}} { + if raw := q.Get(bound.key); raw != "" { + value, err := strconv.ParseInt(raw, 10, 64) + if err != nil || value < 0 { + fail(w, 400, "时间须为非负Unix秒数") + return + } + *bound.dest = value + where += " AND created" + bound.op + "?" + args = append(args, value) + } + } + if q.Get("from") != "" && q.Get("to") != "" && from > to { + fail(w, 400, "开始时间不能晚于结束时间") + return + } + // Count and items share a snapshot so concurrent audit writes or retention do + // not change the response's total and page boundaries mid-query. + tx, err := a.db.BeginTx(r.Context(), &sql.TxOptions{ReadOnly: true}) + if err != nil { + a.serverError(w, err) + return + } + defer tx.Rollback() + var total int + if err = tx.QueryRowContext(r.Context(), "SELECT count(*) FROM audit"+where, args...).Scan(&total); err != nil { + a.serverError(w, err) + return + } + result.setTotal(total) + listArgs := append(append([]any{}, args...), result.PageSize, (result.Page-1)*result.PageSize) + rows, err := tx.QueryContext(r.Context(), "SELECT id,created,action,target,source FROM audit"+where+" ORDER BY id DESC LIMIT ? OFFSET ?", listArgs...) + if err != nil { + a.serverError(w, err) + return + } + for rows.Next() { + var id, created int64 + var action, target, source string + if err = rows.Scan(&id, &created, &action, &target, &source); err != nil { + rows.Close() + a.serverError(w, err) + return + } + result.Items = append(result.Items, map[string]any{"id": id, "created": created, "action": action, "target": target, "source": source}) + } + err = rows.Err() + rows.Close() + if err != nil { + a.serverError(w, err) + return + } + if err = tx.Commit(); err != nil { + a.serverError(w, err) + return + } + respond(w, 200, result) +} diff --git a/internal/app/admin_list_test.go b/internal/app/admin_list_test.go new file mode 100644 index 0000000..c554b81 --- /dev/null +++ b/internal/app/admin_list_test.go @@ -0,0 +1,175 @@ +package app + +import ( + "encoding/json" + "fmt" + "net/url" + "strings" + "testing" +) + +func adminResult(t *testing.T, c *testClient, path string) adminListResult { + t.Helper() + w := c.request(t, "GET", path, nil) + expect(t, w, 200) + var p adminListResult + if err := json.Unmarshal(w.Body.Bytes(), &p); err != nil { + t.Fatal(err) + } + if p.Items == nil { + t.Fatal("items must be an array, including when empty") + } + return p +} +func TestAdminParcelPaginationAndSearch(t *testing.T) { + c := fixture(t, false) + expect(t, c.request(t, "GET", "/admin/parcels?page_size=10", nil), 401) + loginTestAdmin(t, c) + tx, err := c.a.db.Begin() + if err != nil { + t.Fatal(err) + } + defer tx.Rollback() + for i := 1; i <= 25; i++ { + id := fmt.Sprintf("%032x", i) + state := "ready" + if i%2 == 0 { + state = "revoked" + } + _, err = tx.Exec("INSERT INTO parcels(id,visitor,ip,state,created,password_hash,manage_hash,days,downloads,renewal_days,renewal_mode,lifetime_days,upload_expires,usage_day) VALUES(?,?,'203.0.113.1',?,1000,'','',1,1,0,'add',30,2000,'2026-10-05')", id, "visitor", state) + if err != nil { + t.Fatal(err) + } + name := fmt.Sprintf("document-%d.txt", i) + if i == 5 { + name = "说明_%_Report.txt" + } + _, err = tx.Exec("INSERT INTO files(id,parcel,name,size,status,reserved,remaining) VALUES(?,?,?,12,'deleted',0,0)", id, id, name) + if err != nil { + t.Fatal(err) + } + } + if err = tx.Commit(); err != nil { + t.Fatal(err) + } + first := adminResult(t, c, "/admin/parcels") + if first.Total != 25 || first.PageSize != 10 || first.Pages != 3 || len(first.Items) != 10 || first.Items[0]["id"] != fmt.Sprintf("%032x", 25) { + t.Fatalf("bad first page: %+v", first) + } + second := adminResult(t, c, "/admin/parcels?page=2") + if second.Page != 2 || second.Items[0]["id"] != fmt.Sprintf("%032x", 15) { + t.Fatalf("bad second page: %+v", second) + } + last := adminResult(t, c, "/admin/parcels?page=999999999") + if last.Page != 3 || len(last.Items) != 5 { + t.Fatalf("out-of-range page was not clamped: %+v", last) + } + for _, q := range []string{"REPORT", "说明", "%", fmt.Sprintf("%032x", 5)} { + p := adminResult(t, c, "/admin/parcels?q="+url.QueryEscape(q)) + if p.Total != 1 || p.Items[0]["id"] != fmt.Sprintf("%032x", 5) { + t.Fatalf("literal filename/ID query %q: %+v", q, p) + } + } + p := adminResult(t, c, "/admin/parcels?state=revoked&page_size=20") + if p.Total != 12 || p.PageSize != 20 || len(p.Items) != 12 { + t.Fatalf("state filter: %+v", p) + } + p = adminResult(t, c, "/admin/parcels?q="+url.QueryEscape("' OR 1=1 --")) + if p.Total != 0 || len(p.Items) != 0 || p.Page != 1 || p.Pages != 1 { + t.Fatalf("empty/literal injection query: %+v", p) + } + for _, q := range []string{"page=0", "page=-1", "page=oops", "page_size=0", "page_size=101", "page_size=oops", "state=unknown", "q=" + strings.Repeat("a", 257)} { + expect(t, c.request(t, "GET", "/admin/parcels?"+q, nil), 400) + } +} +func TestAdminAuditPaginationAndCombinedFilters(t *testing.T) { + c := fixture(t, false) + expect(t, c.request(t, "GET", "/admin/audit", nil), 401) + loginTestAdmin(t, c) + if _, err := c.a.db.Exec("DELETE FROM audit"); err != nil { + t.Fatal(err) + } + base := c.a.now().Unix() + tx, err := c.a.db.Begin() + if err != nil { + t.Fatal(err) + } + defer tx.Rollback() + for i := 1; i <= 54; i++ { + action := "upload.create" + if i%2 == 0 { + action = "admin.delete" + } + source := "2001:db8::1" + if i%3 == 0 { + source = "203.0.113.1" + } + target := fmt.Sprintf("job-%d", i) + if i == 18 { + target = "job_%_18" + } + if _, err = tx.Exec("INSERT INTO audit(created,action,target,source) VALUES(?,?,?,?)", base+int64(i)*60, action, target, source); err != nil { + t.Fatal(err) + } + } + if err = tx.Commit(); err != nil { + t.Fatal(err) + } + p := adminResult(t, c, "/admin/audit") + if p.Total != 54 || p.PageSize != 20 || p.Pages != 3 || len(p.Items) != 20 || p.Items[0]["target"] != "job-54" { + t.Fatalf("bad audit page: %+v", p) + } + p2 := adminResult(t, c, "/admin/audit?page=2") + if p2.Items[0]["target"] != "job-34" { + t.Fatalf("audit page did not change: %+v", p2) + } + last := adminResult(t, c, "/admin/audit?page=99999") + if last.Page != 3 || len(last.Items) != 14 { + t.Fatalf("bad audit last page: %+v", last) + } + query := fmt.Sprintf("/admin/audit?action=admin.delete&source=203.0.113.1&target=JOB&from=%d&to=%d", base+10*60, base+30*60) + filtered := adminResult(t, c, query) + if filtered.Total != 4 || len(filtered.Items) != 4 { + t.Fatalf("combined/inclusive date filters: %+v", filtered) + } + for _, item := range filtered.Items { + if item["action"] != "admin.delete" || item["source"] != "203.0.113.1" { + t.Fatalf("wrong filter result: %+v", item) + } + } + literal := adminResult(t, c, "/admin/audit?target=%25") + if literal.Total != 1 || literal.Items[0]["target"] != "job_%_18" { + t.Fatalf("wildcard interpreted as pattern: %+v", literal) + } + ipv6 := adminResult(t, c, "/admin/audit?source=2001%3ADB8") + if ipv6.Total != 36 { + t.Fatalf("IPv6 fragment filter: %+v", ipv6) + } + empty := adminResult(t, c, "/admin/audit?action=unknown") + if empty.Total != 0 || empty.Pages != 1 || len(empty.Items) != 0 { + t.Fatal("empty audit search malformed") + } + for _, q := range []string{"page=0", "page_size=101", "from=-1", "from=nope", "to=1.5", "from=20&to=10", "action=" + strings.Repeat("a", 65), "source=" + strings.Repeat("a", 129), "target=" + strings.Repeat("a", 257)} { + expect(t, c.request(t, "GET", "/admin/audit?"+q, nil), 400) + } +} +func TestAdminAuditOneHundredThousandRecordsRemainPaged(t *testing.T) { + c := fixture(t, false) + loginTestAdmin(t, c) + _, err := c.a.db.Exec("DELETE FROM audit; WITH RECURSIVE n(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM n WHERE i<100000) INSERT INTO audit(created,action,target,source) SELECT 1000,'upload.create',printf('target-%d',i),'203.0.113.1' FROM n") + if err != nil { + t.Fatal(err) + } + first := adminResult(t, c, "/admin/audit") + if first.Total != 100000 || first.Pages != 5000 || len(first.Items) != 20 || first.Items[0]["target"] != "target-100000" { + t.Fatalf("large audit query unbounded: %+v", first) + } + last := adminResult(t, c, "/admin/audit?page=5000") + if last.Page != 5000 || len(last.Items) != 20 || last.Items[19]["target"] != "target-1" { + t.Fatalf("large audit final page incorrect: %+v", last) + } + filtered := adminResult(t, c, "/admin/audit?target=target-12345") + if filtered.Total != 1 || len(filtered.Items) != 1 || filtered.Items[0]["target"] != "target-12345" { + t.Fatal("search did not include older/all retained audit events") + } +} diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..6c48961 --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,471 @@ +package app + +import ( + "database/sql" + "encoding/json" + "errors" + "fmt" + "io" + "log/slog" + "net" + "net/http" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "filippo.io/age" + "golang.org/x/sys/unix" + _ "modernc.org/sqlite" +) + +type App struct { + requestWG sync.WaitGroup + cfg Config + db *sql.DB + root *os.Root + lock *os.File + secret []byte + identity *age.X25519Identity + mu sync.Mutex + passwordSlots chan struct{} + rateMu sync.Mutex + rates map[string]*rateEntry + transferMu sync.Mutex + transfers map[string]int + activeFiles map[string]bool + activeObjects map[string]int + extraDisk int64 + proxies []*net.IPNet + now func() time.Time + log *slog.Logger +} + +const schema = ` +CREATE TABLE IF NOT EXISTS schema_version(version INTEGER NOT NULL); +INSERT INTO schema_version SELECT 1 WHERE NOT EXISTS(SELECT 1 FROM schema_version); +CREATE TABLE IF NOT EXISTS settings(id INTEGER PRIMARY KEY CHECK(id=1),json TEXT NOT NULL); +CREATE TABLE IF NOT EXISTS runtime_settings(id INTEGER PRIMARY KEY CHECK(id=1),json TEXT NOT NULL); +CREATE TABLE IF NOT EXISTS admins(id INTEGER PRIMARY KEY CHECK(id=1),username TEXT NOT NULL,password_hash TEXT NOT NULL); +CREATE TABLE IF NOT EXISTS admin_sessions(token_hash TEXT PRIMARY KEY,expires INTEGER NOT NULL); +CREATE TABLE IF NOT EXISTS visitors(id TEXT PRIMARY KEY,token_hash TEXT UNIQUE NOT NULL,expires INTEGER NOT NULL); +CREATE TABLE IF NOT EXISTS parcels(id TEXT PRIMARY KEY,visitor TEXT NOT NULL,ip TEXT NOT NULL,state TEXT NOT NULL,created INTEGER NOT NULL,expires INTEGER NOT NULL DEFAULT 0,hard_expires INTEGER NOT NULL DEFAULT 0,code_hash TEXT UNIQUE,password_hash TEXT NOT NULL,manage_hash TEXT NOT NULL,version INTEGER NOT NULL DEFAULT 1,days INTEGER NOT NULL,downloads INTEGER NOT NULL,renewal_days INTEGER NOT NULL,renewal_mode TEXT NOT NULL,lifetime_days INTEGER NOT NULL,upload_expires INTEGER NOT NULL,usage_day TEXT NOT NULL); +CREATE TABLE IF NOT EXISTS objects(id TEXT PRIMARY KEY,digest TEXT UNIQUE NOT NULL,size INTEGER NOT NULL,stored_bytes INTEGER NOT NULL,encrypted INTEGER NOT NULL,key_id TEXT NOT NULL,created INTEGER NOT NULL); +CREATE TABLE IF NOT EXISTS files(id TEXT PRIMARY KEY,parcel TEXT NOT NULL REFERENCES parcels(id) ON DELETE CASCADE,name TEXT NOT NULL,size INTEGER NOT NULL,status TEXT NOT NULL,object_id TEXT REFERENCES objects(id),reserved INTEGER NOT NULL,remaining INTEGER NOT NULL,received INTEGER NOT NULL DEFAULT 0,charged INTEGER NOT NULL DEFAULT 0,attempts INTEGER NOT NULL DEFAULT 0,expires INTEGER NOT NULL DEFAULT 0,hard_expires INTEGER NOT NULL DEFAULT 0); +CREATE TABLE IF NOT EXISTS grants(token_hash TEXT PRIMARY KEY,parcel TEXT NOT NULL REFERENCES parcels(id) ON DELETE CASCADE,version INTEGER NOT NULL,expires INTEGER NOT NULL); +CREATE TABLE IF NOT EXISTS usage(subject TEXT NOT NULL,day TEXT NOT NULL,bytes INTEGER NOT NULL,uploads INTEGER NOT NULL,PRIMARY KEY(subject,day)); +CREATE TABLE IF NOT EXISTS downloads(id TEXT PRIMARY KEY,file_id TEXT NOT NULL,created INTEGER NOT NULL,started INTEGER NOT NULL DEFAULT 0,completed INTEGER NOT NULL DEFAULT 0,refunded INTEGER NOT NULL DEFAULT 0); +CREATE TABLE IF NOT EXISTS audit(id INTEGER PRIMARY KEY AUTOINCREMENT,created INTEGER NOT NULL,action TEXT NOT NULL,target TEXT NOT NULL,source TEXT NOT NULL); +CREATE INDEX IF NOT EXISTS audit_created ON audit(created); +CREATE INDEX IF NOT EXISTS audit_action_id ON audit(action,id DESC); +CREATE INDEX IF NOT EXISTS parcels_created_id ON parcels(created DESC,id DESC); +CREATE INDEX IF NOT EXISTS files_parcel ON files(parcel); +CREATE INDEX IF NOT EXISTS files_object ON files(object_id); +CREATE INDEX IF NOT EXISTS parcels_expiry ON parcels(state,expires); +CREATE INDEX IF NOT EXISTS parcels_visitor ON parcels(visitor); +` + +func Open(c Config) (*App, error) { return open(c, false) } +func OpenForRestore(c Config) (*App, error) { return open(c, true) } +func open(c Config, restoring bool) (*App, error) { + if e := c.Validate(); e != nil { + return nil, e + } + if e := os.MkdirAll(c.DataDir, 0700); e != nil { + return nil, e + } + root, e := os.OpenRoot(c.DataDir) + if e != nil { + return nil, e + } + if !restoring { + if _, me := root.Stat("restore.incomplete"); me == nil { + root.Close() + return nil, errors.New("恢复未完成,拒绝启动;请检查恢复错误并重新恢复到空目录") + } + } + a := &App{cfg: c, root: root, now: time.Now, log: slog.Default(), passwordSlots: make(chan struct{}, 2), rates: map[string]*rateEntry{}, transfers: map[string]int{}, activeFiles: map[string]bool{}, activeObjects: map[string]int{}} + fail := func(e error) (*App, error) { a.Close(); return nil, e } + for _, d := range []string{"db", "objects", "tmp", "secrets", "quarantine"} { + if e = root.Mkdir(d, 0700); e != nil && !errors.Is(e, os.ErrExist) { + return fail(e) + } + st, se := root.Lstat(d) + if se != nil { + return fail(se) + } + if !st.IsDir() || st.Mode()&os.ModeSymlink != 0 { + return fail(errors.New("数据子目录必须为真实目录")) + } + } + a.lock, e = root.OpenFile("service.lock", os.O_CREATE|os.O_RDWR, 0600) + if e != nil { + return fail(e) + } + if e = unix.Flock(int(a.lock.Fd()), unix.LOCK_EX|unix.LOCK_NB); e != nil { + return fail(errors.New("数据目录已被另一进程使用")) + } + secretPath := "secrets/hmac.key" + if _, de := root.Stat("db/bluebird.db"); de == nil { + if _, se := root.Lstat(secretPath); se != nil { + return fail(errors.New("既有数据库缺少 HMAC 密钥")) + } + } + a.secret, e = readOrCreate(root, secretPath, func() ([]byte, error) { return []byte(randomToken()), nil }) + if e != nil { + return fail(e) + } + if len(a.secret) < 32 { + return fail(errors.New("HMAC 密钥损坏")) + } + if st, se := root.Lstat("secrets/age.key"); se == nil && !st.Mode().IsRegular() { + return fail(errors.New("加密私钥必须为普通文件")) + } + key, e := root.ReadFile("secrets/age.key") + if e == nil { + a.identity, e = age.ParseX25519Identity(strings.TrimSpace(string(key))) + if e != nil { + return fail(e) + } + } else if !errors.Is(e, os.ErrNotExist) { + return fail(e) + } + for _, path := range []string{"db/bluebird.db", "db/bluebird.db-wal", "db/bluebird.db-shm", "service.lock"} { + if st, se := root.Lstat(path); se == nil && !st.Mode().IsRegular() { + return fail(errors.New("数据库和锁文件必须为普通文件")) + } + } + dbPath := filepath.Join(c.DataDir, "db", "bluebird.db") + if st, se := root.Lstat("db/bluebird.db"); se == nil && st.Mode()&os.ModeSymlink != 0 { + return fail(errors.New("禁止数据库符号链接")) + } + a.db, e = sql.Open("sqlite", dbPath) + if e != nil { + return fail(e) + } + a.db.SetMaxOpenConns(1) + if _, e = a.db.Exec("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA synchronous=FULL; PRAGMA busy_timeout=5000; PRAGMA temp_store=MEMORY; " + schema); e != nil { + return fail(e) + } + var version int + if e = a.db.QueryRow("SELECT version FROM schema_version").Scan(&version); e != nil || version != 1 { + return fail(errors.New("不支持的数据库版本")) + } + _ = root.Chmod("db/bluebird.db", 0600) + if a.identity == nil { + var n int + if e = a.db.QueryRow("SELECT count(*) FROM objects WHERE encrypted=1").Scan(&n); e != nil { + return fail(e) + } + if n > 0 { + return fail(errors.New("加密私钥丢失,拒绝生成替代密钥")) + } + if c.Encryption { + b, e := readOrCreate(root, "secrets/age.key", func() ([]byte, error) { + key, e := age.GenerateX25519Identity() + if e != nil { + return nil, e + } + return []byte(key.String() + "\n"), nil + }) + if e != nil { + return fail(e) + } + a.identity, e = age.ParseX25519Identity(strings.TrimSpace(string(b))) + if e != nil { + return fail(e) + } + } + } + b, e := json.Marshal(c.Policy) + if e != nil { + return fail(e) + } + if _, e = a.db.Exec("INSERT OR IGNORE INTO settings VALUES(1,?)", string(b)); e != nil { + return fail(e) + } + if _, e = a.policy(); e != nil { + return fail(e) + } + b, e = json.Marshal(c.InitialRuntimeSettings()) + if e != nil { + return fail(e) + } + if _, e = a.db.Exec("INSERT OR IGNORE INTO runtime_settings VALUES(1,?)", string(b)); e != nil { + return fail(e) + } + if _, e = a.runtimeSettings(); e != nil { + return fail(e) + } + if e = a.initializeAuditUsage(); e != nil { + return fail(e) + } + for _, p := range c.TrustedProxies { + _, n, _ := net.ParseCIDR(p) + a.proxies = append(a.proxies, n) + } + if !restoring { + if e = a.recover(); e != nil { + return fail(e) + } + } + return a, nil +} +func readOrCreate(root *os.Root, path string, generate func() ([]byte, error)) ([]byte, error) { + if st, e := root.Lstat(path); e == nil && !st.Mode().IsRegular() { + return nil, errors.New("密钥必须为普通文件") + } + b, e := root.ReadFile(path) + if e == nil { + return b, nil + } + if !errors.Is(e, os.ErrNotExist) { + return nil, e + } + b, e = generate() + if e != nil { + return nil, e + } + f, e := root.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) + if e != nil { + return nil, e + } + _, e = f.Write(b) + if e == nil { + e = f.Sync() + } + ce := f.Close() + if e == nil { + e = ce + } + if e == nil { + e = syncDir(root, filepath.Dir(path)) + } + return b, e +} +func syncDir(root *os.Root, path string) error { + f, e := root.Open(path) + if e != nil { + return e + } + defer f.Close() + return f.Sync() +} +func (a *App) WaitRequests() { a.requestWG.Wait() } +func (a *App) Close() error { + var e error + if a.db != nil { + e = a.db.Close() + } + if a.root != nil { + a.root.Close() + } + if a.lock != nil { + a.lock.Close() + } + return e +} +func (a *App) policy() (Policy, error) { + var s string + p := Policy{} + e := a.db.QueryRow("SELECT json FROM settings WHERE id=1").Scan(&s) + if e == nil { + e = json.Unmarshal([]byte(s), &p) + } + if e == nil { + e = p.Validate() + } + return p, e +} +func (a *App) audit(action, target, source string) { + tx, e := a.db.Begin() + if e != nil { + a.log.Error("audit failed", "error", e) + return + } + defer tx.Rollback() + _, e = tx.Exec("INSERT INTO audit(created,action,target,source) VALUES(?,?,?,?)", a.now().Unix(), action, target, source) + if e == nil { + e = a.trimAuditTx(tx) + } + if e == nil { + e = tx.Commit() + } + if e != nil { + a.log.Error("audit failed", "action", action, "error", e) + } +} +func (a *App) SetAdmin(username, password string) error { + if len(username) < 1 || len(username) > 64 { + return errors.New("管理员名称范围错误") + } + h, e := hashPassword(password) + if e != nil { + return e + } + tx, e := a.db.Begin() + if e != nil { + return e + } + defer tx.Rollback() + if _, e = tx.Exec("INSERT INTO admins VALUES(1,?,?) ON CONFLICT(id) DO UPDATE SET username=excluded.username,password_hash=excluded.password_hash", username, h); e != nil { + return e + } + if _, e = tx.Exec("DELETE FROM admin_sessions"); e != nil { + return e + } + return tx.Commit() +} +func (a *App) Initialized() bool { + var n int + a.db.QueryRow("SELECT count(*) FROM admins").Scan(&n) + return n == 1 +} +func (a *App) Handler(static http.Handler) http.Handler { + mux := http.NewServeMux() + mux.HandleFunc("GET /api/v1/health", a.health) + mux.HandleFunc("GET /api/v1/policy", a.publicPolicy) + mux.HandleFunc("POST /api/v1/uploads", a.createUpload) + mux.HandleFunc("PUT /api/v1/uploads/{parcel}/files/{file}", a.uploadFile) + mux.HandleFunc("POST /api/v1/uploads/{parcel}/publish", a.publish) + mux.HandleFunc("GET /api/v1/uploads/{parcel}", a.uploadStatus) + mux.HandleFunc("GET /api/v1/mine", a.mine) + mux.HandleFunc("POST /api/v1/parcels/{parcel}/revoke", a.revoke) + mux.HandleFunc("POST /api/v1/parcels/{parcel}/code", a.regenerate) + mux.HandleFunc("POST /api/v1/pickup/resolve", a.resolve) + mux.HandleFunc("GET /api/v1/pickup", a.pickupHandler) + mux.HandleFunc("GET /api/v1/pickup/files/{file}/download", a.download) + mux.HandleFunc("POST /api/v1/admin/login", a.adminLogin) + mux.HandleFunc("POST /api/v1/admin/logout", a.adminLogout) + mux.HandleFunc("GET /api/v1/admin/status", a.adminStatus) + mux.HandleFunc("GET /api/v1/admin/stats", a.adminStats) + mux.HandleFunc("GET /api/v1/admin/runtime", a.adminRuntime) + mux.HandleFunc("PUT /api/v1/admin/runtime", a.adminRuntime) + mux.HandleFunc("GET /api/v1/admin/parcels", a.adminParcels) + mux.HandleFunc("POST /api/v1/admin/parcels/{parcel}/delete", a.adminDelete) + mux.HandleFunc("GET /api/v1/admin/policy", a.adminPolicy) + mux.HandleFunc("PUT /api/v1/admin/policy", a.adminPolicy) + mux.HandleFunc("GET /api/v1/admin/audit", a.adminAudit) + mux.HandleFunc("POST /api/v1/admin/cleanup", a.adminCleanup) + mux.HandleFunc("POST /api/v1/admin/files/{file}/delete", a.adminDeleteFile) + mux.HandleFunc("POST /api/v1/admin/parcels/{parcel}/reissue", a.adminReissue) + mux.HandleFunc("/api/", func(w http.ResponseWriter, r *http.Request) { fail(w, 404, "接口不存在") }) + mux.Handle("/", static) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + a.requestWG.Add(1) + defer a.requestWG.Done() + w.Header().Set("X-Content-Type-Options", "nosniff") + w.Header().Set("Referrer-Policy", "no-referrer") + w.Header().Set("X-Frame-Options", "DENY") + w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self'; font-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'") + if strings.HasPrefix(r.URL.Path, "/api/") { + w.Header().Set("Cache-Control", "no-store") + } + if strings.HasPrefix(r.URL.Path, "/api/") { + var e error + r, e = a.withAccess(r) + if e != nil { + fail(w, 403, e.Error()) + return + } + } + if r.Method != "GET" && r.Method != "HEAD" && r.Method != "OPTIONS" { + origin := r.Header.Get("Origin") + if r.Header.Get("Sec-Fetch-Site") == "cross-site" { + fail(w, 403, "跨站请求被拒绝") + return + } + if origin == "" && strings.Contains(r.Header.Get("Cookie"), "bb_") { + fail(w, 403, "携带会话的修改请求必须有 Origin") + return + } + } + if !a.Initialized() && r.URL.Path != "/api/v1/health" { + fail(w, 503, "请先在服务器上初始化管理员") + return + } + mux.ServeHTTP(w, r) + }) +} +func decode(w http.ResponseWriter, r *http.Request, v any) bool { + r.Body = http.MaxBytesReader(w, r.Body, 1<<20) + d := json.NewDecoder(r.Body) + d.DisallowUnknownFields() + if e := d.Decode(v); e != nil { + fail(w, 400, "请求格式无效") + return false + } + if e := d.Decode(&struct{}{}); e != io.EOF { + fail(w, 400, "请求包含多余内容") + return false + } + return true +} +func respond(w http.ResponseWriter, status int, v any) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(status) + _ = json.NewEncoder(w).Encode(v) +} +func fail(w http.ResponseWriter, status int, message string) { + respond(w, status, map[string]string{"error": message}) +} +func (a *App) serverError(w http.ResponseWriter, e error) { + a.log.Error("operation failed", "error", e) + fail(w, 500, "服务器操作失败,请稍后重试") +} +func (a *App) health(w http.ResponseWriter, r *http.Request) { + if e := a.db.Ping(); e != nil { + fail(w, 503, "数据库不可用") + return + } + respond(w, 200, map[string]any{"ready": a.Initialized(), "version": "0.1.0"}) +} +func (a *App) publicPolicy(w http.ResponseWriter, r *http.Request) { + if !a.allow(r, "policy", 60) { + fail(w, 429, "访问过于频繁") + return + } + a.mu.Lock() + defer a.mu.Unlock() + p, e := a.policy() + if e != nil { + a.serverError(w, e) + return + } + visitor, e := a.visitor(w, r) + if e != nil { + fail(w, 429, "匿名会话暂不可用") + return + } + var visitorExpires int64 + if e = a.db.QueryRow("SELECT expires FROM visitors WHERE id=?", visitor).Scan(&visitorExpires); e != nil { + a.serverError(w, e) + return + } + respond(w, 200, map[string]any{"policy": p, "public_url": a.accessFor(r).base, "access_urls": a.cfg.accessOrigins(), "encryption": a.cfg.Encryption, "deduplication": a.cfg.Deduplication, "visitor_expires": visitorExpires}) +} +func (a *App) RunMaintenance(stop <-chan struct{}) { + t := time.NewTicker(time.Minute) + defer t.Stop() + for { + select { + case <-stop: + return + case <-t.C: + if e := a.Cleanup(); e != nil { + a.log.Error("cleanup failed", "error", e) + } + } + } +} +func (a *App) Check() error { + var result string + if e := a.db.QueryRow("PRAGMA integrity_check").Scan(&result); e != nil { + return e + } + if result != "ok" { + return fmt.Errorf("SQLite: %s", result) + } + return a.verifyObjects() +} diff --git a/internal/app/app_test.go b/internal/app/app_test.go new file mode 100644 index 0000000..a0a9d46 --- /dev/null +++ b/internal/app/app_test.go @@ -0,0 +1,790 @@ +package app + +import ( + "bufio" + "bytes" + "crypto/sha256" + "encoding/json" + "fmt" + "hash" + "io" + "net" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + "testing" + "time" + + "golang.org/x/time/rate" +) + +type testClient struct { + a *App + h http.Handler + cookies map[string]*http.Cookie + ip string +} + +func fixture(t *testing.T, encrypted bool) *testClient { + t.Helper() + c := DefaultConfig() + c.DataDir = t.TempDir() + c.Encryption = encrypted + c.MinFreeBytes = 0 + c.DataBudget = 128 << 20 + c.FileBudget = 64 << 20 + a, e := Open(c) + if e != nil { + t.Fatal(e) + } + t.Cleanup(func() { a.Close() }) + if e = a.SetAdmin("admin", "test-password"); e != nil { + t.Fatal(e) + } + return &testClient{a: a, h: a.Handler(http.NotFoundHandler()), cookies: map[string]*http.Cookie{}, ip: "203.0.113.10"} +} +func (c *testClient) request(t *testing.T, method, path string, body []byte) *httptest.ResponseRecorder { + t.Helper() + r := httptest.NewRequest(method, "http://localhost:8080/api/v1"+path, bytes.NewReader(body)) + r.RemoteAddr = c.ip + ":1234" + if method != "GET" && method != "HEAD" { + r.Header.Set("Origin", "http://localhost:8080") + } + for _, cookie := range c.cookies { + r.AddCookie(cookie) + } + w := httptest.NewRecorder() + c.h.ServeHTTP(w, r) + for _, cookie := range w.Result().Cookies() { + if cookie.MaxAge < 0 { + delete(c.cookies, cookie.Name) + } else { + c.cookies[cookie.Name] = cookie + } + } + return w +} +func (c *testClient) json(t *testing.T, method, path string, v any) *httptest.ResponseRecorder { + b, e := json.Marshal(v) + if e != nil { + t.Fatal(e) + } + return c.request(t, method, path, b) +} +func expect(t *testing.T, w *httptest.ResponseRecorder, status int) { + t.Helper() + if w.Code != status { + t.Fatalf("status=%d want=%d body=%s", w.Code, status, w.Body.String()) + } +} +func (c *testClient) create(t *testing.T, names []string, data [][]byte, downloads int, renew bool) (string, []string) { + t.Helper() + fs := []ManifestFile{} + for i, d := range data { + fs = append(fs, ManifestFile{Name: names[i], Size: int64(len(d))}) + } + w := c.json(t, "POST", "/uploads", Manifest{Files: fs, Days: 7, Downloads: downloads, Renew: renew}) + expect(t, w, 201) + var result struct { + ID string `json:"id"` + Files []struct { + ID string `json:"id"` + } `json:"files"` + } + if e := json.Unmarshal(w.Body.Bytes(), &result); e != nil { + t.Fatal(e) + } + ids := []string{} + for _, f := range result.Files { + ids = append(ids, f.ID) + } + return result.ID, ids +} +func (c *testClient) publish(t *testing.T, id string) string { + w := c.json(t, "POST", "/uploads/"+id+"/publish", struct{}{}) + expect(t, w, 200) + var out struct { + Code string `json:"code"` + } + json.Unmarshal(w.Body.Bytes(), &out) + if len(out.Code) != 10 { + t.Fatal("missing code") + } + return out.Code +} +func (c *testClient) resolve(t *testing.T, code string) { + w := c.json(t, "POST", "/pickup/resolve", map[string]string{"code": code}) + expect(t, w, 200) +} +func (c *testClient) upload(t *testing.T, id, fid string, b []byte) { + expect(t, c.request(t, "PUT", "/uploads/"+id+"/files/"+fid, b), 200) +} +func scalar(t *testing.T, a *App, q string, args ...any) int64 { + t.Helper() + var n int64 + if e := a.db.QueryRow(q, args...).Scan(&n); e != nil { + t.Fatal(e) + } + return n +} +func TestDedupIndependentPermissionsAndCleanup(t *testing.T) { + for _, encrypted := range []bool{false, true} { + t.Run(map[bool]string{false: "plain", true: "age"}[encrypted], func(t *testing.T) { + c := fixture(t, encrypted) + data := []byte(strings.Repeat("confidential-content", 1000)) + id, files := c.create(t, []string{"a.txt", "b.txt"}, [][]byte{data, data}, 3, true) + for _, f := range files { + c.upload(t, id, f, data) + } + code := c.publish(t, id) + if n := scalar(t, c.a, "SELECT count(*) FROM objects"); n != 1 { + t.Fatalf("dedup objects=%d", n) + } + var oid string + c.a.db.QueryRow("SELECT id FROM objects").Scan(&oid) + payload, _ := c.a.root.ReadFile(objectPath(oid)) + if encrypted && bytes.Contains(payload, data) { + t.Fatal("plaintext at rest") + } + c.resolve(t, code) + w := c.request(t, "GET", "/pickup/files/"+files[0]+"/download", nil) + expect(t, w, 200) + if !bytes.Equal(w.Body.Bytes(), data) { + t.Fatal("roundtrip mismatch") + } + if scalar(t, c.a, "SELECT remaining FROM files WHERE id=?", files[0]) != 2 || scalar(t, c.a, "SELECT remaining FROM files WHERE id=?", files[1]) != 3 { + t.Fatal("download counters not independent") + } + aExpiry := scalar(t, c.a, "SELECT expires FROM files WHERE id=?", files[0]) + bExpiry := scalar(t, c.a, "SELECT expires FROM files WHERE id=?", files[1]) + if aExpiry-bExpiry != 3*86400 { + t.Fatalf("renewal delta=%d", aExpiry-bExpiry) + } + _, e := c.a.db.Exec("UPDATE files SET status='deleting' WHERE id=?", files[0]) + if e != nil { + t.Fatal(e) + } + if e = c.a.Cleanup(); e != nil { + t.Fatal(e) + } + if scalar(t, c.a, "SELECT count(*) FROM objects") != 1 { + t.Fatal("deleted shared payload prematurely") + } + _, e = c.a.db.Exec("UPDATE files SET status='deleting' WHERE id=?", files[1]) + if e != nil { + t.Fatal(e) + } + if e = c.a.Cleanup(); e != nil { + t.Fatal(e) + } + if scalar(t, c.a, "SELECT count(*) FROM objects") != 0 { + t.Fatal("unreferenced object leaked") + } + if _, e = c.a.root.Stat(objectPath(oid)); !os.IsNotExist(e) { + t.Fatal("payload not removed") + } + }) + } +} +func TestLimitsAndOversizedBody(t *testing.T) { + c := fixture(t, false) + p := c.a.cfg.Policy + p.MaxFiles = 1 + p.MaxFileBytes = 4 + p.MaxBatchBytes = 4 + b, _ := json.Marshal(p) + c.a.db.Exec("UPDATE settings SET json=?", string(b)) + expect(t, c.json(t, "POST", "/uploads", Manifest{Files: []ManifestFile{{"a", 3}, {"b", 1}}}), 400) + expect(t, c.json(t, "POST", "/uploads", Manifest{Files: []ManifestFile{{"a", 5}}}), 400) + id, files := c.create(t, []string{"a"}, [][]byte{[]byte("1234")}, 2, false) + expect(t, c.request(t, "PUT", "/uploads/"+id+"/files/"+files[0], []byte("12345")), 400) + if scalar(t, c.a, "SELECT count(*) FROM objects") != 0 { + t.Fatal("oversize object published") + } + expect(t, c.json(t, "POST", "/uploads/"+id+"/publish", struct{}{}), 409) + c.upload(t, id, files[0], []byte("1234")) + c.publish(t, id) + var billed int64 + c.a.db.QueryRow("SELECT bytes FROM usage WHERE subject LIKE 'ip:%'").Scan(&billed) + if billed != 8 { + t.Fatalf("retry traffic billed=%d want=8", billed) + } +} +func TestConcurrentLastDownload(t *testing.T) { + c := fixture(t, false) + id, files := c.create(t, []string{"a"}, [][]byte{[]byte("one")}, 1, true) + c.upload(t, id, files[0], []byte("one")) + c.resolve(t, c.publish(t, id)) + cookie := *c.cookies["bb_pickup"] + var wg sync.WaitGroup + codes := make(chan int, 12) + for i := 0; i < 12; i++ { + wg.Add(1) + go func() { + defer wg.Done() + r := httptest.NewRequest("GET", "http://localhost:8080/api/v1/pickup/files/"+files[0]+"/download", nil) + r.RemoteAddr = "203.0.113.11:1234" + r.AddCookie(&cookie) + w := httptest.NewRecorder() + c.h.ServeHTTP(w, r) + codes <- w.Code + }() + } + wg.Wait() + close(codes) + success := 0 + for n := range codes { + if n == 200 { + success++ + } + } + if success != 1 { + t.Fatalf("accepted %d downloads", success) + } + if scalar(t, c.a, "SELECT remaining FROM files WHERE id=?", files[0]) != 0 { + t.Fatal("counter below zero or unconsumed") + } + if scalar(t, c.a, "SELECT expires FROM files WHERE id=?", files[0]) != scalar(t, c.a, "SELECT expires FROM parcels WHERE id=?", id) { + t.Fatal("exhausted file renewed") + } +} +func TestConcurrentCapacityReservation(t *testing.T) { + c := fixture(t, false) + c.a.cfg.FileBudget = reserveSize(1024, false) * 2 + testRuntimeBudgets(t, c.a, c.a.cfg.FileBudget, c.a.cfg.DataBudget) + c.a.cfg.Policy.IPDailyUploads = 100 + c.a.rateMu.Lock() + c.a.rates["upload:"+c.ip] = &rateEntry{limiter: rate.NewLimiter(rate.Inf, 100), touched: time.Now()} + c.a.rateMu.Unlock() + var wg sync.WaitGroup + statuses := make(chan int, 10) + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + b, _ := json.Marshal(Manifest{Files: []ManifestFile{{"a", 1024}}}) + r := httptest.NewRequest("POST", "http://localhost:8080/api/v1/uploads", bytes.NewReader(b)) + r.Header.Set("Origin", "http://localhost:8080") + r.RemoteAddr = c.ip + ":12" + w := httptest.NewRecorder() + c.h.ServeHTTP(w, r) + statuses <- w.Code + }() + } + wg.Wait() + close(statuses) + success := 0 + for n := range statuses { + if n == 201 { + success++ + } else if n != 507 { + t.Fatalf("unexpected %d", n) + } + } + if success != 2 { + t.Fatalf("reserved %d sessions want2", success) + } + _, reserved, _, e := c.a.capacity() + if e != nil || reserved != c.a.cfg.FileBudget { + t.Fatal("reservation ledger inconsistent") + } +} +func TestPasswordOwnershipAndOrigin(t *testing.T) { + c := fixture(t, false) + id, files := c.create(t, []string{"../../evil.html"}, [][]byte{[]byte("")}, 2, false) + other := &testClient{a: c.a, h: c.h, cookies: map[string]*http.Cookie{}, ip: "203.0.113.20"} + expect(t, other.request(t, "PUT", "/uploads/"+id+"/files/"+files[0], []byte("abc")), 404) + c.upload(t, id, files[0], []byte("")) + code := c.publish(t, id) + hash, _ := hashPassword("pickup-password") + c.a.db.Exec("UPDATE parcels SET password_hash=? WHERE id=?", hash, id) + expect(t, c.json(t, "POST", "/pickup/resolve", map[string]string{"code": code}), 401) + expect(t, c.json(t, "POST", "/pickup/resolve", map[string]string{"code": code, "password": "wrong"}), 404) + expect(t, c.json(t, "POST", "/pickup/resolve", map[string]string{"code": code, "password": "pickup-password"}), 200) + w := c.request(t, "GET", "/pickup/files/"+files[0]+"/download", nil) + expect(t, w, 200) + if !strings.HasPrefix(w.Header().Get("Content-Disposition"), "attachment") || w.Header().Get("Content-Type") != "application/octet-stream" { + t.Fatal("active content served inline") + } + r := httptest.NewRequest("POST", "http://localhost:8080/api/v1/parcels/"+id+"/revoke", nil) + r.Header.Set("Origin", "https://evil.example") + r.AddCookie(c.cookies["bb_visitor"]) + w = httptest.NewRecorder() + c.h.ServeHTTP(w, r) + expect(t, w, 403) +} +func TestRenewalModesAndLifetimeCeiling(t *testing.T) { + for _, mode := range []string{"add", "sliding"} { + t.Run(mode, func(t *testing.T) { + c := fixture(t, false) + id, files := c.create(t, []string{"a"}, [][]byte{[]byte("content")}, 4, true) + c.upload(t, id, files[0], []byte("content")) + c.resolve(t, c.publish(t, id)) + expiry := scalar(t, c.a, "SELECT expires FROM files WHERE id=?", files[0]) + hard := expiry + 86400 + c.a.db.Exec("UPDATE parcels SET renewal_mode=? WHERE id=?", mode, id) + c.a.db.Exec("UPDATE files SET hard_expires=? WHERE id=?", hard, files[0]) + expect(t, c.request(t, "GET", "/pickup/files/"+files[0]+"/download", nil), 200) + actual := scalar(t, c.a, "SELECT expires FROM files WHERE id=?", files[0]) + expected := hard + if mode == "sliding" { + expected = expiry + } + if actual != expected { + t.Fatalf("expiry=%d want%d", actual, expected) + } + if scalar(t, c.a, "SELECT remaining FROM files WHERE id=?", files[0]) != 3 { + t.Fatal("renewal reset downloads") + } + }) + } +} + +type failWriter struct { + header http.Header + bytes int64 +} + +func (w *failWriter) Header() http.Header { return w.header } +func (w *failWriter) WriteHeader(int) {} +func (w *failWriter) Write(p []byte) (int, error) { return 0, io.ErrClosedPipe } +func TestInterruptedDownloadDoesNotRenew(t *testing.T) { + c := fixture(t, false) + id, files := c.create(t, []string{"a"}, [][]byte{[]byte("content")}, 3, true) + c.upload(t, id, files[0], []byte("content")) + c.resolve(t, c.publish(t, id)) + expiry := scalar(t, c.a, "SELECT expires FROM files WHERE id=?", files[0]) + r := httptest.NewRequest("GET", "http://localhost:8080/api/v1/pickup/files/"+files[0]+"/download", nil) + r.RemoteAddr = c.ip + ":12" + r.AddCookie(c.cookies["bb_pickup"]) + c.h.ServeHTTP(&failWriter{header: make(http.Header)}, r) + if scalar(t, c.a, "SELECT expires FROM files WHERE id=?", files[0]) != expiry { + t.Fatal("failed download renewed") + } + if scalar(t, c.a, "SELECT remaining FROM files WHERE id=?", files[0]) != 2 { + t.Fatal("failed begun download not charged") + } +} +func TestBackupRestoreAndMissingKey(t *testing.T) { + c := fixture(t, true) + id, files := c.create(t, []string{"a"}, [][]byte{[]byte("content")}, 3, true) + c.upload(t, id, files[0], []byte("content")) + code := c.publish(t, id) + config := filepath.Join(t.TempDir(), "config.toml") + os.WriteFile(config, []byte("listen='127.0.0.1:8080'\n"), 0600) + archive := filepath.Join(t.TempDir(), "backup.tar.gz") + if e := c.a.Backup(config, archive); e != nil { + t.Fatal(e) + } + cfg := c.a.cfg + cfg.DataDir = t.TempDir() + if e := Restore(cfg, archive); e != nil { + t.Fatal(e) + } + restored, e := Open(cfg) + if e != nil { + t.Fatal(e) + } + if scalar(t, restored, "SELECT count(*) FROM objects") != 1 { + t.Fatal("restore lost payload") + } + if scalar(t, restored, "SELECT count(*) FROM parcels WHERE state='restored' AND code_hash IS NULL") != 1 { + t.Fatal("old share remains authorized") + } + if scalar(t, restored, "SELECT count(*) FROM parcels WHERE code_hash=?", restored.keyed(code)) != 0 { + t.Fatal("old code restored") + } + restored.Close() + os.Remove(filepath.Join(cfg.DataDir, "secrets", "age.key")) + if a, e := Open(cfg); e == nil { + a.Close() + t.Fatal("missing encrypted identity silently regenerated") + } +} +func TestRecoveryAndIPPersistence(t *testing.T) { + c := fixture(t, false) + id, files := c.create(t, []string{"a"}, [][]byte{[]byte("content")}, 3, false) + c.upload(t, id, files[0], []byte("content")) + cfg := c.a.cfg + c.a.Close() + c.a.db = nil + c.a.root = nil + c.a.lock = nil + a, e := Open(cfg) + if e != nil { + t.Fatal(e) + } + defer a.Close() + if scalar(t, a, "SELECT count(*) FROM parcels WHERE state='revoked'") != 1 || scalar(t, a, "SELECT count(*) FROM objects") != 0 { + t.Fatal("partial upload became accessible or leaked") + } + if scalar(t, a, "SELECT uploads FROM usage WHERE subject LIKE 'ip:%'") != 1 { + t.Fatal("IP daily counter reset") + } + r := httptest.NewRequest("GET", "http://localhost", nil) + r.RemoteAddr = "203.0.113.21:80" + r.Header.Set("X-Forwarded-For", "8.8.8.8") + if a.remoteIP(r) != "203.0.113.21" { + t.Fatal("untrusted forwarded IP accepted") + } +} + +type gatedReader struct { + r *bytes.Reader + once sync.Once + started chan<- struct{} + release <-chan struct{} +} + +func (r *gatedReader) Read(p []byte) (int, error) { + r.once.Do(func() { r.started <- struct{}{}; <-r.release }) + return r.r.Read(p) +} + +type sinkWriter struct { + header http.Header + status int + hash hash.Hash + count int64 + once sync.Once + started chan<- struct{} + release <-chan struct{} +} + +func (w *sinkWriter) Header() http.Header { return w.header } +func (w *sinkWriter) WriteHeader(n int) { w.status = n } +func (w *sinkWriter) Write(p []byte) (int, error) { + w.once.Do(func() { w.started <- struct{}{}; <-w.release }) + if w.status == 0 { + w.status = 200 + } + n, e := w.hash.Write(p) + w.count += int64(n) + return n, e +} +func TestConcurrentStreamingTransfers(t *testing.T) { + c := fixture(t, true) + mib := 8 + if v, e := strconv.Atoi(os.Getenv("BLUEBIRD_STRESS_MIB")); e == nil && v >= 1 && v <= 100 { + mib = v + } + c.a.cfg.FileBudget = int64(mib*8+64) << 20 + c.a.cfg.DataBudget = c.a.cfg.FileBudget + (64 << 20) + testRuntimeBudgets(t, c.a, c.a.cfg.FileBudget, c.a.cfg.DataBudget) + payload := bytes.Repeat([]byte("transfer"), mib*(1<<17)) + clients := make([]*testClient, 8) + ids := make([]string, 8) + files := make([]string, 8) + for i := range clients { + clients[i] = &testClient{a: c.a, h: c.h, cookies: map[string]*http.Cookie{}, ip: fmt.Sprintf("203.0.113.%d", 40+i)} + id, f := clients[i].create(t, []string{"big.bin"}, [][]byte{payload}, 20, true) + ids[i], files[i] = id, f[0] + } + runtime.GC() + var before runtime.MemStats + runtime.ReadMemStats(&before) + started := make(chan struct{}, 8) + release := make(chan struct{}) + var releaseOnce sync.Once + defer releaseOnce.Do(func() { close(release) }) + var wg sync.WaitGroup + out := make(chan int, 8) + for i := range clients { + wg.Add(1) + go func(i int) { + defer wg.Done() + r := httptest.NewRequest("PUT", "http://localhost:8080/api/v1/uploads/"+ids[i]+"/files/"+files[i], &gatedReader{r: bytes.NewReader(payload), started: started, release: release}) + r.Header.Set("Origin", "http://localhost:8080") + r.RemoteAddr = clients[i].ip + ":1234" + for _, cookie := range clients[i].cookies { + r.AddCookie(cookie) + } + w := httptest.NewRecorder() + c.h.ServeHTTP(w, r) + out <- w.Code + }(i) + } + for range 8 { + select { + case <-started: + case <-time.After(10 * time.Second): + t.Fatal("8 upload streams did not start together") + } + } + var duringUploads runtime.MemStats + runtime.ReadMemStats(&duringUploads) + uploadStart := time.Now() + releaseOnce.Do(func() { close(release) }) + wg.Wait() + uploadDuration := time.Since(uploadStart) + close(out) + for status := range out { + if status != 200 { + t.Fatalf("upload status=%d", status) + } + } + if scalar(t, c.a, "SELECT count(*) FROM objects") != 1 { + t.Fatal("simultaneous identical content not deduplicated") + } + for i := range clients { + clients[i].publish(t, ids[i]) + } + var codeHash string + c.a.db.QueryRow("SELECT code_hash FROM parcels WHERE id=?", ids[0]).Scan(&codeHash) + _ = codeHash + code := pickupCode() + c.a.db.Exec("UPDATE parcels SET code_hash=? WHERE id=?", c.a.keyed(code), ids[0]) + clients[0].resolve(t, code) + cookie := *clients[0].cookies["bb_pickup"] + downloadStarted := make(chan struct{}, 16) + downloadRelease := make(chan struct{}) + var downOnce sync.Once + defer downOnce.Do(func() { close(downloadRelease) }) + sinks := make([]*sinkWriter, 16) + for i := range sinks { + sinks[i] = &sinkWriter{header: make(http.Header), hash: sha256.New(), started: downloadStarted, release: downloadRelease} + wg.Add(1) + go func(i int) { + defer wg.Done() + r := httptest.NewRequest("GET", "http://localhost:8080/api/v1/pickup/files/"+files[0]+"/download", nil) + r.RemoteAddr = fmt.Sprintf("203.0.113.%d:1234", 80+i) + r.AddCookie(&cookie) + c.h.ServeHTTP(sinks[i], r) + }(i) + } + for range 16 { + select { + case <-downloadStarted: + case <-time.After(10 * time.Second): + t.Fatal("16 download streams did not start together") + } + } + var duringDownloads runtime.MemStats + runtime.ReadMemStats(&duringDownloads) + downloadStart := time.Now() + downOnce.Do(func() { close(downloadRelease) }) + wg.Wait() + downloadDuration := time.Since(downloadStart) + want := sha256.Sum256(payload) + for _, s := range sinks { + if s.status != 200 || s.count != int64(len(payload)) || !bytes.Equal(s.hash.Sum(nil), want[:]) { + t.Fatal("stream output checksum/count mismatch") + } + } + t.Logf("age enabled; 8 simultaneous ×%dMiB uploads: %s (%.1f MiB/s); 16 simultaneous ×%dMiB downloads: %s (%.1f MiB/s); heap delta at barrier: uploads=%.2fMiB downloads=%.2fMiB; in-process HTTP handlers, excludes network/TLS", mib, uploadDuration, float64(mib*8)/uploadDuration.Seconds(), mib, downloadDuration, float64(mib*16)/downloadDuration.Seconds(), float64(int64(duringUploads.Alloc)-int64(before.Alloc))/(1<<20), float64(int64(duringDownloads.Alloc)-int64(before.Alloc))/(1<<20)) +} + +func TestInjectedCommitFailureAndDeletionFailure(t *testing.T) { + c := fixture(t, true) + payload := []byte("test fault recovery") + id, files := c.create(t, []string{"a"}, [][]byte{payload}, 3, true) + if _, e := c.a.db.Exec("CREATE TRIGGER reject_object_attach BEFORE UPDATE OF object_id ON files WHEN NEW.object_id IS NOT NULL BEGIN SELECT RAISE(ABORT,'injected commit failure'); END"); e != nil { + t.Fatal(e) + } + expect(t, c.request(t, "PUT", "/uploads/"+id+"/files/"+files[0], payload), 500) + if scalar(t, c.a, "SELECT count(*) FROM objects") != 0 { + t.Fatal("rolled-back object still registered") + } + entries, e := os.ReadDir(filepath.Join(c.a.cfg.DataDir, "objects")) + if e != nil { + t.Fatal(e) + } + for _, d := range entries { + sub, _ := os.ReadDir(filepath.Join(c.a.cfg.DataDir, "objects", d.Name())) + if len(sub) > 0 { + t.Fatal("failed commit left uncharged payload") + } + } + c.a.db.Exec("DROP TRIGGER reject_object_attach") + c.upload(t, id, files[0], payload) + c.publish(t, id) + var oid string + c.a.db.QueryRow("SELECT id FROM objects").Scan(&oid) + path := objectPath(oid) + if e = c.a.root.Remove(path); e != nil { + t.Fatal(e) + } + if e = c.a.root.Mkdir(path, 0700); e != nil { + t.Fatal(e) + } + if e = c.a.root.WriteFile(path+"/blocker", []byte("x"), 0600); e != nil { + t.Fatal(e) + } + c.a.db.Exec("UPDATE files SET status='deleting' WHERE id=?", files[0]) + if e = c.a.Cleanup(); e == nil { + t.Fatal("expected physical deletion failure") + } + if scalar(t, c.a, "SELECT count(*) FROM objects") != 1 || scalar(t, c.a, "SELECT sum(stored_bytes) FROM objects") <= 0 { + t.Fatal("failed deletion incorrectly released capacity") + } + c.a.root.Remove(path + "/blocker") + if e = c.a.Cleanup(); e != nil { + t.Fatal(e) + } + if scalar(t, c.a, "SELECT count(*) FROM objects") != 0 { + t.Fatal("retry deletion failed") + } +} +func TestDeleteDuringUploadDoesNotRepublish(t *testing.T) { + c := fixture(t, false) + payload := []byte("delete while uploading") + id, files := c.create(t, []string{"a"}, [][]byte{payload}, 3, false) + started := make(chan struct{}, 1) + release := make(chan struct{}) + defer func() { + select { + case <-release: + default: + close(release) + } + }() + r := httptest.NewRequest("PUT", "http://localhost:8080/api/v1/uploads/"+id+"/files/"+files[0], &gatedReader{r: bytes.NewReader(payload), started: started, release: release}) + r.Header.Set("Origin", "http://localhost:8080") + r.RemoteAddr = c.ip + ":12" + r.AddCookie(c.cookies["bb_visitor"]) + done := make(chan *httptest.ResponseRecorder, 1) + go func() { w := httptest.NewRecorder(); c.h.ServeHTTP(w, r); done <- w }() + select { + case <-started: + case <-time.After(5 * time.Second): + t.Fatal("stream did not start") + } + expect(t, c.json(t, "POST", "/admin/login", map[string]string{"username": "admin", "password": "test-password"}), 200) + expect(t, c.json(t, "POST", "/admin/files/"+files[0]+"/delete", struct{}{}), 200) + _, reserved, _, e := c.a.capacity() + if e != nil || reserved != reserveSize(int64(len(payload)), false) { + t.Fatal("active deletion released upload reservation too early") + } + close(release) + expect(t, <-done, 500) + if scalar(t, c.a, "SELECT count(*) FROM files WHERE status='ready'") != 0 { + t.Fatal("deleted file became ready") + } + if scalar(t, c.a, "SELECT count(*) FROM objects") != 0 { + t.Fatal("canceled payload leaked") + } + if e := c.a.Cleanup(); e != nil { + t.Fatal(e) + } +} +func TestAnonymousCookieResetDoesNotResetIPQuota(t *testing.T) { + c := fixture(t, false) + p := c.a.cfg.Policy + p.IPDailyUploads = 1 + b, _ := json.Marshal(p) + c.a.db.Exec("UPDATE settings SET json=?", string(b)) + c.create(t, []string{"a"}, [][]byte{[]byte("x")}, 1, false) + fresh := &testClient{a: c.a, h: c.h, cookies: map[string]*http.Cookie{}, ip: c.ip} + expect(t, fresh.json(t, "POST", "/uploads", Manifest{Files: []ManifestFile{{Name: "a", Size: 1}}}), 429) + if scalar(t, c.a, "SELECT uploads FROM usage WHERE subject LIKE 'ip:%'") != 1 { + t.Fatal("daily quota mutated on rejection") + } +} +func TestFailedRestoreCannotStart(t *testing.T) { + cfg := DefaultConfig() + cfg.DataDir = t.TempDir() + cfg.MinFreeBytes = 0 + archive := filepath.Join(t.TempDir(), "broken.tar.gz") + os.WriteFile(archive, []byte("not a backup"), 0600) + if e := Restore(cfg, archive); e == nil { + t.Fatal("invalid archive accepted") + } + if a, e := Open(cfg); e == nil { + a.Close() + t.Fatal("partially restored service can start") + } +} +func TestExclusiveLockAndZeroByteIntegrity(t *testing.T) { + c := fixture(t, true) + if another, e := Open(c.a.cfg); e == nil { + another.Close() + t.Fatal("second process lock succeeded") + } + id, files := c.create(t, []string{"empty"}, [][]byte{{}}, 2, true) + c.upload(t, id, files[0], nil) + c.resolve(t, c.publish(t, id)) + expect(t, c.request(t, "HEAD", "/pickup/files/"+files[0]+"/download", nil), 200) + if scalar(t, c.a, "SELECT remaining FROM files WHERE id=?", files[0]) != 2 { + t.Fatal("HEAD consumed count") + } + var oid string + c.a.db.QueryRow("SELECT id FROM objects").Scan(&oid) + path := objectPath(oid) + raw, _ := c.a.root.ReadFile(path) + raw[len(raw)-1] ^= 1 + c.a.root.WriteFile(path, raw, 0600) + expect(t, c.request(t, "GET", "/pickup/files/"+files[0]+"/download", nil), 500) + if scalar(t, c.a, "SELECT remaining FROM files WHERE id=?", files[0]) != 2 { + t.Fatal("failed empty-content authentication consumed count") + } +} + +func TestSlowUploadIdleDeadline(t *testing.T) { + c := fixture(t, false) + id, files := c.create(t, []string{"slow"}, [][]byte{[]byte("0123456789")}, 2, false) + c.a.cfg.TransferIdleSeconds = 1 + c.a.cfg.MaxTransferSeconds = 10 + srv := httptest.NewServer(c.h) + defer srv.Close() + c.a.cfg.PublicURL = srv.URL + conn, e := net.Dial("tcp", strings.TrimPrefix(srv.URL, "http://")) + if e != nil { + t.Fatal(e) + } + defer conn.Close() + conn.SetDeadline(time.Now().Add(5 * time.Second)) + _, e = fmt.Fprintf(conn, "PUT /api/v1/uploads/%s/files/%s HTTP/1.1\r\nHost: %s\r\nOrigin: %s\r\nCookie: bb_visitor=%s\r\nContent-Length: 10\r\nConnection: close\r\n\r\nx", id, files[0], strings.TrimPrefix(srv.URL, "http://"), srv.URL, c.cookies["bb_visitor"].Value) + if e != nil { + t.Fatal(e) + } + start := time.Now() + response, e := http.ReadResponse(bufio.NewReader(conn), &http.Request{Method: "PUT"}) + if e != nil { + t.Fatal(e) + } + defer response.Body.Close() + io.Copy(io.Discard, response.Body) + if response.StatusCode != 400 || time.Since(start) > 4*time.Second { + t.Fatal("idle upload was not rejected promptly") + } + if scalar(t, c.a, "SELECT count(*) FROM objects") != 0 { + t.Fatal("slow partial content became available") + } +} +func TestTrustedProxyChainAndIPv6(t *testing.T) { + c := fixture(t, false) + _, network, _ := net.ParseCIDR("10.0.0.0/8") + c.a.proxies = []*net.IPNet{network} + r := httptest.NewRequest("GET", "http://localhost", nil) + r.RemoteAddr = "10.0.0.1:80" + r.Header.Set("X-Forwarded-For", "8.8.8.8, 203.0.113.9, 10.0.0.2") + if c.a.remoteIP(r) != "203.0.113.9" { + t.Fatal("trusted proxy chain accepted leftmost forged IP") + } + r.RemoteAddr = "[2001:db8:1:2::1]:80" + r.Header.Del("X-Forwarded-For") + first := c.a.remoteIP(r) + r.RemoteAddr = "[2001:db8:1:2::ffff]:80" + if c.a.remoteIP(r) != first { + t.Fatal("IPv6 rotation evaded prefix grouping") + } +} + +func testRuntimeBudgets(t *testing.T, a *App, file, data int64) { + t.Helper() + s, e := a.runtimeSettings() + if e != nil { + t.Fatal(e) + } + s.FileBudget = file + s.DataBudget = data + b, _ := json.Marshal(s) + if _, e = a.db.Exec("UPDATE runtime_settings SET json=? WHERE id=1", string(b)); e != nil { + t.Fatal(e) + } +} diff --git a/internal/app/backup.go b/internal/app/backup.go new file mode 100644 index 0000000..0984636 --- /dev/null +++ b/internal/app/backup.go @@ -0,0 +1,211 @@ +package app + +import ( + "archive/tar" + "compress/gzip" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strings" +) + +// Backup is an offline operation: Open has already acquired the exclusive service lock. +func (a *App) Backup(configPath, target string) error { + abs, e := filepath.Abs(target) + if e != nil { + return e + } + data, e := filepath.Abs(a.cfg.DataDir) + if e != nil { + return e + } + if abs == data || strings.HasPrefix(abs, data+string(os.PathSeparator)) { + return errors.New("备份目标必须在数据目录之外") + } + if e = a.db.Close(); e != nil { + return e + } + a.db = nil + out, e := os.OpenFile(abs, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) + if e != nil { + return e + } + success := false + defer func() { + out.Close() + if !success { + os.Remove(abs) + } + }() + gz := gzip.NewWriter(out) + tw := tar.NewWriter(gz) + write := func(name string, f *os.File, st os.FileInfo) error { + h := &tar.Header{Name: name, Mode: 0600, Size: st.Size(), ModTime: st.ModTime(), Typeflag: tar.TypeReg} + if e := tw.WriteHeader(h); e != nil { + return e + } + _, e := io.Copy(tw, f) + return e + } + cf, e := os.Open(configPath) + if e != nil { + return e + } + st, e := cf.Stat() + if e == nil { + e = write("config.toml", cf, st) + } + cf.Close() + if e != nil { + return e + } + e = fs.WalkDir(a.root.FS(), ".", func(path string, d fs.DirEntry, e error) error { + if e != nil { + return e + } + if d.Type()&os.ModeSymlink != 0 { + return fmt.Errorf("拒绝备份符号链接: %s", path) + } + if d.IsDir() || path == "service.lock" { + return nil + } + if strings.HasPrefix(path, "tmp/") { + return nil + } + st, e := d.Info() + if e != nil { + return e + } + if !st.Mode().IsRegular() { + return errors.New("数据目录包含特殊文件") + } + f, e := a.root.Open(path) + if e != nil { + return e + } + defer f.Close() + return write("var/"+path, f, st) + }) + if e != nil { + return e + } + if e = tw.Close(); e != nil { + return e + } + if e = gz.Close(); e != nil { + return e + } + if e = out.Sync(); e != nil { + return e + } + success = true + return nil +} +func Restore(c Config, source string) error { + if e := c.Validate(); e != nil { + return e + } + if e := os.MkdirAll(c.DataDir, 0700); e != nil { + return e + } + entries, e := os.ReadDir(c.DataDir) + if e != nil { + return e + } + if len(entries) != 0 { + return errors.New("恢复数据目录必须为空") + } + root, e := os.OpenRoot(c.DataDir) + if e != nil { + return e + } + defer root.Close() + if e = root.WriteFile("restore.incomplete", []byte("restoring"), 0600); e != nil { + return e + } + input, e := os.Open(source) + if e != nil { + return e + } + defer input.Close() + gz, e := gzip.NewReader(input) + if e != nil { + return e + } + defer gz.Close() + tr := tar.NewReader(gz) + var total int64 + var count int + for { + h, e := tr.Next() + if errors.Is(e, io.EOF) { + break + } + if e != nil { + return e + } + if h.Typeflag != tar.TypeReg || !safeRelative(h.Name) { + return errors.New("备份包含非法路径或文件类型") + } + count++ + if count > c.MaxRecords+32 { + return errors.New("备份文件数量超出恢复限制") + } + name := "" + if h.Name == "config.toml" { + name = "restored-config.toml" + } else if strings.HasPrefix(h.Name, "var/") { + name = strings.TrimPrefix(h.Name, "var/") + } else { + return errors.New("未知备份路径") + } + if !safeRelative(name) || h.Size < 0 || h.Size > c.DataBudget || total > c.DataBudget-h.Size { + return errors.New("备份超过恢复容量") + } + total += h.Size + if e = root.MkdirAll(filepath.Dir(name), 0700); e != nil { + return e + } + f, e := root.OpenFile(name, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) + if e != nil { + return e + } + _, e = io.CopyN(f, tr, h.Size) + if e == nil { + e = f.Sync() + } + ce := f.Close() + if e == nil { + e = ce + } + if e != nil { + return e + } + } + if _, e = io.Copy(io.Discard, gz); e != nil { + return e + } + // Invalidate before Open runs lifecycle cleanup, leaving payloads retained for admin review. + dbPath := filepath.Join(c.DataDir, "db", "bluebird.db") + if _, e = os.Stat(dbPath); e != nil { + return errors.New("备份缺少数据库") + } + a, e := OpenForRestore(c) + if e != nil { + return e + } + defer a.Close() + if e = a.Check(); e != nil { + return e + } + if e = a.InvalidateRestored(); e != nil { + return e + } + if e = root.Remove("restore.incomplete"); e != nil { + return e + } + return syncDir(root, ".") +} diff --git a/internal/app/config.go b/internal/app/config.go new file mode 100644 index 0000000..7f936f8 --- /dev/null +++ b/internal/app/config.go @@ -0,0 +1,157 @@ +package app + +import ( + "bytes" + "errors" + "fmt" + "net" + "net/url" + "os" + "path/filepath" + + "github.com/pelletier/go-toml/v2" +) + +type Policy struct { + AnonymousUpload bool `json:"anonymous_upload" toml:"anonymous_upload"` + AnonymousDownload bool `json:"anonymous_download" toml:"anonymous_download"` + MaxFileBytes int64 `json:"max_file_bytes" toml:"max_file_bytes"` + MaxBatchBytes int64 `json:"max_batch_bytes" toml:"max_batch_bytes"` + MaxFiles int `json:"max_files" toml:"max_files"` + DefaultDays int `json:"default_days" toml:"default_days"` + MaxInitialDays int `json:"max_initial_days" toml:"max_initial_days"` + MaxLifetimeDays int `json:"max_lifetime_days" toml:"max_lifetime_days"` + DefaultDownloads int `json:"default_downloads" toml:"default_downloads"` + MaxDownloads int `json:"max_downloads" toml:"max_downloads"` + RenewalDays int `json:"renewal_days" toml:"renewal_days"` + RenewalMode string `json:"renewal_mode" toml:"renewal_mode"` + IPDailyBytes int64 `json:"ip_daily_bytes" toml:"ip_daily_bytes"` + IPDailyUploads int `json:"ip_daily_uploads" toml:"ip_daily_uploads"` + SessionDailyUploads int `json:"session_daily_uploads" toml:"session_daily_uploads"` + RequirePassword bool `json:"require_password" toml:"require_password"` +} +type Config struct { + Listen string `toml:"listen"` + PublicURL string `toml:"public_url"` + AccessURLs []string `toml:"access_urls"` + DataDir string `toml:"data_dir"` + SecureCookies bool `toml:"secure_cookies"` + AllowLANHTTP bool `toml:"allow_lan_http"` + Encryption bool `toml:"encryption"` + Deduplication bool `toml:"deduplication"` + TrustedProxies []string `toml:"trusted_proxies"` + DataBudget int64 `toml:"data_budget"` + FileBudget int64 `toml:"file_budget"` + MinFreeBytes int64 `toml:"min_free_bytes"` + AuditRetentionDays int `toml:"audit_retention_days"` + AuditMaxRecords int `toml:"audit_max_records"` + AuditMaxBytes int64 `toml:"audit_max_bytes"` + MaxUploads int `toml:"max_uploads"` + MaxDownloads int `toml:"max_downloads"` + MaxIPUploads int `toml:"max_ip_uploads"` + MaxIPDownloads int `toml:"max_ip_downloads"` + MaxRecords int `toml:"max_records"` + TransferIdleSeconds int `toml:"transfer_idle_seconds"` + MaxTransferSeconds int `toml:"max_transfer_seconds"` + TLSCert string `toml:"tls_cert"` + TLSKey string `toml:"tls_key"` + Policy Policy `toml:"policy"` +} + +func DefaultConfig() Config { + return Config{ + Listen: "127.0.0.1:8080", PublicURL: "http://localhost:8080", DataDir: "var", Deduplication: true, + DataBudget: 10 << 30, FileBudget: 9 << 30, MinFreeBytes: 1 << 30, MaxUploads: 8, MaxDownloads: 16, MaxIPUploads: 2, MaxIPDownloads: 4, MaxRecords: 200000, MaxTransferSeconds: 3600, TransferIdleSeconds: 30, + AuditRetentionDays: 30, AuditMaxRecords: 100000, AuditMaxBytes: 32 << 20, + Policy: Policy{AnonymousUpload: true, AnonymousDownload: true, MaxFileBytes: 100 << 20, MaxBatchBytes: 500 << 20, MaxFiles: 10, DefaultDays: 1, MaxInitialDays: 7, MaxLifetimeDays: 30, DefaultDownloads: 5, MaxDownloads: 20, RenewalDays: 3, RenewalMode: "add", IPDailyBytes: 1 << 30, IPDailyUploads: 20, SessionDailyUploads: 20}, + } +} +func (p Policy) Validate() error { + if p.MaxBatchBytes > 1<<40 || p.IPDailyBytes > 1<<50 || p.MaxFileBytes <= 0 || p.MaxBatchBytes <= 0 || p.MaxFiles < 1 || p.MaxFiles > 1000 || p.MaxFileBytes > p.MaxBatchBytes || p.DefaultDays < 1 || p.DefaultDays > p.MaxInitialDays || p.MaxInitialDays > p.MaxLifetimeDays || p.MaxLifetimeDays > 3650 || p.DefaultDownloads < 1 || p.DefaultDownloads > p.MaxDownloads || p.MaxDownloads > 100000 || p.RenewalDays < 0 || p.RenewalDays > p.MaxLifetimeDays || p.IPDailyBytes < 1 || p.IPDailyUploads < 1 || p.SessionDailyUploads < 1 { + return errors.New("策略参数范围错误") + } + if p.RenewalMode != "add" && p.RenewalMode != "sliding" { + return errors.New("renewal_mode 必须为 add 或 sliding") + } + return nil +} +func LoadConfig(path string) (Config, error) { + c := DefaultConfig() + b, e := os.ReadFile(path) + if e != nil { + return c, e + } + if e = toml.NewDecoder(bytes.NewReader(b)).DisallowUnknownFields().Decode(&c); e != nil { + return c, e + } + base, e := filepath.Abs(filepath.Dir(path)) + if e != nil { + return c, e + } + if !filepath.IsAbs(c.DataDir) { + c.DataDir = filepath.Join(base, c.DataDir) + } + for _, ptr := range []*string{&c.TLSCert, &c.TLSKey} { + if *ptr != "" && !filepath.IsAbs(*ptr) { + *ptr = filepath.Join(base, *ptr) + } + } + return c, c.Validate() +} +func (c Config) Validate() error { + if e := c.InitialRuntimeSettings().Validate(); e != nil { + return e + } + if e := c.Policy.Validate(); e != nil { + return e + } + primary, e := normalizeAccessURL(c.PublicURL) + if e != nil { + return e + } + u, _ := url.Parse(primary) + if len(c.AccessURLs) > 16 { + return errors.New("额外访问地址最多16个") + } + for _, raw := range append([]string{c.PublicURL}, c.AccessURLs...) { + base, err := normalizeAccessURL(raw) + if err != nil { + return err + } + entry, _ := url.Parse(base) + ip := net.ParseIP(entry.Hostname()) + if entry.Scheme == "http" && ip != nil && ip.IsPrivate() && !ip.IsLoopback() && !c.AllowLANHTTP { + return errors.New("内网HTTP入口须显式开启 allow_lan_http") + } + } + if u.Scheme == "https" && !c.SecureCookies { + return errors.New("HTTPS主入口须设置 secure_cookies = true") + } + if u.Scheme == "http" && c.SecureCookies { + return errors.New("HTTP主入口须设置 secure_cookies = false;额外HTTPS入口自动使用安全cookie") + } + listenHost, port, err := net.SplitHostPort(c.Listen) + if err != nil || port == "" { + return errors.New("listen须包含地址和端口") + } + // Preserve explicit-address protection for a single local/LAN HTTP site. + ip := net.ParseIP(u.Hostname()) + if u.Scheme == "http" && (u.Hostname() == "localhost" || ip != nil && (ip.IsPrivate() || ip.IsLoopback())) { + bound := net.ParseIP(listenHost) + if listenHost != "localhost" && (bound == nil || !bound.IsPrivate() && !bound.IsLoopback()) { + return errors.New("内网HTTP须监听具体私有或回环地址") + } + } + if (c.TLSCert == "") != (c.TLSKey == "") { + return errors.New("TLS 证书和私钥需一起配置") + } + if c.FileBudget <= 0 || c.DataBudget-c.FileBudget < 64<<20 || c.MinFreeBytes < 0 || c.MaxUploads < 1 || c.MaxDownloads < 1 || c.MaxIPUploads < 1 || c.MaxIPDownloads < 1 || c.MaxRecords < 1 || c.MaxTransferSeconds < 1 || c.TransferIdleSeconds < 1 { + return fmt.Errorf("容量、并发或传输配置非法") + } + for _, p := range c.TrustedProxies { + if _, _, e := net.ParseCIDR(p); e != nil { + return fmt.Errorf("非法代理网段: %s", p) + } + } + return nil +} diff --git a/internal/app/config_test.go b/internal/app/config_test.go new file mode 100644 index 0000000..a578563 --- /dev/null +++ b/internal/app/config_test.go @@ -0,0 +1,58 @@ +package app + +import ( + "os" + "path/filepath" + "testing" +) + +func TestConfigurationSafety(t *testing.T) { + c := DefaultConfig() + c.Listen = "0.0.0.0:8080" + if c.Validate() == nil { + t.Fatal("plain HTTP public listener accepted") + } + dir := t.TempDir() + p := filepath.Join(dir, "config.toml") + os.WriteFile(p, []byte("unknown_setting = true\n"), 0600) + if _, e := LoadConfig(p); e == nil { + t.Fatal("unknown configuration silently accepted") + } + os.WriteFile(p, []byte("data_dir = 'var'\n"), 0600) + loaded, e := LoadConfig(p) + if e != nil { + t.Fatal(e) + } + if loaded.DataDir != filepath.Join(dir, "var") { + t.Fatal("relative directory not resolved from configuration") + } +} + +func TestLANHTTPRequiresExplicitPrivateAddress(t *testing.T) { + for _, test := range []struct { + name, listen, publicURL string + allow, valid bool + }{ + {"disabled", "192.168.1.204:8080", "http://192.168.1.204:8080", false, false}, + {"explicit LAN", "192.168.1.204:8080", "http://192.168.1.204:8080", true, true}, + {"wildcard IPv4", "0.0.0.0:8080", "http://192.168.1.204:8080", true, false}, + {"public listener", "8.8.8.8:8080", "http://192.168.1.204:8080", true, false}, + {"public HTTP URL", "192.168.1.204:8080", "http://8.8.8.8:8080", true, true}, + {"private IPv6", "[fd00::204]:8080", "http://[fd00::204]:8080", true, true}, + {"wildcard IPv6", "[::]:8080", "http://[fd00::204]:8080", true, false}, + {"loopback", "127.0.0.1:8080", "http://127.0.0.1:8080", false, true}, + } { + t.Run(test.name, func(t *testing.T) { + c := DefaultConfig() + c.Listen, c.PublicURL, c.AllowLANHTTP = test.listen, test.publicURL, test.allow + if err := c.Validate(); (err == nil) != test.valid { + t.Fatalf("valid=%v, expected %v; error=%v", err == nil, test.valid, err) + } + }) + } + c := DefaultConfig() + c.Listen, c.PublicURL, c.AllowLANHTTP, c.SecureCookies = "192.168.1.204:8080", "http://192.168.1.204:8080", true, true + if c.Validate() == nil { + t.Fatal("Secure cookies accepted for LAN HTTP") + } +} diff --git a/internal/app/download.go b/internal/app/download.go new file mode 100644 index 0000000..be7e004 --- /dev/null +++ b/internal/app/download.go @@ -0,0 +1,381 @@ +package app + +import ( + "database/sql" + "errors" + "io" + "mime" + "net/http" + "strconv" + "time" + + "filippo.io/age" +) + +func (a *App) resolve(w http.ResponseWriter, r *http.Request) { + if !a.allow(r, "pickup", 10) { + fail(w, 429, "取件尝试过于频繁") + return + } + var in struct { + Code string `json:"code"` + Password string `json:"password"` + } + if !decode(w, r, &in) { + return + } + code := normalizeCode(in.Code) + if len(code) != 10 { + fail(w, 404, "取件信息无效或已失效") + return + } + p, e := a.policy() + if e != nil { + a.serverError(w, e) + return + } + if !p.AnonymousDownload { + fail(w, 403, "管理员已关闭取件") + return + } + var id, hash string + var version int + now := a.now().Unix() + e = a.db.QueryRow("SELECT id,password_hash,version FROM parcels WHERE code_hash=? AND state='ready' AND EXISTS(SELECT 1 FROM files WHERE parcel=parcels.id AND status='ready' AND remaining>0 AND expires>?)", a.keyed(code), now).Scan(&id, &hash, &version) + if e != nil { + if !errors.Is(e, sql.ErrNoRows) { + a.serverError(w, e) + return + } + fail(w, 404, "取件信息无效或已失效") + return + } + if hash != "" { + if in.Password == "" { + respond(w, 401, map[string]any{"error": "请输入取件密码", "password_required": true}) + return + } + select { + case a.passwordSlots <- struct{}{}: + default: + fail(w, 429, "密码验证繁忙") + return + } + valid := verifyPassword(in.Password, hash) + <-a.passwordSlots + if !valid { + fail(w, 404, "取件信息无效或已失效") + return + } + } + a.mu.Lock() + defer a.mu.Unlock() + var current int + e = a.db.QueryRow("SELECT version FROM parcels WHERE id=? AND state='ready' AND code_hash=?", id, a.keyed(code)).Scan(¤t) + if e != nil || current != version { + fail(w, 404, "取件信息无效或已失效") + return + } + var count int + if e = a.db.QueryRow("SELECT count(*) FROM grants").Scan(&count); e != nil { + a.serverError(w, e) + return + } + if count >= a.cfg.MaxRecords { + fail(w, 429, "取件会话繁忙") + return + } + token := randomToken() + if _, e = a.db.Exec("INSERT INTO grants VALUES(?,?,?,?)", tokenHash(token), id, version, now+600); e != nil { + a.serverError(w, e) + return + } + a.cookie(w, r, "bb_pickup", token, "/api/v1/pickup", 600) + a.pickup(w, r.Clone(r.Context()), id) +} + +// Explicit parcel IDs are only used immediately after a validated code exchange. +func (a *App) grant(r *http.Request) (string, error) { + var id string + e := a.db.QueryRow("SELECT p.id FROM grants g JOIN parcels p ON p.id=g.parcel WHERE g.token_hash=? AND g.expires>? AND p.state='ready' AND p.version=g.version", tokenHash(cookieValue(r, "bb_pickup")), a.now().Unix()).Scan(&id) + return id, e +} +func (a *App) fileList(id string) ([]map[string]any, error) { + rows, e := a.db.Query("SELECT id,name,size,status,remaining,expires FROM files WHERE parcel=? ORDER BY rowid", id) + if e != nil { + return nil, e + } + defer rows.Close() + out := []map[string]any{} + for rows.Next() { + var fid, name, status string + var size, expires int64 + var remaining int + if e = rows.Scan(&fid, &name, &size, &status, &remaining, &expires); e != nil { + return nil, e + } + out = append(out, map[string]any{"id": fid, "name": name, "size": size, "status": status, "remaining": remaining, "expires": expires, "available": status == "ready" && remaining > 0 && expires > a.now().Unix()}) + } + return out, rows.Err() +} +func (a *App) pickup(w http.ResponseWriter, r *http.Request, explicit ...string) { + id := "" + if len(explicit) > 0 { + id = explicit[0] + } else { + var e error + id, e = a.grant(r) + if e != nil { + fail(w, 401, "取件授权已失效,请重新输入取件码") + return + } + } + p, e := a.policy() + if e != nil { + a.serverError(w, e) + return + } + if !p.AnonymousDownload { + fail(w, 403, "管理员已关闭取件") + return + } + files, e := a.fileList(id) + if e != nil { + a.serverError(w, e) + return + } + var renewal int + var mode string + if e = a.db.QueryRow("SELECT renewal_days,renewal_mode FROM parcels WHERE id=?", id).Scan(&renewal, &mode); e != nil { + a.serverError(w, e) + return + } + respond(w, 200, map[string]any{"id": id, "files": files, "renewal_days": renewal, "renewal_mode": mode}) +} +func (a *App) pickupHandler(w http.ResponseWriter, r *http.Request) { a.pickup(w, r) } + +type startWriter struct { + dst http.ResponseWriter + start func() error + started bool + bytes int64 + controller *http.ResponseController + total time.Time + idle time.Duration +} + +func (w *startWriter) Write(b []byte) (int, error) { + if !w.started { + if e := w.start(); e != nil { + return 0, e + } + w.started = true + } + if w.controller != nil { + end := time.Now().Add(w.idle) + if end.After(w.total) { + end = w.total + } + _ = w.controller.SetWriteDeadline(end) + } + n, e := w.dst.Write(b) + w.bytes += int64(n) + return n, e +} +func (a *App) download(w http.ResponseWriter, r *http.Request) { + ip := a.remoteIP(r) + if !a.acquire("download", ip) { + fail(w, 429, "下载并发已满") + return + } + defer a.release("download", ip) + a.deadlines(w, r) + a.mu.Lock() + parcel, e := a.grant(r) + if e != nil { + a.mu.Unlock() + fail(w, 401, "取件授权失效") + return + } + policy, e := a.policy() + if e != nil { + a.mu.Unlock() + a.serverError(w, e) + return + } + if !policy.AnonymousDownload { + a.mu.Unlock() + fail(w, 403, "取件已关闭") + return + } + fid := r.PathValue("file") + var oid, name, status string + var size, expiry int64 + var remaining int + var encrypted bool + e = a.db.QueryRow("SELECT f.object_id,f.name,f.size,f.status,f.remaining,f.expires,o.encrypted FROM files f JOIN objects o ON o.id=f.object_id WHERE f.id=? AND f.parcel=?", fid, parcel).Scan(&oid, &name, &size, &status, &remaining, &expiry, &encrypted) + if e != nil || status != "ready" || remaining < 1 || expiry <= a.now().Unix() { + a.mu.Unlock() + fail(w, 404, "文件已失效") + return + } + file, e := a.root.Open(objectPath(oid)) + if e != nil { + a.mu.Unlock() + fail(w, 404, "文件不可用") + return + } + a.activeObjects[oid]++ + defer func() { + a.mu.Lock() + _ = file.Close() + a.activeObjects[oid]-- + if a.activeObjects[oid] == 0 { + delete(a.activeObjects, oid) + } + a.mu.Unlock() + }() + var reader io.Reader = file + if encrypted { + if a.identity == nil { + a.mu.Unlock() + a.serverError(w, errors.New("缺少私钥")) + return + } + reader, e = age.Decrypt(file, a.identity) + if e != nil { + a.mu.Unlock() + a.serverError(w, e) + return + } + } + if r.Method == "HEAD" { + a.mu.Unlock() + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Length", strconv.FormatInt(size, 10)) + w.WriteHeader(200) + return + } + attempt := randomID() + tx, e := a.db.Begin() + if e != nil { + a.mu.Unlock() + a.serverError(w, e) + return + } + result, e := tx.Exec("UPDATE files SET remaining=remaining-1 WHERE id=? AND remaining>0 AND status='ready' AND expires>?", fid, a.now().Unix()) + if e == nil { + n, _ := result.RowsAffected() + if n != 1 { + e = errors.New("次数或期限不足") + } + } + if e == nil { + _, e = tx.Exec("INSERT INTO downloads(id,file_id,created) VALUES(?,?,?)", attempt, fid, a.now().Unix()) + } + if e == nil { + e = tx.Commit() + } else { + tx.Rollback() + } + if e != nil { + a.mu.Unlock() + a.serverError(w, e) + return + } + a.mu.Unlock() + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": name})) + w.Header().Set("Content-Length", strconv.FormatInt(size, 10)) + w.Header().Set("Accept-Ranges", "none") + writer := &startWriter{dst: w, controller: http.NewResponseController(w), total: time.Now().Add(time.Duration(a.cfg.MaxTransferSeconds) * time.Second), idle: time.Duration(a.cfg.TransferIdleSeconds) * time.Second, start: func() error { _, e := a.db.Exec("UPDATE downloads SET started=1 WHERE id=?", attempt); return e }} + // A zero-byte download is still an accepted download and must consume a count. + if size == 0 { + var b [1]byte + n, re := reader.Read(b[:]) + if n != 0 || re != io.EOF { + e = errors.New("空文件内容异常") + } + if e == nil { + e = writer.start() + } + if e == nil { + writer.started = true + w.WriteHeader(200) + } + } else { + _, e = io.CopyBuffer(writer, io.LimitReader(reader, size+1), make([]byte, 128<<10)) + } + a.mu.Lock() + defer a.mu.Unlock() + if !writer.started { + a.refund(attempt, fid) + w.Header().Del("Content-Length") + w.Header().Del("Content-Disposition") + a.serverError(w, errors.New("下载开始前失败")) + return + } + if e == nil && writer.bytes == size { + if e = a.completeDownload(attempt, fid, parcel); e != nil { + a.log.Error("download completion failed", "error", e) + } + } else { + a.log.Warn("download interrupted", "file", fid, "bytes", writer.bytes) + } +} +func (a *App) refund(attempt, fid string) { + tx, e := a.db.Begin() + if e != nil { + return + } + defer tx.Rollback() + res, e := tx.Exec("UPDATE downloads SET refunded=1 WHERE id=? AND started=0 AND refunded=0", attempt) + if e != nil { + return + } + n, _ := res.RowsAffected() + if n == 1 { + _, e = tx.Exec("UPDATE files SET remaining=remaining+1 WHERE id=?", fid) + } + if e == nil { + _ = tx.Commit() + } +} +func (a *App) completeDownload(attempt, fid, parcel string) error { + tx, e := a.db.Begin() + if e != nil { + return e + } + defer tx.Rollback() + res, e := tx.Exec("UPDATE downloads SET completed=1 WHERE id=? AND completed=0", attempt) + if e != nil { + return e + } + n, _ := res.RowsAffected() + if n == 0 { + return tx.Commit() + } + var days, remaining int + var mode, state, status string + var expiry, hard int64 + e = tx.QueryRow("SELECT p.renewal_days,p.renewal_mode,p.state,f.status,f.expires,f.hard_expires,f.remaining FROM files f JOIN parcels p ON p.id=f.parcel WHERE f.id=?", fid).Scan(&days, &mode, &state, &status, &expiry, &hard, &remaining) + if e != nil { + return e + } + if state == "ready" && status == "ready" && remaining > 0 && days > 0 { + newExpiry := max(expiry, a.now().Unix()+int64(days)*86400) + if mode == "add" { + newExpiry = max(expiry, a.now().Unix()) + int64(days)*86400 + } + newExpiry = min(newExpiry, hard) + if newExpiry > expiry { + if _, e = tx.Exec("UPDATE files SET expires=? WHERE id=?", newExpiry, fid); e != nil { + return e + } + if _, e = tx.Exec("UPDATE parcels SET expires=(SELECT max(expires) FROM files WHERE parcel=?) WHERE id=?", parcel, parcel); e != nil { + return e + } + } + } + return tx.Commit() +} diff --git a/internal/app/integrity.go b/internal/app/integrity.go new file mode 100644 index 0000000..dad3be6 --- /dev/null +++ b/internal/app/integrity.go @@ -0,0 +1,71 @@ +package app + +import ( + "crypto/sha256" + "encoding/hex" + "errors" + "io" + "strings" + + "filippo.io/age" +) + +func (a *App) verifyObjects() error { + rows, e := a.db.Query("SELECT id,digest,size,encrypted,key_id FROM objects") + if e != nil { + return e + } + type object struct { + id, digest, key string + size int64 + encrypted bool + } + objects := []object{} + for rows.Next() { + var o object + if e = rows.Scan(&o.id, &o.digest, &o.size, &o.encrypted, &o.key); e != nil { + rows.Close() + return e + } + objects = append(objects, o) + } + e = rows.Err() + rows.Close() + if e != nil { + return e + } + for _, o := range objects { + if len(o.id) != 32 { + return errors.New("非法对象ID") + } + if _, e = hex.DecodeString(o.id); e != nil { + return e + } + f, e := a.root.Open(objectPath(o.id)) + if e != nil { + return e + } + var reader io.Reader = f + if o.encrypted { + if a.identity == nil { + f.Close() + return errors.New("缺少加密私钥") + } + reader, e = age.Decrypt(f, a.identity) + if e != nil { + f.Close() + return e + } + } + h := sha256.New() + n, e := io.CopyBuffer(h, reader, make([]byte, 128<<10)) + f.Close() + if e != nil { + return e + } + if n != o.size || a.keyed(hex.EncodeToString(h.Sum(nil))+"/"+o.key) != strings.Split(o.digest, "/")[0] { + return errors.New("对象内容完整性检查失败: " + o.id) + } + } + return nil +} diff --git a/internal/app/maintenance.go b/internal/app/maintenance.go new file mode 100644 index 0000000..9b0f4cd --- /dev/null +++ b/internal/app/maintenance.go @@ -0,0 +1,337 @@ +package app + +import ( + "encoding/hex" + "errors" + "io/fs" + "os" + "strings" +) + +func (a *App) recover() error { + // No other process owns this data directory. Partial sessions are never published. + // An interrupted attempt may have transferred bytes not yet recorded; charge its full allocation conservatively. + if _, e := a.db.Exec("UPDATE files SET received=max(received,charged) WHERE status='pending' AND attempts>0 AND parcel IN(SELECT id FROM parcels WHERE state='uploading')"); e != nil { + return e + } + if _, e := a.db.Exec("UPDATE parcels SET state='revoked',version=version+1 WHERE state='uploading'"); e != nil { + return e + } + entries, e := fs.ReadDir(a.root.FS(), "tmp") + if e != nil { + return e + } + for _, entry := range entries { + if entry.Type()&os.ModeSymlink != 0 { + return errors.New("临时目录存在符号链接") + } + if e = a.root.RemoveAll("tmp/" + entry.Name()); e != nil { + return e + } + } + rows, e := a.db.Query("SELECT id FROM objects") + if e != nil { + return e + } + known := map[string]bool{} + for rows.Next() { + var id string + if e = rows.Scan(&id); e != nil { + rows.Close() + return e + } + known[id] = true + } + e = rows.Err() + rows.Close() + if e != nil { + return e + } + for id := range known { + if len(id) != 32 { + return errors.New("非法对象标识") + } + if _, e := hex.DecodeString(id); e != nil { + return errors.New("非法对象标识") + } + st, e := a.root.Lstat(objectPath(id)) + if e != nil || !st.Mode().IsRegular() { + if e != nil && !errors.Is(e, os.ErrNotExist) { + return e + } + if _, e = a.db.Exec("UPDATE files SET status='missing' WHERE object_id=?", id); e != nil { + return e + } + } + } + e = fs.WalkDir(a.root.FS(), "objects", func(path string, d fs.DirEntry, e error) error { + if e != nil { + return e + } + if d.Type()&os.ModeSymlink != 0 { + return errors.New("对象目录存在符号链接") + } + if d.IsDir() { + return nil + } + if !known[d.Name()] { + return a.root.Rename(path, "quarantine/"+randomID()) + } + return nil + }) + if e != nil { + return e + } + return a.Cleanup() +} +func (a *App) settleUsage(parcel string) error { + var ip, visitor, day string + if e := a.db.QueryRow("SELECT ip,visitor,usage_day FROM parcels WHERE id=?", parcel).Scan(&ip, &visitor, &day); e != nil { + return e + } + var refund int64 + if e := a.db.QueryRow("SELECT COALESCE(sum(max(charged-received,0)),0) FROM files WHERE parcel=?", parcel).Scan(&refund); e != nil { + return e + } + tx, e := a.db.Begin() + if e != nil { + return e + } + defer tx.Rollback() + for _, s := range []string{"ip:" + a.keyed(ip), "visitor:" + visitor} { + if _, e = tx.Exec("UPDATE usage SET bytes=max(0,bytes-?) WHERE subject=? AND day=?", refund, s, day); e != nil { + return e + } + } + if _, e = tx.Exec("UPDATE files SET charged=received WHERE parcel=?", parcel); e != nil { + return e + } + return tx.Commit() +} +func (a *App) Cleanup() error { + a.mu.Lock() + defer a.mu.Unlock() + now := a.now().Unix() + rows, e := a.db.Query("SELECT id,state,upload_expires FROM parcels WHERE state='uploading' OR state IN('revoked','expired','exhausted')") + if e != nil { + return e + } + type parcel struct { + id, state string + expiry int64 + } + parcels := []parcel{} + for rows.Next() { + var p parcel + if e = rows.Scan(&p.id, &p.state, &p.expiry); e != nil { + rows.Close() + return e + } + parcels = append(parcels, p) + } + e = rows.Err() + rows.Close() + if e != nil { + return e + } + for _, p := range parcels { + if p.state == "uploading" && p.expiry > now { + continue + } + rows, e := a.db.Query("SELECT id FROM files WHERE parcel=?", p.id) + if e != nil { + return e + } + active := false + for rows.Next() { + var id string + if e = rows.Scan(&id); e != nil { + rows.Close() + return e + } + if a.activeFiles[id] { + active = true + } + } + e = rows.Err() + rows.Close() + if e != nil { + return e + } + if active { + continue + } + if p.state == "uploading" { + if _, e = a.db.Exec("UPDATE parcels SET state='expired',version=version+1 WHERE id=?", p.id); e != nil { + return e + } + } + if e = a.settleUsage(p.id); e != nil { + return e + } + if _, e = a.db.Exec("UPDATE files SET status='deleting' WHERE parcel=?", p.id); e != nil { + return e + } + } + rows, e = a.db.Query("SELECT f.id,COALESCE(f.object_id,''),f.status,f.expires,f.remaining,p.state FROM files f JOIN parcels p ON p.id=f.parcel WHERE f.status!='deleted'") + if e != nil { + return e + } + type file struct { + id, object, status, state string + expiry int64 + remaining int + } + files := []file{} + for rows.Next() { + var f file + if e = rows.Scan(&f.id, &f.object, &f.status, &f.expiry, &f.remaining, &f.state); e != nil { + rows.Close() + return e + } + files = append(files, f) + } + e = rows.Err() + rows.Close() + if e != nil { + return e + } + for _, f := range files { + if a.activeFiles[f.id] || a.activeObjects[f.object] > 0 { + continue + } + remove := f.status == "deleting" || f.status == "missing" || (f.state == "ready" && (f.remaining == 0 || f.expiry <= now)) + if !remove { + continue + } + if e = a.root.Remove("tmp/" + f.id); e != nil && !errors.Is(e, os.ErrNotExist) { + return e + } + if _, e = a.db.Exec("UPDATE files SET status='deleted',reserved=0,object_id=NULL WHERE id=?", f.id); e != nil { + return e + } + } + // Objects remain charged until unlink succeeds. Sharing records never unlink each other's payloads. + rows, e = a.db.Query("SELECT id FROM objects WHERE NOT EXISTS(SELECT 1 FROM files WHERE object_id=objects.id)") + if e != nil { + return e + } + ids := []string{} + for rows.Next() { + var id string + if e = rows.Scan(&id); e != nil { + rows.Close() + return e + } + ids = append(ids, id) + } + e = rows.Err() + rows.Close() + if e != nil { + return e + } + for _, id := range ids { + if a.activeObjects[id] > 0 { + continue + } + if e = a.root.Remove(objectPath(id)); e != nil && !errors.Is(e, os.ErrNotExist) { + return e + } + if _, e = a.db.Exec("DELETE FROM objects WHERE id=?", id); e != nil { + return e + } + } + if _, e = a.db.Exec("UPDATE parcels SET state='exhausted',version=version+1 WHERE state='ready' AND NOT EXISTS(SELECT 1 FROM files WHERE parcel=parcels.id AND status='ready' AND remaining>0)"); e != nil { + return e + } + for _, q := range []struct { + sql string + args []any + }{ + {"DELETE FROM visitors WHERE expires<=?", []any{now}}, {"DELETE FROM grants WHERE expires<=?", []any{now}}, {"DELETE FROM admin_sessions WHERE expires<=?", []any{now}}, {"DELETE FROM usage WHERE day 86400 { + if e = a.root.Remove("quarantine/" + d.Name()); e != nil { + return e + } + } + } + if e = a.scanOrphans(); e != nil { + return e + } + a.extraDisk = 0 + e = fs.WalkDir(a.root.FS(), "quarantine", func(path string, d fs.DirEntry, e error) error { + if e != nil { + return e + } + st, e := d.Info() + if e == nil { + a.extraDisk += allocated(st) + } + return e + }) + if e != nil { + return e + } + _, e = a.db.Exec("PRAGMA wal_checkpoint(PASSIVE)") + return e +} +func (a *App) InvalidateRestored() error { + _, e := a.db.Exec("UPDATE parcels SET state='restored',code_hash=NULL,manage_hash=lower(hex(randomblob(32))),version=version+1; DELETE FROM grants; DELETE FROM admin_sessions; DELETE FROM visitors;") + return e +} +func safeRelative(s string) bool { + return s != "" && !strings.HasPrefix(s, "/") && !strings.Contains(s, "\\") && fs.ValidPath(s) +} + +func (a *App) scanOrphans() error { + rows, e := a.db.Query("SELECT id FROM objects") + if e != nil { + return e + } + known := map[string]bool{} + for rows.Next() { + var id string + if e = rows.Scan(&id); e != nil { + rows.Close() + return e + } + known[id] = true + } + e = rows.Err() + rows.Close() + if e != nil { + return e + } + return fs.WalkDir(a.root.FS(), "objects", func(path string, d fs.DirEntry, e error) error { + if e != nil { + return e + } + if d.Type()&os.ModeSymlink != 0 { + return errors.New("对象目录存在符号链接") + } + if d.IsDir() { + return nil + } + if !known[d.Name()] { + return a.root.Rename(path, "quarantine/"+randomID()) + } + return nil + }) +} diff --git a/internal/app/runtime.go b/internal/app/runtime.go new file mode 100644 index 0000000..6aadb9d --- /dev/null +++ b/internal/app/runtime.go @@ -0,0 +1,143 @@ +package app + +import ( + "database/sql" + "encoding/json" + "errors" + "net/http" + "strings" +) + +type RuntimeSettings struct { + FileBudget int64 `json:"file_budget"` + DataBudget int64 `json:"data_budget"` + MinFreeBytes int64 `json:"min_free_bytes"` + AuditRetentionDays int `json:"audit_retention_days"` + AuditMaxRecords int `json:"audit_max_records"` + AuditMaxBytes int64 `json:"audit_max_bytes"` +} + +func (c Config) InitialRuntimeSettings() RuntimeSettings { + return RuntimeSettings{c.FileBudget, c.DataBudget, c.MinFreeBytes, c.AuditRetentionDays, c.AuditMaxRecords, c.AuditMaxBytes} +} +func (s RuntimeSettings) Validate() error { + if s.FileBudget < 1 || s.FileBudget > 1<<50 || s.DataBudget > 1<<51 || s.DataBudget-s.FileBudget < 64<<20 || s.MinFreeBytes < 0 || s.MinFreeBytes > 1<<50 { + return errors.New("文件预算须为正数,数据预算须另留至少64MiB维护空间") + } + if s.AuditRetentionDays < 1 || s.AuditRetentionDays > 3650 || s.AuditMaxRecords < 1 || s.AuditMaxRecords > 1000000 || s.AuditMaxBytes < 1024 || s.AuditMaxBytes > 1<<30 || s.AuditMaxBytes > s.DataBudget-s.FileBudget { + return errors.New("审计需保留1–3650天,条数1–100万,内容预算1KiB–1GiB且不超过维护空间") + } + return nil +} +func readRuntime(q interface{ QueryRow(string, ...any) *sql.Row }) (RuntimeSettings, error) { + var text string + s := RuntimeSettings{} + e := q.QueryRow("SELECT json FROM runtime_settings WHERE id=1").Scan(&text) + if e == nil { + e = json.Unmarshal([]byte(text), &s) + } + if e == nil { + e = s.Validate() + } + return s, e +} +func (a *App) runtimeSettings() (RuntimeSettings, error) { return readRuntime(a.db) } +func (a *App) adminRuntime(w http.ResponseWriter, r *http.Request) { + if !a.requireAdmin(w, r) { + return + } + if r.Method == "GET" { + s, e := a.runtimeSettings() + if e != nil { + a.serverError(w, e) + return + } + respond(w, 200, s) + return + } + var settings RuntimeSettings + if !decode(w, r, &settings) { + return + } + if e := settings.Validate(); e != nil { + fail(w, 400, e.Error()) + return + } + a.mu.Lock() + defer a.mu.Unlock() + b, e := json.Marshal(settings) + if e != nil { + a.serverError(w, e) + return + } + tx, e := a.db.Begin() + if e != nil { + a.serverError(w, e) + return + } + defer tx.Rollback() + _, e = tx.Exec("UPDATE runtime_settings SET json=? WHERE id=1", string(b)) + if e == nil { + e = a.trimAuditTx(tx) + } + if e == nil { + e = tx.Commit() + } + if e != nil { + a.serverError(w, e) + return + } + a.audit("runtime.update", "", a.remoteIP(r)) + respond(w, 200, settings) +} + +// The content budget includes UTF-8 fields and a conservative per-record allowance. +// SQLite reuses freed pages; this budget is not the physical database-file size. +const auditRecordBytes = "length(CAST(action AS BLOB))+length(CAST(target AS BLOB))+length(CAST(source AS BLOB))+96" + +func (a *App) initializeAuditUsage() error { + expression := func(prefix string) string { + s := auditRecordBytes + for _, name := range []string{"action", "target", "source"} { + s = strings.ReplaceAll(s, name, prefix+"."+name) + } + return s + } + _, e := a.db.Exec("CREATE TABLE IF NOT EXISTS audit_usage(id INTEGER PRIMARY KEY CHECK(id=1),records INTEGER NOT NULL,content_bytes INTEGER NOT NULL); INSERT OR IGNORE INTO audit_usage SELECT 1,count(*),COALESCE(sum(" + auditRecordBytes + "),0) FROM audit; CREATE TRIGGER IF NOT EXISTS audit_insert_usage AFTER INSERT ON audit BEGIN UPDATE audit_usage SET records=records+1,content_bytes=content_bytes+(" + expression("NEW") + ") WHERE id=1; END; CREATE TRIGGER IF NOT EXISTS audit_delete_usage AFTER DELETE ON audit BEGIN UPDATE audit_usage SET records=records-1,content_bytes=content_bytes-(" + expression("OLD") + ") WHERE id=1; END;") + return e +} + +func (a *App) trimAuditTx(tx *sql.Tx) error { + s, e := readRuntime(tx) + if e != nil { + return e + } + if _, e = tx.Exec("DELETE FROM audit WHERE created int64(s.AuditMaxRecords) { + if _, e = tx.Exec("DELETE FROM audit WHERE id IN(SELECT id FROM audit ORDER BY id DESC LIMIT -1 OFFSET ?)", s.AuditMaxRecords); e != nil { + return e + } + } + if bytes <= s.AuditMaxBytes { + return nil + } + _, e = tx.Exec("WITH sizes AS (SELECT id,sum("+auditRecordBytes+") OVER(ORDER BY id DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS bytes FROM audit) DELETE FROM audit WHERE id IN(SELECT id FROM sizes WHERE bytes>?)", s.AuditMaxBytes) + return e +} +func (a *App) trimAudit() error { + tx, e := a.db.Begin() + if e != nil { + return e + } + defer tx.Rollback() + if e = a.trimAuditTx(tx); e != nil { + return e + } + return tx.Commit() +} diff --git a/internal/app/runtime_test.go b/internal/app/runtime_test.go new file mode 100644 index 0000000..9def572 --- /dev/null +++ b/internal/app/runtime_test.go @@ -0,0 +1,177 @@ +package app + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" +) + +func loginTestAdmin(t *testing.T, c *testClient) { + t.Helper() + expect(t, c.json(t, "POST", "/admin/login", map[string]string{"username": "admin", "password": "test-password"}), 200) +} +func TestAdminRuntimeBudgetsPersistAndProtectExistingFiles(t *testing.T) { + c := fixture(t, false) + expect(t, c.request(t, "GET", "/admin/runtime", nil), 401) + data := []byte("preserved file") + id, files := c.create(t, []string{"a"}, [][]byte{data}, 3, false) + c.upload(t, id, files[0], data) + code := c.publish(t, id) + loginTestAdmin(t, c) + settings, e := c.a.runtimeSettings() + if e != nil { + t.Fatal(e) + } + settings.FileBudget = 1 + expect(t, c.json(t, "PUT", "/admin/runtime", settings), 200) + expect(t, c.json(t, "POST", "/uploads", Manifest{Files: []ManifestFile{{Name: "new", Size: 1}}}), 507) + c.resolve(t, code) + expect(t, c.request(t, "GET", "/pickup/files/"+files[0]+"/download", nil), 200) + cfg := c.a.cfg + c.a.Close() + c.a.db = nil + c.a.root = nil + c.a.lock = nil + a, e := Open(cfg) + if e != nil { + t.Fatal(e) + } + defer a.Close() + saved, e := a.runtimeSettings() + if e != nil || saved.FileBudget != 1 { + t.Fatal("runtime budget reverted on restart") + } + if scalar(t, a, "SELECT count(*) FROM objects") != 1 { + t.Fatal("reducing quota deleted existing files") + } +} +func TestAuditRetentionRecordsAndUTF8ByteBudget(t *testing.T) { + c := fixture(t, false) + settings, e := c.a.runtimeSettings() + if e != nil { + t.Fatal(e) + } + settings.AuditRetentionDays = 2 + settings.AuditMaxRecords = 3 + settings.AuditMaxBytes = 1024 + b, _ := json.Marshal(settings) + c.a.db.Exec("UPDATE runtime_settings SET json=? WHERE id=1", string(b)) + _, e = c.a.db.Exec("INSERT INTO audit(created,action,target,source) VALUES(?,?,?,?)", c.a.now().Unix()-3*86400, "old", "", "") + if e != nil { + t.Fatal(e) + } + for i := 0; i < 8; i++ { + c.a.audit("new", "target", "203.0.113.1") + } + if scalar(t, c.a, "SELECT count(*) FROM audit") != 3 || scalar(t, c.a, "SELECT count(*) FROM audit WHERE action='old'") != 0 { + t.Fatal("retention/count limits failed") + } + settings.AuditMaxRecords = 100 + b, _ = json.Marshal(settings) + c.a.db.Exec("UPDATE runtime_settings SET json=? WHERE id=1", string(b)) + for i := 0; i < 10; i++ { + c.a.audit("utf8", strings.Repeat("鸟", 60), "source") + } + actual := scalar(t, c.a, "SELECT COALESCE(sum("+auditRecordBytes+"),0) FROM audit") + ledger := scalar(t, c.a, "SELECT content_bytes FROM audit_usage WHERE id=1") + if actual != ledger || actual > 1024 { + t.Fatalf("audit bytes actual=%d ledger=%d", actual, ledger) + } + if scalar(t, c.a, "SELECT records FROM audit_usage WHERE id=1") != scalar(t, c.a, "SELECT count(*) FROM audit") { + t.Fatal("audit count ledger incorrect") + } + _, e = c.a.db.Exec("UPDATE audit SET created=?", c.a.now().Unix()-3*86400) + if e != nil { + t.Fatal(e) + } + if e = c.a.Cleanup(); e != nil { + t.Fatal(e) + } + if scalar(t, c.a, "SELECT count(*) FROM audit") != 0 { + t.Fatal("background cleanup did not age audit events") + } +} +func TestAnonymousIdentityAndReceiptHaveIndependentLifetimes(t *testing.T) { + c := fixture(t, false) + base := time.Now() + c.a.now = func() time.Time { return base } + var info struct { + Expires int64 `json:"visitor_expires"` + } + w := c.request(t, "GET", "/policy", nil) + expect(t, w, 200) + json.Unmarshal(w.Body.Bytes(), &info) + if info.Expires != base.Unix()+7*86400 { + t.Fatal("cookie lifetime not seven days") + } + w = c.json(t, "POST", "/uploads", Manifest{Files: []ManifestFile{{Name: "a", Size: 3}}, Days: 7, Downloads: 5, Renew: true}) + expect(t, w, 201) + var receipt struct { + ID string `json:"id"` + Token string `json:"manage_token"` + Files []struct { + ID string `json:"id"` + } `json:"files"` + } + json.Unmarshal(w.Body.Bytes(), &receipt) + c.upload(t, receipt.ID, receipt.Files[0].ID, []byte("abc")) + code := c.publish(t, receipt.ID) + c.a.now = func() time.Time { return base.Add(6 * 24 * time.Hour) } + w = c.request(t, "GET", "/policy", nil) + json.Unmarshal(w.Body.Bytes(), &info) + if info.Expires != base.Unix()+7*86400 { + t.Fatal("fixed visitor lifetime unexpectedly extended") + } + c.resolve(t, code) + expect(t, c.request(t, "GET", "/pickup/files/"+receipt.Files[0].ID+"/download", nil), 200) + c.a.now = func() time.Time { return base.Add(8 * 24 * time.Hour) } + expect(t, c.request(t, "GET", "/policy", nil), 200) + w = c.request(t, "GET", "/mine", nil) + expect(t, w, 200) + if strings.TrimSpace(w.Body.String()) != "[]" { + t.Fatal("expired browser identity still owns old list") + } + r := c.makeReceiptRequest("GET", "/uploads/"+receipt.ID, receipt.Token, nil) + response := serveTest(c, r) + expect(t, response, 200) + c.a.now = func() time.Time { return base.Add(31 * 24 * time.Hour) } + if e := c.a.Cleanup(); e != nil { + t.Fatal(e) + } + expect(t, serveTest(c, c.makeReceiptRequest("GET", "/uploads/"+receipt.ID, receipt.Token, nil)), 404) +} +func TestServiceSwitchesDoNotGiveAdminUploadOrPickupBypass(t *testing.T) { + c := fixture(t, false) + data := []byte("content") + id, files := c.create(t, []string{"a"}, [][]byte{data}, 3, false) + c.upload(t, id, files[0], data) + c.resolve(t, c.publish(t, id)) + loginTestAdmin(t, c) + policy, e := c.a.policy() + if e != nil { + t.Fatal(e) + } + policy.AnonymousUpload = false + policy.AnonymousDownload = false + expect(t, c.json(t, "PUT", "/admin/policy", policy), 200) + expect(t, c.json(t, "POST", "/uploads", Manifest{Files: []ManifestFile{{Name: "a", Size: 1}}}), 403) + expect(t, c.request(t, "GET", "/pickup/files/"+files[0]+"/download", nil), 403) + expect(t, c.request(t, "GET", "/admin/stats", nil), 200) +} + +func (c *testClient) makeReceiptRequest(method, path, token string, body []byte) *http.Request { + r := httptest.NewRequest(method, "http://localhost:8080/api/v1"+path, bytes.NewReader(body)) + r.RemoteAddr = c.ip + ":80" + r.Header.Set("Origin", "http://localhost:8080") + r.Header.Set("Authorization", "Bearer "+token) + return r +} +func serveTest(c *testClient, r *http.Request) *httptest.ResponseRecorder { + w := httptest.NewRecorder() + c.h.ServeHTTP(w, r) + return w +} diff --git a/internal/app/security.go b/internal/app/security.go new file mode 100644 index 0000000..3ee585b --- /dev/null +++ b/internal/app/security.go @@ -0,0 +1,254 @@ +package app + +import ( + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "crypto/subtle" + "encoding/base64" + "encoding/hex" + "errors" + "fmt" + "io" + "net" + "net/http" + "strconv" + "strings" + "time" + + "golang.org/x/crypto/argon2" + "golang.org/x/time/rate" +) + +func randomToken() string { + b := make([]byte, 32) + if _, e := rand.Read(b); e != nil { + panic(e) + } + return base64.RawURLEncoding.EncodeToString(b) +} +func randomID() string { + b := make([]byte, 16) + if _, e := rand.Read(b); e != nil { + panic(e) + } + return hex.EncodeToString(b) +} +func tokenHash(s string) string { h := sha256.Sum256([]byte(s)); return hex.EncodeToString(h[:]) } +func (a *App) keyed(s string) string { + h := hmac.New(sha256.New, a.secret) + h.Write([]byte(s)) + return hex.EncodeToString(h.Sum(nil)) +} +func pickupCode() string { + b := make([]byte, 10) + if _, e := rand.Read(b); e != nil { + panic(e) + } + const alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" + for i := range b { + b[i] = alphabet[b[i]&31] + } + return string(b) +} +func normalizeCode(s string) string { + s = strings.ToUpper(strings.ReplaceAll(strings.TrimSpace(s), "-", "")) + s = strings.ReplaceAll(s, "O", "0") + s = strings.ReplaceAll(s, "I", "1") + return strings.ReplaceAll(s, "L", "1") +} +func hashPassword(p string) (string, error) { + if len(p) < 8 || len(p) > 128 { + return "", errors.New("密码需为 8–128 字节") + } + salt := make([]byte, 16) + if _, e := rand.Read(salt); e != nil { + return "", e + } + h := argon2.IDKey([]byte(p), salt, 2, 32*1024, 1, 32) + return fmt.Sprintf("v1$%s$%s", base64.RawStdEncoding.EncodeToString(salt), base64.RawStdEncoding.EncodeToString(h)), nil +} +func verifyPassword(p, h string) bool { + parts := strings.Split(h, "$") + if len(p) > 128 || len(parts) != 3 || parts[0] != "v1" { + return false + } + salt, e := base64.RawStdEncoding.DecodeString(parts[1]) + if e != nil || len(salt) != 16 { + return false + } + want, e := base64.RawStdEncoding.DecodeString(parts[2]) + if e != nil || len(want) != 32 { + return false + } + got := argon2.IDKey([]byte(p), salt, 2, 32*1024, 1, 32) + return subtle.ConstantTimeCompare(want, got) == 1 +} +func (a *App) remoteIP(r *http.Request) string { + host, _, e := net.SplitHostPort(r.RemoteAddr) + if e != nil { + host = r.RemoteAddr + } + ip := net.ParseIP(host) + if ip == nil { + return "unknown" + } + trusted := func(v net.IP) bool { + for _, n := range a.proxies { + if n.Contains(v) { + return true + } + } + return false + } + if trusted(ip) { + parts := strings.Split(r.Header.Get("X-Forwarded-For"), ",") + if len(parts) <= 16 { + for i := len(parts) - 1; i >= 0; i-- { + candidate := net.ParseIP(strings.TrimSpace(parts[i])) + if candidate == nil { + break + } + ip = candidate + if !trusted(ip) { + break + } + } + } + } + if v := ip.To4(); v != nil { + return v.String() + } + return ip.Mask(net.CIDRMask(64, 128)).String() + "/64" +} +func (a *App) cookie(w http.ResponseWriter, r *http.Request, name, value, path string, seconds int) { + state := a.accessFor(r) + if state.httpCookie { + name += "_http" + } + http.SetCookie(w, &http.Cookie{Name: name, Value: value, Path: path, MaxAge: seconds, HttpOnly: true, Secure: strings.HasPrefix(state.base, "https://"), SameSite: http.SameSiteStrictMode}) +} +func (a *App) visitor(w http.ResponseWriter, r *http.Request) (string, error) { + now := a.now().Unix() + if token := cookieValue(r, "bb_visitor"); token != "" && len(token) < 128 { + var id string + e := a.db.QueryRow("SELECT id FROM visitors WHERE token_hash=? AND expires>?", tokenHash(token), now).Scan(&id) + if e == nil { + return id, nil + } + } + var count int + if e := a.db.QueryRow("SELECT count(*) FROM visitors").Scan(&count); e != nil { + return "", e + } + if count >= a.cfg.MaxRecords { + return "", errors.New("匿名会话已满,请稍后重试") + } + id, t := randomID(), randomToken() + _, e := a.db.Exec("INSERT INTO visitors(id,token_hash,expires) VALUES(?,?,?)", id, tokenHash(t), now+7*86400) + if e == nil { + a.cookie(w, r, "bb_visitor", t, "/api/v1", 7*86400) + } + return id, e +} +func (a *App) currentVisitor(r *http.Request) string { + token := cookieValue(r, "bb_visitor") + if token == "" { + return "" + } + var id string + a.db.QueryRow("SELECT id FROM visitors WHERE token_hash=? AND expires>?", tokenHash(token), a.now().Unix()).Scan(&id) + return id +} +func (a *App) isAdmin(r *http.Request) bool { + token := cookieValue(r, "bb_admin") + if token == "" { + return false + } + var n int + e := a.db.QueryRow("SELECT count(*) FROM admin_sessions WHERE token_hash=? AND expires>?", tokenHash(token), a.now().Unix()).Scan(&n) + return e == nil && n == 1 +} + +type rateEntry struct { + limiter *rate.Limiter + touched time.Time +} + +func (a *App) allow(r *http.Request, kind string, perMinute int) bool { + a.rateMu.Lock() + defer a.rateMu.Unlock() + now := a.now() + key := kind + ":" + a.remoteIP(r) + entry := a.rates[key] + if entry == nil { + if len(a.rates) >= 10000 { + for k, v := range a.rates { + if now.Sub(v.touched) > 10*time.Minute { + delete(a.rates, k) + } + } + if len(a.rates) >= 10000 { + return false + } + } + entry = &rateEntry{limiter: rate.NewLimiter(rate.Limit(float64(perMinute)/60), max(2, perMinute/3))} + a.rates[key] = entry + } + entry.touched = now + return entry.limiter.Allow() +} +func (a *App) acquire(kind, ip string) bool { + a.transferMu.Lock() + defer a.transferMu.Unlock() + limit, perIP := a.cfg.MaxUploads, a.cfg.MaxIPUploads + if kind == "download" { + limit, perIP = a.cfg.MaxDownloads, a.cfg.MaxIPDownloads + } + if a.transfers[kind] >= limit || a.transfers[kind+":"+ip] >= perIP { + return false + } + a.transfers[kind]++ + a.transfers[kind+":"+ip]++ + return true +} +func (a *App) release(kind, ip string) { + a.transferMu.Lock() + defer a.transferMu.Unlock() + a.transfers[kind]-- + a.transfers[kind+":"+ip]-- + if a.transfers[kind+":"+ip] == 0 { + delete(a.transfers, kind+":"+ip) + } +} +func (a *App) deadlines(w http.ResponseWriter, r *http.Request) { + c := http.NewResponseController(w) + end := time.Now().Add(time.Duration(a.cfg.MaxTransferSeconds) * time.Second) + _ = c.SetReadDeadline(end) + _ = c.SetWriteDeadline(end) +} +func cookieValue(r *http.Request, name string) string { + for _, key := range cookieNames(r, name) { + if c, err := r.Cookie(key); err == nil && c.Value != "" { + return c.Value + } + } + return "" +} +func parseInt(s string) int { v, _ := strconv.Atoi(s); return v } + +type idleReader struct { + reader io.Reader + controller *http.ResponseController + total time.Time + idle time.Duration +} + +func (r *idleReader) Read(p []byte) (int, error) { + end := time.Now().Add(r.idle) + if end.After(r.total) { + end = r.total + } + _ = r.controller.SetReadDeadline(end) + return r.reader.Read(p) +} diff --git a/internal/app/upload.go b/internal/app/upload.go new file mode 100644 index 0000000..e7f3883 --- /dev/null +++ b/internal/app/upload.go @@ -0,0 +1,678 @@ +package app + +import ( + "crypto/sha256" + "database/sql" + "encoding/hex" + "errors" + "io" + "net" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" + "syscall" + "time" + "unicode" + + "filippo.io/age" + "golang.org/x/sys/unix" +) + +type ManifestFile struct { + Name string `json:"name"` + Size int64 `json:"size"` +} +type Manifest struct { + Files []ManifestFile `json:"files"` + Days int `json:"days"` + Downloads int `json:"downloads"` + Password string `json:"password"` + Renew bool `json:"renew"` +} + +func reserveSize(n int64, encrypted bool) int64 { + if encrypted { + n += 1024 + ((n/65536)+1)*16 + } + return ((n+4095)/4096)*4096 + 4096 +} +func cleanName(s string) string { + s = strings.ReplaceAll(s, "\\", "/") + s = filepath.Base(s) + s = strings.Map(func(r rune) rune { + if unicode.IsControl(r) { + return -1 + } + return r + }, s) + return strings.TrimSpace(s) +} +func objectPath(id string) string { return "objects/" + id[:2] + "/" + id } +func (a *App) capacity() (used, reserved, metadata int64, err error) { + err = a.db.QueryRow("SELECT COALESCE(sum(stored_bytes),0) FROM objects").Scan(&used) + if err != nil { + return + } + err = a.db.QueryRow("SELECT COALESCE(sum(reserved),0) FROM files WHERE object_id IS NULL AND reserved>0 AND status!='deleted'").Scan(&reserved) + if err != nil { + return + } + for _, p := range []string{"db/bluebird.db", "db/bluebird.db-wal", "db/bluebird.db-shm"} { + s, e := a.root.Stat(p) + if e == nil { + metadata += allocated(s) + } else if !errors.Is(e, os.ErrNotExist) { + err = e + return + } + } + metadata += a.extraDisk + return +} +func allocated(s os.FileInfo) int64 { + if st, ok := s.Sys().(*syscall.Stat_t); ok { + return max(st.Blocks*512, s.Size()) + } + if st, ok := s.Sys().(*unix.Stat_t); ok { + return max(st.Blocks*512, s.Size()) + } + return ((s.Size() + 4095) / 4096) * 4096 +} +func (a *App) createUpload(w http.ResponseWriter, r *http.Request) { + if !a.allow(r, "upload", 20) { + fail(w, 429, "上传请求过于频繁") + return + } + var m Manifest + if !decode(w, r, &m) { + return + } + a.mu.Lock() + defer a.mu.Unlock() + p, e := a.policy() + if e != nil { + a.serverError(w, e) + return + } + if !p.AnonymousUpload { + fail(w, 403, "管理员已关闭上传") + return + } + if m.Days == 0 { + m.Days = p.DefaultDays + } + if m.Downloads == 0 { + m.Downloads = p.DefaultDownloads + } + if m.Days < 1 || m.Days > p.MaxInitialDays || m.Downloads < 1 || m.Downloads > p.MaxDownloads || len(m.Files) < 1 || len(m.Files) > p.MaxFiles { + fail(w, 400, "文件数量、期限或下载次数超出策略") + return + } + if p.RequirePassword && m.Password == "" { + fail(w, 400, "当前策略要求取件密码") + return + } + hash := "" + if m.Password != "" { + if len(m.Password) < 8 || len(m.Password) > 128 { + fail(w, 400, "取件密码需为 8–128 字节") + return + } + select { + case a.passwordSlots <- struct{}{}: + default: + fail(w, 429, "密码验证繁忙") + return + } + hash, e = hashPassword(m.Password) + <-a.passwordSlots + if e != nil { + fail(w, 400, e.Error()) + return + } + } + var total, reservation int64 + for i, f := range m.Files { + m.Files[i].Name = cleanName(f.Name) + if m.Files[i].Name == "" || m.Files[i].Name == "." || m.Files[i].Name == ".." || len(m.Files[i].Name) > 240 || f.Size < 0 || f.Size > p.MaxFileBytes || total > p.MaxBatchBytes-f.Size { + fail(w, 400, "文件名、文件大小或批次总大小无效") + return + } + total += f.Size + reservation += reserveSize(f.Size, a.cfg.Encryption) + } + if total > p.MaxBatchBytes { + fail(w, 400, "批次总大小超出限制") + return + } + var count int + if e = a.db.QueryRow("SELECT count(*) FROM files").Scan(&count); e != nil { + a.serverError(w, e) + return + } + if count+len(m.Files) > a.cfg.MaxRecords { + fail(w, 507, "文件记录已满") + return + } + used, reserved, meta, e := a.capacity() + if e != nil { + a.serverError(w, e) + return + } + var disk unix.Statfs_t + if e = unix.Statfs(a.cfg.DataDir, &disk); e != nil { + a.serverError(w, e) + return + } + free := int64(disk.Bavail) * int64(disk.Bsize) + limits, e := a.runtimeSettings() + if e != nil { + a.serverError(w, e) + return + } + if used+reserved+reservation > limits.FileBudget || used+reserved+reservation+meta > limits.DataBudget || free-reserved-reservation < limits.MinFreeBytes || (disk.Files > 0 && disk.Ffree < uint64(len(m.Files)+32)) { + fail(w, 507, "可用存储不足,暂时无法接收文件") + return + } + visitor, e := a.visitor(w, r) + if e != nil { + fail(w, 429, "匿名会话暂不可用") + return + } + ip := a.remoteIP(r) + day := a.now().UTC().Format("2006-01-02") + subjects := []string{"ip:" + a.keyed(ip), "visitor:" + visitor} + tx, e := a.db.Begin() + if e != nil { + a.serverError(w, e) + return + } + defer tx.Rollback() + for i, subject := range subjects { + if _, e = tx.Exec("INSERT OR IGNORE INTO usage VALUES(?,?,0,0)", subject, day); e != nil { + a.serverError(w, e) + return + } + var b int64 + var n int + if e = tx.QueryRow("SELECT bytes,uploads FROM usage WHERE subject=? AND day=?", subject, day).Scan(&b, &n); e != nil { + a.serverError(w, e) + return + } + limit := p.IPDailyUploads + if i == 1 { + limit = p.SessionDailyUploads + } + if n >= limit || (i == 0 && b > p.IPDailyBytes-total) { + fail(w, 429, "今日上传次数或流量额度已用完") + return + } + if _, e = tx.Exec("UPDATE usage SET bytes=bytes+?,uploads=uploads+1 WHERE subject=? AND day=?", total, subject, day); e != nil { + a.serverError(w, e) + return + } + } + id, manage := randomID(), randomToken() + now := a.now().Unix() + renewal := 0 + if m.Renew { + renewal = p.RenewalDays + } + _, e = tx.Exec("INSERT INTO parcels(id,visitor,ip,state,created,password_hash,manage_hash,days,downloads,renewal_days,renewal_mode,lifetime_days,upload_expires,usage_day) VALUES(?,?,?,'uploading',?,?,?,?,?,?,?,?,?,?)", id, visitor, ip, now, hash, tokenHash(manage), m.Days, m.Downloads, renewal, p.RenewalMode, p.MaxLifetimeDays, now+3600, day) + if e != nil { + a.serverError(w, e) + return + } + out := []map[string]any{} + for _, f := range m.Files { + fid := randomID() + if _, e = tx.Exec("INSERT INTO files(id,parcel,name,size,status,reserved,remaining,charged) VALUES(?,?,?,?,'pending',?,?,?)", fid, id, f.Name, f.Size, reserveSize(f.Size, a.cfg.Encryption), m.Downloads, f.Size); e != nil { + a.serverError(w, e) + return + } + out = append(out, map[string]any{"id": fid, "name": f.Name, "size": f.Size}) + } + if e = tx.Commit(); e != nil { + a.serverError(w, e) + return + } + a.audit("upload.create", id, ip) + respond(w, 201, map[string]any{"id": id, "manage_token": manage, "files": out}) +} +func (a *App) owner(r *http.Request, id string) bool { + var owner, manage string + if e := a.db.QueryRow("SELECT visitor,manage_hash FROM parcels WHERE id=?", id).Scan(&owner, &manage); e != nil { + return false + } + token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ") + if token != "" && tokenHash(token) == manage { + return true + } + v := a.currentVisitor(r) + return v != "" && v == owner +} +func (a *App) uploadFile(w http.ResponseWriter, r *http.Request) { + id, fid := r.PathValue("parcel"), r.PathValue("file") + ip := a.remoteIP(r) + if !a.allow(r, "file-transfer", 120) { + fail(w, 429, "文件传输请求过于频繁") + return + } + if !a.acquire("upload", ip) { + fail(w, 429, "上传并发已满,请稍后重试") + return + } + defer a.release("upload", ip) + a.deadlines(w, r) + a.mu.Lock() + if !a.owner(r, id) { + a.mu.Unlock() + fail(w, 404, "上传会话不存在") + return + } + var state, status string + var expected, reserved, expiry int64 + e := a.db.QueryRow("SELECT p.state,f.status,f.size,f.reserved,p.upload_expires FROM files f JOIN parcels p ON p.id=f.parcel WHERE f.id=? AND p.id=?", fid, id).Scan(&state, &status, &expected, &reserved, &expiry) + if e != nil || state != "uploading" || expiry <= a.now().Unix() { + a.mu.Unlock() + fail(w, 409, "上传会话已失效") + return + } + if status == "ready" { + a.mu.Unlock() + respond(w, 200, map[string]bool{"ready": true}) + return + } + if a.activeFiles[fid] { + a.mu.Unlock() + fail(w, 409, "该文件正在上传") + return + } + var attempts int + var sessionIP, visitor, day string + if e = a.db.QueryRow("SELECT f.attempts,p.ip,p.visitor,p.usage_day FROM files f JOIN parcels p ON p.id=f.parcel WHERE f.id=?", fid).Scan(&attempts, &sessionIP, &visitor, &day); e != nil { + a.mu.Unlock() + a.serverError(w, e) + return + } + policy, pe := a.policy() + if pe != nil { + a.mu.Unlock() + a.serverError(w, pe) + return + } + if !policy.AnonymousUpload { + a.mu.Unlock() + fail(w, 403, "上传已关闭") + return + } + tx, e := a.db.Begin() + if e != nil { + a.mu.Unlock() + a.serverError(w, e) + return + } + if attempts > 0 { + var charged int64 + e = tx.QueryRow("SELECT bytes FROM usage WHERE subject=? AND day=?", "ip:"+a.keyed(sessionIP), day).Scan(&charged) + if e == nil && charged > policy.IPDailyBytes-expected { + tx.Rollback() + a.mu.Unlock() + fail(w, 429, "重试上传超过日流量限制") + return + } + if e == nil { + for _, subject := range []string{"ip:" + a.keyed(sessionIP), "visitor:" + visitor} { + _, e = tx.Exec("UPDATE usage SET bytes=bytes+? WHERE subject=? AND day=?", expected, subject, day) + if e != nil { + break + } + } + } + if e == nil { + _, e = tx.Exec("UPDATE files SET charged=charged+? WHERE id=?", expected, fid) + } + } + if e == nil { + _, e = tx.Exec("UPDATE files SET attempts=attempts+1 WHERE id=?", fid) + } + if e == nil { + e = tx.Commit() + } else { + tx.Rollback() + } + if e != nil { + a.mu.Unlock() + a.serverError(w, e) + return + } + a.activeFiles[fid] = true + a.mu.Unlock() + defer func() { a.mu.Lock(); delete(a.activeFiles, fid); a.mu.Unlock() }() + tmp := "tmp/" + fid + _ = a.root.Remove(tmp) + f, e := a.root.OpenFile(tmp, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) + if e != nil { + a.serverError(w, e) + return + } + defer a.root.Remove(tmp) + var dst io.Writer = f + var enc io.WriteCloser + if a.cfg.Encryption { + enc, e = age.Encrypt(f, a.identity.Recipient()) + if e != nil { + f.Close() + a.serverError(w, e) + return + } + dst = enc + } + h := sha256.New() + r.Body = http.MaxBytesReader(w, r.Body, expected) + totalDeadline := time.Now().Add(time.Duration(a.cfg.MaxTransferSeconds) * time.Second) + if sessionEnd := time.Unix(expiry, 0); sessionEnd.Before(totalDeadline) { + totalDeadline = sessionEnd + } + src := &idleReader{reader: r.Body, controller: http.NewResponseController(w), total: totalDeadline, idle: time.Duration(a.cfg.TransferIdleSeconds) * time.Second} + received, e := io.CopyBuffer(io.MultiWriter(dst, h), src, make([]byte, 128<<10)) + if e == nil && received != expected { + e = errors.New("实际大小与声明不一致") + } + if e == nil && enc != nil { + e = enc.Close() + } + if e == nil { + e = f.Sync() + } + st, se := f.Stat() + ce := f.Close() + if e == nil { + e = ce + } + if e == nil { + e = se + } + a.mu.Lock() + defer a.mu.Unlock() + if _, ue := a.db.Exec("UPDATE files SET received=received+? WHERE id=?", received, fid); ue != nil { + a.serverError(w, ue) + return + } + if e != nil { + fail(w, 400, "文件接收失败或大小与声明不一致,请重试") + return + } + stored := allocated(st) + if stored > reserved { + fail(w, 507, "文件实际落盘开销超出预留") + return + } + if e = a.db.QueryRow("SELECT state FROM parcels WHERE id=?", id).Scan(&state); e != nil || state != "uploading" || expiry <= a.now().Unix() { + fail(w, 409, "上传已取消") + return + } + keyID := "plain" + if a.cfg.Encryption { + keyID = tokenHash(a.identity.Recipient().String()) + } + digest := a.keyed(hex.EncodeToString(h.Sum(nil)) + "/" + keyID) + if !a.cfg.Deduplication { + digest += "/" + randomID() + } + var oid string + e = a.db.QueryRow("SELECT id FROM objects WHERE digest=? AND size=?", digest, expected).Scan(&oid) + if e != nil && !errors.Is(e, sql.ErrNoRows) { + a.serverError(w, e) + return + } + newObject := oid == "" + committed := false + defer func() { + if newObject && oid != "" && !committed { + if de := a.root.Remove(objectPath(oid)); de != nil && !errors.Is(de, os.ErrNotExist) { + a.extraDisk += stored + a.log.Error("orphan removal failed", "error", de) + } + } + }() + if newObject { + oid = randomID() + path := objectPath(oid) + if e = a.root.MkdirAll(filepath.Dir(path), 0700); e != nil { + a.serverError(w, e) + return + } + if e = a.root.Rename(tmp, path); e != nil { + a.serverError(w, e) + return + } + if e = syncDir(a.root, filepath.Dir(path)); e != nil { + a.serverError(w, e) + return + } + if e = syncDir(a.root, "tmp"); e != nil { + a.serverError(w, e) + return + } + } else { + if _, e = a.root.Stat(objectPath(oid)); e != nil { + a.serverError(w, e) + return + } + if e = a.root.Remove(tmp); e != nil { + a.serverError(w, e) + return + } + } + tx, e = a.db.Begin() + if e != nil { + a.serverError(w, e) + return + } + defer tx.Rollback() + if newObject { + _, e = tx.Exec("INSERT INTO objects VALUES(?,?,?,?,?,?,?)", oid, digest, expected, stored, a.cfg.Encryption, keyID, a.now().Unix()) + if e != nil { + _ = a.root.Remove(objectPath(oid)) + a.serverError(w, e) + return + } + } + var attached sql.Result + attached, e = tx.Exec("UPDATE files SET object_id=?,status='ready',reserved=0 WHERE id=? AND status='pending'", oid, fid) + if e == nil { + n, _ := attached.RowsAffected() + if n != 1 { + e = errors.New("文件已被管理员删除") + } + } + if e == nil { + e = tx.Commit() + } + if e != nil { + if newObject { + _ = a.root.Remove(objectPath(oid)) + } + a.serverError(w, e) + return + } + committed = true + respond(w, 200, map[string]bool{"ready": true}) +} +func (a *App) publish(w http.ResponseWriter, r *http.Request) { + a.mu.Lock() + defer a.mu.Unlock() + id := r.PathValue("parcel") + if !a.owner(r, id) { + fail(w, 404, "包裹不存在") + return + } + var state string + var days, lifetime int + var uploadExpiry int64 + e := a.db.QueryRow("SELECT state,days,lifetime_days,upload_expires FROM parcels WHERE id=?", id).Scan(&state, &days, &lifetime, &uploadExpiry) + if e != nil { + a.serverError(w, e) + return + } + if state == "ready" { + respond(w, 200, map[string]any{"published": true, "message": "已发布;如丢失取件码,请重新生成"}) + return + } + policy, pe := a.policy() + if pe != nil { + a.serverError(w, pe) + return + } + if !policy.AnonymousUpload { + fail(w, 403, "上传已关闭") + return + } + if state != "uploading" || uploadExpiry <= a.now().Unix() { + fail(w, 409, "上传会话已失效") + return + } + var pending int + e = a.db.QueryRow("SELECT count(*) FROM files WHERE parcel=? AND status!='ready'", id).Scan(&pending) + if e != nil { + a.serverError(w, e) + return + } + if pending > 0 { + fail(w, 409, "尚有文件未上传完成") + return + } + now := a.now().Unix() + expires := now + int64(days)*86400 + hard := now + int64(lifetime)*86400 + code := pickupCode() + tx, e := a.db.Begin() + if e != nil { + a.serverError(w, e) + return + } + defer tx.Rollback() + _, e = tx.Exec("UPDATE parcels SET state='ready',created=?,expires=?,hard_expires=?,code_hash=? WHERE id=?", now, expires, hard, a.keyed(code), id) + if e == nil { + _, e = tx.Exec("UPDATE files SET expires=?,hard_expires=? WHERE parcel=?", expires, hard, id) + } + if e == nil { + e = tx.Commit() + } + if e != nil { + a.serverError(w, e) + return + } + if e = a.settleUsage(id); e != nil { + a.serverError(w, e) + return + } + a.audit("parcel.publish", id, a.remoteIP(r)) + respond(w, 200, a.shareResult(r, code, expires)) +} +func (a *App) shareResult(r *http.Request, code string, expires int64) map[string]any { + links := []map[string]string{} + for _, base := range a.cfg.accessOrigins() { + label := "公网HTTP" + if strings.HasPrefix(base, "https://") { + label = "公网HTTPS" + } + u, _ := url.Parse(base) + ip := net.ParseIP(u.Hostname()) + if ip != nil && (ip.IsPrivate() || ip.IsLoopback()) { + label = "内网入口" + } + links = append(links, map[string]string{"label": label + " · " + base, "url": base + "/#/pickup/" + code}) + } + return map[string]any{"code": code, "url": a.accessFor(r).base + "/#/pickup/" + code, "urls": links, "expires": expires} +} +func (a *App) regenerate(w http.ResponseWriter, r *http.Request) { + a.mu.Lock() + defer a.mu.Unlock() + id := r.PathValue("parcel") + if !a.owner(r, id) { + fail(w, 404, "包裹不存在") + return + } + var expiry int64 + if e := a.db.QueryRow("SELECT expires FROM parcels WHERE id=? AND state='ready' AND EXISTS(SELECT 1 FROM files WHERE parcel=parcels.id AND status='ready' AND remaining>0 AND expires>?)", id, a.now().Unix()).Scan(&expiry); e != nil || expiry <= a.now().Unix() { + fail(w, 409, "包裹不可取件") + return + } + code := pickupCode() + if _, e := a.db.Exec("UPDATE parcels SET code_hash=?,version=version+1 WHERE id=?", a.keyed(code), id); e != nil { + a.serverError(w, e) + return + } + respond(w, 200, a.shareResult(r, code, expiry)) +} +func (a *App) revoke(w http.ResponseWriter, r *http.Request) { + a.mu.Lock() + defer a.mu.Unlock() + id := r.PathValue("parcel") + if !a.owner(r, id) { + fail(w, 404, "包裹不存在") + return + } + if _, e := a.db.Exec("UPDATE parcels SET state='revoked',version=version+1 WHERE id=?", id); e != nil { + a.serverError(w, e) + return + } + a.audit("parcel.revoke", id, a.remoteIP(r)) + respond(w, 200, map[string]bool{"revoked": true}) +} +func (a *App) uploadStatus(w http.ResponseWriter, r *http.Request) { + a.mu.Lock() + defer a.mu.Unlock() + id := r.PathValue("parcel") + if !a.owner(r, id) { + fail(w, 404, "包裹不存在") + return + } + var state string + var created, expires int64 + if e := a.db.QueryRow("SELECT state,created,expires FROM parcels WHERE id=?", id).Scan(&state, &created, &expires); e != nil { + a.serverError(w, e) + return + } + files, e := a.fileList(id) + if e != nil { + a.serverError(w, e) + return + } + respond(w, 200, map[string]any{"id": id, "state": state, "created": created, "expires": expires, "files": files}) +} +func (a *App) mine(w http.ResponseWriter, r *http.Request) { + a.mu.Lock() + defer a.mu.Unlock() + v := a.currentVisitor(r) + if v == "" { + respond(w, 200, []any{}) + return + } + rows, e := a.db.Query("SELECT p.id,p.state,p.created,p.expires,(SELECT count(*) FROM files WHERE parcel=p.id),(SELECT COALESCE(sum(size),0) FROM files WHERE parcel=p.id) FROM parcels p WHERE visitor=? ORDER BY created DESC LIMIT 100", v) + if e != nil { + a.serverError(w, e) + return + } + defer rows.Close() + out := []map[string]any{} + for rows.Next() { + var id, state string + var created, expires, size int64 + var n int + if e = rows.Scan(&id, &state, &created, &expires, &n, &size); e != nil { + a.serverError(w, e) + return + } + out = append(out, map[string]any{"id": id, "state": state, "created": created, "expires": expires, "count": n, "size": size}) + } + if e = rows.Err(); e != nil { + a.serverError(w, e) + return + } + respond(w, 200, out) +} diff --git a/internal/web/dist/.gitkeep b/internal/web/dist/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/internal/web/embed.go b/internal/web/embed.go new file mode 100644 index 0000000..6bab87e --- /dev/null +++ b/internal/web/embed.go @@ -0,0 +1,12 @@ +package web + +import ( + "embed" + "io/fs" + "net/http" +) + +//go:embed dist +var assets embed.FS + +func Handler() http.Handler { sub, _ := fs.Sub(assets, "dist"); return http.FileServer(http.FS(sub)) } diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..996895e --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,33 @@ +#!/bin/sh +set -eu +project_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +cd "$project_root" +build_mode=${1:-build} +case "$build_mode" in build|release|web) ;; *) echo 'Usage: scripts/build.sh [build|release|web] [all|amd64|arm64]' >&2; exit 2;; esac +release_target=${2:-all} +case "$release_target" in all) release_arches='amd64 arm64' ;; amd64|arm64) release_arches=$release_target ;; *) echo 'Release architecture must be all, amd64 or arm64' >&2; exit 2 ;; esac +npm --prefix web ci +npm --prefix web run build +touch internal/web/dist/.gitkeep +if [ "$build_mode" = web ]; then exit 0; fi +node scripts/notices.mjs +mkdir -p build +if [ "$build_mode" = build ]; then + CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o build/bluebird ./cmd/bluebird + exit 0 +fi +for build_arch in $release_arches; do + build_dir="build/linux-$build_arch" + rm -rf -- "$build_dir" + mkdir -p "$build_dir" + CGO_ENABLED=0 GOOS=linux GOARCH="$build_arch" go build -trimpath -ldflags='-s -w' -o "$build_dir/bluebird" ./cmd/bluebird + cp deploy/config.example.toml "$build_dir/config.toml" + cp deploy/install.sh "$build_dir/install.sh" + cp deploy/upgrade.sh "$build_dir/upgrade.sh" + printf '%s\n' "$build_arch" > "$build_dir/ARCH" + cp -R deploy docs "$build_dir/" + cp README.md THIRD_PARTY_NOTICES.txt "$build_dir/" + tar -czf "build/bluebird-linux-$build_arch.tar.gz" -C "$build_dir" . +done +if [ "$release_target" != arm64 ]; then cp build/linux-amd64/bluebird build/bluebird; fi +(cd build && sha256sum bluebird-linux-*.tar.gz > SHA256SUMS) diff --git a/scripts/icons.mjs b/scripts/icons.mjs new file mode 100644 index 0000000..6758a87 --- /dev/null +++ b/scripts/icons.mjs @@ -0,0 +1,15 @@ +import { chromium } from '../web/node_modules/playwright/index.mjs' +import { readFileSync, writeFileSync } from 'node:fs' +const svg=readFileSync('web/public/favicon.svg','utf8') +const browser=await chromium.launch({headless:true}) +try { + const page=await browser.newPage();await page.setContent('') + const images=[] + for(const size of [16,32,48,180,192,512]){ + const data=await page.evaluate(async({svg,size})=>{const canvas=document.querySelector('canvas');canvas.width=size;canvas.height=size;const image=new Image();image.src='data:image/svg+xml;base64,'+btoa(unescape(encodeURIComponent(svg)));await image.decode();canvas.getContext('2d').drawImage(image,0,0,size,size);return canvas.toDataURL('image/png').split(',')[1]},{svg,size}) + const bytes=Buffer.from(data,'base64');if(size<=48)images.push({size,bytes});if(size===180)writeFileSync('web/public/apple-touch-icon.png',bytes);if(size===192||size===512)writeFileSync(`web/public/icon-${size}.png`,bytes) + } + const header=Buffer.alloc(6+16*images.length);header.writeUInt16LE(1,2);header.writeUInt16LE(images.length,4);let offset=header.length + images.forEach(({size,bytes},i)=>{const index=6+16*i;header[index]=size;header[index+1]=size;header.writeUInt16LE(1,index+4);header.writeUInt16LE(32,index+6);header.writeUInt32LE(bytes.length,index+8);header.writeUInt32LE(offset,index+12);offset+=bytes.length}) + writeFileSync('web/public/favicon.ico',Buffer.concat([header,...images.map(image=>image.bytes)])) +} finally{await browser.close()} diff --git a/scripts/install-smoke.sh b/scripts/install-smoke.sh new file mode 100755 index 0000000..9f35107 --- /dev/null +++ b/scripts/install-smoke.sh @@ -0,0 +1,72 @@ +#!/bin/sh +# Only run inside a disposable container with /pkg mounted read-only. +set -eu +[ "${BLUEBIRD_INSTALL_SMOKE:-}" = 1 ] && [ "$(id -u)" = 0 ] && [ -f /pkg/ARCH ] || { echo 'Only for the isolated deployment test container' >&2; exit 1; } +fixture_password=install-fixture-password +worker_pid= +trap 'if [ -n "$worker_pid" ]; then kill "$worker_pid" 2>/dev/null || :; wait "$worker_pid" 2>/dev/null || :; fi' EXIT + +/pkg/bluebird validate-config --config /pkg/config.toml +[ ! -e /pkg/var ] +# Invalid site paths must fail before publishing configuration or credentials. +if BLUEBIRD_ADMIN_PASSWORD=$fixture_password /pkg/install.sh --prefix /opt/rejected --url http://files.example.test/path --encrypt --no-start >/tmp/rejected.log 2>&1; then echo 'Invalid site path accepted' >&2; exit 1; fi +[ ! -e /opt/rejected ] +[ -z "$(find /opt -maxdepth 1 -name '.bluebird-install-*' -print)" ] +if [ "${1:-https}" = http ]; then + BLUEBIRD_ADMIN_PASSWORD=$fixture_password /pkg/install.sh --url http://files.example.test --encrypt --no-start + grep -q '^listen = "127.0.0.1:8080"$' /opt/bluebird/config.toml + grep -q '^secure_cookies = false$' /opt/bluebird/config.toml + runuser -u bluebird -- /opt/bluebird/bluebird check --config /opt/bluebird/config.toml + echo 'Public HTTP hostname without a port installed successfully' + exit 0 +fi + +if [ "${1:-https}" = lan ]; then + if BLUEBIRD_ADMIN_PASSWORD=$fixture_password /pkg/install.sh --prefix /opt/rejected --url http://192.168.1.10 --lan-http --no-start >/tmp/rejected-no-port.log 2>&1; then echo 'Portless LAN HTTP accepted' >&2; exit 1; fi + grep -q '内网HTTP地址须包含端口' /tmp/rejected-no-port.log + if BLUEBIRD_ADMIN_PASSWORD=$fixture_password /pkg/install.sh --prefix /opt/rejected --url http://192.168.1.10:8080 --no-start >/tmp/rejected-lan.log 2>&1; then echo 'LAN HTTP accepted without explicit flag' >&2; exit 1; fi + BLUEBIRD_ADMIN_PASSWORD=$fixture_password /pkg/install.sh --url http://192.168.1.10:8080 --lan-http --encrypt --no-start + grep -q '^listen = "192.168.1.10:8080"$' /opt/bluebird/config.toml + grep -q '^allow_lan_http = true$' /opt/bluebird/config.toml + grep -q '^secure_cookies = false$' /opt/bluebird/config.toml + echo 'LAN installation and explicit HTTP opt-in passed' + exit 0 +fi +# A failed password prompt/init leaves no final data directory or stale stage. +if BLUEBIRD_ADMIN_PASSWORD=short /pkg/install.sh --prefix /opt/rejected --url https://files.example.com --no-start >/tmp/rejected-password.log 2>&1; then echo 'Short password accepted' >&2; exit 1; fi +[ ! -e /opt/rejected ] +[ -z "$(find /opt -maxdepth 1 -name '.bluebird-install-*' -print)" ] +BLUEBIRD_ADMIN_PASSWORD=$fixture_password /pkg/install.sh --prefix /opt/bluebird-fixture --lan-url http://127.0.0.1:18995 --access-url http://files.example.com --url https://files.example.com --encrypt --no-start +prefix=/opt/bluebird-fixture +[ "$(stat -c '%U:%G:%a' "$prefix")" = root:bluebird:750 ] +[ "$(stat -c '%U:%G:%a' "$prefix/bluebird")" = root:bluebird:750 ] +[ "$(stat -c '%U:%G:%a' "$prefix/config.toml")" = root:bluebird:640 ] +[ "$(stat -c '%U:%G:%a' "$prefix/var")" = bluebird:bluebird:700 ] +grep -q '^public_url = .*https://files.example.com' "$prefix/config.toml" +grep -q '^secure_cookies = true$' "$prefix/config.toml" +grep -q 'http://files.example.com' "$prefix/config.toml" +grep -q 'http://127.0.0.1:18995' "$prefix/config.toml" +grep -q '^allow_lan_http = true$' "$prefix/config.toml" +grep -q '^encryption = true$' "$prefix/config.toml" +grep -q "^ExecStart=$prefix/bluebird serve --config $prefix/config.toml$" /etc/systemd/system/bluebird.service +[ -f "$prefix/var/secrets/age.key" ] +if grep -raq "$fixture_password" "$prefix"; then echo 'Plaintext bootstrap password persisted' >&2; exit 1; fi +before=$(sha256sum "$prefix/config.toml") +if BLUEBIRD_ADMIN_PASSWORD=another-password /pkg/install.sh --prefix "$prefix" --url https://other.example.com --no-start >/tmp/reinstall.log 2>&1; then echo 'Existing installation overwritten' >&2; exit 1; fi +[ "$(sha256sum "$prefix/config.toml")" = "$before" ] +runuser -u bluebird -- "$prefix/bluebird" check --config "$prefix/config.toml" +runuser -u bluebird -- "$prefix/bluebird" serve --config "$prefix/config.toml" >/tmp/bluebird-serve.log 2>&1 & +worker_pid=$! +ready=false +for attempt in 1 2 3 4 5 6 7 8 9 10; do + if bash -c 'exec 3<>/dev/tcp/127.0.0.1/18995; printf "GET /api/v1/health HTTP/1.1\r\nHost: files.example.com\r\nConnection: close\r\n\r\n" >&3; cat <&3' >/tmp/health.txt 2>/dev/null && grep -q '"ready":true' /tmp/health.txt; then ready=true; break; fi + sleep .1 +done +[ "$ready" = true ] || { cat /tmp/bluebird-serve.log; exit 1; } +# Config validation is read-only and may run while the service holds its lock. +runuser -u bluebird -- "$prefix/bluebird" validate-config --config "$prefix/config.toml" +if runuser -u bluebird -- "$prefix/bluebird" check --config "$prefix/config.toml" >/tmp/locked-check.log 2>&1; then echo 'Maintenance ignored service lock' >&2; exit 1; fi +kill "$worker_pid" +wait "$worker_pid" || : +worker_pid= +echo 'Installation, ownership, protected credentials, repeat-install guard and native service passed' diff --git a/scripts/notices.mjs b/scripts/notices.mjs new file mode 100644 index 0000000..0392ddb --- /dev/null +++ b/scripts/notices.mjs @@ -0,0 +1,11 @@ +import { readFileSync, readdirSync, writeFileSync } from 'node:fs' +import { join } from 'node:path' +import { execFileSync } from 'node:child_process' +const notices=['蓝鸟第三方组件许可声明\nGenerated from pinned dependency sources. Some development/test dependencies are included.'] +function include(label,dir){if(!dir)return;let files;try{files=readdirSync(dir)}catch{return}for(const file of files){if(/^(LICENSE|COPYING)([.\-_]|$)/i.test(file)){try{notices.push(`\n${'='.repeat(72)}\n${label} / ${file}\n${readFileSync(join(dir,file),'utf8')}`)}catch{}}}} +const modules=execFileSync('go',['list','-m','-json','all'],{encoding:'utf8'}).trim().split(/\n(?=\{)/).map(s=>JSON.parse(s)) +for(const module of modules){if(!module.Main)include(`${module.Path}@${module.Version}`,module.Dir)} +include('Go runtime / standard library',execFileSync('go',['env','GOROOT'],{encoding:'utf8'}).trim()) +const lock=JSON.parse(readFileSync('web/package-lock.json','utf8')) +for(const [path,entry] of Object.entries(lock.packages)){if(path.startsWith('node_modules/'))include(`${path.slice(13)}@${entry.version}`,join('web',path))} +writeFileSync('THIRD_PARTY_NOTICES.txt',notices.join('\n')) diff --git a/scripts/smoke.py b/scripts/smoke.py new file mode 100644 index 0000000..dd6d4b3 --- /dev/null +++ b/scripts/smoke.py @@ -0,0 +1,109 @@ +"""Verify the native release in an isolated directory on the workspace filesystem.""" +import http.cookiejar +import json +import os +from pathlib import Path +import secrets +import shutil +import socket +import sqlite3 +import subprocess +import tarfile +import tempfile +import time +import urllib.request + +project = Path(__file__).resolve().parent.parent +stage = Path(tempfile.mkdtemp(prefix=".bluebird-smoke-", dir=project)) +server = None +try: + with tarfile.open(project / "build/bluebird-linux-amd64.tar.gz") as archive: + archive.extractall(stage, filter="data") + with socket.socket() as sock: + sock.bind(("127.0.0.1", 0)) + port = sock.getsockname()[1] + base = f"http://127.0.0.1:{port}" + config = stage / "config.toml" + source = (config.read_text().replace("127.0.0.1:8080", f"127.0.0.1:{port}") + .replace("http://localhost:8080", base) + .replace("min_free_bytes = 1073741824", "min_free_bytes = 0") + .replace("encryption = false", "encryption = true")) + config.write_text(source) + binary = str(stage / "bluebird") + password = secrets.token_urlsafe(24) + env = os.environ.copy() + env["BLUEBIRD_ADMIN_PASSWORD"] = password + subprocess.run([binary, "init", "--config", str(config)], env=env, check=True, + capture_output=True, timeout=15) + server = subprocess.Popen([binary, "serve", "--config", str(config)], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + opener = urllib.request.build_opener( + urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar())) + + def call(method, path, body=None, raw=False): + headers = {} + if body is not None: + if not raw: + body = json.dumps(body).encode() + headers = {"Origin": base, "Content-Type": + "application/octet-stream" if raw else "application/json"} + request = urllib.request.Request(base + "/api/v1" + path, data=body, + headers=headers, method=method) + with opener.open(request, timeout=10) as response: + return json.loads(response.read()) + + for _ in range(50): + try: + assert call("GET", "/health")["ready"] + break + except (OSError, AssertionError): + time.sleep(.1) + else: + raise AssertionError("Release server did not start") + with opener.open(base + "/", timeout=10) as response: + assert b'
' in response.read() + call("GET", "/policy") + payload = b"bluebird-release-smoke\n" * 4096 + parcel = call("POST", "/uploads", { + "files": [{"name": "first.dat", "size": len(payload)}, + {"name": "same.dat", "size": len(payload)}], + "days": 7, "downloads": 2, "renew": True, "password": ""}) + for file in parcel["files"]: + call("PUT", f"/uploads/{parcel['id']}/files/{file['id']}", payload, True) + share = call("POST", f"/uploads/{parcel['id']}/publish", {}) + call("POST", "/pickup/resolve", {"code": share["code"]}) + request = urllib.request.Request( + base + f"/api/v1/pickup/files/{parcel['files'][0]['id']}/download") + with opener.open(request, timeout=10) as response: + assert response.read() == payload + call("POST", "/admin/login", {"username": "admin", "password": password}) + stats = call("GET", "/admin/stats") + assert stats["objects"] == 1 and stats["files"] == 2 + server.terminate() + server.wait(timeout=10) + server = None + subprocess.run([binary, "check", "--config", str(config)], check=True, + capture_output=True, timeout=15) + backup = stage / "backup.tar.gz" + subprocess.run([binary, "backup", "--config", str(config), "--output", str(backup)], + check=True, capture_output=True, timeout=15) + restored = stage / "restore.toml" + restored.write_text(source.replace('data_dir = "var"', 'data_dir = "restored-var"')) + subprocess.run([binary, "restore", "--config", str(restored), "--input", str(backup)], + check=True, capture_output=True, timeout=15) + subprocess.run([binary, "check", "--config", str(restored)], check=True, + capture_output=True, timeout=15) + with sqlite3.connect(stage / "restored-var/db/bluebird.db") as db: + assert db.execute("SELECT state,code_hash FROM parcels").fetchone() == ("restored", None) + assert db.execute("SELECT count(*) FROM grants").fetchone()[0] == 0 + assert db.execute("SELECT count(*) FROM objects").fetchone()[0] == 1 + print("发布包验证通过:初始化、内嵌前端、匿名去重、加密下载、管理、检查、备份恢复。") +finally: + if server is not None: + server.terminate() + try: + server.wait(timeout=10) + except subprocess.TimeoutExpired: + server.kill() + server.wait() + shutil.rmtree(stage) diff --git a/scripts/upgrade-smoke.py b/scripts/upgrade-smoke.py new file mode 100644 index 0000000..f20dcd7 --- /dev/null +++ b/scripts/upgrade-smoke.py @@ -0,0 +1,103 @@ +"""Exercise a real previous installation inside a disposable rootless container. +Requires podman, the old tarball argument and the newly built amd64 release. +""" +import http.cookies +import json +from pathlib import Path +import secrets +import subprocess +import sys +import tarfile +import tempfile +import time +import urllib.request + +project = Path(__file__).resolve().parent.parent +name = 'bluebird-upgrade-' + secrets.token_hex(4) +password = 'upgrade-preserved-password' +base = 'http://127.0.0.1:18996' +origin = 'https://files.example.test' + +def run(*args, **kw): + return subprocess.run(args, check=True, capture_output=True, text=True, **kw).stdout + +def inside(*args, **kw): + return run('podman', 'exec', name, *args, **kw) + +with tempfile.TemporaryDirectory(prefix='bluebird-upgrade-old-') as old: + with tarfile.open(sys.argv[1]) as archive: + archive.extractall(old, filter='data') + try: + run('podman', 'run', '--rm', '-d', '--security-opt', 'label=disable', '--name', name, + '-p', '127.0.0.1:18996:18996', '-v', old+':/oldpkg:ro', + '-v', str(project/'build/linux-amd64')+':/pkg:ro', + 'docker.io/library/debian:bookworm-slim', 'sleep', '300') + inside('env', 'BLUEBIRD_ADMIN_PASSWORD='+password, '/oldpkg/install.sh', + '--url', origin, '--listen', '0.0.0.0:18996', '--encrypt', '--no-start') + inside('sed','-i','s/min_free_bytes = 1073741824/min_free_bytes = 0/', '/opt/bluebird/config.toml') + key_before = inside('sha256sum', '/opt/bluebird/var/secrets/age.key') + cookies = {} + def call(method, path, body=None): + data = body if isinstance(body, bytes) else json.dumps(body).encode() if body is not None else None + req = urllib.request.Request(base+'/api/v1'+path, data=data, method=method, headers={ + 'Origin': origin, 'X-Bluebird-Origin': origin, + 'Cookie': '; '.join(k+'='+v for k,v in cookies.items()), + 'Content-Type': 'application/octet-stream' if isinstance(body,bytes) else 'application/json'}) + with urllib.request.urlopen(req, timeout=10) as response: + for line in response.headers.get_all('Set-Cookie',[]): + parsed = http.cookies.SimpleCookie(); parsed.load(line) + cookies.update({k:v.value for k,v in parsed.items()}) + result=response.read() + return result if path.endswith('/download') else json.loads(result) + def start(): + subprocess.run(['podman','exec','-d',name,'runuser','-u','bluebird','--','/opt/bluebird/bluebird','serve','--config','/opt/bluebird/config.toml'],check=True) + for _ in range(50): + try: + if call('GET','/health')['ready']: return + except OSError: time.sleep(.1) + raise AssertionError('Container service not ready') + start() + call('GET','/policy') + parcel=call('POST','/uploads',{'files':[{'name':'preserved.txt','size':21}],'days':7,'downloads':5,'renew':True}) + call('PUT',f"/uploads/{parcel['id']}/files/{parcel['files'][0]['id']}", b'old encrypted payload') + share=call('POST',f"/uploads/{parcel['id']}/publish",{}) + call('POST','/admin/login',{'username':'admin','password':password}) + before=inside('sha256sum','/opt/bluebird/config.toml','/opt/bluebird/bluebird') + # Invalid candidates must not stop a running service or change files. + bad=subprocess.run(['podman','exec',name,'/pkg/upgrade.sh','--access-url','http://bad.test/path','--no-start'],capture_output=True) + assert bad.returncode != 0 + assert before == inside('sha256sum','/opt/bluebird/config.toml','/opt/bluebird/bluebird') + assert call('GET','/health')['ready'] + # No pgrep dependency: /proc/comm identifies only the native service. + inside('sh','-c','for p in /proc/[0-9]*; do [ "$(cat "$p/comm" 2>/dev/null)" != bluebird ] || kill -TERM "${p##*/}"; done') + for _ in range(50): + try: call('GET','/health') + except OSError: break + time.sleep(.1) + # Simulate a new-version integrity-check failure after files were replaced. + inside('sh','-c','mkdir /failpkg; cp /pkg/bluebird /failpkg/bluebird.real; cp /pkg/ARCH /pkg/upgrade.sh /failpkg/') + wrapper='#!/bin/sh\nif [ "$1" = check ]; then exit 1; fi\nexec "$(dirname "$0")/bluebird.real" "$@"\n' + inside('sh','-c','printf "%s" "$1" > /failpkg/bluebird; chmod 755 /failpkg/bluebird','wrapper',wrapper) + failed=subprocess.run(['podman','exec',name,'/failpkg/upgrade.sh','--lan-url',base,'--listen','0.0.0.0:18996','--no-start'],capture_output=True) + assert failed.returncode != 0 and '已恢复旧程序和配置' in failed.stderr.decode() + assert before == inside('sha256sum','/opt/bluebird/config.toml','/opt/bluebird/bluebird') + result=inside('/pkg/upgrade.sh','--lan-url',base,'--listen','0.0.0.0:18996', + '--access-url','http://files.example.test','--no-start') + assert '升级完成' in result + assert key_before == inside('sha256sum','/opt/bluebird/var/secrets/age.key') + assert inside('stat','-c','%U:%G:%a','/opt/bluebird/config.toml').strip() == 'root:bluebird:640' + assert 'data.tar.gz' in inside('find','/opt/bluebird/backups','-type','f') + start();cookies.clear() + call('POST','/admin/login',{'username':'admin','password':password}) + assert call('GET','/admin/stats')['objects'] == 1 + policy=call('GET','/policy');assert set(policy['access_urls']) == {origin,base,'http://files.example.test'} + for entrance in [origin,base,'http://files.example.test']: + origin=entrance;cookies.clear() + call('POST','/pickup/resolve',{'code':share['code']}) + assert call('GET',f"/pickup/files/{parcel['files'][0]['id']}/download") == b'old encrypted payload' + print('升级验证通过:旧管理员密码、加密文件、密钥及取件码保留;三入口下载、权限、备份、非法配置保护及检查失败回滚通过。') + except subprocess.CalledProcessError as error: + print(error.stdout, error.stderr, file=sys.stderr) + raise + finally: + subprocess.run(['podman','rm','-f',name],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..087e98a --- /dev/null +++ b/web/index.html @@ -0,0 +1 @@ +蓝鸟 · 文件快递柜
diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 0000000..8b14ce6 --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,2222 @@ +{ + "name": "bluebird-web", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "bluebird-web", + "version": "0.1.0", + "dependencies": { + "naive-ui": "^2.43.0", + "pinia": "^3.0.0", + "qrcode": "^1.5.4", + "vue": "^3.5.0", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@playwright/test": "^1.55.0", + "@types/node": "^24.0.0", + "@types/qrcode": "^1.5.5", + "@vitejs/plugin-vue": "^6.0.0", + "typescript": "^5.9.0", + "vite": "^7.1.0", + "vue-tsc": "^3.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@css-render/plugin-bem": { + "version": "0.15.14", + "resolved": "https://registry.npmjs.org/@css-render/plugin-bem/-/plugin-bem-0.15.14.tgz", + "integrity": "sha512-QK513CJ7yEQxm/P3EwsI+d+ha8kSOcjGvD6SevM41neEMxdULE+18iuQK6tEChAWMOQNQPLG/Rw3Khb69r5neg==", + "license": "MIT", + "peerDependencies": { + "css-render": "~0.15.14" + } + }, + "node_modules/@css-render/vue3-ssr": { + "version": "0.15.14", + "resolved": "https://registry.npmjs.org/@css-render/vue3-ssr/-/vue3-ssr-0.15.14.tgz", + "integrity": "sha512-//8027GSbxE9n3QlD73xFY6z4ZbHbvrOVB7AO6hsmrEzGbg+h2A09HboUyDgu+xsmj7JnvJD39Irt+2D0+iV8g==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", + "license": "Apache-2.0" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@playwright/test": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz", + "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.64.0.tgz", + "integrity": "sha512-FLLpX941CD/7aBvX3GqLGBM9OvY5PGXTryPJkG5r5/OuBpZpV5k45Uo3RxAvqd35tsyc3hH1bhtz921IKzoIzg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.64.0.tgz", + "integrity": "sha512-iI32XM/jnDYJsvx5ai/za/7gWO9XoSeFFtU06/ZCaRgyilfJOjaopD3nOpk2WrTCGOyeDt4hleUiGOAnd5tHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.64.0.tgz", + "integrity": "sha512-dRBZlbt2sEtF8Mn4MODThze9fIPdDoOhVSlXMAKy5dQbOYK9pOOoPY4Fat3OTtr0+8zbJo5TTZwlsfxY/kDNJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.64.0.tgz", + "integrity": "sha512-Y3whrJOMucfkhgNkvcM93M6j8C53iEAMmPMYD99C6G1kUy4ELigLWk9ar2/4aCBSRgsql0zdlOemLgW2m5GZgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.64.0.tgz", + "integrity": "sha512-S6S1GEzPjFF6IONYJX3AKt/799+8RgBgCcwRnUo+Ep1IvBxUbw5M51iU0TDdK2NWWVnSvCQ8yi6Em0fGASMOng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.64.0.tgz", + "integrity": "sha512-Ed1DPoFENkXj4IGzSB12SesV40iP6LxCCrbTIkSGIQBpOOxpBM+0kj62lmaeIHfMWTDoddqTOtqc7s8LYfn+Pw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.64.0.tgz", + "integrity": "sha512-J/6xhutJPtFyIkmHdf4olOdHHKH5kOJNumojdWn+Elv5ICEYTVOa362Vw27TxsK0BLzb1le3EDlO3UbvFK1lnQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.64.0.tgz", + "integrity": "sha512-G4L/Nnx59d2LvDoV/P4S5lZ7oVLT6hodOHMacqjiq2LL2gts86KbkEBTYL53+fBt0odhf7XPIbIATGoYiJctxg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.64.0.tgz", + "integrity": "sha512-zF16pvJDEUksGYRbfnXne7pdklAyj2XDmJeYKgP93oq0cUyX9Y8O7lUo1R+IY1GagP/YBvCEDCe7eEcAKpw//g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.64.0.tgz", + "integrity": "sha512-nIggWn8n+w12+R5lsV+PYYoUY8vuObxPAQqJI7WES+9v0qspYuAHoZMfYM0yLxlipasT5BY9ht5W8t5c/rSCkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.64.0.tgz", + "integrity": "sha512-WLOTiHPzszP0dxIMsHpFeDSOSB5I49Mj8K4+yKE3H1X/D0JRG5XZ5jrctJLMjnUc/LUzaKiwq6EHWNJ8pup8PA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.64.0.tgz", + "integrity": "sha512-35RK9aRhh7g7Pmeu5gQ01aCvrhXzj9LoBE6xtUcEH1ns+nyTaSzxSpADmBQINu4QiTDGvmQYwo1j5hlg1rRXFw==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.64.0.tgz", + "integrity": "sha512-RqTfnpoTjuWfPAmqK5p2edt2AMCDJWm6oTn7sKfmyx+6tRga0ip05SJ977o6udTcIiDeF/CJMUfkaN4gtWt6Aw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.64.0.tgz", + "integrity": "sha512-RQjZPigjxFvFZDijG6rjlSXxhRaTf9zLYVpGK+lVcga64oMqP1GbgVqCb46eHTTceSIgp89Ja0y5aPe3GFBYHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.64.0.tgz", + "integrity": "sha512-tjL58mgQcTRnDRpeky+Sth1MKQeYi2Ct6r3Luq9E5aTSjnM/i/JbozqZkKqp9thM5+oDSZxo+05f9kdQedfTGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.64.0.tgz", + "integrity": "sha512-ictoRyVaoZ8C67myHef+8hhCa6IplXJJ/xUCkVCxziV4zxMZh+jvhkhGsPBLqj1S72fnqD0QH5zL4lcaa4X7DQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.64.0.tgz", + "integrity": "sha512-8aVvZ2hfr9RLFzBQkQNmYeHLDja8fLeQAmLt757zdKN/B+8L809KlLJKctQjS22wZotptV8G6o7fVPuAs8S1tQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.64.0.tgz", + "integrity": "sha512-2dEF8GAcDKwshUfydD+GhosppNyBzhVUHkdFF3CXd3FpOkzj/RZzF5aoEtzklujklT5qFVRU2GK/cFeLaTNKVg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.64.0.tgz", + "integrity": "sha512-3XHTvoo2hKkh0MiOfS7SkhHjMaaWF7P4F3drocZZaho/Z8w7h/jpnuBHm91BOQLfvrKHs1yR+zir4E8RI3XzTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.64.0.tgz", + "integrity": "sha512-HKtkzodL4a3KFWDXDoO61cRynAWTR3LnAE0oGtfG9lcNFpNuVYUFOByxDgNqEvic9rvaQbAj+SFwTEWDptRaIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.64.0.tgz", + "integrity": "sha512-JBsxqiY16uyyCujtoC3s7zL+SrYyNElptD7K4V+KUoJ2V8/XG3hPWpheK+SuPwNh1cikJvIZmIdsrYXvD6PLJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.64.0.tgz", + "integrity": "sha512-S5Qrijh37qhnUYQ0ay8WN3LuWeKjow7wTUyCOPMZtnXiBXYXdsZETI6ZLfYnQAKovltckPLBFdNeD61P2rJc7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.64.0.tgz", + "integrity": "sha512-zbZ6iMmLFEsCtTdc2/y1F/cDFVNNCPF7Ae8PCT/wdPE//hGwYp6r+MyCLMFX+kOeIe09Pz6ASfnnfpSTN1FJQQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.64.0.tgz", + "integrity": "sha512-L6Hw69oNpuYaOr1zCG30v0/DqKYCSRwodPGwIcVZ/6f8SOzq9rxtL/xNVI5MO4wjli+Ko0ErK9uHJL3mNPt8wQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.64.0.tgz", + "integrity": "sha512-nl+CTWIgOGszQmUz/kanfgrfFZ4qiwxKIfT/ryl3N3Ewk5tqizmYtNXiqb7Bu/fDtNgu1kyxdFiAUJNur3iysg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.25.tgz", + "integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "24.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.1.tgz", + "integrity": "sha512-aS3/DG0oM05K0RIXXP+hKjinGG5IgSSVGzswZxW3O0sS3pH4/fycXundUC9XsszgKCk4gHXylTEK6hyFxVxnoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/qrcode": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", + "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.9.tgz", + "integrity": "sha512-rD/MORlhaZMlXWW0rEn4FB4wMinWC0z/D7Ye160S5+Rs1mC8ZDAdcDp4SjKfUhG5t8q1ktcPVw4xuTkPRlzrSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.43.tgz", + "integrity": "sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/shared": "3.5.43", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.43.tgz", + "integrity": "sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.43", + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.43.tgz", + "integrity": "sha512-FCbrG3XNCRl+js3huuKx4IVHBLTvMkJhVepjbxSPu1gn4yWLaYtGNQdjJGZaMytXB6qb76qQDDmDSLy/vkmleQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/compiler-core": "3.5.43", + "@vue/compiler-dom": "3.5.43", + "@vue/compiler-ssr": "3.5.43", + "@vue/shared": "3.5.43", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.28", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.43.tgz", + "integrity": "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.43", + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.10.tgz", + "integrity": "sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.10" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.10.tgz", + "integrity": "sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.10", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.10.tgz", + "integrity": "sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.12.tgz", + "integrity": "sha512-k7uIKR0n4CBidk233y7cac9osnDQ5WRidTl6nsrNvpZ3nf8cx9iJxRVsRzVsoPRHbowT/iKgGMTS9RGov3jeIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.2.1", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.4" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.43.tgz", + "integrity": "sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.43.tgz", + "integrity": "sha512-hU6U6VnVhBGQDpvlnnDlIB8ZGJBiOcgk2lh/0InltHiz3D8oSkluvuvY+do1G2H3+udeKFsmaBlgVYP7gXQzEw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.43", + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.43.tgz", + "integrity": "sha512-Bb2Jc0YjjJdMt1SJmb9b2L/IWd3I8lIT9x9eS/xvvP9CiVgna0ffua74xKRmt4/uSJ+0r4iN8ex1jrqkhQGWQw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.43", + "@vue/runtime-core": "3.5.43", + "@vue/shared": "3.5.43", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.43.tgz", + "integrity": "sha512-l2Ygjv9NehV94PSBxNWsAHC0j/eIIKbn92mBuWXAPGLnn6HfJ6MH5ubsd+Nk0YoZ5FRuxWI1P2VSoh+dbPQhCQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.43", + "@vue/runtime-dom": "3.5.43", + "@vue/shared": "3.5.43" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.43.tgz", + "integrity": "sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q==", + "license": "MIT" + }, + "node_modules/alien-signals": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz", + "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.1.5.tgz", + "integrity": "sha512-vePst2VLTSqPglW7Q98VwFpGiGpOHTyXIo81MO5VyLecA5zMbZw/P/V1vmwD1jksa2iJpn/YkDrJ3XhTfzUyhQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/css-render": { + "version": "0.15.14", + "resolved": "https://registry.npmjs.org/css-render/-/css-render-0.15.14.tgz", + "integrity": "sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "~0.8.0", + "csstype": "~3.0.5" + } + }, + "node_modules/css-render/node_modules/csstype": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", + "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.4.0.tgz", + "integrity": "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/date-fns-tz": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.2.0.tgz", + "integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==", + "license": "MIT", + "peerDependencies": { + "date-fns": "^3.0.0 || ^4.0.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/evtd": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/evtd/-/evtd-0.2.4.tgz", + "integrity": "sha512-qaeGN5bx63s/AXgQo8gj6fBkxge+OoLddLniox5qtLAEY5HSnuSlISXVPxnSae1dWblvTh4/HoMIB+mbMsvZzw==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/highlight.js": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.12.0.tgz", + "integrity": "sha512-nbfWpyRMcMrPMmDwJB+dhX/eiaPKtc2RB+0QZskqJ3WjRA/FDS0e9hZrx8EC/lbEv8gXy98FcDbNa/dspAaJMg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/naive-ui": { + "version": "2.45.3", + "resolved": "https://registry.npmjs.org/naive-ui/-/naive-ui-2.45.3.tgz", + "integrity": "sha512-D83gJ+6zUdkQ5DuUbmlfyaM1t8pV5WMPa6KmL9mByqQuiL12qBdA0n/iEIth4dQw1x/duNGpOS2anpt5FKO1kw==", + "license": "MIT", + "dependencies": { + "@css-render/plugin-bem": "^0.15.14", + "@css-render/vue3-ssr": "^0.15.14", + "@types/lodash": "^4.17.20", + "@types/lodash-es": "^4.17.12", + "async-validator": "^4.2.5", + "css-render": "^0.15.14", + "csstype": "^3.1.3", + "date-fns": "^4.1.0", + "date-fns-tz": "^3.2.0", + "evtd": "^0.2.4", + "highlight.js": "^11.8.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "seemly": "^0.3.10", + "treemate": "^0.3.11", + "vdirs": "^0.1.8", + "vooks": "^0.2.12", + "vueuc": "^0.4.66" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.7" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.5.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.64.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.64.0.tgz", + "integrity": "sha512-gjA/kFeDfffII8COoPsvLPvE16hWDTEc1+0QKQNKUkuV8iJjqsC4xXs8/FzuL0v9EV39kuxTbAgcFoTRyy2TGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.64.0", + "@rollup/rollup-android-arm64": "4.64.0", + "@rollup/rollup-darwin-arm64": "4.64.0", + "@rollup/rollup-darwin-x64": "4.64.0", + "@rollup/rollup-freebsd-arm64": "4.64.0", + "@rollup/rollup-freebsd-x64": "4.64.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.64.0", + "@rollup/rollup-linux-arm-musleabihf": "4.64.0", + "@rollup/rollup-linux-arm64-gnu": "4.64.0", + "@rollup/rollup-linux-arm64-musl": "4.64.0", + "@rollup/rollup-linux-loong64-gnu": "4.64.0", + "@rollup/rollup-linux-loong64-musl": "4.64.0", + "@rollup/rollup-linux-ppc64-gnu": "4.64.0", + "@rollup/rollup-linux-ppc64-musl": "4.64.0", + "@rollup/rollup-linux-riscv64-gnu": "4.64.0", + "@rollup/rollup-linux-riscv64-musl": "4.64.0", + "@rollup/rollup-linux-s390x-gnu": "4.64.0", + "@rollup/rollup-linux-x64-gnu": "4.64.0", + "@rollup/rollup-linux-x64-musl": "4.64.0", + "@rollup/rollup-openbsd-x64": "4.64.0", + "@rollup/rollup-openharmony-arm64": "4.64.0", + "@rollup/rollup-win32-arm64-msvc": "4.64.0", + "@rollup/rollup-win32-ia32-msvc": "4.64.0", + "@rollup/rollup-win32-x64-gnu": "4.64.0", + "@rollup/rollup-win32-x64-msvc": "4.64.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/seemly": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/seemly/-/seemly-0.3.10.tgz", + "integrity": "sha512-2+SMxtG1PcsL0uyhkumlOU6Qo9TAQ/WyH7tthnPIOQB05/12jz9naq6GZ6iZ6ApVsO3rr2gsnTf3++OV63kE1Q==", + "license": "MIT" + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/treemate": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/treemate/-/treemate-0.3.11.tgz", + "integrity": "sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vdirs": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/vdirs/-/vdirs-0.1.8.tgz", + "integrity": "sha512-H9V1zGRLQZg9b+GdMk8MXDN2Lva0zx72MPahDKc30v+DtwKjfyOSXWRIX4t2mhDubM1H09gPhWeth/BJWPHGUw==", + "license": "MIT", + "dependencies": { + "evtd": "^0.2.2" + }, + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vooks": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/vooks/-/vooks-0.2.12.tgz", + "integrity": "sha512-iox0I3RZzxtKlcgYaStQYKEzWWGAduMmq+jS7OrNdQo1FgGfPMubGL3uGHOU9n97NIvfFDBGnpSvkWyb/NSn/Q==", + "license": "MIT", + "dependencies": { + "evtd": "^0.2.2" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vscode-uri": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.2.0.tgz", + "integrity": "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.43", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.43.tgz", + "integrity": "sha512-o5qZoksdnjIKvW1srZ3ab7pcDNYAerBjRe54D0LBLfRdCYFrSgBHVXokMas35czQc0//lmx4/tuY4ZNQ+Rf2Ng==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.43", + "@vue/compiler-sfc": "3.5.43", + "@vue/runtime-dom": "3.5.43", + "@vue/server-renderer": "3.5.43", + "@vue/shared": "3.5.43" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue-tsc": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.12.tgz", + "integrity": "sha512-kbpaz18O9jPVkXVHD1BEjpUXM6WhXSu+62L1sGGr6Wbzd8GbvkBQWjoj3KZ/C0aq2MZllce7UdtWzwCIYsBMGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.28", + "@vue/language-core": "3.3.12" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/vueuc": { + "version": "0.4.66", + "resolved": "https://registry.npmjs.org/vueuc/-/vueuc-0.4.66.tgz", + "integrity": "sha512-hHrZl+0AsrUvqYPBXSXQ2QaSrO3razjeUseLAhJWlOS/Hdfw/Y2MU05fOTP1qn1A3121gMxsHLaEz2V6CkWdTg==", + "license": "MIT", + "dependencies": { + "@css-render/vue3-ssr": "^0.15.10", + "@juggle/resize-observer": "^3.3.1", + "css-render": "^0.15.10", + "evtd": "^0.2.4", + "seemly": "^0.3.6", + "vdirs": "^0.1.4", + "vooks": "^0.2.4" + }, + "peerDependencies": { + "vue": "^3.0.11" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..30ac7ce --- /dev/null +++ b/web/package.json @@ -0,0 +1 @@ +{"name":"bluebird-web","version":"0.1.0","private":true,"type":"module","scripts":{"dev":"vite --host 127.0.0.1","build":"vue-tsc --noEmit && vite build","test:e2e":"playwright test"},"dependencies":{"vue":"^3.5.0","vue-router":"^4.5.0","naive-ui":"^2.43.0","pinia":"^3.0.0","qrcode":"^1.5.4"},"devDependencies":{"@vitejs/plugin-vue":"^6.0.0","typescript":"^5.9.0","vite":"^7.1.0","vue-tsc":"^3.0.0","@types/qrcode":"^1.5.5","@playwright/test":"^1.55.0","@types/node":"^24.0.0"}} diff --git a/web/playwright.config.ts b/web/playwright.config.ts new file mode 100644 index 0000000..dbcec13 --- /dev/null +++ b/web/playwright.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from '@playwright/test' +import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +const root = mkdtempSync(join(tmpdir(), 'bluebird-browser-')) +const config = join(root, 'config.toml') +writeFileSync(config, readFileSync('../deploy/config.example.toml','utf8').replaceAll('127.0.0.1:8080','127.0.0.1:18981').replace('http://localhost:8080','http://127.0.0.1:18981').replace('min_free_bytes = 1073741824','min_free_bytes = 0')) +export default defineConfig({ testDir: './tests', timeout: 30_000, workers: 1, use: { baseURL:'http://127.0.0.1:18981', headless: true, trace: 'retain-on-failure' }, webServer: { command: `BLUEBIRD_ADMIN_PASSWORD=e2e-secret-password ../build/bluebird init --config ${config} && ../build/bluebird serve --config ${config}`, url: 'http://127.0.0.1:18981/api/v1/health', reuseExistingServer:false, timeout:30_000 } }) diff --git a/web/public/apple-touch-icon.png b/web/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3b7812b2522fc57ccad4140ac88d8c56b5254e34 GIT binary patch literal 5813 zcmV;m7E0-fP)Emb!F>T#R7P=m=-`gKGvoSviYVY?06_s2NDy!)K9MDYfcO-6f}*0h zfD4EMioiICpn!vdC<=-Wm>>jU2uqTB=kHE4-ASj@OLccGr@p#fb(gyL-2eP_`)+lw z4>mfQ^fnL89cW&aop1KY$uo!Mv5raIlnAlSXf>WSr1VA2M5I=b8NkqyH*_BvW_S zt-x|gY?5U5VtK3OlCCUjI+;L#Iu>(@m}l#nSSB3ki~_Sxs$~(5r6`F?BIlflb&{zm zVp^mqC5<*F1Tq&$O0Slp|L1c`u{PDjP6?s22b$*yk(Wv)l*pOmk?m3r5<9mPA2-c2 z+onQ5O9?H#6-PM@I}$ zi(}k`W|y4Qmq}*rSFQzI^-c@{A$1Giek^U$&pa*O7~&#y_8{{}pNZEbksL94Dgr4( zKuCQ|RucYz!{kRqOH23~#k(96rDA?~ zlo0Br2&4@GsrDXe_%I<_f;AFa-ULnZb@~0D&7SxfwC($b>F1>%zcT=;0j% zoRm`^#(*4+Aag`Q=kCTJscM`OA-p;%0vSg@svfO!O7UPML8=kDalUz&1k4kory^iy z1o95Q->lZYt48R9OgsPs4eXo~l}!7y0{-zjxa*AbT)y$Rl=xM5wIl!(v8eBT?g$pBe9iXSwovRh&p%3W2B48H;SW0K_#yz3(gb^AhxL#5+gM9Pz3CO zfK)pw3EkVQCoRSE>>-v)p$H_5fK;1J!3#YYv+UpF`^d7q$R;T?`Dk;@^h$ zyV?RboB>=U{>>U?LOOe<>1jd-Rgxtr{INCgW_Msh2jJw~pig?Q$2S4)lNao{yMPq~ zfo}_d8Qp-tUI+9SLI(})qizN!-2uEI{{N7FnkD^#$F2vi;-VDk728&+i7IU+kEOzV z{$qU6`J^Z0rZa&>QpZjwv2Q;I_@EE4EDv~CszBGB4jdilXBRE9fj_qcUhEur8CZ%p ztQ%kc-4NQS>*gdmnU9a1k) z4SBUIz+N6pBDHxWt|m0GU$Bn9$(hCtfx?@B52adg_OZ#u^#`e|&+Y}VzMNc4u|Iw3 zNx;YbfHRJc{TZi*bU&fLu}(ECS?35D2VD)Y^o}>?-j@QCI|2<4iucLn!?W7N2Ii?Q zU$nyYgkJsw@Y|l`2@#Be)sDWG0VGE}f8zyz0ES$Hj1IxG8*rxdU@;OK9C>WRnLA_x9STZzYD-zPIk=(M5oxAj2r&eS9qtb(w|f&(<6sc?w>=>yx@ zg!*Luo?V8QpOSewtd_tNgf5f5=;@zZ4PAJAceVwdml3{TgYTfMKm)Kr1{#3jlGSW` zMC$c5`#RId6NIK}CNBZXq(062ofb9{8DG$F*m;IDy4d)7=qli(+@Oy=uau7?qxO_8 zF{wO5=*_g6s<1Tm5^s^iy*fo0Fcbjyo0caehcD;>}c?o*^^=etI!r znt{njuY5+A)R=S*nk1WpmeN49a@ajtd6IqJQ-tPp()ZT}21V9x0rr+93!gil|I_bM zosaaz?HheB{SOcJr4;cLcUDTX&95AaV{2vUQBMn=pA|fhvs_2Q! zy?Y}0Od6Lis_p={?J<=%TpiBN+ zbWszEKzkWQILQ@s&c-!XI#)M8kMrKHovsMFFjtABk zhd6rdmTLn(u>fd26u3uTaISM=cLVBg)q~FKevuMsY@kG<-7DtvO+c^d zz*)nAk@HaFqL+5WLkS(C6+fv%?7ZDq4zSL6?f(K_3&H8;M#GthuYL}+mny||lGhol z0DfTXw7e>zt7wd*4lSjF-YcZkx?~LS;0$M#SV8$~y8cuGJoY|t#$!OA*MYA#*A&tj z?n>wy^dz;9h(6Pn1LuqY2D}OUuq|Q`$2iXUDv*~a7Xn;1%`t*U5mX5s0UVan?D?pP z)ckvX;ZvY(A@JvEz#A)_Il+mG@I=Ud`kgFHQ3)LdOinN5$XJvhU~kC1uL9?e1lkM( z@}%UhsOe3J7EuQL*yznbvMEhc2^|IAi5&pWU5ItruU-3qJKG|mb?>5xkuon1lB6v1 z3`MMjt^s(ij?W1>^yan@Tm+q8xzHZTaB=aO-K24KbR=KJY)a@5O_GqD`^(RfLY%{H zS*Kpo514YNjG!GQWUecX!tT;r;IPk@7JnO-E!S9-(3PrWiTrL)pxL374pA=b1>xsg z{G#KN{wXDM6e$w-;(*EAwTVA(=O;Z+==ybJVBvazb>_NqAFr~Gh~Wi@H_lXtU+@jU z?+8*EZI9D@Dr-XM(_0)AS^vx#;&(w*jwp1&(c86%u#bxtqb9 zFWs9*&k>q?0&sVk zoOJc<5;x~i2DKcUy%+@Nras)LXZ2wCw_rHVW7UhQ5Y77<_|f4Ls;cIxs@;_(H239u z=62xAApo~hYgE5#w!LZp>umSu+ckt{>C4S|Rz3u9=hU0qxLLZ!1ocT!GZbZ+wwV55{e ztYXklO=JY}-U3QJvTU&J2+h@bxPuf&@Hm5wMCP`>>_4Ttw(kbMEe7}@=XI?E)eA0- zOFuQ$5#U!TA4p@cx{q^Q;*r#Lgy!UU&NX0PDTilC1Q%sM-cj+ivkmg4z8C;sGN~D z7f|FE>CTYWdMRyBXwJRhp49C9us-`vk12kB9`O+TYZG7dRA9-if_DMV=Y4Mt{2Tmm)G%76IVf}AYe2{60XMBd!9M0k<^b1E z@LoT2G=0<0R)pq;J^Z5L-!GIv;Sk41Faxd&jqokZme!-n1Q zL!`Ph7GRemyCM0pLd1vFF%|^E!YHA`;HYk4Re;qJ4lCdP25_@9D7jjHvw^_LQmXQv zJ4f9x5ny98Xs#L$|I5&_0JvKE(OH#Xm4*#Fe!NgUC3Ud^0VQ-2D00}Dr8ResTDk#X zcVN(b@iV}eA&Kbq(F71sLMH&4K4M=4l+gCojY^gP0!ruvK+{L;i+~c^zPd?XG7rX} zg!Z7)-JE45w41f-!MrJ%n|$mfeVeaM@=X*yF~$)siRm zb|V4?O1RvG655T*^x+Z|s-B}2b%RZ@hYzz}yEsB6c5g?a+!e}M5LlvlEha*5P`-=BqtvGAf zoq(XHgAp*XxA}6&gzc{31qAMZkFom?$cK+*n1y{ePD9(pJOPDCJty9Wr(B3pVp);TdhDzkSHAZN+lAOZ&nO$P(eY6pQ( z;}`_0ayE?6`}Se1)TOsOCO0V)R3ml*0#Z7c6g9+%P+7wWT{6}vk?xNtLj&l&B4Aep z3}74zjIdGeFhWBPmeMlpQYyE4DgyRJz(nbO17oTRSdGvurKQ@TrF2!K)NVHf3b&6m zc2pIr8ln9G8e*tie-xvqB49fNr0%>)>dwO=2^Nvi#RbN$3Lrny1L~#-WEuhCW*-z7 z`w-!XNN9%JI?|X96Z3f=YV=+a$Rq+%I?w&J(0HpR8EPanAJ|upZW2KW@6{**nMQzI z++U6!(d39q=#tUK4^q6}9W4U&Qv}k5fOJ-LCMTn%7nRU_kj1-1G>-Rb6oE`3Kn`v# zG~TH#{aOjl2#U93fYhg#@m`H0kZuHo%*%@#pnojs#zbgLGWPHD;Wi=lhFBuiFs?wr zE}7W4zYMpcz$iluM{I*ESigSgF6l?zo{&#XL+MOKE(z6lO14(R&dqI_V>Hh(z0-Fg8hMhlx3LZ#kNaS7$N*Cx1(8 z#Rwi^)6@0{RO)JV1@MfRjxx}8JasB&D0Q5P3}l4Rp&8vV%Ge;K_6uUV%iq#z!sf%u zO#Hs00^Q_(oPai0%nG^Z+>zqYXZ1cU2ow|hN*&CT>ytv>?q%g@=c_;?(c2kYM;g7v zyetE48DA;j{{a91|NlK{!!!T@00v1!K~w_(ENXXL8)e7v00000NkvXXu0mjf>ffu~ literal 0 HcmV?d00001 diff --git a/web/public/favicon.ico b/web/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..74c817352db533545b7ba961cf5c875ec9f8b2ad GIT binary patch literal 2836 zcmaJ@XH*l&8cj$jLV!dFd}81EZfdn}BrSvG0GQ?JzWNQ2AJ+?pClkD{UmP5R{O86}exOFa% z=9%+!RjoaRGJ9P%+klCsW2?hbSwWqlA9yxltjL(dbVYs$tvTqFz&IJ&^FitNs(Jsm z3%vV+YocPC;f#!4+p=1_T;ESYardV!It@4yt7Qj;#+Xta4vH3=T_co|UeRO-DHUfw zV{yoN@XpYgzFQovMAz{4&fObdVrqCZy~gDQyEtZo-bw$E{V%aQCD!$1S8s=hB-ajs_yEI2R7 zSWO0Jw8u8jgifU^zTX>s=-8?LAt$pqXxy{%j*RW7RxaIPeEA276M6vP{G7ERMV#7n zy1%;(0&IqbytOWpN~0nGl!1kQg)SQZKgbfKZVUcHR$yr80RRBH`irdYOsuhuCA<&k zS6mjal&iF%XDNEv0ma)T;w3zFZb;xgZHpy7d)$Z@O>$~yWiQDn{KV>#1s{DyV=4_9 zsw|eMWpLtoDKClKzE4VK?dwq9w+eAy2%!`_Bx#!P_)M1l^@ zUd)9B0h__3nH}A=bz+ZN z#^)1|mCjt)ZTWDy^S>m&J6(U;)$Ybk)6+z7}a2$_CG_c0|6c^hQ7e_@1K?v3(nLr|V4P4ebYa z{oRooDhvxSp{cPqi%UZN3jmB=*YNzcrKkiEy}#`J`kef1b^8jhshpD2;40O8EV4|8 zG4Ed`$C~OYw0x}C&eHbHel*j!BZq8Rbetg?)n||*_Pv@hQlEU+pIiXnSnBpEtEh>^ z>l{=E!P5C-9oV)``PfFFepmFYDpqtPYlH5jv?>^r_+(-HAmM3LY&+KzEV(~-rjcIeSli)V_%Gc=r&I!i3^b5Ek5lgs7AOpsDQO@#QM zYKSkhO+mDq!Fxi!a^6 zNUolz?CSPC^!9=5ZD+~bULn&LgKb%P-*!~cs61rNaMKG{edJdY8@utPns>)e&x-$# zVc_stYR}AUhW5&LwSk}3LFflOe`FHNBBaW9C8PIp`eCGdec(U*o6ZX-V*%{7Vz&*Y zwO-X-j1MqVVIIT0H%xI#;@g?N5;e_YSA8*s{HpCKdfEw{=7BYCQ$y93vQzQVqjm)?-QWS!Or zMBAJJA>hkoT?WCYja6)HsCi*T3I3BOfe1F`wefVfbj4445}tD@G7{abg?J!z#_SGT z7Y&3NrVS*yXzN-F_HW)iIbJwu4KW>CA5=$D76MoEc5~G!b4XXVC1tr;i1DCOO2U@$ zu)M8sW{HoQm4e}&`qyTI^0Nl~wh)*g*;vr$CAZX*o0~w+6*5scP$p?q__Soc2=~~f zgG+if0e*>2=%D;|=MtwO+PFR&n^Oie8tiV%G{&4iyijD;qaZpsbb*#lzujKmJtSkv z`W*pR=7OQHKkH}7er$^_SvMNjMH_Rh%e%k%N@|ceMJ%XFDFGW0igA6QvPWRIJEchh zrs$FVHIGEPwJh$$F{AeSez?c{eZr;qxh?*A$kj`B&AS25JRmMfde3ii^yD>lLTaqN zIT%E;0l#-K_myTDtj^GJ#J*|njYk(^Ojol<6c^P9wT)vLExR$pAArsWLOm785oFVA zBN^$?&-^i~Ja%*7(iA*xb!h1Qy&afxWU0rr^wDOt4Z_Q#YeeGyM!c8Hx`MsX38ojG zrOHA_xf}LkRxvrZJqWY3fg8DM)lMGpmteA+rw4^LJbu}|U=imRC`bvnmj&-(I-674 zUEU$WZabdXKfKG<_=r|29LqfA$VkEL;NKR+{3-HtSs8*4%a+5mycY=Fnq^bcWZ3s( znP}YYGuPJh))b8)ZXrIi^!rP8nXM={D@l=oJt$-Cb9=NH&U>Wa#*)^48v&UPI^ni>NMrU)+A6TbQVi zF8H`#cWl1yr)1;ZO0A*krOIRAH{X*J-zNfgja32l2|{LXq8uyscL`c6nsJeB!9h_3Z@U5a;EYMQTlRK=OzZ{pOT%tA15zgYrAOFQ@(d0V#*mS^2 zIA*q=6YVBzc7xSgS*Q1eaHO?3G<>uY(nw*RuKf7;{ypjqWKFUOs>g3}vubl^nsY+A zQ@y8z6njS>uibl~biP<|6VcYTf*-D98{AGXhBm=kVhQ7wu7(h-YSlN*ewjFTfsUWT zS>LNuAJJyuXqqkX6|b%dBpk3IpnO03)TTpdYIRSw15P@u{m-l_4fbYHVyTaUM>Ril zRTwJom&UX_QP_YO12@ehEG@ZZr}umgJZ|QaM?kHrFzh3l%ArYHcK``G{ya3}>T;4aqfn=V_|BO*J;IKN!Y2Z%p(e^yr(S z?WKK#2lghauFxh5BSDiz89J<;Iu1gE{a-e@%JTGSUNV$J=SNpBZTbN)9P zs=FNG69E|%`MPf^qg=v@!p&k2+VWmWa`ddg4{=m)JwM-Sv&2&u|Jw4nc6)9A9*bhq5uE@ literal 0 HcmV?d00001 diff --git a/web/public/favicon.svg b/web/public/favicon.svg new file mode 100644 index 0000000..7f3864a --- /dev/null +++ b/web/public/favicon.svg @@ -0,0 +1,9 @@ + + 蓝鸟 + + + + + + + diff --git a/web/public/icon-192.png b/web/public/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..5d4f28f39861940c1cd37d1c70bab308dc0722c2 GIT binary patch literal 6277 zcmWlecR1Bw7{I^x>*8MX;@aZ6nc0NwYmacPLPjDhv&_tU?NLT3WoP!26+c_PWhA5< zWs_u=z03Id{c+BDpEKU~ectCh&v`%ZNi;FmrKRGa0sw$kUr)>IVub#02#Sl|Y}%6& z08oIwmbyhS?6(~<7u~ae780mHZsCxa$f2O6eOl?D1>hUou^&NQbTcWBbU1B|vCl9G6F>uG1teMcL3iDY`?YiY*R(iOza1 z2G8~*FO+|Tcq=Zs)c{ywtDE$`+2(Sq;~*&T>nl`A0}x#H5MBAo@5`yAt-G9`OmZ~} zmw!O1wYkGRIGkhcbpGM$H)Qh-BQ8FPc4{o|d0V ze>`c~(Q^BtQzmP)iNVO#b~OKJKu*RrYwil$+0O!I?278rjV96tK^m$uR*|R7gmKJ5aHLugnTI~3$GwkZ_DJKC8g+_@sd_K`? zVA)pG+ZH7o1j(^vqPhaUtCf2nd-wA3x2^%ik%ipkQ?}p1lN??{DV};^E!tfM2KAsQ zv%$efF@X*nhCUkDh5gK@7p>h7CL_yxLYUk#>ZgnC0vJJlh)jrUTz40vcm6eA(E>2{k*8#IbrT zp`q*GDQAABW$Fo^PU&(IArz5d#FAuc>|uV7JM%I60p8`E^-q?kDk4QWNh6vJTUgMD z+E96{wR>97{D(9aO5tBRh>gv@40n6(-pygm;EN>6#X_$yRZ9l-@tr zmb5=YOR8w`n@f(xv9Ufu;IVJc*Xn@Ln^)lbFI*YYns5VUQ_9dX{i^2Dv|il?B*2OSsib zVGid5qs&e^7}cW%L;ZHs&&;szylLHy9t?$EuZ}}Metq}Lq2%vBvPAv)7 zf#~*9#TscmEbM$ox|V4@>+52Fp;M*K!g z(Fnl`3na0)snl$NOn`)EV<=qz#ff?JVoccvVdVp?a6+JFXA}{4vjsJk>LzG)GtGSt z!|PC}&!>0%rCkV|z!t*$Sfprqgy5L~(X8A>z~(}jz?S_Wi^UNTD4`BFmz@{_BLnG~#$y(@MB+p!A))I|#L)3mBg1R&b%<_Oa z1z9-{qQQHEZl$sO9|k!#A_+D3Gl@;-69??WI_SF=AIx>PN*jH_V2(8pt+E z#`cwYrA^+KN0+&-kJw&UM zzZi|5u_3`%8Gw5Uh#&Z2Za@EGn)sscD_8%FMe_aA-}KWo;|CTz-s17{wo_YO{OytaC9&O>;~((*yyar>2F*Y|gKy6~A# zOG3T)NY3TmBJb4bnPC+}$LI4+;oeXL#ObKqf=-?^czzbkK*(8qAour;*-Twk`1ld8 z01s2)ovpJG7tn-CwZmG}vEIE=J2CGT%ugKF?hqW7QTOK5BR%n$Dw_Hd>}eP*Y(HA& zpT(n>4|n)B(|*)aaZCw_g!6HqwxGlk|rdu6~Xv_&#*)Y4PoLnw2J> zwIdt-c+RP%j=LxK6O~wD^4DvEPn7#Qi})a!&1;orjo;OLS$r4M_sjjhv(JX?2pT9Z zpbGJbKU5m}088}EDt(KV)8*1|!nWYIb(!WP$+#i!wXkZFYd)i&@N*KQ!=#x7eahcJ6^9o? z?uB^Pxy@z3bNk2L-1p$}@EfJ2fe8AmKAu6ZXA>nF;8sogx0Igtpx<$ik@I{wQ`-Mz z|MQtRSwvg0x$re6i8}Tx`!H9>Ahl5vBY~89O1b64|N7y3pD+U+SGy^eq25i4-{paR zS6mK%(#pa{J%No#*|L~|Ch*)?+Cwx@cKW;L-hq!dCHRxzxvL7?K*_?!ae?XDJ! zvx9;`MoT%7YHj8;DzMS(3~ssTSh}f8*~j8r_Ag##1d2cs+q7z2%{N~7PlT?sK9@;y%}ErNC0p}CV|Pu2VT>l$=erWoV>&}={o9i< zK*$S5STbVxZol<|!(2N2rvN>HW=0H*S#zk#J3~T2&KqVN3RvxCE@yD`A-F*V=2FG1jD zn01#-cVx@#+SeXj%{IhN4>P704g&A-oEDXhw=YQ^9eY0y14AzztnM+0f}=gAP9EV4FzILn!m-MekLT z#o<*l0d}G47x0f(n8OhvI&R8b73N^)(qQV_s^13qGAf0lMUIv(u3`Dnk|GoDYzG;X|D>81e5^XY@iF`J$sF2Hjl$rL!Qp{Wz>M3oeSlQ5i)%9?DZCIjR zD62zS7$yy6a4y`}TqldPQSz-xF>7E)deBRyB-ey9&$XV*NPs79CxZhbbTwJsSEmEH z1qf55?%`$bThnxj__J*Mx{r8_QV6gWC@gv8fH8)v9xu~TDjxfHAya<*V5^vHp`Eqn z3$}hEp_{w&L8U?3nv_(@|LAX|l}vHmKH*=^pWapL8T{X+Iu;-u?D3GMz^X9pNnyAC zC8wTjg;k}Gi<$Oq8;HyS&UX?rFV_YJJy%cu*}Ro@k_8|q7g%0O+=kJl&l|okCFOb_ zO-wl*ty{z}7E&4AwAxfrx#_<;UEMEJqQ>>7R+b}WFg_m;-SEvJ_4Gjcl)+l7;>l9Z z=>y<`)UgQ@p_04v0l#n8HelnolhEdx5wa^2bMLL@3IY;~6kj11&2kxNGmiC{TFG-H zbx`LeP2_CH#Q{oklu4z;$pF|jEGcE59okEG(9h|O(NazRkboE|QJ#ldi zu)IW`hM2t(-}dA_V_O^Jg4#aBKK~42t@1X*|paRxxUwtXtzI>wO0C=5Kz99#RIp34L=Su)B+bJxR3e z25(^RqmfDr$`79g(wQj?wWM)rh4Q-sP-b=fnmhA?%)uT-*Y*8H`3 zwu!guj&nKB@^*zF0q30JgK+zlVT|xak4Kv7pG9$->QoT z4%!*mlx1_LxaILSXBd+PkHJD`;9KrPEHtV!f}M1~+&`DAh9<}mi8(g8((KJ^ujby;jyoAo|1pHm zhjP0{8Ci7&KLzj-l+ByyCjOUZ>lUP9A8_UaI6W(P-zqk5bwR1xQVkX|Lp6Q+;H~+A zPe?vLVI=L%5VRitXPUhI`1!~y1x~dlp?Quo!E@%L2YL1G6PMbiwqu_H_vfs2SJaT# z&V2w~o!-;atpPiC9m9&;%8R}1XHAx_Er!;|~tHnno| z4pQEK%m_Jt-MdAd6TF4+3Lq%hV(^4ExFFPWy{W!uCFQtIIyiKd@JmrqC+Rvj<(V>l^M23Qlnj$2i_P#h zl)=U(_LS2zao%RhW%ba(G~Q#kEwh8fUkUFsU7RL+RFci>yoaO)j0f?H*O0OJfQ`;9 zcS`#d?Ap38$^-!ztLo&q!g zqRjO|ORx^}4^+2D%Oj}!vpti+qA7eQ>Vn9+ubMo6 z)_s*h8}dM~S$&Xo$pY-nAN6POzzr>Q44D*@(SMMaL4g<`uASn2_QOy07pUecLXtlw zYM7hCmE$3KQLt(&*Xl|dHjNTqa@2{9LSzCD0h^314Cweb-OH4pKzXAWy9k3#M%-N& z5Y1?45VpXPE?Xchh7T?`4>;hrc1)aAzCZuAryNhnJZBv_n4p5Y)7vd5Aa7hyqB@+$ z0k+`8fVuZxk$AzXX&8(T@TFXe;FhW|m6)U)XN>pCNtogT+?yzq@iLh!LiQcHTe*0=8z zdcRS}eO;u5Jv=&d;8@AKZIF>%4?RQ`8uhX7H=5xtq69$7vx5^)7h z0O80g+Zmdf*sN~@Mx@($SOeM@+rLI4v*^8IJulL+k_z{;tPf^kQs7=J5 zQp9)+f$c2L3&PdaF`#Usjd=bdO!(6*^vb-alESs-*8a8pM|?FEOTxexh>GDkiQ?ZM zOS#AiMWN*pe*dSiu_Ii0+YS&bs?Cms{>43gIJAZPI34fwG=o^-9^3K~vBQ`Q69UT4gl& z?mnB7n!={YM#29ztu+j$H1^m9Ett@8QgHRPA#vO|4<{FT4Nu?zsge1tL7BB}Iqj3& z&uFf9l<5GcoQm*nipB&nWy74bmqY2p)oStun1w80`w_D3*~iLtnvcFeZZfIBW0V#q zuiOubAtT-}Q{xicz|;|Y9jP_ln7r@KFoRzQD=fTOkO<5EoT3pIvP%n-{PQkU!VSVV z2#EKlr325+ShVL?&{`?7n$V zhiJ{fBDhdKKl-)Z^1LmYkagw5kDQ~-);j;YQLR_91W$mqwAK>iQqM^G0MD79={4st o060JQ$68rkd?rx*pE-~L>3QG7z111*E@l9IZDXx!4Tq@z0N^5dtpET3 literal 0 HcmV?d00001 diff --git a/web/public/icon-512.png b/web/public/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..3375c1fb039c1fda6dc98b2f94cc65701e775cdc GIT binary patch literal 21105 zcmY&=bzGC(`|vgf64GVSAstF6ITT3=L0U=#q@|JCrl2Ac(hW+eAYGE1A|RpYq>+%6 zZWyrm-2lPzEx3yIUz<=b~|Ggz=ef?ojQ2zL&$2Qk_C9%*SYqs?Z z>%+LX74L5>_oXg9y+Y)|)}iaEoHh=3JihYn;XE_f$LJ=X-Fw&vcPl@tn}d$EuB?n@ zg?e3!njqH#-Qzj)8`su$z;;dK-fsfqejOZRyh;#kf;;@YbSOi)o8&)RD3|?E?|Szd z{aMJW=kGmaP2{+j-L{!o-rQ<9RY+_6_uL^)-nK zLt$e+Z#`@!c0FPRD1W;L4-_qn3!n4Q{>(}CEVAUFt~+HeE$TX_B$`xv>K$p$(y(`_|Ab0MBX-Bc`W`1dfjOeN*w#GB2$6dR z+w+&+Ozgk~k2)2GDGq7fErlX0-ZO!r3=g&J4&~f#HmUa3JGPMF$LhSwEpI#`pzZ%k z@Iz@UH5wDxH6T;eR0i2}SfoT17*4Mp9mNG@faTdi>cTGGr~0f?H#Wt@^u*myf(iz~ zqY=5FD<<3sSQ%7{60=g~I?fL=dHxOFC(oNT8St3H{kKE1^CWBo|Y=T_Zym5vWX{&=`c=0fwV-$orB#8IbmDJdz1ZK&giVCD6ye^x(!y6!&x z3r&;Y90uCVjvq4OJBAg959vzvf0dyPrI(_MJ@ ztK^w~p7@niy*~^vU}KelRH^{v%t2l7M#ToxWPr11^U5s;2g|)D-8a$k_K*k$|6Y=Ts1zopNbk)p$!`60Bk8b?og9J1EFZ~({0 z2ZASy#{8sOR_A4=c+FsNI9%`*7%)9|M}@7rqlxI8uhbfIjg67jk5%@CfsA zTQHp5qX#63`y$QCV=B*T`O2iH6A1giu1{5lj61wzA>wxXCsgX@pBf{;77op)9B!m% zOiT&sTBAD0RI{4n@=d;Wv(3s%w&?(1MKJ{;4!iW7GD^}X-jL;a5r$n`;A?4~w0 zpplJ=@hOYV2fepQvH`O*MRupzJ3L>#u3|&Iy7;l2ea?c18KfF}_2DPa+K%jpO#z>t z*xn&nx||^~ARZCNQMZ6)S4qL9B^cIL-u-RzMouGO*GOf7UPa+vldYt(oA6l2fjjsP z31P|yx6qwTT*jg9T{^e#wBN~s+8tG-u5Z<6%kH25@kGx1=J6%CsgdMi?jJ5ORa6ST zgd~dQFK1A{28l_D-FxtpX{!fY)q`4Yoo5BE3qn$mw<_`5Iq2P?qncF#pHmY1^WMyG zWUg=b=pwGoN%nZnC=}w7-OU5O7~*QhH(uCSqKu+Khud?FaaT0|T_DLT z2*OA$_RpaYa1}x$<}CINQEbV5BvZ>6-wl1hTp{ik)*N2D z-|iOF+`ls?0Zv;;d-QWEsCBqH$oJ{GjD~e{wX`4*lZMz=U`(I-pi;^tOWqJBgDZJ{dUOUt-YGeZG($q!1Kztqu^*I??tH;_V1^hGU9T_6tlonCnLW;F3{Nsw-p7 zwE0vAxsnPRmL}rGck)kdD+0DgzLLFCPC-vY-)(vGN-}783KglAohZvceQefXdFPQ* zKfT;vjTNU~akZ`=oV$_;8um`u6-z4-2Uj#%wmDxvL83}>${~~1(~RyCo722V9h<@7 zBZ@5!MN^-rxBh4B7=+%U@1;=Qt4D7*uN3A+?Ekj~HVPqLJN`+JXwi)kt7IpSiJ!w&54 z4rLA7{n2dv`NgVN@WjmlYwmFWoDQ4sC+BeP+*NuFwF7(!$PHchpX!5<5>R&obinV< zUWt@{p#glp4hA5$Ygg`>BK6()uMM8EIs)=e(7RzOh#3JydJ`@Fh5k0Gna2j3q-_Qe zSRNI!d)U>%o%vR8o$CWYD;>TRLKZ1EpC01@g1n}|o>P2e{%Pmclgtnn z3kbvQ-;b$foPGaR$enPQ(oe+B;iCZ9+5gaz?5foTbLTpfBKSP~>4=RT#px1tLA&P= zITux)ijf3$7-?i%mC$$*=Wrfq+>Xn{}dhoqguhM)@D5X#9v<{2|0Pmiyi{`AWHVp92yP94+F zm=NU8BHhgAD<@tt1Vx-uMVkg|zT)7+w=0(f+M$zq`?}lw*vA65&0zlbDInj$XPgS1 z$M`p1s-Iqg0_}T>sFn@7b5>3B`Q*p?`9@|?Z5L|e^80$;6wOmKoyeVRC+Wzb)H~H9 zwu|BV)>H`8QN2#UVyl+TiJt`_)yW~NS2l(>N_P7#%peRA3L^2rL&o5q<1h#iVi|~a z^0b4mlwMm?a7*=pc=q=d_Z$vebFDW4%PX?;Cn|@4HA40T!%)F{kp3*t`&)eO`|wG> zOwH93$|RZOJr6jubn*7UUuCh~xgJ>{7NMtK*zTR~sSP?2aS&vLnX<-~$oGX%3pF){ z{1olasp@^30?!@f@)(As+rJ;6;Np-N`F%lI5Y$pMkfObFm+{n@Az(O2-H=K} z+g{V&UE??^n0VyQU8?_moPq3?ou^pZd>b|n*d@(~WvfEn;jgr(SM%Xz(7G9Xn1c3X zMx?}#6R$72eNiXzK+a@#w*J`Z{vUf`c~K&4mv+)yCkGC#1wnf^^m6%DitXl4{4WC54H>^gQQ;lF zH^rtTk~KDg_mVym?LAdits zuv>{c`9FAPjUKI0&{jaa;Jjnax;NW z%>p1icLpa$h9`Ky6_=1R#5)} zCvDIQUbTtHC;tw10szDQ?^;J+0?04m6*oRU>QoVYsJ{Lhjdj4?rb5h7Tq4G`W$C$0 znfzA^xN>5=zCA&UEr3JQL62~%G+{T-o-paFpj6LekS#!cLhcN)GTm6}gnNrLy-I`d zbPyDyGyR{k8nM2p-b(L0lw<~#Jd*x@j=-C|_0lrxHs}egho!!y=Ik-0kV=$aFV6Hj zgb9HE!PSQ+i|_sXpL;UAha`U&cTmg^68GI$LwyR<>p5o27H_qkhW!{VlFgnTZu>UEA4y5bu7?bJb4)Dcr;BwbpJ@J$E-0>-}qcmG>5KBC$ zV1Thd`dA(iH2iXUT-zm7h_5{VV~#LX#38&VFTX7k;?im)ldf@M8RE5(vro39Cp!r# z73@vKM2B~R|8ZcW5_Rhhn~o|A@2Tl*hD6o>=^&(}7~=nD5tB5~z*Z>+D#S@T2;&w< z&Mxm(PSS(pLHu3@F{e5r$PkfHatk@HBMuSKIA&+wEW|}dHAw)3#OvhyEFCKv5N7&8 zA&R(3CLbm{g~R@nco%`;0q)XXJbwvvbLu!$M!=$=hxJ%b1e0u9(J3P5ku{Lb%G{eE zmqlaA6Xpt2Jz}!c*|>X3mZY~}F0$KV?yj4At+CDOzR#3%I)nVa$+i)U~HoXkoZU& zZi*8^GO=-kwa#TN9WU;dh>zJz1U<;-dI32RA9bMW!!%cPg|rx1Mf0E#&kwR#q?$gV zIky^^2?4GAKz*z)E07TvRVrA(;S;S>(6<55(yk&mj;&M^EQJKRaDtod6f{r~)1u69 z{SS0033Q3-ixXRnREGkf?TeWH16@i2z07zT0CR&$pyNLr-!l&Y?FN9}JB)a70{VqD z06O5l$niae0IbIV(Cs!nr{Z%T!1~4cwCQ6~16Y42fv&cFaf}EEX%1k0f3EP+f1qng zpjXdNA0qnaDoNE4N*0E1*&=y$Hzs|XC zdZ8tzW`f~EV9zVZV+THK&E2zE-SFLKokgt9%u3Pg=Z$ThkdHjp4RHcws)-sN;-!Z% zKT;hfNTLO}Q-08|YeqV>kn-;}XYxwg&-@TyZ7A}aL1G-*3K%cy_J|D+V0uX+R3#R_ zIt*NQTs87#S&1^pT6XIqjn+;1q_vA8KMel=M)(ZCjVei-qGVg3iG%5Da42VPjDFz1-iM*N%@z{HMVvoMRE0t0T|Bzgkt? z?-i?|bDx8Buq$#URTC1AR}r}z(I@MDE>H2rdZ@KRnBwB~G0}~lQhrr+i_(=n`-tAClf+x#|U{pbb9r{ zg_#9vcUN5Jo+QStwtSQoIO*lvADVvI!CQu(wMmOS0>lTRgJ znHPVw6_{!2l#k5D;S+wPuJGVL+bB9~F23S2k>m=SW=nv7?*DaByS2>L=x}IEcVS)3 z+O@~F9<_sgwAgZI;K*&Sv}{@X=}_0LqPPFlx*s8%Z}Wp5FMfcuA3V6R0Nzm_WYV$< zvueZ}nzTN*t8EXqd^(iR@^47fGK6YzYPXmS|GQI1@{%Opv2O{nwmw~75qLl-gWH7c zb2*L^z9%h;AXAj_wDgRaCR@TKGW+izQ#w4-c`k2GEbJeR0gTt*L&0%P?=Z zhZ@;>{4ZAwgBG@Yybm448UJ}^i?!6P#V@g5#{YTUM3)#~9ivQUvBY)X z1+cYw-im|nhG+w5=%FfYS=LY2Ov%g2p-Cd02@BPMkyIvDX|9KRulKC18r$f~v9jl% zjd&mM-m7|i5Lsvp7gF|mE}}nTDRLu|dRsH~^|M*wvw&ponj-IqVPjV0onmc@v-AkV zQYHc5qpqhTaSG#YuWCECd6KJlO$`CoA~N2zoN5_Uu_i=)srXG&~gDn7e1S1a%f*feO}oSR(c7Fba{_`pwKYG=4iS3GbomG7`DP8gdW#J!D~( z|GxbeVlu}Zn{)}gbi4X-)^ia;UnLIn+ya}EPVOysQ>Ep@d5 zIc${KR&uSp8qt^7rk-o!zP3J(OR2M#eop&BdUjpBwEGR1sQ>a@$(w&?sb|IcnGnsp z=E&*qZI40L-bb=f9LGAJ9O!UiAPlzRI`a1WJ~Ka;g3cE65jHDyp=HdAu1iueY>WpZ zf5yM{tS_UM6`;tgoI>9C#|rd$C7xS7zYk3oIB_5D?ku0dCU4=cZW0)^QStg_g1xys z6&T(GTW^fPxh&~a&32Y=wk2Aits&>gsB3WxV82LW3^>77b*h(3`;KjGS&2eo7HY8y z6Lz!E|2e;foA=UI+}T6NcMBq$*O}TEzfadhK=ZDJr8lL_9w;HoDgqDncd_fw$Qv^9 z;5n1!weU^P_S^1_OP$x|j~w5vU%PGjC!zgX_q$#{@-1^kDi~)-DNK42-v^O(3N~iT zFgxtAF`C$?z+UE9Q+^xVf?L#^Dbog{9>v4o{K|-yw_qsYV?Y9i5c-1U*C({Nq)e;l z;85@O1Y*@RVXgA>q~uJd|3Y*7f~{`YDxo-3&wJwj<%u%r*7aN{cP`#E?Y*sAa;V7d zrwTIsaUo;@7AA+QbQ~AH{QgEBj!LXKT6)c{i=^xq11$JeR+?^1j?z#sboO(d z$%Eidv#Q-tYzJm>Ok0gR{65>G8R;>doAR$3cwfd@bChs*yB9f8!AwwPu;UA3DXbWC%8m%m1f!C`|q)0clQ!(9V-uCO;Uc9gh3ck z(z5;BkIb~`$a-)8Wskgma3%rXp;GGAw_U!nyUpwr8{N4`h0Qrv#vGpm=aXG{%mJwG zz76s|YV+}8br-p-A&1^($;v@0jd-;khv=<}fA-6|N2MHUYAlTD2>5gKttM?pf@%9V zDdG2;fWC4@nR8Piql^m_ZS>z3xer!q!50T_78Jyig($0z$z-xQ#&t8ROV=2DwFW({AwA_j8t8(}p7Cg`r{9P35?Nt75p4D8+8?4IXo5{*t zo5u>w?bs3a^TkTCf+A(x$DPIl(Zu$|8c=QP$w(Whe_Zza%I z)1r2sK5@ebkdpYBAENjF4XIdvHI*GKIyllcTNC%-`mDKQ30Y*ih-1&HuQWFo(;{{UB(M)7heiI8kxcLyR^F+vy^TkaT#HS<-HamE~24&TVLPo$(mn@ zhBTE4ipz0Fzh*Sf0iwmNKOy8Xcodg*VA4i6qSkL=DOwwdUBbeUIjMWmU0F>IeLa=x zDo~v6(=A4q^KZp8E^32b{)&rqYj2NZDbs!}`*4xG5H*^ExOEt4I}5n&XJ?nf&)a;D zL)VGw^hhM-21Usn0iJ(kJ>N(ps86*pbyC#vx0~zE+il&UHA(U772EAY7vJ6v{bjiD zOS=9JGGE@+Pj4{VC{M`oHHf9~GR<(TF4uDFo%Jvbe)$%BAQ7>wpbeQ{?(rtR-NQTe zjQ-6%h#plKO|Aiq1!lV6tQrBofJX7j*OC(^MiD%<@ zm8l_%@w+ek-zwD=UXxA)vAmO{8A_nPtdj%v8>o=)(9ONQv^22QyU&mAqr(0eD^ z^Vllgs%d#8J|0Hvf(&I^6tDJ7w; zA77pA;k&IVspaiWA5B~3eoy}c`1Dr3@aqfm@W;9fj_8GR znRbDf;arIGLgdHFPkuw97~S;%f`TujP%&CgRIA16!beC%pCXb?w4{C3`2~s44MiDZ6@|+&rYqRdz^8yajK+D~)0ldWvrL)pnDx zd6rT%*}F)NeL2#$S^E;otzxAUduQt9<-&9n zZG|#;L&Ph@WpF5zlNoZ0eM1ttr9PFQTrkHj5$u-eVSC4o4=m)`my&1QMiA6H6LsL&5ZN^D_(9v^ z!(q7UUdn6;1Sic5ab$UD0F{{3wcK=bWO-5iy;T zx)!f1#lu~?uh+$lEZKW)YQgf_lKf@!B|ykS>iIpI=58%cYZgA9Pm((v{1~G$Q+7Ut z(kU#`nKH>3U7qQ^{TU;Blt7uZEorAwLV8Ny5bA{Rc_k5cTX))$ojkrKxnR7r7_Ny@ z_c2Hz{Zo>Z7_npF7)#yZv!2Q<=0ZXpsgW-TbC!#`sViVhg;G`EtdxaY-Fv;9-`HYa=5ISAmk46Z=NA z+kxg8F9ofwIHn)1>c19PP+@?4t@?VdvwFN>p_`Ja6*1v1Q)LxT_Vr7ND2h^H%o={p2x!V4F0RP6Hn6x0VqS7wp~NH4*MD@l?M{dt1aCC>>1nC zjtLhU;5izp1x>NmI&3p2IC`d|YR;3uE6gtlSfMmKVL4UEyKw>Q%?IJr0!Gj+?uyiW2vsIaNF(GlWt`RI0W#_*bcTU-DjS z!3f4&RRTEk(*t4oKu8GQVk??*PT~48S$~DeoWnlJP8fY086>^wV@20wiF(G_*13y?T zhk#wd8~o@vYnAO>%M_>c&~AZ^zv_!CM+M0SA2=uBv)_GcQCg(}U?C$fObRD?W0M5B zR}`+KJvzUSx#`ubl-fy|WQOjGyS`P9c^ME&EU+rrR}s#+HDaj_F|6a1d>kS=ME?Gl z-875KJ5`TFo6i9Ls0z%U>SG8fKtNStI zobVKhkmf;Ib2=oBCUmF4?+1Qh9Cu_RctBpgHCdcvY1lfX4eB0Rs2PU3Dzn%{cbD@> z?Obr_UnRVvLQYfTe6wU$yfDzDAnOhKst5WUG=4iRlU5)*rcO_gQ zfcXm^dhi%%oZ0B&Uks$zDTz%9XfbbGuXS(Nl-Z<-2YvOrO>4ngF%D`CK}~GnmYkNe zdB(SHw73E9u8TCygCZQh*8cKDDtR3uPkM4vII4gVOa;5J)SrGHX<1JDQ+mokP&eEs zk4w@q&_$CY$5{`}JlUZwC`)a z#-)Z6#TKLLMGX!jVMWl;cvT{g%Wfs+2m4l}nYF*Z#X=$|sdm9-KNZr-j*g`WJ!qad zQ$8wTg7TTF92#pX7TzuUzD21nxiNo)@s`)>$-UbGUw-;;IqxeDKy4O9@1mv-iCH7x zSPGN;!o`^~s9@5VTFx54R1`su^V(=NE3}qK7_11y5q=P_GGQ(RziEz+2PM_7yF5Mv z1|o~wOjGSG+m$s>Ih@CrnHE>+!|0eHo77lVQORI+%ijroTtv0T?YW@k{GDl_YzTx| zF4|B?4piOnm1ppT+_yc6m7pf=(9OQosiUjfFTDSwxJRqc|P*FQpFhkicV7xb_4 zk%FA8BuF&(RHt#i>vMQ@o~@~WVPc{2dV-~DO!0$m;Y73vF@8|_=Hah{P&E@jyvUDQu>UlAe_6K>ooXE!56x z%pI!?zN&jb^|Zc)f7SOxO|N9Z&9bDj5YuVjKUYPQ_ct%j^o;FcE%m$jK{#4&!>6!6 zArL3J&#FUV1IC!}RBhduq&gIAE4p!mHUikNTnR@SG+5qtOJo3Q+Beb_7#d{B- zwq-#^7lLa=QbVi>shBqbpSa&n3k&8$LhtkEb0lt8Xn}ktkP*2b(fg?>Wg}*db9OFK z$gaV03y<8}2>}+h)n;jCEMSypguyKr1G!N(76};8d3c+$pSxnj2h-l99Tu!TX%1^# zpmZ4;a5a}Mx&;)Z%KmijLB%+Y$yqX~z!P}qw->3;tLySu-O6WpVQ#m~S`ZzO96UBP zX|r>$1t?URXHR;??lpA%IhumEPb6@~Y87d?YU@s2mpI3IU76gJ+_E?jrvXBiLF+8k z6aqWTY?4F!&_GRuwRLd!%AM9PQ(T2kdp*I^r7N9U5eF=QpLa$v`rm+zm5XK9C-#>- zCnlBmNsb3s_w21<3JW*eS0T{u%N0jU zq=c~6h76q;@pLGW6Z@fNLZ0;?j-HUZ#uIR)DUmr2!Oy_&2`V(<6ZgLXIorh3uUye+ zAlK?}A2a_IJvxC9UB%+o-TOZ0J-#5CKV!N2lo>MHOk0R&f+ z-~(95FHelxvRqu{_m`NPnmR}2ze6oj*Mz09^6P2!Oq`FedNDG4+37E}w49D`~< zLH2RgpFDI+9QzeZKvd|DoJykU=r_x{tU-*5Nz%aL&T-UT)OPAoEUB@XG z?ViKw?hRdY^IZ>j99&Sk$?Xcb_V>4hCD|b=Q#PMU{QeXzK3mQ`x8Vx~9yJuaXx8ILydo#z2Ni;GpS;~4;Qv3NFWX;eLVXQ)6$qJ~Y%hhLT{cxb$gk*2^B2mM`Z~XLMvP80OOdur!4zaj#dw1P)7%S~;|E2e zl>9ni)g}TE>n7G2eVqKk?%10YNecBZ=Ctv+g1eVVrD0$8`FV4@+}eU8xJYT8dF(~C ze}ya?fyBfSylr=@LH@6kNKOp0x_$`XCHr8TYhIjZ2))nx3D;#FE$Q-Crb!e)zU)C- zt|+oiKAux4ArRlNB=aQu)>OL;-tk9D4+rv_e!+He`!JXC@ z=lB?M(d}^^W&!ECMXS~TnfyH3M9;IC2UqOW+)>h9_2+mgV935{}2;8UCrZf zF}yGYj=ORDP-&2x-yf1k6 zp-Y@BChj>UgO<)zWMILVjZSgh*1*l^5a2!W-^dFAUbYRIy_JTdou98sLHb28R&R2a z=P){`euMZKHKzM-fD#1tc+2qKLEeA0fDzX}LknheV+S@B70N#;fNNR^fX53{7QQOzygy|wx}nZ7Jq_Rg1-EH~_CVTLSalp5rGmgs5gs?&m)7ONW zrk!c3f#sd@8m+;6K@}*55sQC{8RIH24t(rdSXfIP59$|b_pzl8^|x_ZuPt!ft5qWg zEdtL=nW4H{)kmm%FU}k%SDy?Dqyu}eL|>*X5CH-=5qP+WZ2-^W1}*<&%evX3;=vh= zg04;rmZsa_GLuU&q|-)HFC)X1vhaPD#-IdxzA2!@NPN-uOZ9;dW<)h5M^zv2Y$TZn z3OXv;_e7{KM8keV2(M8WbKDxwQNy^608(mQ-?mE2eS_+Li@7tI=&A5Nm?_uxqw|-A zf$6H3g4e^lIM7|_hIeJJ~MXD zR#V~d2(x8djU@V$!g|qcF%LM>?++~|moqu%AkJ@;-lHLgIs#_Qpaq)(Qi4EL{XE`c zSe`y`6NS%Pjac9@ck&YdK_za0J<+t{GogW~ZKfoQUsE&D*WU``VCG0`deu=IhJ_N`M138^SpxB4DLt4#GVC;}H))VbCy&BwPKtg%`Uxdbu zwDR(u81jN!hq{EdiOE9Ap9aa*ukdO4xgJW756^cJKD}0Gt*-%Il?S#iTIBjO2vwsu z+zRWlz3?Y+$X%0iKn`2(Q%SC`PlQ!)N!8CjZH808e;o**-llooBV2tG99juHkUxzw zI;X*$2cKQXC>MnLsvC0_avKUFpHIX~1Q*;EFDiXZiVhx??=BmMX|6XdO%iW!sS&c{ z3gK`ey(}i%sf87D{I^{A74B9`q4UW5)S*|j?+?MZ!gOPH0Tb8nl`?#R8Kw2HSm)OE zexj$c`KV`Mw{bWR0+tPFJ>%J@TO2aCbT7G`@uX8?%iZ+agLuCodrWt<(^ZwXH@N8` z?2_jpShd_|CToe2s$zL&9&YsiYQ~!AIPR@G*c5l9^9As>SJs{uohH2(SJl zA4$O-UyOE6EaV^x9dtE*ptvzd*$>q03@@8AvhByQ+SH?J#=CDlr1gMtD+A4fUgf$4 z@*VHk$z=I{z6S`N?{nR{&tPv`^ayAO$dW@;Fn)_ichzg}k5&@TMyd;)KGm2I*l}wN zT?TtchqwbT8gUr62eKzODLl+;^p2q_BnBXyjAv?z?7p>C<9$i}y&jWyjP6e4o}v0c z!rl8!(~h#AAGj2=aX7mxRl!v_Ki)^Q%K+EorTgL%(w!3P7`}g`>z6wIL!)Ht0=c(5 z9}M#Il}*RcZXzDAT%(JM=joF+7`{cJXS}qV?g{C+^=P9bqG0lX=#5X!9u&4yyx)>- zT^37UFlA7Lj4%ex>Md$6=;mGyjaplwd?3lh>#7e3`*qaqvX=R2IV_imca^PR)AKwZ zp{dmUal(_ORNpzN&>I?g#sItYEw&VW_Xi5y_ETH0n1zqQW+@+NPjxJ;W}|2c#zl_8 zBKKcQJv0RlwD|n$JY#fYr3YF)%fbsn_mZ-0Zwc``;b!c4>vG_cN2f@dhTv4%pQu{ zYVEvt`M`ug{afh56WC<{-R!(s$HBl>8x9;hS6b?eC}yNPO{jh6!G6o_!mL>Ptg(4C3=s=tZP7O01>Wkj4F_aJQWn|Oh5U{dgY zcW}V~RwYdc@TE%*;e7G8kVO4>xuepZefjEh9Mb+QQ;%gJ;=fpK3TspDWZrCaH&{fk z3*NC>^$&eWhN~tf<7SK&{zx8d{w8IwiZ?BBL5o(XcsW8$&0GV9s8ZZ=FNNe;k8Bg= zu3H6{%0`X8Cqu4~BBFPajWG}Y9u!Z`?za>+S&OBxkDbA0=A@3E!RFS`Z(P>u83v(V z({6;=@@5D5?qV0SC3PiHm-Vt1i(SIc5BM9TmW9ZrNC~qb8=*K~JYj#S32&uZ>Sn^G zZ-VH~2U>?-=v?XW5xIQCAsFWSNGTa8i0u3!7G&DSlakkwT}&Cf?!_!&ap~V6)X;4* z9LFjdF6r?8;e)E0Nk_~ZGo9SgnZrRR7wB8Yjld&9*(CGmL!AN^=+c>4Ih(c+7e!Y<=X(db%6p(lXgP=r+4axEG(jd^bEmV7?o zUS019`?(jmK#&-pEIrCwTc1F=JZ(nhkF-&Q9ane<5|HfT!q^8!wXZ%B_M*#h^Ak(j z=EO^>142=v>yU7jmqgUPK24m@L~;ZdDd*h+M27&(8?pEao8Z z(nIueLaz7v<#|1;rXf}J%ng){?~k_zzIgZ=ozUi8S10oaO3=HOjGD|XKF9CIOGYg zNeJgqVe#s9+B8F6?=>I;oi;4&RfppE62kxHI~pYXXw>XJycNf!CTu4V{%W$^r-6_@ zu>OAPUOXh=ik{9b;6AJYA~8m2Rgs`A4@`G{F+0=NSMKgDVNqo;k^ftxvsQSrt|~o$ zi`M(=df=s4O<)J7nU03yi#RYrI&vl)Gs!m=VBFs9kWc#`!K$>%3NYc;c?8Pv*n3i|<2 z*=Nu`;$k+!_VzqbkITj3GwxHUpdO*jnkM;0b)UW z#yiu#^5OL>z)Mnrcc|^nKe$4nT#XK(T*^=`S-4=1E*G7eyt3f@PBOf?rfUS#=aqmn zMB0W5b?TKU?g;-bUZ2HBw2Tw^#XX+u;eNBKwrFe_ST;+yuJKv`Ez?G%R?-Ht_bOZv zk2gCz@Pcj5_)&UREbCGzkZS#{g=GEQJ77<+_2`pq3Ms4g{j}P>KkFP*iFvD9l7n90 z8fn_S6;&cWKeH}3d50ub&~sY>{FIUlxuO#)ZFO#AdG*LKP;)dVHrm~+m5UH`_37%j95F_o zcvXOCxZU%N&UexDG}!uqgU+-2jp~-;6W8H0*Ic4~`H7>Wj6){nkM`eb3cyqG!>9t_ ziB3>Isz@#T&-j^rpy-TqOT?eWIh2bWQy2&RwuV?+jzXH!BSzK@D-;@ zO!QJLdB7zH6b>T&aIZpaC)dLsO&;8E`8O~isd3oWHqe-htCt~EGX+BRG(pG_Qc;S} zo&uPG!KSh+JCg0Td1=+^hYMT77jpH#_P>PPgCu2=tSeJ@Kk~Q<8`c5rxiZKTL7JIK zvPSG(1|qchS66FKCLQ910TSKzKl7RNrz3kFUK`L0egQq@lYZ$3T7bge%vKX3HJgr* zKZN@Lyy~(r23~te8`4S7cDKzKD1ko>1HhxX1pw%T(f-I2&>AGr7{&`FeCLc&=zvBVW zdZyri+J#*OT%?-=w)@{^1z_{yCu7mm&46bp=r)-9@2e&7)uroy?b@WTFAN6$`x;Ez zJ7RqGbT2@l0u6)q|Gs9B_AVLApL`W2eT5p-{`a*JB2F!PeVpMv2l$JO%j^GY0i=Fo zFqDb4g4E?L_<}SZe*AY{)m@;=dtVupXhi#@`=mb{#%+N#Otica1?9E~IBx#dkpuxl z4k~-~(7KD<<5IxcC;Jmvfc|uWv+dVGCo=@Vh^(uQ6}mHgk_^p*s0n#;c-nDpYWHLv z4CpXh_9&@8*@)&R_BQ41EoIOp$TxLc2IvVcU6fEHoEqT^O88ZD(#fsO{Xi|xhYrLJ z>AqpcKz6c>iW)k72?1?5ZVne}0f9ysDBaiLx-`Hl4KD`r(^XUv;sd@QLb74l-Q)2s zLs#&H-1A76;QF~}o8aqL!Hgs+BC+$U#FPWPC!%2|;GC=FO6?sm>37%%@`s^v&>M@! zTt`+iR~{zrWc)XU2+PAl%6mEpY$FB?kItdbolMMq6@-1ZV5BVQZh$94(RAQPod1mm z0Lxb$!Ue#FG$EjulXEo;0Qs~G5E)nbrNVm4`9Z%&0kQ3ujd%jw1pw@Rvif1>4G2S~ z3^{39rjQ-Pt$Xrw+9M|^o+OoSBgEH}^`xn^<^_k=R7-6TPK|pH@`>zNTVOSiq!bS1 z9y3V!tt2xd-T9E__6fe+hS!kYnxGbwt4yFpm;a_%3Lq>g0${~I#5r@KBQQzTX!gH3 z4101XPSOB~W#`iQ$iuvf(jxoglcH!0?E|1IhQx@$%FinyApRa2h%)zoBRF}FAZmF> zPM-a#A)rvFi_gJXNZ_3ra4k$=>aHZ%dn@Ny=M*S<51+$X{^ZguI!^kP^Xe@gX=z#6mz{AD_w`2>F&O0^=TnRpbXTEr3r%8(Bmy z#BB@_U%C#SQu^O0$Qx+udq;XwXGw9N3u&|}{v{~cNh>dLhR8YY{`~(tlhQ8$H#s(_ z4dISNma(3WsmSo7b<&ys&0`ft<5OI7&-VOv*AZmC%(MqOqLTE`J)DcIW zQ={WqA9XsTOj#ht0BxL@z!UQ)q=4_ukTDvm?o)y2mVh1oW=p$J{6@Or4WK8GKW?<) z{r?-XicdhS5>Vyuyzt8+HO~O_-7TMR79z&z3e^O+4T1MfZAEC(LVOhLr8*h_N!yp7ir0L^ql z%Y}Z9e21bN|1%-)5HT$?22=!#1vM(T0PCYEAykn zWA3YZmqDd`PYv2&Z5gx?yQhvO&l$|+8@96gxN9x-p#pegx-*VWVj%*SYDYSun-`|| zo1r3*xPQ0f52Z*PKX~IBE&`?t5j=7@owfm95V`wjoeXf9*WZ2v{&xU8Qc11^&if}O zE#3+YF#twPhmXH6ePcrCyut72jwyepLb9F(%5)?tW{80~y4Om}WnhX6)q$B|ZK`me z8?_I^k7pEFj1i^+;&#tR=(nZix`X-jKuQ(eKz7ITQ#kEJXCGRUC~4Yo==odVyCrOv z3c;@Gn6X!fxQsoyBl14g&x~1Us2Ki!#$H^02QdFk^^V+YWbLK$3}xEs=$=qCOlWn@ z!S&K=i~x8878V#u9-gA=bVA0Lpj9m$bmz}ahbPN3#gcVUG?^#fP*b$u;XgqILtU9G zJ`*m3ANnvYgEW>L++=Jb&kU(+HbaEwUAp!(ptmzd&c_rd3|5s=XpM#_viijd@rB3 zN$74K6w|HORqTXAv$$4*^*+z-Q;@!`&E7JGY)+$sj6qH!@%*1D&0(ZCC+F=G-d zHq6V_zw#BiXAWZT3*U2&wDan|>pOw{meTZ@z(LJlEA(uXk~T+vAbUp`7NZa&wDg*6 zX5lSuKRdO0Z!`*VaVVFN;6@HkSnoM2Kh?wT`^<*gGtqu;+=ie%CV~6lm^U&Yw^xbO zw;p7SSbrk{yVT%Vx7fbhWcnV|3ZzT}|4|7CFt^nt8tm)^Jak66d6r{;}Z*4gH7%W!;Hlx}l_ zL#;XM-Gwqn}B`x|1dh!u@^cDq`ZG3A)*0OG_tiv0-cK-jwB7P zu?QIx2X+6#|0L!)_Wlx_v#&{uOl9W6>Y^_UF$%xOEljOfBVOA6CoCbXSy`n6w+|@| zd-Y7_ReNI^+Oq{D%l9P$#=qdzs^MjSP zK*JIJ4^DqnP5VK||4;^m)gnt)4-r#`uY5Zj>RfW~=X?M)(Av;DThjJ24qd$D z#`=?W`ebeXv+#%uXu^3>(TUQJI&1lXQUINUO&_bxVBQyqS2U=lgX|voNgxFp&x7Aa%XzFzEEw=r72s3n~d=Wk~$I!aJPxq#%YMWNiQtpaqNi@!D zJJ9^g%`DA{H^SSDYlCaDNMwx7!;3>~DBL7Hd)E!jeSNYd*A#8l%HE_?6Kw>)rY<?o&*oE#N2_XUvxpNer z;w=jR6_YMsTegGyKvA|1uc!x<7VCk8y?-BXYSIxZO5u$(Keq zv_0Ca33LV}82Z~{q>LqtKU1y%k0R4RLTdT;9^q&U64qK7%tjUV~B7q58LC2aJu2{a_{L=~WPIu0!vn1@JV2k>qRSM!PDY|lA zE;c?9>*8vxX1l-hydCP~DZdH;9M!hfG0gr!)Hvqqb*VtLb9xW%e#L{hu z5z2dfB^M>yA)Xp#Ax;TdJ2!X=R~5L@yWdMV^;E={1O4PM=9)qqrUDu(l>APZB3_`% zQ@RE|63SpQPw*6zo-t3o^PCqu9;&pEHHK&ZdG*5-=^uyI@5>gR-g*X%*6un8i^qXK^~r+Nkj~eo|}Hm~~K0l~9c4CovCp z4HsT|$sh^Pv#7K|(4*eA{xO%U6T5Kr(}`)ss`x#zGU`FMS1sleX#!nRY(>EL*GvhG z$0zyCFP8}1e2MgATDs?i_!6tOF}ZC5BT}dVfI@-l|Lz+A05p_d-vA_TsPpWmpSTS^ O0lW_T{!r&0e&zok7ziE! literal 0 HcmV?d00001 diff --git a/web/public/logo.svg b/web/public/logo.svg new file mode 100644 index 0000000..7f3864a --- /dev/null +++ b/web/public/logo.svg @@ -0,0 +1,9 @@ + + 蓝鸟 + + + + + + + diff --git a/web/src/Admin.vue b/web/src/Admin.vue new file mode 100644 index 0000000..3bae149 --- /dev/null +++ b/web/src/Admin.vue @@ -0,0 +1,139 @@ + + diff --git a/web/src/AdminPager.vue b/web/src/AdminPager.vue new file mode 100644 index 0000000..bce6d55 --- /dev/null +++ b/web/src/AdminPager.vue @@ -0,0 +1,17 @@ + + diff --git a/web/src/App.vue b/web/src/App.vue new file mode 100644 index 0000000..9cea06a --- /dev/null +++ b/web/src/App.vue @@ -0,0 +1,10 @@ + + diff --git a/web/src/Mine.vue b/web/src/Mine.vue new file mode 100644 index 0000000..301ff98 --- /dev/null +++ b/web/src/Mine.vue @@ -0,0 +1,16 @@ + + diff --git a/web/src/Pickup.vue b/web/src/Pickup.vue new file mode 100644 index 0000000..8049c1b --- /dev/null +++ b/web/src/Pickup.vue @@ -0,0 +1,12 @@ + + diff --git a/web/src/Send.vue b/web/src/Send.vue new file mode 100644 index 0000000..6a60fb4 --- /dev/null +++ b/web/src/Send.vue @@ -0,0 +1,24 @@ + + diff --git a/web/src/ShareDialog.vue b/web/src/ShareDialog.vue new file mode 100644 index 0000000..83995a6 --- /dev/null +++ b/web/src/ShareDialog.vue @@ -0,0 +1,14 @@ + + diff --git a/web/src/api.ts b/web/src/api.ts new file mode 100644 index 0000000..0e190d1 --- /dev/null +++ b/web/src/api.ts @@ -0,0 +1,23 @@ +export interface Policy { anonymous_upload: boolean; anonymous_download: boolean; max_file_bytes: number; max_batch_bytes: number; max_files: number; default_days: number; max_initial_days: number; max_lifetime_days: number; default_downloads: number; max_downloads: number; renewal_days: number; renewal_mode: 'add' | 'sliding'; ip_daily_bytes: number; ip_daily_uploads: number; session_daily_uploads: number; require_password: boolean } +export interface FileRecord { id: string; name: string; size: number; status: string; remaining: number; expires: number; available: boolean } +export interface Parcel { id: string; state: string; created: number; expires: number; count: number; size: number; ip?: string; files?: FileRecord[]; renewal_days?: number } +export interface Share { code: string; url: string; urls?: {label:string;url:string}[]; expires: number } +export interface PublicPolicy { policy: Policy; public_url: string; encryption: boolean; deduplication: boolean; visitor_expires:number } +export class APIError extends Error { constructor(public status: number, public body: Record) { super(String(body.error ?? '请求失败')) } } +export async function api(path: string, options: RequestInit = {}): Promise { + const response = await fetch('/api/v1' + path, { credentials: 'same-origin', ...options, headers: { 'X-Bluebird-Origin': location.origin, ...(options.body ? { 'Content-Type': 'application/json' } : {}), ...options.headers } }) + const body = await response.json().catch(() => ({ error: '服务器返回异常响应' })) + if (!response.ok) throw new APIError(response.status, body) + return body as T +} +export const post = (path: string, body: unknown = {}, token?: string) => api(path, { method: 'POST', body: JSON.stringify(body), headers: token ? { Authorization: `Bearer ${token}` } : {} }) +export const size = (n: number) => { if (!n) return '0 B'; const i = Math.min(3, Math.floor(Math.log(n) / Math.log(1024))); return (n / 1024 ** i).toFixed(i ? 1 : 0) + ' ' + ['B', 'KiB', 'MiB', 'GiB'][i] } +export const date = (n: number) => n ? new Date(n * 1000).toLocaleString('zh-CN', { hour12: false }) : '—' +export const states: Record = { uploading: '上传中', ready: '可取件', revoked: '已撤销', expired: '已过期', exhausted: '已失效', restored: '恢复待审核', deleted: '已清理', pending: '待上传', missing: '文件异常', deleting: '待清理' } +export function upload(path: string, file: File, token: string, progress: (n: number) => void, signal: AbortSignal): Promise { + return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('PUT', '/api/v1' + path); xhr.setRequestHeader('Authorization', `Bearer ${token}`); xhr.setRequestHeader('Content-Type', 'application/octet-stream'); xhr.setRequestHeader('X-Bluebird-Origin',location.origin); xhr.upload.onprogress = e => progress(e.lengthComputable ? Math.round(e.loaded / e.total * 100) : 0); const abort = () => xhr.abort(); signal.addEventListener('abort', abort, { once: true }); const done = () => signal.removeEventListener('abort', abort); xhr.onload = () => { done(); if (xhr.status >= 200 && xhr.status < 300) resolve(); else { let text = '上传失败'; try { text = JSON.parse(xhr.responseText).error } catch {} reject(new Error(text)) } }; xhr.onerror = () => { done(); reject(new Error('网络中断,请重试')) }; xhr.onabort = () => { done(); reject(new Error('已取消上传')) }; xhr.send(file) }) +} +export interface RuntimeSettings { file_budget:number; data_budget:number; min_free_bytes:number; audit_retention_days:number; audit_max_records:number; audit_max_bytes:number } + +export interface PageResult { items:T[]; total:number; page:number; page_size:number; pages:number } +export interface AuditEntry { id:number; created:number; action:string; target:string; source:string } diff --git a/web/src/clipboard.ts b/web/src/clipboard.ts new file mode 100644 index 0000000..f5d79a3 --- /dev/null +++ b/web/src/clipboard.ts @@ -0,0 +1,27 @@ +// LAN HTTP uses a selected readonly field; keep it inside the active modal so +// its focus trap cannot redirect focus and silently copy different content. +export async function copyText(value: string, container: HTMLElement): Promise { + if (navigator.clipboard?.writeText) { + try { await navigator.clipboard.writeText(value); return } catch {} + } + const previous = document.activeElement + const node = document.createElement('textarea') + node.value = value + node.readOnly = true + node.setAttribute('inputmode', 'none') + // Stay in the viewport for mobile selection. Readonly avoids a soft keyboard; + // 16px avoids iOS zoom, and opacity keeps this temporary field invisible. + node.style.cssText = 'position:fixed;left:0;top:0;width:1px;height:1px;padding:0;border:0;font-size:16px;opacity:0;pointer-events:none' + container.appendChild(node) + try { + node.focus({ preventScroll: true }) + node.select() + node.setSelectionRange(0, value.length) + if (document.activeElement !== node || node.selectionStart !== 0 || node.selectionEnd !== value.length || !document.execCommand('copy')) { + throw new Error('复制未完成,请选择或长按取件码、链接手动复制') + } + } finally { + node.remove() + if (previous instanceof HTMLElement && previous.isConnected) previous.focus({ preventScroll: true }) + } +} diff --git a/web/src/main.ts b/web/src/main.ts new file mode 100644 index 0000000..94b9bb1 --- /dev/null +++ b/web/src/main.ts @@ -0,0 +1,7 @@ +import { createApp } from 'vue' +import { createRouter, createWebHashHistory } from 'vue-router' +import { createPinia } from 'pinia' +import App from './App.vue' +import './style.css' +const router = createRouter({ history: createWebHashHistory(), routes: [{ path: '/', component: () => import('./Send.vue') }, { path: '/pickup/:code?', component: () => import('./Pickup.vue') }, { path: '/mine', component: () => import('./Mine.vue') }, { path: '/admin', component: () => import('./Admin.vue') }] }) +createApp(App).use(router).use(createPinia()).mount('#app') diff --git a/web/src/style.css b/web/src/style.css new file mode 100644 index 0000000..dae6f9a --- /dev/null +++ b/web/src/style.css @@ -0,0 +1,26 @@ +:root{font-family:Inter,"PingFang SC","Microsoft YaHei",sans-serif;color:#1d2939;background:#f5f7fb;font-synthesis:none}*{box-sizing:border-box}body{margin:0}a{color:#1677ff;text-decoration:none}button,input{font:inherit}.site{max-width:1200px;margin:auto;padding:0 36px}.header{height:106px;display:flex;align-items:center;gap:32px;border-bottom:1px solid #e4eaf3}.brand{display:flex;align-items:center;gap:12px;font-size:22px;font-weight:750;color:#1d2939;letter-spacing:-.8px}.brand small{display:block;font-size:11px;letter-spacing:2px;font-weight:450;margin-top:1px;color:#667085}.brand-mark{height:42px;width:42px;background:#1677ff;color:white;border-radius:13px 13px 13px 3px;display:grid;place-items:center;font:italic 28px Georgia}.header nav{display:flex;gap:30px;margin-left:auto}.header nav a{color:#667085;font-size:14px}.header nav a.router-link-exact-active{color:#1677ff;font-weight:700}.admin-link{margin-left:24px;color:#7c8b92;font-size:13px}main{min-height:calc(100vh - 204px);padding-bottom:56px}.intro{padding:58px 0 40px}.eyebrow{display:block;color:#1677ff;letter-spacing:2.4px;font-size:10px;font-weight:700}.intro h1{font-size:46px;line-height:1.3;letter-spacing:-1.8px;margin:14px 0 18px;font-weight:650}.intro h1 span{color:#1677ff}.intro p{color:#667085;font-size:14px}.intro.compact{padding:46px 0 24px}.intro.compact h1{font-size:38px}.card{background:#fff;border:1px solid #e4eaf3;border-radius:18px;padding:28px;box-shadow:0 6px 26px #1677ff08}.send-layout{display:grid;grid-template-columns:minmax(0,1.65fr) minmax(280px,1fr);gap:24px;align-items:start}.card-heading{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:20px}.card-heading h2{font-size:19px;margin:0;font-weight:650}.card-heading>span{font-size:12px;color:#8b989d}.dropzone{border:1.5px dashed #b7d4ff;background:#f7faff;border-radius:13px;padding:30px 16px;text-align:center;cursor:pointer}.dropzone.dragging{border-color:#1677ff;background:#e6f4ff}.upload-glyph{display:grid;place-items:center;border:1px solid #d6e8ff;border-radius:14px;background:white;width:54px;height:54px;margin:auto;font-size:30px;color:#1677ff}.dropzone h3{font-size:16px;font-weight:600;margin:18px 0 6px}.dropzone p{font-size:13px;color:#667085;margin:0 0 15px}.dropzone small{font-size:11px;color:#7a8699}.file-list{margin-top:12px}.file-row{display:flex;align-items:center;gap:14px;padding:16px 0;border-bottom:1px solid #edf1f7;min-width:0}.file-icon{display:grid;place-items:center;flex-shrink:0;width:38px;height:44px;background:#e6f4ff;border-radius:8px;color:#1677ff;font-size:21px}.file-info{min-width:0;flex:1}.file-info strong{font-size:13px;font-weight:550;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-info small{display:block;font-size:11px;color:#87969d;margin:5px 0}.upload-bottom{margin:22px 0 10px;display:flex;align-items:center;justify-content:space-between;gap:16px}.upload-bottom>span{font-size:11px;color:#8d9b9e}.settings-card h2{font-size:19px;margin:12px 0 26px}.settings-card label:not(.n-checkbox){display:flex;justify-content:space-between;align-items:center;font-size:12px;margin:20px 0 9px}.settings-card label span{color:#9aa6aa;font-size:11px}.renew-option{margin-top:24px}.renew-option p,.privacy-note p{font-size:11px;line-height:1.9;color:#88979b;margin:8px 0}.privacy-note{margin-top:25px;border-top:1px solid #e4eaf3;padding-top:20px}.privacy-note strong{font-size:12px;color:#475467}.share-dialog{max-width:420px;width:calc(100vw - 32px);background:white;border-radius:20px;padding:32px;text-align:center}.share-dialog h2{font-size:27px;margin:12px 0}.share-dialog p{color:#88979b;font-size:13px}.pickup-code{font:700 25px monospace;letter-spacing:2px;color:#1677ff;background:#e6f4ff;padding:16px 8px;border-radius:10px;margin:22px 0}.share-dialog img{display:block;margin:12px auto}.share-url{width:100%;font-size:11px;border:1px solid #d6e8ff;border-radius:7px;background:#f7faff;padding:10px;outline:none}.share-dialog small{display:block;color:#8c9ca0;margin:14px 0 20px}.actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap}.share-dialog .actions{justify-content:center}.notice{padding:12px 16px;background:#e6f4ff;color:#0958d9;border-radius:10px;margin:16px 0;font-size:12px;line-height:1.8}.notice.error{background:#fbefed;color:#b35d52}.pickup-card,.login-card{max-width:680px;margin:auto}.pickup-card .n-button--block{margin-top:22px}.field-label{display:block;font-size:12px;color:#475467;margin-bottom:9px}.password-field{margin-top:20px}.muted{font-size:12px;color:#8b9b9f;line-height:1.8}.pickup-file{padding:22px 0}.parcel-row{display:flex;justify-content:space-between;align-items:center;gap:20px;padding:22px 0;border-top:1px solid #e4eaf3}.parcel-row strong{display:inline-block;margin-left:12px;font-size:14px}.parcel-row small,.parcel-row code{display:block;color:#8c9ca2;font-size:11px;margin-top:9px}.empty{text-align:center;padding:32px;color:#879a9e;font-size:14px}.login-card{max-width:420px}.login-card .field-label{margin-top:18px}.login-card .n-button{margin-top:24px}.admin-toolbar{display:flex;gap:12px;justify-content:flex-end;margin-bottom:22px}.stats-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:24px}.stat small{display:block;color:#667085;font-size:12px}.stat strong{display:block;font-size:25px;margin:12px 0}.stat span{display:block;color:#9aa7a7;font-size:10px}.capacity-label{display:flex;justify-content:space-between;font-size:11px;color:#7b9296;gap:10px}.capacity-track{height:6px;background:#edf1f7;border-radius:4px;margin:12px 0 28px}.capacity-track div{height:100%;background:#1677ff;border-radius:4px}.policy-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:24px;margin:28px 0}.policy-switches{display:flex;gap:22px;flex-wrap:wrap;margin-top:16px}.admin-parcel{padding:25px 0;border-bottom:1px solid #e4eaf3}.admin-parcel .card-heading{margin-bottom:12px}.admin-parcel strong{font-size:13px;margin-left:10px}.admin-parcel small{color:#8a9c9f;display:block;font-size:11px;margin-top:8px}.admin-file{display:flex;gap:14px;align-items:center;margin:10px 0}.admin-file>span{font-size:13px;max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.admin-file>small{margin:0;flex:1}.admin-parcel>code{font-size:10px;color:#9ba9ae}.pagination{justify-content:center;margin-top:24px;font-size:12px;color:#667085}.audit-row{display:grid;grid-template-columns:1.1fr 1.2fr .8fr 1fr;gap:12px;padding:15px 0;border-bottom:1px solid #edf1f7;font-size:11px;color:#83979c}.audit-row strong{color:#0958d9;font-weight:550}footer{height:98px;border-top:1px solid #e4eaf3;display:flex;align-items:center;justify-content:space-between;font-size:11px;color:#667085;letter-spacing:.4px}@media(max-width:800px){.site{padding:0 22px}.header{height:86px;gap:16px}.header nav{gap:17px}.brand{font-size:18px}.brand-mark{height:34px;width:34px;font-size:24px}.admin-link{margin-left:0}.send-layout{grid-template-columns:1fr}.intro{padding:40px 0 25px}.intro h1{font-size:36px}.stats-grid{grid-template-columns:repeat(2,1fr)}.policy-grid{grid-template-columns:repeat(2,1fr)}.parcel-row{align-items:flex-start;flex-direction:column}.admin-file{flex-wrap:wrap}.admin-file>span{max-width:100%;flex-basis:100%}.audit-row{grid-template-columns:1fr 1fr}.card{padding:22px}}@media(max-width:480px){.site{padding:0 16px}.header{flex-wrap:wrap;height:106px;gap:0;justify-content:space-between;padding:16px 0}.header nav{order:3;width:100%;margin:10px 0 0;justify-content:center;gap:34px}.brand small{font-size:9px}.intro h1{font-size:31px}.intro.compact h1{font-size:30px}.card{padding:20px;border-radius:14px}.upload-bottom{align-items:stretch;flex-direction:column}.pickup-code{font-size:21px}.policy-grid{grid-template-columns:1fr}.stats-grid{gap:10px}.stat{padding:16px}.stat strong{font-size:21px}.capacity-label{flex-direction:column}.admin-parcel .card-heading{flex-wrap:wrap}.pickup-file{gap:10px}.pickup-file .file-icon{display:none}footer{font-size:10px}} + +.admin-badge{margin-left:auto;color:#1677ff;background:#e6f4ff;padding:7px 13px;border-radius:8px;font-size:12px}.brand-mark{background:none;border-radius:12px;flex-shrink:0}.share-hint{font-size:11px!important;line-height:1.8}.settings-explanation{line-height:1.8;font-size:12px;color:#667085}.runtime-grid{margin-top:20px}.settings-section-title{font-size:17px;margin:26px 0 10px}.capacity-details{display:flex;gap:20px;flex-wrap:wrap;font-size:12px;color:#667085;margin-bottom:15px}.receipt-info summary{cursor:pointer;color:#1677ff;font-size:12px}.receipt-info p{font-size:12px;color:#667085;line-height:1.9}.parcel-note{font-size:12px;color:#667085;margin:10px 0}.closed-service{margin-bottom:18px}.capacity-warning{color:#cf1322}.public-nav-hidden{display:none} + +.pickup-code{-webkit-user-select:text;user-select:text} + +html{scrollbar-gutter:stable} +.admin-panel{display:flex;flex-direction:column;gap:16px;min-width:0} +.admin-panel>.notice{margin:0;flex-shrink:0} +.admin-scroll{min-width:0} +.admin-panel-footer,.admin-pagination{flex-shrink:0;border-top:1px solid #e4eaf3;padding-top:16px} +.admin-filters{display:grid;gap:12px;flex-shrink:0;align-items:end} +.admin-filters>div{min-width:0}.parcel-filters{grid-template-columns:minmax(0,2fr) minmax(150px,1fr) auto}.audit-filters{grid-template-columns:repeat(4,minmax(0,1fr))}.filter-target,.audit-filters .filter-actions{grid-column:span 2} +.filter-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.filter-date{width:100%;height:34px;padding:0 9px;border:1px solid #e0e0e6;border-radius:8px;color:#1d2939;background:white;font:inherit;font-size:13px;min-width:0} +.filter-date:focus,.admin-pagination select:focus,.page-jump input:focus{outline:2px solid #91caff;outline-offset:1px} +.admin-pagination{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;color:#667085;font-size:12px} +.page-size,.page-jump,.page-jump label{display:flex;align-items:center;gap:8px}.page-size select,.page-jump input{border:1px solid #d6e8ff;border-radius:6px;height:30px;padding:0 6px;background:#fff;color:#1d2939;font:inherit}.page-jump input{width:60px}.page-size select{width:62px}.page-buttons{flex-wrap:nowrap}.page-buttons span{white-space:nowrap} +.audit-summary{margin:0;color:#667085;font-size:12px;line-height:1.8;flex-shrink:0} +.audit-table{width:100%;border-collapse:separate;border-spacing:0;table-layout:fixed;font-size:12px}.audit-table th{position:sticky;top:0;text-align:left;padding:12px 10px;background:#f7faff;color:#667085;z-index:1;border-bottom:1px solid #e4eaf3;font-weight:550}.audit-table th:nth-child(1){width:22%}.audit-table th:nth-child(2){width:23%}.audit-table th:nth-child(3){width:33%}.audit-table th:nth-child(4){width:22%}.audit-table td{padding:14px 10px;border-bottom:1px solid #edf1f7;vertical-align:top;overflow-wrap:anywhere;color:#475467}.audit-table strong{display:block;color:#0958d9;font-weight:550}.audit-table small{display:block;color:#8b9b9f;font-size:11px;margin-top:5px}.audit-table code{font-size:11px} +.admin-panel .admin-parcel:first-child{padding-top:8px}.admin-parcel>code{overflow-wrap:anywhere} +@media(max-width:900px){.audit-filters{grid-template-columns:repeat(2,minmax(0,1fr))}.parcel-filters{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}.parcel-filters .filter-actions{grid-column:1/-1}} +@media(max-width:600px){.admin-toolbar{flex-wrap:wrap}.admin-toolbar>.muted{width:100%;text-align:right}.filter-date{font-size:16px}.admin-pagination{gap:10px}.page-jump{margin-left:auto}.page-buttons{width:100%;justify-content:center;order:3}.audit-table thead{display:none}.audit-table,.audit-table tbody,.audit-table tr,.audit-table td{display:block;width:100%}.audit-table tr{padding:10px 0;border-bottom:1px solid #e4eaf3}.audit-table td{padding:4px 0;border:0;display:grid;grid-template-columns:70px minmax(0,1fr);gap:6px}.audit-table td::before{content:attr(data-label);color:#8b9b9f;font-size:11px}.audit-table td small{grid-column:2;margin:0}.audit-filters .filter-actions{grid-column:1/-1}.audit-filters .filter-actions>.muted{font-size:11px}.admin-filters{gap:10px}.page-size select,.page-jump input{font-size:16px}} + +@media(max-width:600px){.audit-filters>div:nth-child(-n+2){grid-column:1/-1}.admin-management .n-tabs{--n-tab-gap:16px!important;--n-tab-font-size:12px!important}} + +.share-entry{text-align:left;margin-top:20px}.share-entry label{display:block;margin-bottom:8px;font-size:12px;color:#667085} diff --git a/web/tests/access.spec.ts b/web/tests/access.spec.ts new file mode 100644 index 0000000..40a7b8f --- /dev/null +++ b/web/tests/access.spec.ts @@ -0,0 +1,80 @@ +import { test, expect, chromium } from '@playwright/test' +import { mkdtempSync, readFileSync, writeFileSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join, resolve } from 'node:path' +import { spawn, execFileSync, type ChildProcess } from 'node:child_process' +import { createServer } from 'node:https' +import { request } from 'node:http' + +test('LAN works without public DNS; public HTTP and HTTPS share files through a remote proxy', async () => { + test.setTimeout(60_000) + const root=mkdtempSync(join(tmpdir(),'bluebird-multiple-access-')) + const binary=resolve('../build/bluebird'), config=join(root,'config.toml') + const lan='http://127.0.0.1:19081', http='http://files.example.test:19081', https='https://files.example.test:19082' + writeFileSync(config,readFileSync('../deploy/config.example.toml','utf8') + .replace('listen = "127.0.0.1:8080"','listen = "127.0.0.1:19081"') + .replace('public_url = "http://localhost:8080"',`public_url = "${https}"`) + .replace('access_urls = []',`access_urls = ["${http}", "${lan}"]`) + .replace('secure_cookies = false','secure_cookies = true') + .replace('allow_lan_http = false','allow_lan_http = true') + .replace('trusted_proxies = []','trusted_proxies = ["127.0.0.1/32"]') + .replace('min_free_bytes = 1073741824','min_free_bytes = 0')) + execFileSync(binary,['init','--config',config],{env:{...process.env,BLUEBIRD_ADMIN_PASSWORD:'multiple-access-password'}}) + execFileSync('openssl',['req','-x509','-newkey','rsa:2048','-nodes','-keyout',join(root,'key.pem'),'-out',join(root,'cert.pem'),'-days','1','-subj','/CN=files.example.test'],{stdio:'ignore'}) + let worker:ChildProcess|undefined + const proxy=createServer({key:readFileSync(join(root,'key.pem')),cert:readFileSync(join(root,'cert.pem'))},(incoming,outgoing)=>{ + const upstream=request(lan+incoming.url,{method:incoming.method,headers:{...incoming.headers,host:'127.0.0.1:19081','x-forwarded-host':incoming.headers.host,'x-forwarded-proto':'https','x-forwarded-for':'192.168.1.61'}},response=>{ + outgoing.writeHead(response.statusCode!,response.headers);response.pipe(outgoing) + });upstream.on('error',()=>{outgoing.writeHead(502);outgoing.end()});incoming.pipe(upstream) + }) + const browser=await chromium.launch({env:{...process.env,HTTP_PROXY:'',HTTPS_PROXY:'',ALL_PROXY:'',http_proxy:'',https_proxy:'',all_proxy:''},args:['--host-resolver-rules=MAP files.example.test 127.0.0.1','--no-proxy-server']}) + try { + worker=spawn(binary,['serve','--config',config],{stdio:'ignore'}) + await expect.poll(async()=>{try{return(await fetch(lan+'/api/v1/health')).status}catch{return 0}}).toBe(200) + await new Promise(ready=>proxy.listen(19082,'127.0.0.1',ready)) + const context=await browser.newContext({ignoreHTTPSErrors:true}), page=await context.newPage() + page.setDefaultTimeout(10_000) + const errors:string[]=[];page.on('pageerror',e=>errors.push(e.message)) + // Abort every request to the public hostname: the LAN entrance must remain usable. + await page.route('**://files.example.test:*/**',route=>route.abort('namenotresolved')) + await page.goto(lan);await expect(page.getByRole('heading',{name:'准备寄件'})).toBeVisible() + await page.locator('input[type=file]').setInputFiles({name:'three-entrances.txt',mimeType:'text/plain',buffer:Buffer.from('same file in LAN, HTTP and HTTPS')}) + await page.getByRole('button',{name:'上传并生成取件码'}).click() + await expect(page.getByRole('heading',{name:'文件已寄出'})).toBeVisible() + const lanLink=await page.getByRole('textbox',{name:'取件链接'}).inputValue() + expect(lanLink.startsWith(lan+'/#/pickup/')).toBeTruthy() + const qrBefore=await page.getByAltText('取件二维码').getAttribute('src') + await page.locator('.share-entry .n-base-selection').click() + await page.getByText('公网HTTPS · '+https,{exact:true}).click() + const httpsLink=await page.getByRole('textbox',{name:'取件链接'}).inputValue() + expect(httpsLink).toBe(lanLink.replace(lan,https)) + await expect.poll(()=>page.getByAltText('取件二维码').getAttribute('src')).not.toBe(qrBefore) + await page.getByRole('button',{name:'完成',exact:true}).click() + await page.unroute('**://files.example.test:*/**') + for(const base of [lan,https,http]) { + if(base!==lan){ + await page.goto(base);await expect(page.getByRole('heading',{name:'准备寄件'})).toBeVisible() + await page.locator('input[type=file]').setInputFiles({name:'public-upload.txt',mimeType:'text/plain',buffer:Buffer.from('public upload')}) + await page.getByRole('button',{name:'上传并生成取件码'}).click();await expect(page.getByRole('heading',{name:'文件已寄出'})).toBeVisible() + expect((await page.getByRole('textbox',{name:'取件链接'}).inputValue()).startsWith(base+'/#/pickup/')).toBeTruthy() + await page.getByRole('button',{name:'完成',exact:true}).click() + } + await page.goto(lanLink.replace(lan,base)) + await expect(page.getByRole('heading',{name:'你的文件已就位'})).toBeVisible() + const pending=page.waitForEvent('download');await page.getByRole('button',{name:'下载',exact:true}).click() + expect(readFileSync((await (await pending).path())!,'utf8')).toBe('same file in LAN, HTTP and HTTPS') + await page.goto(base+'/#/admin');await page.getByRole('textbox').first().fill('admin');await page.locator('input[type=password]').fill('multiple-access-password') + await page.getByRole('button',{name:'登录',exact:true}).click();await expect(page.getByRole('tab',{name:'服务策略'})).toBeVisible() + } + const cookies=await context.cookies(https+'/api/v1/admin/status') + expect(cookies.some(c=>c.name==='bb_admin'&&c.secure)).toBeTruthy() + expect(cookies.some(c=>c.name==='bb_admin_http'&&!c.secure)).toBeTruthy() + await page.goto(http+'/#/admin');await expect(page.getByRole('tab',{name:'服务策略'})).toBeVisible() + expect((await context.cookies(http+'/api/v1/admin/status')).some(c=>c.name==='bb_admin')).toBeFalsy() + expect(errors).toEqual([]) + } finally { + await browser.close();proxy.closeAllConnections();await new Promise(done=>proxy.close(()=>done())) + if(worker&&worker.exitCode===null){worker.kill('SIGTERM');await new Promise(done=>worker!.once('exit',done))} + rmSync(root,{recursive:true,force:true}) + } +}) diff --git a/web/tests/admin.spec.ts b/web/tests/admin.spec.ts new file mode 100644 index 0000000..8f93d75 --- /dev/null +++ b/web/tests/admin.spec.ts @@ -0,0 +1,92 @@ +import { test as base, expect, type Page, type Cookie } from '@playwright/test' + +// Reuse one administrator login for UI fixtures, preserving the real login +// rate limiter and leaving its remaining burst for the full transfer scenario. +const test=base.extend<{}, {adminCookies:Cookie[]}>({ + adminCookies:[async({browser},use)=>{ + const context=await browser.newContext() + try{ + const response=await context.request.post('http://127.0.0.1:18981/api/v1/admin/login',{headers:{Origin:'http://127.0.0.1:18981'},data:{username:'admin',password:'e2e-secret-password'}}) + expect(response.status()).toBe(200) + await use(await context.cookies()) + }finally{await context.close()} + },{scope:'worker'}] +}) + +async function adminFixture(page: Page) { + const parcelQueries: URLSearchParams[] = [], auditQueries: URLSearchParams[] = [] + await page.route('**/api/v1/admin/parcels?**', async route => { + const q = new URL(route.request().url()).searchParams; parcelQueries.push(q) + const size = Number(q.get('page_size') ?? 10), total = q.get('q') ? 1 : 23, pages = Math.max(1, Math.ceil(total / size)), current = Math.min(Number(q.get('page') ?? 1), pages) + const items = Array.from({length: Math.min(size, total-(current-1)*size)}, (_,i) => { + const n = (current-1)*size+i+1 + return { id: n.toString(16).padStart(32,'0'), state:'ready', ip:'203.0.113.1', created:1791187200, expires:1791792000, count:1, size:12, files:[{id:'file-'+n,name:'样例文件-'+n+'.txt',size:12,status:'ready',remaining:5,expires:1791792000}] } + }) + await route.fulfill({json:{items,total,page:current,page_size:size,pages}}) + }) + await page.route('**/api/v1/admin/audit?**', async route => { + const q = new URL(route.request().url()).searchParams; auditQueries.push(q) + const size = Number(q.get('page_size') ?? 20), total = q.get('source') ? (q.get('source')==='no-match' ? 0 : 2) : 100000, pages = Math.max(1,Math.ceil(total/size)), current = Math.min(Number(q.get('page') ?? 1),pages) + const items = Array.from({length:Math.min(size,Math.max(0,total-(current-1)*size))}, (_,i) => ({id:total-(current-1)*size-i,created:1791187200,action:q.get('action')||'admin.login',target:q.get('target')||'audit-'+(total-(current-1)*size-i),source:q.get('source')||'203.0.113.1'})) + await route.fulfill({json:{items,total,page:current,page_size:size,pages}}) + }) + await page.goto('/#/admin') + await expect(page.getByRole('tab',{name:'文件管理'})).toBeVisible() + await expect(page.getByRole('navigation',{name:'文件管理分页'})).toContainText('共 23 个包裹') + return {parcelQueries,auditQueries} +} + +for(const mobile of [false,true]) { + test(`admin tabs expand naturally and lists paginate/filter (${mobile?'mobile':'desktop'})`,async({page,adminCookies})=>{ + await page.context().addCookies(adminCookies) + if(mobile)await page.setViewportSize({width:390,height:844}) + const errors:string[]=[];page.on('pageerror',e=>errors.push(e.message)) + const {parcelQueries,auditQueries}=await adminFixture(page) + const pager=page.getByRole('navigation',{name:'文件管理分页'}) + await expect(page.locator('.admin-parcel:visible')).toHaveCount(10) + await pager.getByRole('button',{name:'上一页',exact:true}).isDisabled().then(v=>expect(v).toBeTruthy()) + await pager.getByRole('button',{name:'下一页',exact:true}).click() + await expect(pager).toContainText('第 2 / 3 页');await expect(page.getByText('样例文件-11.txt',{exact:true})).toBeVisible() + expect(parcelQueries.at(-1)?.get('page')).toBe('2') + await pager.getByRole('button',{name:'下一页',exact:true}).click() + await expect(page.locator('.admin-parcel:visible')).toHaveCount(3) + await expect(pager.getByRole('button',{name:'下一页',exact:true})).toBeDisabled() + await page.getByLabel('文件名或包裹 ID',{exact:true}).fill('样例文件-2') + await page.locator('.parcel-filters').getByRole('button',{name:'搜索',exact:true}).click() + await expect(pager).toContainText('共 1 个包裹');await expect(pager).toContainText('第 1 / 1 页') + expect(parcelQueries.at(-1)?.get('q')).toBe('样例文件-2') + await expect(pager.getByRole('button',{name:'下一页',exact:true})).toBeDisabled() + await page.locator('.parcel-filters').getByRole('button',{name:'重置',exact:true}).click() + await expect(pager).toContainText('共 23 个包裹') + for(const name of ['文件管理','服务策略','容量与保留','操作审计']){ + await page.getByRole('tab',{name,exact:true}).click() + expect(await page.locator('.admin-panel:visible .admin-scroll').evaluate(node=>node.scrollHeight<=node.clientHeight+1)).toBeTruthy() + expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBeTruthy() + } + const auditPager=page.getByRole('navigation',{name:'操作审计分页'}) + await expect(page.locator('.audit-table tbody tr')).toHaveCount(20) + await expect(auditPager).toContainText('共 100000 条') + await expect(auditPager).toContainText('第 1 / 5000 页') + await auditPager.getByRole('button',{name:'下一页',exact:true}).click() + await expect(auditPager).toContainText('第 2 / 5000 页');expect(auditQueries.at(-1)?.get('page')).toBe('2') + await page.getByLabel('操作审计分页跳转页码',{exact:true}).fill('5000') + await auditPager.getByRole('button',{name:'跳转',exact:true}).click() + await expect(auditPager).toContainText('第 5000 / 5000 页');await expect(auditPager.getByRole('button',{name:'下一页',exact:true})).toBeDisabled() + await page.getByLabel('开始时间',{exact:true}).fill('2026-10-05T00:00');await page.getByLabel('结束时间',{exact:true}).fill('2026-10-06T00:00') + await page.getByLabel('来源 IP',{exact:true}).fill('203.0.113') + await page.getByLabel('目标标识',{exact:true}).fill('target-fragment') + await page.locator('.audit-filters .n-base-selection').click() + await page.getByText('删除包裹 · admin.delete',{exact:true}).click() + await page.locator('.audit-filters').getByRole('button',{name:'搜索',exact:true}).click() + await expect(auditPager).toContainText('共 2 条');await expect(auditPager).toContainText('第 1 / 1 页') + const query=auditQueries.at(-1)!;expect(query.get('action')).toBe('admin.delete');expect(query.get('source')).toBe('203.0.113');expect(query.get('target')).toBe('target-fragment');expect(query.get('from')).toBe(await page.evaluate(()=>String(new Date('2026-10-05T00:00').getTime()/1000)));expect(query.get('to')).toBe(await page.evaluate(()=>String(new Date('2026-10-06T00:00').getTime()/1000))) + await expect(page.locator('.audit-table tbody tr')).toHaveCount(2) + await page.getByLabel('来源 IP',{exact:true}).fill('no-match');await page.locator('.audit-filters').getByRole('button',{name:'搜索',exact:true}).click() + await expect(page.getByText('没有符合条件的审计记录。',{exact:true})).toBeVisible();await expect(auditPager).toContainText('共 0 条');await expect(auditPager.getByRole('button',{name:'下一页',exact:true})).toBeDisabled() + await page.locator('.audit-filters').getByRole('button',{name:'重置',exact:true}).click() + await expect(auditPager).toContainText('共 100000 条');expect(auditQueries.at(-1)?.has('source')).toBeFalsy() + await page.getByLabel('操作审计分页每页条数',{exact:true}).selectOption('50');await expect(page.locator('.audit-table tbody tr')).toHaveCount(50);await expect(auditPager).toContainText('第 1 / 2000 页') + + expect(errors).toEqual([]) + }) +} diff --git a/web/tests/cabinet.spec.ts b/web/tests/cabinet.spec.ts new file mode 100644 index 0000000..de81dc4 --- /dev/null +++ b/web/tests/cabinet.spec.ts @@ -0,0 +1,34 @@ +import { test, expect } from '@playwright/test' +import { readFileSync } from 'node:fs' + +test('anonymous batch → share QR → pickup download → admin management',async({page})=>{ + const errors:string[]=[];page.on('pageerror',e=>errors.push(e.message)) + await page.goto('/');await expect(page.getByRole('heading',{name:'准备寄件'})).toBeVisible();await expect(page).toHaveTitle('蓝鸟 · 文件快递柜');await expect(page.getByRole('link',{name:'管理',exact:true})).toHaveCount(0) + await page.locator('input[type=file]').setInputFiles([{name:'hello.txt',mimeType:'text/plain',buffer:Buffer.from('Hello Bluebird')},{name:'copy.txt',mimeType:'text/plain',buffer:Buffer.from('Hello Bluebird')}]) + await page.getByRole('button',{name:'上传并生成取件码'}).click();await expect(page.getByRole('heading',{name:'文件已寄出'})).toBeVisible();await expect(page.getByAltText('取件二维码')).toBeVisible();await expect(page.getByRole('button',{name:'复制取件码',exact:true})).toBeVisible() + const url=await page.getByRole('textbox',{name:'取件链接'}).inputValue();await page.getByRole('button',{name:'完成',exact:true}).click();await page.goto(url) + await expect(page.getByRole('heading',{name:'你的文件已就位'})).toBeVisible();const downloadPromise=page.waitForEvent('download');await page.getByRole('button',{name:'下载',exact:true}).first().click();const file=await downloadPromise;expect(file.suggestedFilename()).toBe('hello.txt');expect(readFileSync((await file.path())!,'utf8')).toBe('Hello Bluebird') + await page.getByRole('button',{name:'刷新状态'}).click();await expect(page.getByText('14 B · 剩余 4 次')).toBeVisible() + await page.goto('/#/admin');await page.getByRole('textbox').first().fill('admin');await page.locator('input[type=password]').fill('e2e-secret-password');await page.getByRole('button',{name:'登录',exact:true}).click();await expect(page.getByRole('tab',{name:'服务策略'})).toBeVisible();await expect(page.getByRole('link',{name:'寄文件',exact:true})).toHaveCount(0);await expect(page).toHaveTitle('管理控制台 · 蓝鸟');await expect(page.getByText('hello.txt',{exact:true})).toBeVisible();await expect(page.getByText('1 个对象 · 2 份文件记录')).toBeVisible() + await page.getByRole('tab',{name:'服务策略'}).click();await expect(page.getByText('匿名会话日寄件次数',{exact:true})).toBeVisible();await page.getByRole('button',{name:'保存策略'}).click();await expect(page.getByText('策略已保存,新上传会采用新策略')).toBeVisible() + await page.getByRole('tab',{name:'容量与保留'}).click();await expect(page.getByText('文件存储预算(MiB)',{exact:true})).toBeVisible();await expect(page.getByText('审计保留天数',{exact:true})).toBeVisible();expect(errors).toEqual([]) +}) + +test('mobile upload/pickup views remain within viewport',async({page})=>{ + await page.setViewportSize({width:390,height:844});await page.goto('/');await expect(page.getByRole('heading',{name:'准备寄件'})).toBeVisible();expect(await page.evaluate(()=>document.documentElement.scrollWidth<=window.innerWidth)).toBeTruthy();await page.getByRole('link',{name:'取文件',exact:true}).click();await expect(page.getByRole('button',{name:'查找文件'})).toBeVisible();expect(await page.evaluate(()=>document.documentElement.scrollWidth<=window.innerWidth)).toBeTruthy() +}) + +test('anonymous visitor can revoke their own parcel without administrator login',async({page})=>{ + await page.goto('/');await page.locator('input[type=file]').setInputFiles({name:'revoke.txt',mimeType:'text/plain',buffer:Buffer.from('revoke me')});await page.getByRole('button',{name:'上传并生成取件码'}).click();await expect(page.getByRole('heading',{name:'文件已寄出'})).toBeVisible();const url=await page.getByRole('textbox',{name:'取件链接'}).inputValue();await page.getByRole('button',{name:'完成',exact:true}).click();await page.getByRole('link',{name:'我的寄件',exact:true}).click();await expect(page.getByText('1 个文件 · 9 B')).toBeVisible();await page.getByRole('button',{name:'撤销',exact:true}).click();await page.getByRole('button',{name:'撤销',exact:true}).last().click();await expect(page.getByText('已撤销',{exact:true})).toBeVisible();await page.goto(url);await expect(page.getByRole('alert')).toContainText('取件信息无效或已失效') +}) + +test('management receipt works after changing anonymous browser session',async({page,browser})=>{ + await page.goto('/');await page.locator('input[type=file]').setInputFiles({name:'managed.txt',mimeType:'text/plain',buffer:Buffer.from('manage with receipt')});await page.getByRole('button',{name:'上传并生成取件码'}).click();await expect(page.getByRole('heading',{name:'文件已寄出'})).toBeVisible();const shareUrl=await page.getByRole('textbox',{name:'取件链接'}).inputValue();await page.getByRole('button',{name:'完成',exact:true}).click();await page.getByRole('link',{name:'我的寄件',exact:true}).click();const exported=page.waitForEvent('download');await page.getByRole('button',{name:'导出管理凭据',exact:true}).click();const receipt=await exported;const contents=readFileSync((await receipt.path())!) + const context=await browser.newContext();const recipient=await context.newPage();await recipient.goto('http://127.0.0.1:18981/#/mine');await recipient.locator('input[type=file]').setInputFiles({name:'receipt.json',mimeType:'application/json',buffer:contents});await expect(recipient.getByText('管理凭据已导入,仅保存在当前标签页')).toBeVisible();await recipient.getByRole('button',{name:'撤销',exact:true}).click();await recipient.getByRole('button',{name:'撤销',exact:true}).last().click();await expect(recipient.getByText('已撤销',{exact:true})).toBeVisible();await recipient.goto(shareUrl);await expect(recipient.getByRole('alert')).toContainText('取件信息无效或已失效');await context.close() +}) + + +test('secure context copies the actual code with Clipboard API',async({page,context})=>{ + await context.grantPermissions(['clipboard-read','clipboard-write']) + await page.goto('/');await page.locator('input[type=file]').setInputFiles({name:'copy-code.txt',mimeType:'text/plain',buffer:Buffer.from('copy code only')});await page.getByRole('button',{name:'上传并生成取件码'}).click();await expect(page.getByRole('heading',{name:'文件已寄出'})).toBeVisible();const code=(await page.locator('.pickup-code').innerText()).replaceAll('-','');await page.getByRole('button',{name:'复制取件码',exact:true}).click();expect(await page.evaluate(()=>navigator.clipboard.readText())).toBe(code);await expect(page.getByText('取件码已复制',{exact:true})).toBeVisible();const icon=await page.request.get('/favicon.ico');expect(icon.status()).toBe(200); +}) diff --git a/web/tests/clipboard.spec.ts b/web/tests/clipboard.spec.ts new file mode 100644 index 0000000..ba47d9f --- /dev/null +++ b/web/tests/clipboard.spec.ts @@ -0,0 +1,98 @@ +import { test, expect, devices, type Page } from '@playwright/test' + +// These tests exercise real browser clipboard operations. Upload responses are +// fixtures; the end-to-end cabinet tests cover actual transfers independently. +async function mockShareUpload(page: Page) { + await page.route('**/api/v1/uploads**', async route => { + const path = new URL(route.request().url()).pathname + if (path.endsWith('/publish')) await route.fulfill({ json: { code: '7K3MP9TX2R', url: 'http://127.0.0.1:18981/#/pickup/7K3MP9TX2R', expires: 2000000000 } }) + else if (path.endsWith('/uploads')) await route.fulfill({ json: { id: 'a'.repeat(32), manage_token: 'b'.repeat(43), files: [{ id: 'c'.repeat(32) }] } }) + else if (route.request().method() === 'PUT') await route.fulfill({ json: {} }) + else await route.abort() + }) +} + +async function openShare(page: Page) { + await mockShareUpload(page) + await page.goto('http://127.0.0.1:18981/') + await page.locator('input[type=file]').setInputFiles({ name: 'clipboard.txt', mimeType: 'text/plain', buffer: Buffer.from('clipboard regression') }) + await page.getByRole('button', { name: '上传并生成取件码' }).click() + await expect(page.getByRole('heading', { name: '文件已寄出' })).toBeVisible() +} + +const profiles = [ + { name: 'desktop HTTP fallback', device: undefined, mode: 'absent' }, + { name: 'Android touch HTTP fallback', device: devices['Pixel 7'], mode: 'absent' }, + // Chromium emulates the iPhone viewport/touch configuration, not Safari's engine. + { name: 'iPhone touch layout HTTP fallback', device: devices['iPhone 13'], mode: 'absent' }, + { name: 'Android touch denied Clipboard API', device: devices['Pixel 7'], mode: 'denied' }, + { name: 'Android touch secure Clipboard API', device: devices['Pixel 7'], mode: 'native' }, +] +for (const profile of profiles) { + test(`actual clipboard copy and paste (${profile.name})`, async ({ browser }) => { + const context = await browser.newContext(profile.device ?? {}) + try { + const page = await context.newPage() + await mockShareUpload(page) + // Localhost exposes Clipboard API. Reproduce its absence on LAN HTTP or its + // rejection without replacing the browser's actual copy/paste operations. + if (profile.mode !== 'native') await page.addInitScript(mode => { + Object.defineProperty(navigator, 'clipboard', { configurable: true, value: mode === 'absent' ? undefined : { writeText: async () => { throw new DOMException('Denied', 'NotAllowedError') } } }) + }, profile.mode) + await page.goto('http://127.0.0.1:18981/') + await page.evaluate(() => { + const input = document.createElement('textarea') + input.id = 'clipboard-seed' + input.value = 'previous clipboard content' + document.body.appendChild(input) + }) + await page.locator('#clipboard-seed').selectText() + await page.keyboard.press('Control+c') + await page.locator('#clipboard-seed').fill('') + await page.keyboard.press('Control+v') + await expect(page.locator('#clipboard-seed')).toHaveValue('previous clipboard content') + await page.locator('#clipboard-seed').evaluate(node => node.remove()) + // Preserve this document's seeded clipboard and upload through the normal UI. + await page.locator('input[type=file]').setInputFiles({ name: 'clipboard.txt', mimeType: 'text/plain', buffer: Buffer.from('clipboard regression') }) + await page.getByRole('button', { name: '上传并生成取件码' }).click() + await expect(page.getByRole('heading', { name: '文件已寄出' })).toBeVisible() + const code = (await page.locator('.pickup-code').innerText()).replaceAll('-', '') + expect(code).toMatch(/^[0-9A-Z]{10}$/) + const url = await page.getByRole('textbox', { name: '取件链接' }).inputValue() + await page.locator('.share-dialog').evaluate(node => { + const input = document.createElement('input') + input.setAttribute('aria-label', '粘贴检查') + node.appendChild(input) + }) + for (const [name, value] of [['复制取件码', code], ['复制取件链接', url]]) { + const button = page.getByRole('button', { name, exact: true }) + if (profile.device) await button.tap(); else await button.click() + await expect(page.getByText(name === '复制取件码' ? '取件码已复制' : '取件链接已复制', { exact: true })).toBeVisible() + const paste = page.getByRole('textbox', { name: '粘贴检查' }) + await paste.fill('') + await paste.focus() + await expect(paste).toBeFocused() + // Native paste reads the actual clipboard, including in touch emulation. + await page.keyboard.press('Control+v') + await expect(paste).toHaveValue(value) + await expect(page.locator('.share-dialog textarea')).toHaveCount(0) + } + expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBeTruthy() + } finally { await context.close() } + }) +} + +test('blocked copy offers manual copying without a success message', async ({ page }) => { + await page.addInitScript(() => { + Object.defineProperty(navigator, 'clipboard', { value: undefined, configurable: true }) + const native = document.execCommand.bind(document) + document.execCommand = (command, showUI, value) => command === 'copy' ? false : native(command, showUI, value) + }) + await openShare(page) + await page.getByRole('button', { name: '复制取件码', exact: true }).click() + await expect(page.getByText('复制未完成,请选择或长按取件码、链接手动复制', { exact: true })).toBeVisible() + await expect(page.getByText('取件码已复制', { exact: true })).toHaveCount(0) + await expect(page.locator('.share-dialog textarea')).toHaveCount(0) + await page.locator('.pickup-code').selectText() + expect(await page.evaluate(() => getSelection()?.toString())).toBe(await page.locator('.pickup-code').innerText()) +}) diff --git a/web/tsconfig.json b/web/tsconfig.json new file mode 100644 index 0000000..56573ed --- /dev/null +++ b/web/tsconfig.json @@ -0,0 +1 @@ +{"compilerOptions":{"target":"ES2022","module":"ESNext","moduleResolution":"Bundler","strict":true,"skipLibCheck":true,"lib":["ES2022","DOM","DOM.Iterable"],"types":["vite/client","node"],"esModuleInterop":true},"include":["src/**/*.ts","src/**/*.vue","vite.config.ts"]} diff --git a/web/vite.config.ts b/web/vite.config.ts new file mode 100644 index 0000000..6e01cb1 --- /dev/null +++ b/web/vite.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +export default defineConfig({ plugins: [vue()], build: { outDir: '../internal/web/dist', emptyOutDir: true }, server: { proxy: { '/api': 'http://127.0.0.1:8080' } } })