mirror of
https://github.com/alireza0/s-ui.git
synced 2026-10-21 11:22:26 +00:00
sub: add support for custom subscription headers
This commit is contained in:
parent
8586674477
commit
9637e006c8
3 changed files with 139 additions and 0 deletions
|
|
@ -91,6 +91,7 @@ var defaultValueMap = map[string]string{
|
|||
"subURI": "",
|
||||
"subJsonExt": "",
|
||||
"subClashExt": "",
|
||||
"subCustomHeaders": defaultCustomHeaders,
|
||||
"subClashNoDefGrp": "false",
|
||||
"subClashSprtAll": "false",
|
||||
"subClashUdp": "false",
|
||||
|
|
@ -540,6 +541,10 @@ func (s *SettingService) GetSubClashExt() (string, error) {
|
|||
return s.getString("subClashExt")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubCustomHeaders() (string, error) {
|
||||
return s.getString("subCustomHeaders")
|
||||
}
|
||||
|
||||
// GetSubClashNoDefGrp reports whether the default "Proxy"/"Auto" proxy-groups
|
||||
// should never be injected into a Clash subscription. When true, the config is
|
||||
// left with exactly the groups the user defined.
|
||||
|
|
@ -576,3 +581,13 @@ func (s *SettingService) fileExists(path string) error {
|
|||
_, err := os.Stat(path)
|
||||
return err
|
||||
}
|
||||
|
||||
var defaultCustomHeaders = `# Custom Subscription Headers
|
||||
# Syntax:
|
||||
# Header-Name: Value
|
||||
#
|
||||
# Lines starting with # are comments and will be ignored.
|
||||
#
|
||||
# Example:
|
||||
# announce: 42!
|
||||
# announce-url: https://42.example/`
|
||||
|
|
|
|||
|
|
@ -79,6 +79,52 @@ func (s *SubHandler) addHeaders(c *gin.Context, headers []string) {
|
|||
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
|
||||
c.Writer.Header().Set("Profile-Title", headers[2])
|
||||
c.Writer.Header().Set("Content-Disposition", contentDispositionHeader(headers[2]))
|
||||
|
||||
s.applyCustomHeaders(c)
|
||||
}
|
||||
|
||||
type customHeader struct {
|
||||
key string
|
||||
value string
|
||||
}
|
||||
|
||||
func parseCustomHeaders(raw string) []customHeader {
|
||||
if len(strings.TrimSpace(raw)) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var headers []customHeader
|
||||
lines := strings.Split(strings.ReplaceAll(raw, "\r\n", "\n"), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "//") {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(parts[0])
|
||||
val := strings.TrimSpace(parts[1])
|
||||
|
||||
if key != "" && val != "" {
|
||||
headers = append(headers, customHeader{key: key, value: val})
|
||||
}
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func (s *SubHandler) applyCustomHeaders(c *gin.Context) {
|
||||
customHeadersStr, err := s.SettingService.GetSubCustomHeaders()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, h := range parseCustomHeaders(customHeadersStr) {
|
||||
c.Writer.Header().Set(h.key, h.value)
|
||||
}
|
||||
}
|
||||
|
||||
func contentDispositionHeader(name string) string {
|
||||
|
|
|
|||
78
sub/subHandler_test.go
Normal file
78
sub/subHandler_test.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package sub
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseCustomHeaders(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
in string
|
||||
want []customHeader
|
||||
}{
|
||||
{
|
||||
name: "empty string",
|
||||
in: "",
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "whitespace only",
|
||||
in: " \n\t \r\n ",
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "comments test",
|
||||
in: `# 424242`,
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "valid headers with extra spaces",
|
||||
in: `
|
||||
announce : 42!
|
||||
announce-url: t.me/4242
|
||||
`,
|
||||
want: []customHeader{
|
||||
{key: "announce", value: "42!"},
|
||||
{key: "announce-url", value: "t.me/4242"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "urls containing colons and ports are preserved",
|
||||
in: "Profile-Web-Page-Url: https://sub.example.com:8443/login?redirect=https://a.com",
|
||||
want: []customHeader{
|
||||
{key: "Profile-Web-Page-Url", value: "https://sub.example.com:8443/login?redirect=https://a.com"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "windows crlf line endings and inline comments",
|
||||
in: "# header 1\r\nX-Custom: value1\r\n// comment 2\r\nX-Other: value2\r\n",
|
||||
want: []customHeader{
|
||||
{key: "X-Custom", value: "value1"},
|
||||
{key: "X-Other", value: "value2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "malformed lines are safely skipped without panic",
|
||||
in: `
|
||||
random text without colon
|
||||
: missing key
|
||||
empty value:
|
||||
:
|
||||
valid-key: ok
|
||||
`,
|
||||
want: []customHeader{
|
||||
{key: "valid-key", value: "ok"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := parseCustomHeaders(tc.in)
|
||||
if !reflect.DeepEqual(got, tc.want) {
|
||||
t.Errorf("parseCustomHeaders() mismatch for %q:\n got: %+v\n want: %+v", tc.name, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue