mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 06:47:20 +08:00
430 lines
12 KiB
Go
430 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
|
||
}
|
||
|
||
if nextToken.Type == NEWLINE {
|
||
continue
|
||
}
|
||
|
||
if nextToken.Type == DIRECTIVE {
|
||
directive, err := p.parseDirective(nextToken)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
block.Directives = append(block.Directives, directive)
|
||
} else if nextToken.Type == COMMENT {
|
||
// 处理块内注释
|
||
comment := &Comment{
|
||
Text: nextToken.Value,
|
||
Line: nextToken.Line,
|
||
Column: nextToken.Column,
|
||
}
|
||
block.Comments = append(block.Comments, comment)
|
||
}
|
||
}
|
||
|
||
return block, nil
|
||
}
|