diff --git a/pkg/config/v1/server.go b/pkg/config/v1/server.go index 54aac080..11e397e0 100644 --- a/pkg/config/v1/server.go +++ b/pkg/config/v1/server.go @@ -99,6 +99,8 @@ type ServerConfig struct { AllowPorts []types.PortsRange `json:"allowPorts,omitempty"` HTTPPlugins []HTTPPluginOptions `json:"httpPlugins,omitempty"` + + CustomResponse *CustomResponseConfig `json:"customResponse,omitempty" toml:"customResponse" yaml:"customResponse"` } func (c *ServerConfig) Complete() error { @@ -227,3 +229,16 @@ type SSHTunnelGateway struct { func (c *SSHTunnelGateway) Complete() { c.AutoGenPrivateKeyPath = util.EmptyOr(c.AutoGenPrivateKeyPath, "./.autogen_ssh_key") } + +type CustomResponseConfig struct { + Enable bool `toml:"enable" json:"enable" yaml:"enable"` + Rules []CustomResponseRule `toml:"rules" json:"rules" yaml:"rules"` +} + +type CustomResponseRule struct { + Hostname []string `toml:"hostname" json:"hostname" yaml:"hostname"` + StatusCode int `toml:"statusCode" json:"statusCode" yaml:"statusCode"` + ContentType string `toml:"contentType" json:"contentType" yaml:"contentType"` + Body string `toml:"body" json:"body" yaml:"body"` + Headers map[string]string `toml:"headers" json:"headers" yaml:"headers"` +} diff --git a/pkg/util/http/http.go b/pkg/util/http/http.go index b85a46a3..e4c2f5b6 100644 --- a/pkg/util/http/http.go +++ b/pkg/util/http/http.go @@ -100,3 +100,26 @@ func BasicAuth(username, passwd string) string { auth := username + ":" + passwd return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) } + +// MatchDomain checks if the request host matches the config host. +// The config host can be a wildcard domain like "*.example.com". +func MatchDomain(requestHost, configHost string) bool { + if len(requestHost) == 0 || len(configHost) == 0 { + return false + } + if configHost == "*" { + return true + } + if requestHost == configHost { + return true + } + if strings.HasPrefix(configHost, "*.") { + // The config host is a wildcard domain like "*.example.com". + // We need to check if the request host ends with the config host. + suffix := configHost[1:] // Remove '*' + if strings.HasSuffix(requestHost, suffix) && len(requestHost) > len(suffix) { + return true + } + } + return false +} diff --git a/pkg/util/vhost/http.go b/pkg/util/vhost/http.go index 05ec174b..de26ca3f 100644 --- a/pkg/util/vhost/http.go +++ b/pkg/util/vhost/http.go @@ -40,6 +40,7 @@ var ErrNoRouteFound = errors.New("no route found") type HTTPReverseProxyOptions struct { ResponseHeaderTimeoutS int64 + CustomErrorPage *CustomErrorPage } type HTTPReverseProxy struct { @@ -136,6 +137,10 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) * return } } + customResponseStatus := CustomErrorResponse(rw, req, option.CustomErrorPage) + if customResponseStatus { + return + } rw.WriteHeader(http.StatusNotFound) _, _ = rw.Write(getNotFoundPageContent()) }, diff --git a/pkg/util/vhost/resource.go b/pkg/util/vhost/resource.go index a65e2997..5c55777a 100644 --- a/pkg/util/vhost/resource.go +++ b/pkg/util/vhost/resource.go @@ -20,11 +20,26 @@ import ( "net/http" "os" + httppkg "github.com/fatedier/frp/pkg/util/http" "github.com/fatedier/frp/pkg/util/log" "github.com/fatedier/frp/pkg/util/version" ) +type CustomErrorPage struct { + Enable bool + Rules []CustomResponseRule +} + +type CustomResponseRule struct { + Hostname []string + StatusCode int + ContentType string + Body string + Headers map[string]string +} + var NotFoundPagePath = "" +var ServiceUnavailablePagePath = "" const ( NotFound = ` @@ -47,6 +62,27 @@ Please try again later.
Faithfully yours, frp.