hysteria/extras/outbounds/acl/parse.go
2023-08-13 22:04:21 -07:00

79 lines
1.8 KiB
Go

package acl
import (
"fmt"
"os"
"regexp"
"strings"
)
var linePattern = regexp.MustCompile(`^(\w+)\s*\(([^,]+)(?:,([^,]+))?(?:,([^,]+))?\)$`)
type InvalidSyntaxError struct {
Line string
LineNum int
}
func (e *InvalidSyntaxError) Error() string {
return fmt.Sprintf("invalid syntax at line %d: %s", e.LineNum, e.Line)
}
// TextRule is the struct representation of a (non-comment) line parsed from an ACL file.
// A line can be parsed into a TextRule as long as it matches one of the following patterns:
//
// outbound(address)
// outbound(address,protoPort)
// outbound(address,protoPort,hijackAddress)
//
// It does not check whether any of the fields is valid - it's up to the compiler to do so.
type TextRule struct {
Outbound string
Address string
ProtoPort string
HijackAddress string
}
func parseLine(line string) *TextRule {
matches := linePattern.FindStringSubmatch(line)
if matches == nil {
return nil
}
return &TextRule{
Outbound: matches[1],
Address: strings.TrimSpace(matches[2]),
ProtoPort: strings.TrimSpace(matches[3]),
HijackAddress: strings.TrimSpace(matches[4]),
}
}
func ParseTextRules(text string) ([]TextRule, error) {
rules := make([]TextRule, 0)
lineNum := 0
for _, line := range strings.Split(text, "\n") {
lineNum++
// Remove comments
if i := strings.Index(line, "#"); i >= 0 {
line = line[:i]
}
line = strings.TrimSpace(line)
// Skip empty lines
if len(line) == 0 {
continue
}
// Parse line
rule := parseLine(line)
if rule == nil {
return nil, &InvalidSyntaxError{line, lineNum}
}
rules = append(rules, *rule)
}
return rules, nil
}
func ParseTextRulesFile(filename string) ([]TextRule, error) {
bs, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
return ParseTextRules(string(bs))
}