123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- package option
- import (
- "encoding/base64"
- "net/http"
- "net/url"
- "regexp"
- "strconv"
- "strings"
- "unicode"
- "github.com/sagernet/sing/common"
- E "github.com/sagernet/sing/common/exceptions"
- F "github.com/sagernet/sing/common/format"
- "github.com/sagernet/sing/common/json"
- )
- type SurgeURLRewriteLine struct {
- Pattern *regexp.Regexp
- Destination *url.URL
- Redirect bool
- Reject bool
- }
- func (l SurgeURLRewriteLine) String() string {
- var fields []string
- fields = append(fields, l.Pattern.String())
- if l.Reject {
- fields = append(fields, "_")
- } else {
- fields = append(fields, l.Destination.String())
- }
- switch {
- case l.Redirect:
- fields = append(fields, "302")
- case l.Reject:
- fields = append(fields, "reject")
- default:
- fields = append(fields, "header")
- }
- return encodeSurgeKeys(fields)
- }
- func (l SurgeURLRewriteLine) MarshalJSON() ([]byte, error) {
- return json.Marshal(l.String())
- }
- func (l *SurgeURLRewriteLine) UnmarshalJSON(bytes []byte) error {
- var stringValue string
- err := json.Unmarshal(bytes, &stringValue)
- if err != nil {
- return err
- }
- fields, err := surgeFields(stringValue)
- if err != nil {
- return E.Cause(err, "invalid surge_url_rewrite line: ", stringValue)
- } else if len(fields) < 2 || len(fields) > 3 {
- return E.New("invalid surge_url_rewrite line: ", stringValue)
- }
- pattern, err := regexp.Compile(fields[0].Key)
- if err != nil {
- return E.Cause(err, "invalid surge_url_rewrite line: invalid pattern: ", stringValue)
- }
- l.Pattern = pattern
- l.Destination, err = url.Parse(fields[1].Key)
- if err != nil {
- return E.Cause(err, "invalid surge_url_rewrite line: invalid destination: ", stringValue)
- }
- if len(fields) == 3 {
- switch fields[2].Key {
- case "header":
- case "302":
- l.Redirect = true
- case "reject":
- l.Reject = true
- default:
- return E.New("invalid surge_url_rewrite line: invalid action: ", stringValue)
- }
- }
- return nil
- }
- type SurgeHeaderRewriteLine struct {
- Response bool
- Pattern *regexp.Regexp
- Add bool
- Delete bool
- Replace bool
- ReplaceRegex bool
- Key string
- Match *regexp.Regexp
- Value string
- }
- func (l SurgeHeaderRewriteLine) String() string {
- var fields []string
- if !l.Response {
- fields = append(fields, "http-request")
- } else {
- fields = append(fields, "http-response")
- }
- fields = append(fields, l.Pattern.String())
- if l.Add {
- fields = append(fields, "header-add")
- } else if l.Delete {
- fields = append(fields, "header-del")
- } else if l.Replace {
- fields = append(fields, "header-replace")
- } else if l.ReplaceRegex {
- fields = append(fields, "header-replace-regex")
- }
- fields = append(fields, l.Key)
- if l.Add || l.Replace {
- fields = append(fields, l.Value)
- } else if l.ReplaceRegex {
- fields = append(fields, l.Match.String(), l.Value)
- }
- return encodeSurgeKeys(fields)
- }
- func (l SurgeHeaderRewriteLine) MarshalJSON() ([]byte, error) {
- return json.Marshal(l.String())
- }
- func (l *SurgeHeaderRewriteLine) UnmarshalJSON(bytes []byte) error {
- var stringValue string
- err := json.Unmarshal(bytes, &stringValue)
- if err != nil {
- return err
- }
- fields, err := surgeFields(stringValue)
- if err != nil {
- return E.Cause(err, "invalid surge_header_rewrite line: ", stringValue)
- } else if len(fields) < 4 {
- return E.New("invalid surge_header_rewrite line: ", stringValue)
- }
- switch fields[0].Key {
- case "http-request":
- case "http-response":
- l.Response = true
- default:
- return E.New("invalid surge_header_rewrite line: invalid type: ", stringValue)
- }
- l.Pattern, err = regexp.Compile(fields[1].Key)
- if err != nil {
- return E.Cause(err, "invalid surge_header_rewrite line: invalid pattern: ", stringValue)
- }
- switch fields[2].Key {
- case "header-add":
- l.Add = true
- if len(fields) != 5 {
- return E.New("invalid surge_header_rewrite line: " + stringValue)
- }
- l.Key = fields[3].Key
- l.Value = fields[4].Key
- case "header-del":
- l.Delete = true
- l.Key = fields[3].Key
- case "header-replace":
- l.Replace = true
- if len(fields) != 5 {
- return E.New("invalid surge_header_rewrite line: " + stringValue)
- }
- l.Key = fields[3].Key
- l.Value = fields[4].Key
- case "header-replace-regex":
- l.ReplaceRegex = true
- if len(fields) != 6 {
- return E.New("invalid surge_header_rewrite line: " + stringValue)
- }
- l.Key = fields[3].Key
- l.Match, err = regexp.Compile(fields[4].Key)
- if err != nil {
- return E.Cause(err, "invalid surge_header_rewrite line: invalid match: ", stringValue)
- }
- l.Value = fields[5].Key
- default:
- return E.New("invalid surge_header_rewrite line: invalid action: ", stringValue)
- }
- return nil
- }
- type SurgeBodyRewriteLine struct {
- Response bool
- Pattern *regexp.Regexp
- Match []*regexp.Regexp
- Replace []string
- }
- func (l SurgeBodyRewriteLine) String() string {
- var fields []string
- if !l.Response {
- fields = append(fields, "http-request")
- } else {
- fields = append(fields, "http-response")
- }
- for i := 0; i < len(l.Match); i += 2 {
- fields = append(fields, l.Match[i].String(), l.Replace[i])
- }
- return strings.Join(fields, " ")
- }
- func (l SurgeBodyRewriteLine) MarshalJSON() ([]byte, error) {
- return json.Marshal(l.String())
- }
- func (l *SurgeBodyRewriteLine) UnmarshalJSON(bytes []byte) error {
- var stringValue string
- err := json.Unmarshal(bytes, &stringValue)
- if err != nil {
- return err
- }
- fields, err := surgeFields(stringValue)
- if err != nil {
- return E.Cause(err, "invalid surge_body_rewrite line: ", stringValue)
- } else if len(fields) < 4 {
- return E.New("invalid surge_body_rewrite line: ", stringValue)
- } else if len(fields)%2 != 0 {
- return E.New("invalid surge_body_rewrite line: ", stringValue)
- }
- switch fields[0].Key {
- case "http-request":
- case "http-response":
- l.Response = true
- default:
- return E.New("invalid surge_body_rewrite line: invalid type: ", stringValue)
- }
- l.Pattern, err = regexp.Compile(fields[1].Key)
- for i := 2; i < len(fields); i += 2 {
- var match *regexp.Regexp
- match, err = regexp.Compile(fields[i].Key)
- if err != nil {
- return E.Cause(err, "invalid surge_body_rewrite line: invalid match: ", stringValue)
- }
- l.Match = append(l.Match, match)
- l.Replace = append(l.Replace, fields[i+1].Key)
- }
- return nil
- }
- type SurgeMapLocalLine struct {
- Pattern *regexp.Regexp
- StatusCode int
- File bool
- Text bool
- TinyGif bool
- Base64 bool
- Data string
- Base64Data []byte
- Headers http.Header
- }
- func (l SurgeMapLocalLine) String() string {
- var fields []surgeField
- fields = append(fields, surgeField{Key: l.Pattern.String()})
- if l.File {
- fields = append(fields, surgeField{Key: "data-type", Value: "file"})
- fields = append(fields, surgeField{Key: "data", Value: l.Data})
- } else if l.Text {
- fields = append(fields, surgeField{Key: "data-type", Value: "text"})
- fields = append(fields, surgeField{Key: "data", Value: l.Data})
- } else if l.TinyGif {
- fields = append(fields, surgeField{Key: "data-type", Value: "tiny-gif"})
- } else if l.Base64 {
- fields = append(fields, surgeField{Key: "data-type", Value: "base64"})
- fields = append(fields, surgeField{Key: "data-type", Value: base64.StdEncoding.EncodeToString(l.Base64Data)})
- }
- if l.StatusCode != 0 {
- fields = append(fields, surgeField{Key: "status-code", Value: F.ToString(l.StatusCode), ValueSet: true})
- }
- if len(l.Headers) > 0 {
- var headers []string
- for key, values := range l.Headers {
- for _, value := range values {
- headers = append(headers, key+":"+value)
- }
- }
- fields = append(fields, surgeField{Key: "headers", Value: strings.Join(headers, "|")})
- }
- return encodeSurgeFields(fields)
- }
- func (l SurgeMapLocalLine) MarshalJSON() ([]byte, error) {
- return json.Marshal(l.String())
- }
- func (l *SurgeMapLocalLine) UnmarshalJSON(bytes []byte) error {
- var stringValue string
- err := json.Unmarshal(bytes, &stringValue)
- if err != nil {
- return err
- }
- fields, err := surgeFields(stringValue)
- if err != nil {
- return E.Cause(err, "invalid surge_map_local line: ", stringValue)
- } else if len(fields) < 1 {
- return E.New("invalid surge_map_local line: ", stringValue)
- }
- l.Pattern, err = regexp.Compile(fields[0].Key)
- if err != nil {
- return E.Cause(err, "invalid surge_map_local line: invalid pattern: ", stringValue)
- }
- dataTypeField := common.Find(fields, func(it surgeField) bool {
- return it.Key == "data-type"
- })
- if !dataTypeField.ValueSet {
- return E.New("invalid surge_map_local line: missing data-type: ", stringValue)
- }
- switch dataTypeField.Value {
- case "file":
- l.File = true
- case "text":
- l.Text = true
- case "tiny-gif":
- l.TinyGif = true
- case "base64":
- l.Base64 = true
- default:
- return E.New("unsupported data-type ", dataTypeField.Value)
- }
- for i := 1; i < len(fields); i++ {
- switch fields[i].Key {
- case "data-type":
- continue
- case "data":
- if l.File {
- l.Data = fields[i].Value
- } else if l.Text {
- l.Data = fields[i].Value
- } else if l.Base64 {
- l.Base64Data, err = base64.StdEncoding.DecodeString(fields[i].Value)
- if err != nil {
- return E.New("invalid surge_map_local line: invalid base64 data: ", stringValue)
- }
- }
- case "status-code":
- statusCode, err := strconv.ParseInt(fields[i].Value, 10, 16)
- if err != nil {
- return E.New("invalid surge_map_local line: invalid status code: ", stringValue)
- }
- l.StatusCode = int(statusCode)
- case "header":
- headers := make(http.Header)
- for _, headerLine := range strings.Split(fields[i].Value, "|") {
- if !strings.Contains(headerLine, ":") {
- return E.New("invalid surge_map_local line: headers: missing `:` in item: ", stringValue, ": ", headerLine)
- }
- headers.Add(common.SubstringBefore(headerLine, ":"), common.SubstringAfter(headerLine, ":"))
- }
- l.Headers = headers
- default:
- return E.New("invalid surge_map_local line: unknown options: ", fields[i].Key)
- }
- }
- return nil
- }
- type surgeField struct {
- Key string
- Value string
- ValueSet bool
- }
- func encodeSurgeKeys(keys []string) string {
- keys = common.Map(keys, func(it string) string {
- if strings.ContainsFunc(it, unicode.IsSpace) {
- return "\"" + it + "\""
- } else {
- return it
- }
- })
- return strings.Join(keys, " ")
- }
- func encodeSurgeFields(fields []surgeField) string {
- return strings.Join(common.Map(fields, func(it surgeField) string {
- if !it.ValueSet {
- if strings.ContainsFunc(it.Key, unicode.IsSpace) {
- return "\"" + it.Key + "\""
- } else {
- return it.Key
- }
- } else {
- if strings.ContainsFunc(it.Value, unicode.IsSpace) {
- return it.Key + "=\"" + it.Value + "\""
- } else {
- return it.Key + "=" + it.Value
- }
- }
- }), " ")
- }
- func surgeFields(s string) ([]surgeField, error) {
- var (
- fields []surgeField
- currentField *surgeField
- )
- for _, field := range strings.Fields(s) {
- if currentField != nil {
- field = " " + field
- if strings.HasSuffix(field, "\"") {
- field = field[:len(field)-1]
- if !currentField.ValueSet {
- currentField.Key += field
- } else {
- currentField.Value += field
- }
- fields = append(fields, *currentField)
- currentField = nil
- } else {
- if !currentField.ValueSet {
- currentField.Key += field
- } else {
- currentField.Value += field
- }
- }
- continue
- }
- if !strings.Contains(field, "=") {
- if strings.HasPrefix(field, "\"") {
- field = field[1:]
- if strings.HasSuffix(field, "\"") {
- field = field[:len(field)-1]
- } else {
- currentField = &surgeField{Key: field}
- continue
- }
- }
- fields = append(fields, surgeField{Key: field})
- } else {
- key := common.SubstringBefore(field, "=")
- value := common.SubstringAfter(field, "=")
- if strings.HasPrefix(value, "\"") {
- value = value[1:]
- if strings.HasSuffix(field, "\"") {
- value = value[:len(value)-1]
- } else {
- currentField = &surgeField{Key: key, Value: value, ValueSet: true}
- continue
- }
- }
- fields = append(fields, surgeField{Key: key, Value: value, ValueSet: true})
- }
- }
- if currentField != nil {
- return nil, E.New("invalid surge fields line: ", s)
- }
- return fields, nil
- }
|