@@ -68,6 +68,8 @@ func FsListSplit(c *gin.Context) {
6868 SharingList (c , & req )
6969 return
7070 }
71+ // 虚拟主机路径重映射:根据 Host 头匹配虚拟主机规则,将请求路径映射到实际路径
72+ req .Path = applyVhostPathMapping (c , req .Path )
7173 user := c .Request .Context ().Value (conf .UserKey ).(* model.User )
7274 if user .IsGuest () && user .Disabled {
7375 common .ErrorStrResp (c , "Guest user is disabled, login please" , 401 )
@@ -273,6 +275,11 @@ func FsGetSplit(c *gin.Context) {
273275 SharingGet (c , & req )
274276 return
275277 }
278+ // 虚拟主机路径重映射:根据 Host 头匹配虚拟主机规则,将请求路径映射到实际路径
279+ // 同时将 vhost.Path 前缀存入 context,供 FsGet 生成 /p/ 链接时去掉前缀
280+ var vhostPrefix string
281+ req .Path , vhostPrefix = applyVhostPathMappingWithPrefix (c , req .Path )
282+ common .GinWithValue (c , conf .VhostPrefixKey , vhostPrefix )
276283 user := c .Request .Context ().Value (conf .UserKey ).(* model.User )
277284 if user .IsGuest () && user .Disabled {
278285 common .ErrorStrResp (c , "Guest user is disabled, login please" , 401 )
@@ -322,12 +329,14 @@ func FsGet(c *gin.Context, req *FsGetReq, user *model.User) {
322329 rawURL = common .GenerateDownProxyURL (storage .GetStorage (), reqPath )
323330 if rawURL == "" {
324331 query := ""
332+ // 生成 /p/ 链接时,去掉 vhost 路径前缀,保持前端看到的路径一致
333+ downPath := stripVhostPrefix (c , reqPath )
325334 if isEncrypt (meta , reqPath ) || setting .GetBool (conf .SignAll ) {
326335 query = "?sign=" + sign .Sign (reqPath )
327336 }
328337 rawURL = fmt .Sprintf ("%s/p%s%s" ,
329338 common .GetApiUrl (c ),
330- utils .EncodePath (reqPath , true ),
339+ utils .EncodePath (downPath , true ),
331340 query )
332341 }
333342 } else {
@@ -432,3 +441,60 @@ func FsOther(c *gin.Context) {
432441 }
433442 common .SuccessResp (c , res )
434443}
444+
445+ // applyVhostPathMapping 根据请求的 Host 头匹配虚拟主机规则,将请求路径映射到实际路径。
446+ func applyVhostPathMapping (c * gin.Context , reqPath string ) string {
447+ mapped , _ := applyVhostPathMappingWithPrefix (c , reqPath )
448+ return mapped
449+ }
450+
451+ // applyVhostPathMappingWithPrefix 根据请求的 Host 头匹配虚拟主机规则,
452+ // 将请求路径映射到虚拟主机配置的实际路径,同时返回 vhost.Path 前缀(用于生成下载链接时去掉前缀)。
453+ // 例如:vhost.Path="/123pan/Downloads",reqPath="/",则返回 ("/123pan/Downloads", "/123pan/Downloads")
454+ // 例如:vhost.Path="/123pan/Downloads",reqPath="/subdir",则返回 ("/123pan/Downloads/subdir", "/123pan/Downloads")
455+ // 如果没有匹配的虚拟主机规则,则返回 (原始路径, "")
456+ func applyVhostPathMappingWithPrefix (c * gin.Context , reqPath string ) (string , string ) {
457+ rawHost := c .Request .Host
458+ domain := stripHostPortForVhost (rawHost )
459+ if domain == "" {
460+ return reqPath , ""
461+ }
462+ vhost , err := op .GetVirtualHostByDomain (domain )
463+ if err != nil || vhost == nil {
464+ return reqPath , ""
465+ }
466+ if ! vhost .Enabled || vhost .WebHosting {
467+ // 未启用,或者是 Web 托管模式(Web 托管不做路径重映射)
468+ return reqPath , ""
469+ }
470+ // 路径重映射:将 reqPath 拼接到 vhost.Path 后面
471+ mapped := stdpath .Join (vhost .Path , reqPath )
472+ utils .Log .Debugf ("[VirtualHost] API path remapping: domain=%q reqPath=%q -> mappedPath=%q" , domain , reqPath , mapped )
473+ return mapped , vhost .Path
474+ }
475+
476+ // stripVhostPrefix 从 gin context 中取出 vhost 路径前缀,并从 path 中去掉该前缀。
477+ // 用于生成 /p/ 下载链接时,将真实路径还原为前端看到的路径。
478+ func stripVhostPrefix (c * gin.Context , path string ) string {
479+ prefix , ok := c .Request .Context ().Value (conf .VhostPrefixKey ).(string )
480+ if ! ok || prefix == "" {
481+ return path
482+ }
483+ if strings .HasPrefix (path , prefix + "/" ) {
484+ return path [len (prefix ):]
485+ }
486+ if path == prefix {
487+ return "/"
488+ }
489+ return path
490+ }
491+
492+ // stripHostPortForVhost 去掉 host 中的端口号,返回纯域名
493+ func stripHostPortForVhost (host string ) string {
494+ if idx := strings .LastIndex (host , ":" ); idx != - 1 {
495+ if ! strings .Contains (host , "[" ) {
496+ return host [:idx ]
497+ }
498+ }
499+ return host
500+ }
0 commit comments