Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds full Virtual Host (vhost) support, enabling domain-based path remapping for the SPA and an optional “Web Hosting” mode that serves static site files (e.g., index.html) directly from a configured backend path.
Changes:
- Add vhost CRUD APIs and persistence (model/db/op + admin routes).
- Implement vhost-aware path remapping for FS APIs and download/preview routes, including stripping vhost prefixes when generating
/p/...links. - Add vhost-aware static catch-all handling, including a Web Hosting file-serving path with forced
Content-Type/Content-Disposition.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| server/static/static.go | Adds vhost-aware catch-all handler and Web Hosting file proxying with header overrides. |
| server/router.go | Adds /api/admin/vhost/* routes for vhost management. |
| server/middlewares/virtual_host.go | Introduces a vhost context middleware (currently not wired). |
| server/middlewares/down.go | Applies vhost path remapping for /d//p style routes. |
| server/handles/virtual_host.go | Adds admin handlers for listing/getting/creating/updating/deleting vhosts. |
| server/handles/fsread.go | Applies vhost path remapping to fs/list and fs/get, and strips vhost prefix when generating /p links. |
| internal/op/virtual_host.go | Adds cached vhost lookup and CRUD wrappers with cache invalidation. |
| internal/model/virtual_host.go | Adds VirtualHost model. |
| internal/db/virtual_host.go | Adds DB layer CRUD for vhosts. |
| internal/db/db.go | Adds VirtualHost to AutoMigrate. |
| internal/conf/const.go | Adds context keys for vhost and vhost-prefix propagation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Pikachu Ren <40362270+PIKACHUIM@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Pikachu Ren <40362270+PIKACHUIM@users.noreply.github.com>
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as outdated.
This comment was marked as outdated.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Pikachu Ren <40362270+PIKACHUIM@users.noreply.github.com>
|
@PIKACHUIM I've opened a new pull request, #2212, to work on those changes. Once the pull request is ready, I'll request review from you. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
* Initial plan * Fix vhost security and code quality issues from review feedback Co-authored-by: PIKACHUIM <40362270+PIKACHUIM@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: PIKACHUIM <40362270+PIKACHUIM@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 13 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
这个功能是不是相当于一种另类的分享,做成分享的子功能会不会好点 |
我认为不是,定位的是类似于Lucky或者Netpanel的网站托管 进一步,如果启用了静态页面托管,会直接返回/path/path/index.html |
我知道这个功能是网站托管,我理解的是这两个功能都是面向不特定人群,提供某个路径的公开下载服务,只不过一个访问的是
改设计为
这样这个功能就只用处理路由和响应头相关的一些问题,也不用加一张表以及与之配套的CRUD,还可以充分的利用分享的限时、限次数访问功能,由于二者权限方面的模型及其相近,也不容易写出权限方面的bug,真有这方面bug修一个等于修了俩 |
嗯,我来看下能不能合并一下 |
a31fd53 to
7bea29c
Compare
Signed-off-by: Pikachu Ren <40362270+PIKACHUIM@users.noreply.github.com>
…into dev-vhost
# Conflicts: # server/middlewares/down.go
Summary / 摘要
实现虚拟主机(Virtual Host)功能的完整支持,包含两种工作模式:
模式一:路径重映射(Domain 填写,Web Hosting 关闭)
将指定域名的访问路径透明映射到后端真实路径,实现"伪静态"效果:
http://example.com/,地址栏保持不变,面包屑显示 🏠Home//api/fs/list、/api/fs/get)自动将请求路径映射到sharing.Files[0](如/123pan/Downloads)/p/、/d/)自动去掉 vhost 路径前缀,保持前端路径一致性,避免路径重复叠加模式二:Web Hosting(Domain 填写 + Web Hosting 开启)
将指定域名作为静态网站托管,直接返回 index.html 等静态文件内容:
index.html>index.htm>index.mhtml>index.md>default.htm>default.html>default.mhtml>default.md>readme.html>readme.htm>readme.mhtml>readme.mdforceContentTypeWriter包装器强制覆盖响应头中的 Content-Type,确保 HTML 文件在浏览器中正确渲染而非触发下载vhostInternalUser到请求 context,解决文件访问时的 401 权限问题.md文件服务端渲染预览(marked.js + DOMPurify).mhtml文件浏览器原生预览安全增强:
HasPrefix沙箱校验/api/admin/、/dav/、/s3/等管理路由Architecture / 架构原理图
请求处理流程
flowchart TD A[浏览器请求] --> B{gin Router} B --> C{VhostRouteGuard 中间件} C -->|Host 匹配 vhost 域名| D{WebHosting?} C -->|Host 不匹配| E[正常路由处理] C -->|"管理路由 /api/admin/ /dav/ /s3/"| F[404 拦截] D -->|WebHosting = true| G[virtualHostHandler] D -->|WebHosting = false| H[virtualHostHandler] G --> G1[访问码门禁 handleSharePwdGate] G1 -->|未通过| G2["密码输入页 / 403"] G1 -->|通过| G3[注入 vhostInternalUser] G3 --> G4[handleWebHosting] G4 --> G5{文件存在?} G5 -->|是| G6["serveWebHostingFile<br/>forceContentTypeWriter"] G5 -->|否| G7{indexCandidates 匹配?} G7 -->|命中| G6 G7 -->|全部未命中| G8["404"] H --> H1[访问码门禁 handleSharePwdGate] H1 -->|未通过| H2["密码输入页 / 403"] H1 -->|通过| H3[返回 SPA IndexHtml] H3 --> H4[浏览器加载 SPA] H4 --> H5["/api/fs/list 请求"] H5 --> H6[auth 中间件填入 user] H6 --> H7["applyVhostPathMapping<br/>路径重映射到 sharing.Files[0]"] H7 --> H8[返回文件列表]路径重映射模式数据流
sequenceDiagram participant Browser as 浏览器 participant Gin as Gin Router participant VHost as virtualHostHandler participant Auth as Auth 中间件 participant FSRead as fsread.go participant Storage as 存储后端 Browser->>Gin: GET http://vhost.test/ Gin->>VHost: NoRoute → virtualHostHandler VHost->>VHost: Host 匹配 sharing (WebHosting=false) VHost->>Browser: 200 SPA HTML (IndexHtml) Browser->>Gin: POST /api/fs/list (path: /) Gin->>Auth: Auth 中间件 → 填入 guest user Auth->>FSRead: FsListSplit handler FSRead->>FSRead: applyVhostPathMapping<br/>/ → /123pan/Downloads FSRead->>Storage: fs.List(ctx, /123pan/Downloads) Storage->>FSRead: 文件列表 FSRead->>FSRead: stripVhostPrefix 去掉前缀 FSRead->>Browser: 返回文件列表 Browser->>Gin: GET /p/filename sign=xxx Gin->>FSRead: PathParse applyDownVhostPathMapping FSRead->>FSRead: /filename → /123pan/Downloads/filename FSRead->>Storage: 获取文件链接 Storage->>Browser: 文件内容Web Hosting 模式数据流
sequenceDiagram participant Browser as 浏览器 participant Gin as Gin Router participant VHost as virtualHostHandler participant WH as handleWebHosting participant FS as internalfs participant Storage as 存储后端 Browser->>Gin: GET http://vhost.test/ Gin->>VHost: NoRoute → virtualHostHandler VHost->>VHost: Host 匹配 sharing (WebHosting=true) VHost->>VHost: 注入 vhostInternalUser VHost->>WH: handleWebHosting WH->>FS: internalfs.Get /root/index.html FS->>Storage: 查询文件 Storage->>FS: 文件对象 FS->>WH: obj 非目录 WH->>FS: internalfs.Link /root/index.html FS->>Storage: 获取下载链接 Storage->>FS: link WH->>WH: forceContentTypeWriter Content-Type text/html WH->>Browser: 200 HTML 内容安全架构
flowchart LR subgraph 入口防护 A1[VhostRouteGuard<br/>阻止管理路由] A2[Domain 格式校验<br/>ToLower + 正则] end subgraph 访问控制 B1[handleSharePwdGate<br/>访问码门禁] B2[HMAC-SHA256 Cookie<br/>非明文存储] B3[ConstantTimeCompare<br/>防时序侧信道] end subgraph 沙箱隔离 C1[HasPrefix 路径校验<br/>防穿越] C2[vhostInternalUser<br/>受限系统用户] C3[sharing.Files 0 根目录<br/>不可逃逸] end subgraph 响应安全 D1[X-Content-Type-Options<br/>nosniff] D2[Referrer-Policy<br/>strict-origin] D3[Cache-Control<br/>按类型区分] end A1 --> B1 --> C1 --> D1Motivation and Context / 背景与动机
原有虚拟主机功能存在以下问题:
too many redirects错误history.replaceState注入脚本修改地址栏,不符合"伪静态"语义Content-Type覆盖了正确的 MIME 类型,导致index.html被当作application/octet-stream下载forceContentTypeWriter在响应阶段强制覆盖正确的 Content-Typeinternalfs.Get/Link时,context 中缺少用户信息vhostInternalUser(非 nil、非 Disabled 的系统用户)/p/链接经过中间件再次映射后路径翻倍stripVhostPrefix在生成链接前去掉前缀;VhostPrefixKey通过 context 传递(*model.User)(nil)到 context,下游 hook 可能解引用 panicvhostInternalUser实例(Role=GUEST, Permission=0x7FFF)Vhost.Test,浏览器请求vhost.test,匹配失败ToLower + TrimSpace归一化,查询时同样归一化/api/admin/、/dav/等仍可访问VhostRouteGuard中间件拦截Description / 详细描述
核心设计决策
复用 Sharing 模型:虚拟主机配置直接挂载在
SharingDB上(新增Domain+WebHosting字段),无需新建表,复用已有的过期、禁用、访问计数等能力。双模式分发:
virtualHostHandler作为 gin NoRoute 的 catch-all handler,根据sharing.WebHosting字段决定走 Web Hosting 还是路径重映射。vhostInternalUser:定义一个
ID=0, Username="_vhost_internal", Role=GUEST, Permission=0x7FFF, BasePath="/", Disabled=false的系统用户,仅在 Web Hosting 模式注入 context。实际访问范围由handleWebHosting的HasPrefix沙箱保证。缓存策略:
domainSharingCache按域名缓存 sharing 对象(正向 1h,负向 5min),通过singleflight防击穿。Create/Update/Delete 成功后才失效缓存。访问码门禁:cookie 存储
HMAC-SHA256(sharingID + ":" + pwd, salt)而非明文密码,即使 cookie 泄露也无法反推原始密码。主要变更列表:
internal/model/sharing.go:新增Domain、WebHosting字段及ValidForVhost()方法internal/op/sharing.go:新增GetSharingByDomain()带缓存查询(含负缓存防穿透)internal/db/sharing.go:新增GetSharingByDomain()DB 查询 + Create/Update 时域名唯一性校验server/static/static.go:virtualHostHandler实现双模式分发 +handleWebHosting+forceContentTypeWriterserver/static/share_pwd.go:新增 访问码门禁完整实现server/static/render.go:新增 Markdown 服务端预览渲染server/handles/fsread.go:applyVhostPathMappingAPI 路径重映射 +stripVhostPrefix下载链接修正server/handles/sharing.go:normalizeDomain()域名归一化与格式校验server/middlewares/down.go:applyDownVhostPathMapping下载路由路径重映射server/middlewares/vhost.go:新增VhostRouteGuard路由守卫中间件server/common/common.go:新增StripHostPort工具函数internal/conf/const.go:新增VhostPrefixKeycontext keyThis PR has breaking changes.
/ 此 PR 包含破坏性变更。
This PR changes public API, config, storage format, or migration behavior.
/ 此 PR 修改了公开 API、配置、存储格式或迁移行为。
This PR requires corresponding changes in related repositories.
/ 此 PR 需要关联仓库同步修改。
破坏性变更说明:
SharingDB新增Domain string和WebHosting bool字段,AutoMigrate 会自动添加列Domain字段使用普通index(非 uniqueIndex),唯一性由应用层校验,避免已有空字符串记录在 MySQL 下冲突Related repository PRs / 关联仓库 PR:
dev-vhost分支(新增 Domain / WebHosting UI 字段)Related Issues / 关联 Issue
Relates to virtual host / web hosting feature request.
Testing / 测试
go test ./...测试步骤:
localhost,映射路径/123pan/Downloadshttp://localhost:5244/,验证:http://localhost:5244/不变 ✅/123pan/Downloads的内容 ✅/subdir,文件列表正确显示/123pan/Downloads/subdir的内容 ✅/p/filename,不含 vhost 路径前缀 ✅index.html到映射路径:/api/admin/setting/list:Checklist / 检查清单
/ 我已阅读 CONTRIBUTING。
/ 我确认此贡献符合仓库许可证、贡献规范和行为准则。
gofmt,go fmt, orprettierwhere applicable./ 我已按适用情况使用
gofmt、go fmt或prettier格式化变更代码。/ 我已在适用情况下请求相关维护者或代码所有者审查。
AI Disclosure / AI 使用声明
/ 此 PR 包含 AI 辅助内容。
Tools used / 使用工具:
Usage scope / 使用范围:
/ 我已审核并验证此 PR 中的所有 AI 辅助内容。
Co-Authored-Byattribution./ 我已确保所有 AI 辅助提交都包含
Co-Authored-By归属信息。/ 我可以在没有任何 AI 工具的情况下重现此 PR 中包含的所有 AI 辅助内容。