Files
panel/pkg/webserver/apache/parser.go
T
2025-12-01 16:29:37 +08:00

431 lines
12 KiB
Go

package apache
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
// Parser Apache 配置文件解析器
type Parser struct {
lexer *Lexer
options *ParseOptions
includeStack []string // 用于检测循环包含
currentDepth int // 当前包含深度
currentBaseDir string // 当前文件的基础目录
}
// NewParser 创建一个新的 Apache 配置解析器(使用默认选项)
func NewParser(input io.Reader) (*Parser, error) {
return NewParserWithOptions(input, DefaultParseOptions())
}
// NewParserWithOptions 创建一个带选项的 Apache 配置解析器
func NewParserWithOptions(input io.Reader, options *ParseOptions) (*Parser, error) {
lexer, err := NewLexer(input)
if err != nil {
return nil, err
}
return &Parser{
lexer: lexer,
options: options,
includeStack: make([]string, 0),
currentDepth: 0,
currentBaseDir: options.BaseDir,
}, nil
}
// Parse 解析 Apache 配置文件并返回 AST
func (p *Parser) Parse() (*Config, error) {
config := &Config{
Directives: make([]*Directive, 0),
VirtualHosts: make([]*VirtualHost, 0),
Comments: make([]*Comment, 0),
}
for {
token := p.lexer.NextToken()
if token.Type == EOF {
break
}
switch token.Type {
case COMMENT:
comment := &Comment{
Text: token.Value,
Line: token.Line,
Column: token.Column,
}
config.Comments = append(config.Comments, comment)
case DIRECTIVE:
directive, err := p.parseDirective(token)
if err != nil {
return nil, fmt.Errorf("error parsing directive: %w", err)
}
// 检查是否是Include类指令
if p.options.ProcessIncludes && (strings.EqualFold(directive.Name, "Include") || strings.EqualFold(directive.Name, "IncludeOptional")) {
includeConfig, err := p.processInclude(directive)
if err != nil {
// 对于IncludeOptional,如果文件不存在则忽略错误
if strings.EqualFold(directive.Name, "IncludeOptional") && os.IsNotExist(err) {
// 仍然记录Include指令,但不合并内容
include := &Include{
Path: directive.Args[0],
Line: directive.Line,
Column: directive.Column,
}
config.Includes = append(config.Includes, include)
continue
}
return nil, fmt.Errorf("error processing include file '%s': %w", directive.Args[0], err)
}
if includeConfig != nil {
// 合并包含的配置到当前配置
config.Directives = append(config.Directives, includeConfig.Directives...)
config.VirtualHosts = append(config.VirtualHosts, includeConfig.VirtualHosts...)
config.Comments = append(config.Comments, includeConfig.Comments...)
config.Includes = append(config.Includes, includeConfig.Includes...)
}
// 记录Include指令
include := &Include{
Path: directive.Args[0],
Line: directive.Line,
Column: directive.Column,
}
config.Includes = append(config.Includes, include)
} else {
config.Directives = append(config.Directives, directive)
}
case VIRTUALHOST:
vhost, err := p.parseVirtualHost(token)
if err != nil {
return nil, fmt.Errorf("error parsing virtual host: %w", err)
}
config.VirtualHosts = append(config.VirtualHosts, vhost)
case BLOCKDIRECTIVE:
block, err := p.parseBlockDirective(token)
if err != nil {
return nil, fmt.Errorf("error parsing block directive: %w", err)
}
// 将块指令作为带Block的Directive添加到配置中
directive := &Directive{
Name: block.Type,
Args: block.Args,
Line: block.Line,
Column: block.Column,
Block: block,
}
config.Directives = append(config.Directives, directive)
case NEWLINE:
// 跳过换行符
continue
case ILLEGAL:
return nil, fmt.Errorf("invalid syntax: '%s' at line %d, column %d", token.Value, token.Line, token.Column)
default:
return nil, fmt.Errorf("unknown token type: %v at line %d, column %d", token.Type, token.Line, token.Column)
}
}
return config, nil
}
// parseDirective 解析单个指令
func (p *Parser) parseDirective(token Token) (*Directive, error) {
directive := &Directive{
Name: token.Value,
Line: token.Line,
Column: token.Column,
Args: make([]string, 0),
}
// 读取指令参数
for {
nextToken := p.lexer.PeekToken()
if nextToken.Type == NEWLINE || nextToken.Type == EOF {
break
}
argToken := p.lexer.NextToken()
if argToken.Type == STRING || argToken.Type == DIRECTIVE {
directive.Args = append(directive.Args, argToken.Value)
}
}
return directive, nil
}
// parseVirtualHost 解析虚拟主机配置
func (p *Parser) parseVirtualHost(token Token) (*VirtualHost, error) {
// 从 token.Value 中提取虚拟主机名称和参数
parts := strings.Fields(token.Value)
vhost := &VirtualHost{
Name: "VirtualHost",
Line: token.Line,
Column: token.Column,
Directives: make([]*Directive, 0),
Comments: make([]*Comment, 0),
}
// 如果有参数,添加到 Args 中
if len(parts) > 1 {
vhost.Args = parts[1:]
}
// 跳过换行符到虚拟主机内容
for {
nextToken := p.lexer.NextToken()
if nextToken.Type == EOF {
return nil, fmt.Errorf("unexpected end of virtual host")
}
// 检查结束标签(可能是VIRTUALHOST或DIRECTIVE类型)
if (nextToken.Type == VIRTUALHOST && strings.HasPrefix(nextToken.Value, "/VirtualHost")) ||
(nextToken.Type == DIRECTIVE && strings.HasPrefix(nextToken.Value, "/VirtualHost")) {
// 遇到结束标签
break
}
if nextToken.Type == NEWLINE {
continue
}
if nextToken.Type == DIRECTIVE {
directive, err := p.parseDirective(nextToken)
if err != nil {
return nil, err
}
// 检查是否是Include类指令
if p.options.ProcessIncludes && (strings.EqualFold(directive.Name, "Include") || strings.EqualFold(directive.Name, "IncludeOptional")) {
includeConfig, err := p.processInclude(directive)
if err != nil {
// 对于IncludeOptional,如果文件不存在则忽略错误
if strings.EqualFold(directive.Name, "IncludeOptional") && os.IsNotExist(err) {
continue
}
return nil, fmt.Errorf("error processing include file '%s': %w", directive.Args[0], err)
}
if includeConfig != nil {
// 合并包含的配置到虚拟主机
vhost.Directives = append(vhost.Directives, includeConfig.Directives...)
vhost.Comments = append(vhost.Comments, includeConfig.Comments...)
// 注意:虚拟主机内的Include不应该包含其他虚拟主机,但如果包含了也要处理
if len(includeConfig.VirtualHosts) > 0 {
return nil, fmt.Errorf("include files inside virtual host cannot contain other virtual hosts")
}
}
} else {
vhost.Directives = append(vhost.Directives, directive)
}
} else if nextToken.Type == COMMENT {
// 收集虚拟主机内的注释
comment := &Comment{
Text: nextToken.Value,
Line: nextToken.Line,
Column: nextToken.Column,
}
vhost.Comments = append(vhost.Comments, comment)
}
}
return vhost, nil
}
// ParseFile 从文件解析 Apache 配置
func ParseFile(filename string) (*Config, error) {
file, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("failed to open file: %w", err)
}
defer func(file *os.File) { _ = file.Close() }(file)
parser, err := NewParser(file)
if err != nil {
return nil, err
}
return parser.Parse()
}
// ParseString 从字符串解析 Apache 配置
func ParseString(content string) (*Config, error) {
reader := strings.NewReader(content)
parser, err := NewParser(reader)
if err != nil {
return nil, err
}
return parser.Parse()
}
// ParseStringWithOptions 从字符串解析 Apache 配置(带选项)
func ParseStringWithOptions(content string, options *ParseOptions) (*Config, error) {
reader := strings.NewReader(content)
parser, err := NewParserWithOptions(reader, options)
if err != nil {
return nil, err
}
return parser.Parse()
}
// ParseFileWithOptions 从文件解析 Apache 配置(带选项)
func ParseFileWithOptions(filename string, options *ParseOptions) (*Config, error) {
file, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("failed to open file: %w", err)
}
defer func(file *os.File) { _ = file.Close() }(file)
// 设置基础目录为文件所在目录
if options.BaseDir == "" {
options.BaseDir = filepath.Dir(filename)
}
parser, err := NewParserWithOptions(file, options)
if err != nil {
return nil, err
}
// 设置当前文件路径用于循环检测
absPath, _ := filepath.Abs(filename)
parser.includeStack = append(parser.includeStack, absPath)
return parser.Parse()
}
// processInclude 处理Include指令
func (p *Parser) processInclude(directive *Directive) (*Config, error) {
if len(directive.Args) == 0 {
return nil, fmt.Errorf("include directive missing file path argument")
}
// 检查递归深度
if p.currentDepth >= p.options.MaxIncludeDepth {
return nil, fmt.Errorf("include nesting depth exceeds limit %d", p.options.MaxIncludeDepth)
}
includePath := directive.Args[0]
// 解析包含文件的完整路径
var fullPath string
var err error
if filepath.IsAbs(includePath) {
fullPath = includePath
} else {
// 相对路径基于当前文件所在目录
fullPath = filepath.Join(p.currentBaseDir, includePath)
}
// 获取绝对路径用于循环检测
absPath, err := filepath.Abs(fullPath)
if err != nil {
return nil, fmt.Errorf("failed to get absolute path of file '%s': %w", fullPath, err)
}
// 检查循环包含
for _, stackPath := range p.includeStack {
if stackPath == absPath {
return nil, fmt.Errorf("circular include detected: %s", absPath)
}
}
// 检查文件是否存在
if _, err := os.Stat(fullPath); err != nil {
return nil, err
}
// 打开并解析包含的文件
file, err := os.Open(fullPath)
if err != nil {
return nil, fmt.Errorf("failed to open include file: %w", err)
}
defer func(file *os.File) { _ = file.Close() }(file)
// 创建新的解析器选项,继承当前选项但更新基础目录
includeOptions := *p.options
includeOptions.BaseDir = filepath.Dir(fullPath)
// 创建新的解析器实例
includeParser, err := NewParserWithOptions(file, &includeOptions)
if err != nil {
return nil, fmt.Errorf("failed to create include file parser: %w", err)
}
// 设置包含解析器的状态
includeParser.includeStack = append(p.includeStack, absPath)
includeParser.currentDepth = p.currentDepth + 1
includeParser.currentBaseDir = filepath.Dir(fullPath)
// 解析包含的文件
return includeParser.Parse()
}
// parseBlockDirective 解析块指令(如Directory, Location等)
func (p *Parser) parseBlockDirective(token Token) (*Block, error) {
// 从token.Value中提取块类型和参数
parts := strings.Fields(token.Value)
block := &Block{
Type: parts[0],
Line: token.Line,
Column: token.Column,
Directives: make([]*Directive, 0),
Comments: make([]*Comment, 0),
}
// 如果有参数,添加到Args中
if len(parts) > 1 {
block.Args = parts[1:]
}
// 跳过换行符到块内容
for {
nextToken := p.lexer.NextToken()
if nextToken.Type == EOF {
return nil, fmt.Errorf("unexpected end of block directive")
}
// 检查结束标签
if (nextToken.Type == BLOCKDIRECTIVE && strings.HasPrefix(nextToken.Value, "/"+block.Type)) ||
(nextToken.Type == DIRECTIVE && strings.HasPrefix(nextToken.Value, "/"+block.Type)) {
// 遇到结束标签
break
}
switch nextToken.Type {
case NEWLINE:
continue
case DIRECTIVE:
directive, err := p.parseDirective(nextToken)
if err != nil {
return nil, err
}
block.Directives = append(block.Directives, directive)
case COMMENT:
// 处理块内注释
comment := &Comment{
Text: nextToken.Value,
Line: nextToken.Line,
Column: nextToken.Column,
}
block.Comments = append(block.Comments, comment)
default:
continue
}
}
return block, nil
}