-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
feat(func): support virtual host #2202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
12bd5a0
8a878fa
38f761f
0b54be2
a3b1029
166af74
d825769
8489950
bedf0a9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -191,4 +191,6 @@ const ( | |
| PathKey | ||
| SharingIDKey | ||
| SkipHookKey | ||
| VirtualHostKey | ||
| VhostPrefixKey | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package db | ||
|
|
||
| import ( | ||
| "github.com/OpenListTeam/OpenList/v4/internal/model" | ||
| "github.com/pkg/errors" | ||
| ) | ||
|
|
||
| func GetVirtualHostByDomain(domain string) (*model.VirtualHost, error) { | ||
| var v model.VirtualHost | ||
| if err := db.Where("domain = ?", domain).First(&v).Error; err != nil { | ||
| return nil, errors.Wrapf(err, "failed to select virtual host") | ||
| } | ||
| return &v, nil | ||
| } | ||
|
|
||
| func GetVirtualHostById(id uint) (*model.VirtualHost, error) { | ||
| var v model.VirtualHost | ||
| if err := db.First(&v, id).Error; err != nil { | ||
| return nil, errors.Wrapf(err, "failed get virtual host") | ||
| } | ||
| return &v, nil | ||
| } | ||
|
|
||
| func CreateVirtualHost(v *model.VirtualHost) error { | ||
| return errors.WithStack(db.Create(v).Error) | ||
| } | ||
|
|
||
| func UpdateVirtualHost(v *model.VirtualHost) error { | ||
| return errors.WithStack(db.Save(v).Error) | ||
| } | ||
|
|
||
| func GetVirtualHosts(pageIndex, pageSize int) (vhosts []model.VirtualHost, count int64, err error) { | ||
| vhostDB := db.Model(&model.VirtualHost{}) | ||
| if err = vhostDB.Count(&count).Error; err != nil { | ||
| return nil, 0, errors.Wrapf(err, "failed get virtual hosts count") | ||
| } | ||
| if err = vhostDB.Order(columnName("id")).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&vhosts).Error; err != nil { | ||
| return nil, 0, errors.Wrapf(err, "failed find virtual hosts") | ||
| } | ||
| return vhosts, count, nil | ||
| } | ||
|
|
||
| func DeleteVirtualHostById(id uint) error { | ||
| return errors.WithStack(db.Delete(&model.VirtualHost{}, id).Error) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package model | ||
|
|
||
| type VirtualHost struct { | ||
| ID uint `json:"id" gorm:"primaryKey"` | ||
| Enabled bool `json:"enabled"` | ||
| Domain string `json:"domain" gorm:"unique" binding:"required"` | ||
| Path string `json:"path" binding:"required"` | ||
| WebHosting bool `json:"web_hosting"` | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| package op | ||
|
|
||
| import ( | ||
| "time" | ||
|
|
||
| "github.com/OpenListTeam/OpenList/v4/internal/db" | ||
| "github.com/OpenListTeam/OpenList/v4/internal/model" | ||
| "github.com/OpenListTeam/OpenList/v4/pkg/utils" | ||
| "github.com/OpenListTeam/go-cache" | ||
| "github.com/pkg/errors" | ||
| "gorm.io/gorm" | ||
| ) | ||
|
|
||
| var vhostCache = cache.NewMemCache(cache.WithShards[*model.VirtualHost](2)) | ||
|
|
||
| // GetVirtualHostByDomain 根据域名获取虚拟主机配置(带缓存) | ||
| func GetVirtualHostByDomain(domain string) (*model.VirtualHost, error) { | ||
| if v, ok := vhostCache.Get(domain); ok { | ||
| if v == nil { | ||
| utils.Log.Debugf("[VirtualHost] cache hit (nil) for domain=%q", domain) | ||
| return nil, errors.New("virtual host not found") | ||
| } | ||
| utils.Log.Debugf("[VirtualHost] cache hit for domain=%q id=%d", domain, v.ID) | ||
| return v, nil | ||
| } | ||
| utils.Log.Debugf("[VirtualHost] cache miss for domain=%q, querying db...", domain) | ||
| v, err := db.GetVirtualHostByDomain(domain) | ||
| if err != nil { | ||
| if errors.Is(errors.Cause(err), gorm.ErrRecordNotFound) { | ||
| utils.Log.Debugf("[VirtualHost] domain=%q not found in db, caching nil", domain) | ||
| vhostCache.Set(domain, nil, cache.WithEx[*model.VirtualHost](time.Minute*5)) | ||
| return nil, errors.New("virtual host not found") | ||
| } | ||
| utils.Log.Errorf("[VirtualHost] db error for domain=%q: %v", domain, err) | ||
| return nil, err | ||
| } | ||
| utils.Log.Debugf("[VirtualHost] db found domain=%q id=%d enabled=%v web_hosting=%v", domain, v.ID, v.Enabled, v.WebHosting) | ||
| vhostCache.Set(domain, v, cache.WithEx[*model.VirtualHost](time.Hour)) | ||
| return v, nil | ||
| } | ||
|
|
||
| func GetVirtualHostById(id uint) (*model.VirtualHost, error) { | ||
| return db.GetVirtualHostById(id) | ||
| } | ||
|
|
||
| func CreateVirtualHost(v *model.VirtualHost) error { | ||
| v.Path = utils.FixAndCleanPath(v.Path) | ||
| vhostCache.Del(v.Domain) | ||
| return db.CreateVirtualHost(v) | ||
| } | ||
|
|
||
| func UpdateVirtualHost(v *model.VirtualHost) error { | ||
| v.Path = utils.FixAndCleanPath(v.Path) | ||
| old, err := db.GetVirtualHostById(v.ID) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // 如果域名变更,清除旧域名缓存 | ||
| vhostCache.Del(old.Domain) | ||
| vhostCache.Del(v.Domain) | ||
| return db.UpdateVirtualHost(v) | ||
|
Comment on lines
+46
to
+61
|
||
| } | ||
|
|
||
| func DeleteVirtualHostById(id uint) error { | ||
| old, err := db.GetVirtualHostById(id) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| vhostCache.Del(old.Domain) | ||
| return db.DeleteVirtualHostById(id) | ||
| } | ||
|
|
||
| func GetVirtualHosts(pageIndex, pageSize int) ([]model.VirtualHost, int64, error) { | ||
| return db.GetVirtualHosts(pageIndex, pageSize) | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,6 +2,7 @@ package handles | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "net" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stdpath "path" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "time" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -69,6 +70,8 @@ func FsListSplit(c *gin.Context) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SharingList(c, &req) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 虚拟主机路径重映射:根据 Host 头匹配虚拟主机规则,将请求路径映射到实际路径 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| req.Path = applyVhostPathMapping(c, req.Path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user := c.Request.Context().Value(conf.UserKey).(*model.User) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if user.IsGuest() && user.Disabled { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| common.ErrorStrResp(c, "Guest user is disabled, login please", 401) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -272,6 +275,11 @@ func FsGetSplit(c *gin.Context) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SharingGet(c, &req) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 虚拟主机路径重映射:根据 Host 头匹配虚拟主机规则,将请求路径映射到实际路径 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 同时将 vhost.Path 前缀存入 context,供 FsGet 生成 /p/ 链接时去掉前缀 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var vhostPrefix string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| req.Path, vhostPrefix = applyVhostPathMappingWithPrefix(c, req.Path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| common.GinWithValue(c, conf.VhostPrefixKey, vhostPrefix) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user := c.Request.Context().Value(conf.UserKey).(*model.User) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if user.IsGuest() && user.Disabled { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| common.ErrorStrResp(c, "Guest user is disabled, login please", 401) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -319,12 +327,14 @@ func FsGet(c *gin.Context, req *FsGetReq, user *model.User) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rawURL = common.GenerateDownProxyURL(storage.GetStorage(), reqPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if rawURL == "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| query := "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 生成 /p/ 链接时,去掉 vhost 路径前缀,保持前端看到的路径一致 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| downPath := stripVhostPrefix(c, reqPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isEncrypt(meta, reqPath) || setting.GetBool(conf.SignAll) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| query = "?sign=" + sign.Sign(reqPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rawURL = fmt.Sprintf("%s/p%s%s", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| common.GetApiUrl(c), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| utils.EncodePath(reqPath, true), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| utils.EncodePath(downPath, true), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| query) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -427,3 +437,66 @@ func FsOther(c *gin.Context) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| common.SuccessResp(c, res) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // applyVhostPathMapping 根据请求的 Host 头匹配虚拟主机规则,将请求路径映射到实际路径。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func applyVhostPathMapping(c *gin.Context, reqPath string) string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mapped, _ := applyVhostPathMappingWithPrefix(c, reqPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return mapped | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // applyVhostPathMappingWithPrefix 根据请求的 Host 头匹配虚拟主机规则, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 将请求路径映射到虚拟主机配置的实际路径,同时返回 vhost.Path 前缀(用于生成下载链接时去掉前缀)。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 例如:vhost.Path="/123pan/Downloads",reqPath="/",则返回 ("/123pan/Downloads", "/123pan/Downloads") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 例如:vhost.Path="/123pan/Downloads",reqPath="/subdir",则返回 ("/123pan/Downloads/subdir", "/123pan/Downloads") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 如果没有匹配的虚拟主机规则,则返回 (原始路径, "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func applyVhostPathMappingWithPrefix(c *gin.Context, reqPath string) (string, string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rawHost := c.Request.Host | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| domain := stripHostPortForVhost(rawHost) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if domain == "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return reqPath, "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vhost, err := op.GetVirtualHostByDomain(domain) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil || vhost == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return reqPath, "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !vhost.Enabled || vhost.WebHosting { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 未启用,或者是 Web 托管模式(Web 托管不做路径重映射) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return reqPath, "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Map request path into the vhost root and verify it does not escape via traversal. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // stdpath.Join calls Clean internally, which collapses ".." segments, so we only need | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // to confirm the result still lives under vhost.Path. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mapped := stdpath.Join(vhost.Path, reqPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !strings.HasPrefix(mapped, strings.TrimRight(vhost.Path, "/")+"/") && mapped != vhost.Path { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| utils.Log.Warnf("[VirtualHost] path traversal rejected for API remapping: domain=%q reqPath=%q", domain, reqPath) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return reqPath, "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| utils.Log.Debugf("[VirtualHost] API path remapping: domain=%q reqPath=%q -> mappedPath=%q", domain, reqPath, mapped) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+462
to
+474
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return mapped, vhost.Path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+465
to
+477
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | |
| // 路径重映射:将 reqPath 拼接到 vhost.Path 后面 | |
| mapped := stdpath.Join(vhost.Path, reqPath) | |
| utils.Log.Debugf("[VirtualHost] API path remapping: domain=%q reqPath=%q -> mappedPath=%q", domain, reqPath, mapped) | |
| return mapped, vhost.Path | |
| } | |
| } | |
| // 安全检查:在进行路径拼接之前,确保请求路径中没有 ".." 段,避免通过 path.Join 清理后绕过上层的相对路径检测。 | |
| if !isSafeVhostReqPath(reqPath) { | |
| utils.Log.Warnf("[VirtualHost] Suspicious path detected, skip vhost remapping: domain=%q reqPath=%q", domain, reqPath) | |
| // 返回原始路径并不携带 vhost 前缀,让后续的基础路径校验逻辑基于原始路径继续处理。 | |
| return reqPath, "" | |
| } | |
| // 路径重映射:将 reqPath 拼接到 vhost.Path 后面 | |
| mapped := stdpath.Join(vhost.Path, reqPath) | |
| utils.Log.Debugf("[VirtualHost] API path remapping: domain=%q reqPath=%q -> mappedPath=%q", domain, reqPath, mapped) | |
| return mapped, vhost.Path | |
| } | |
| // isSafeVhostReqPath 检查虚拟主机请求路径中是否包含 ".." 段,以防止目录遍历。 | |
| // 仅当某个路径段恰好为 ".." 时视为不安全,例如 "/a/../b"。 | |
| func isSafeVhostReqPath(p string) bool { | |
| if p == "" { | |
| return true | |
| } | |
| for _, seg := range strings.Split(p, "/") { | |
| if seg == ".." { | |
| return false | |
| } | |
| } | |
| return true | |
| } |
Copilot
AI
Mar 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are now three near-identical helpers for stripping the port from Host (stripHostPort, stripDownHostPort, stripHostPortForVhost). This duplication makes it easy for them to drift (e.g. future normalization tweaks). Consider centralizing this logic in a shared helper (e.g. under server/common or pkg/utils) and reusing it across static/handles/middlewares.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| package handles | ||
|
|
||
| import ( | ||
| "strconv" | ||
|
|
||
| "github.com/OpenListTeam/OpenList/v4/internal/model" | ||
| "github.com/OpenListTeam/OpenList/v4/internal/op" | ||
| "github.com/OpenListTeam/OpenList/v4/server/common" | ||
| "github.com/gin-gonic/gin" | ||
| ) | ||
|
|
||
| func ListVirtualHosts(c *gin.Context) { | ||
| var req model.PageReq | ||
| if err := c.ShouldBind(&req); err != nil { | ||
| common.ErrorResp(c, err, 400) | ||
| return | ||
| } | ||
| req.Validate() | ||
| vhosts, total, err := op.GetVirtualHosts(req.Page, req.PerPage) | ||
| if err != nil { | ||
| common.ErrorResp(c, err, 500, true) | ||
| return | ||
| } | ||
| common.SuccessResp(c, common.PageResp{ | ||
| Content: vhosts, | ||
| Total: total, | ||
| }) | ||
| } | ||
|
|
||
| func GetVirtualHost(c *gin.Context) { | ||
| idStr := c.Query("id") | ||
| id, err := strconv.Atoi(idStr) | ||
| if err != nil { | ||
| common.ErrorResp(c, err, 400) | ||
| return | ||
| } | ||
| vhost, err := op.GetVirtualHostById(uint(id)) | ||
| if err != nil { | ||
| common.ErrorResp(c, err, 500, true) | ||
| return | ||
| } | ||
| common.SuccessResp(c, vhost) | ||
| } | ||
|
|
||
| func CreateVirtualHost(c *gin.Context) { | ||
| var req model.VirtualHost | ||
| if err := c.ShouldBind(&req); err != nil { | ||
| common.ErrorResp(c, err, 400) | ||
| return | ||
| } | ||
| if err := op.CreateVirtualHost(&req); err != nil { | ||
| common.ErrorResp(c, err, 500, true) | ||
| } else { | ||
| common.SuccessResp(c) | ||
| } | ||
| } | ||
|
|
||
| func UpdateVirtualHost(c *gin.Context) { | ||
| var req model.VirtualHost | ||
| if err := c.ShouldBind(&req); err != nil { | ||
| common.ErrorResp(c, err, 400) | ||
| return | ||
| } | ||
| if err := op.UpdateVirtualHost(&req); err != nil { | ||
| common.ErrorResp(c, err, 500, true) | ||
| } else { | ||
| common.SuccessResp(c) | ||
| } | ||
| } | ||
|
|
||
| func DeleteVirtualHost(c *gin.Context) { | ||
| idStr := c.Query("id") | ||
| id, err := strconv.Atoi(idStr) | ||
| if err != nil { | ||
| common.ErrorResp(c, err, 400) | ||
| return | ||
| } | ||
| if err := op.DeleteVirtualHostById(uint(id)); err != nil { | ||
| common.ErrorResp(c, err, 500, true) | ||
| return | ||
| } | ||
| common.SuccessResp(c) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Domain matching/caching is currently case-sensitive (
vhostCache.Get(domain)/ DB query use the rawHostvalue). DNS hostnames are case-insensitive, so requests likeExample.comvsexample.commay miss the cache/DB record depending on how the domain was stored. Normalize domains (e.g.,strings.ToLower) consistently inGetVirtualHostByDomainand when creating/updatingVirtualHost.Domain.