From a1ac689a5e9051b4317c8d1e69816807f461fbd7 Mon Sep 17 00:00:00 2001 From: zhengkunwang223 Date: Mon, 24 Oct 2022 23:06:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0nginx=20config=20?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/utils/common/common.go | 5 + backend/utils/nginx/components/block.go | 50 +++++ backend/utils/nginx/components/comment.go | 21 ++ backend/utils/nginx/components/config.go | 28 +++ backend/utils/nginx/components/directive.go | 24 ++ backend/utils/nginx/components/http.go | 93 ++++++++ backend/utils/nginx/components/location.go | 32 +++ backend/utils/nginx/components/server.go | 107 +++++++++ .../utils/nginx/components/server_listen.go | 68 ++++++ backend/utils/nginx/components/statement.go | 24 ++ backend/utils/nginx/components/upstream.go | 94 ++++++++ .../utils/nginx/components/upstream_server.go | 77 +++++++ backend/utils/nginx/dumper.go | 132 +++++++++++ backend/utils/nginx/nginx.go | 14 ++ backend/utils/nginx/parser/flag/flag.go | 82 +++++++ backend/utils/nginx/parser/lexer.go | 209 ++++++++++++++++++ backend/utils/nginx/parser/parser.go | 172 ++++++++++++++ 17 files changed, 1232 insertions(+) create mode 100644 backend/utils/nginx/components/block.go create mode 100644 backend/utils/nginx/components/comment.go create mode 100644 backend/utils/nginx/components/config.go create mode 100644 backend/utils/nginx/components/directive.go create mode 100644 backend/utils/nginx/components/http.go create mode 100644 backend/utils/nginx/components/location.go create mode 100644 backend/utils/nginx/components/server.go create mode 100644 backend/utils/nginx/components/server_listen.go create mode 100644 backend/utils/nginx/components/statement.go create mode 100644 backend/utils/nginx/components/upstream.go create mode 100644 backend/utils/nginx/components/upstream_server.go create mode 100644 backend/utils/nginx/dumper.go create mode 100644 backend/utils/nginx/nginx.go create mode 100644 backend/utils/nginx/parser/flag/flag.go create mode 100644 backend/utils/nginx/parser/lexer.go create mode 100644 backend/utils/nginx/parser/parser.go diff --git a/backend/utils/common/common.go b/backend/utils/common/common.go index edcfe1f5d..e91a9d3bd 100644 --- a/backend/utils/common/common.go +++ b/backend/utils/common/common.go @@ -99,3 +99,8 @@ func ExistWithStrArray(str string, arr []string) bool { } return false } + +func IsNum(s string) bool { + _, err := strconv.ParseFloat(s, 64) + return err == nil +} diff --git a/backend/utils/nginx/components/block.go b/backend/utils/nginx/components/block.go new file mode 100644 index 000000000..3b48d4593 --- /dev/null +++ b/backend/utils/nginx/components/block.go @@ -0,0 +1,50 @@ +package components + +type Block struct { + Comment string + Directives []IDirective +} + +func (b *Block) GetDirectives() []IDirective { + return b.Directives +} + +func (b *Block) GetComment() string { + return b.Comment +} + +func (b *Block) FindDirectives(directiveName string) []IDirective { + directives := make([]IDirective, 0) + for _, directive := range b.GetDirectives() { + if directive.GetName() == directiveName { + directives = append(directives, directive) + } + if directive.GetBlock() != nil { + directives = append(directives, directive.GetBlock().FindDirectives(directiveName)...) + } + } + + return directives +} + +func (b *Block) UpdateDirectives(directiveName string, directive Directive) { + directives := make([]IDirective, len(b.GetDirectives())) + index := -1 + for i, dir := range b.GetDirectives() { + if dir.GetName() == directiveName { + index = i + break + } + } + if index > -1 { + directives[index] = &directive + } else { + directives = append(directives, &directive) + } + b.Directives = directives +} + +func (b *Block) AddDirectives(directive Directive) { + directives := append(b.GetDirectives(), &directive) + b.Directives = directives +} diff --git a/backend/utils/nginx/components/comment.go b/backend/utils/nginx/components/comment.go new file mode 100644 index 000000000..014092aca --- /dev/null +++ b/backend/utils/nginx/components/comment.go @@ -0,0 +1,21 @@ +package components + +type Comment struct { + Detail string +} + +func (c *Comment) GetName() string { + return "" +} + +func (c *Comment) GetParameters() []string { + return []string{} +} + +func (c *Comment) GetBlock() IBlock { + return nil +} + +func (c *Comment) GetComment() string { + return c.Detail +} diff --git a/backend/utils/nginx/components/config.go b/backend/utils/nginx/components/config.go new file mode 100644 index 000000000..342ddbb7c --- /dev/null +++ b/backend/utils/nginx/components/config.go @@ -0,0 +1,28 @@ +package components + +type Config struct { + *Block + FilePath string +} + +func (c *Config) FindDirectives(directiveName string) []IDirective { + return c.Block.FindDirectives(directiveName) +} + +func (c *Config) FindUpstreams() []*Upstream { + var upstreams []*Upstream + directives := c.Block.FindDirectives("upstream") + for _, directive := range directives { + upstreams = append(upstreams, directive.(*Upstream)) + } + return upstreams +} + +func (c *Config) FindServers() []*Server { + var servers []*Server + directives := c.Block.FindDirectives("server") + for _, directive := range directives { + servers = append(servers, directive.(*Server)) + } + return servers +} diff --git a/backend/utils/nginx/components/directive.go b/backend/utils/nginx/components/directive.go new file mode 100644 index 000000000..04e63ea1e --- /dev/null +++ b/backend/utils/nginx/components/directive.go @@ -0,0 +1,24 @@ +package components + +type Directive struct { + Block IBlock + Name string + Comment string + Parameters []string +} + +func (d *Directive) GetComment() string { + return d.Comment +} + +func (d *Directive) GetName() string { + return d.Name +} + +func (d *Directive) GetParameters() []string { + return d.Parameters +} + +func (d *Directive) GetBlock() IBlock { + return d.Block +} diff --git a/backend/utils/nginx/components/http.go b/backend/utils/nginx/components/http.go new file mode 100644 index 000000000..dea2c3604 --- /dev/null +++ b/backend/utils/nginx/components/http.go @@ -0,0 +1,93 @@ +package components + +import ( + "errors" +) + +type Http struct { + Comment string + Servers []*Server + Directives []IDirective +} + +func (h *Http) GetComment() string { + return h.Comment +} + +func NewHttp(directive IDirective) (*Http, error) { + if block := directive.GetBlock(); block != nil { + http := &Http{ + Servers: []*Server{}, + Directives: []IDirective{}, + Comment: block.GetComment(), + } + + for _, directive := range block.GetDirectives() { + if server, ok := directive.(*Server); ok { + http.Servers = append(http.Servers, server) + continue + } + http.Directives = append(http.Directives, directive) + } + + return http, nil + } + return nil, errors.New("http directive must have a block") +} + +func (h *Http) GetName() string { + return "http" +} + +func (h *Http) GetParameters() []string { + return []string{} +} + +func (h *Http) GetDirectives() []IDirective { + directives := make([]IDirective, 0) + directives = append(directives, h.Directives...) + for _, directive := range h.Servers { + directives = append(directives, directive) + } + return directives +} + +func (h *Http) FindDirectives(directiveName string) []IDirective { + directives := make([]IDirective, 0) + for _, directive := range h.GetDirectives() { + if directive.GetName() == directiveName { + directives = append(directives, directive) + } + if directive.GetBlock() != nil { + directives = append(directives, directive.GetBlock().FindDirectives(directiveName)...) + } + } + + return directives +} + +func (h *Http) UpdateDirectives(directiveName string, directive Directive) { + directives := make([]IDirective, len(h.GetDirectives())) + index := -1 + for i, dir := range h.GetDirectives() { + if dir.GetName() == directiveName { + index = i + break + } + } + if index > -1 { + directives[index] = &directive + } else { + directives = append(directives, &directive) + } + h.Directives = directives +} + +func (h *Http) AddDirectives(directive Directive) { + directives := append(h.GetDirectives(), &directive) + h.Directives = directives +} + +func (h *Http) GetBlock() IBlock { + return h +} diff --git a/backend/utils/nginx/components/location.go b/backend/utils/nginx/components/location.go new file mode 100644 index 000000000..f3d585af0 --- /dev/null +++ b/backend/utils/nginx/components/location.go @@ -0,0 +1,32 @@ +package components + +type Location struct { + *Directive + Modifier string + Match string +} + +func NewLocation(directive *Directive) *Location { + location := &Location{ + Modifier: "", + Match: "", + Directive: directive, + } + if directive.GetBlock() != nil { + directive.Comment = directive.GetBlock().GetComment() + } + + if len(directive.Parameters) == 0 { + panic("no enough parameter for location") + } + + if len(directive.Parameters) == 1 { + location.Match = directive.Parameters[0] + return location + } else if len(directive.Parameters) == 2 { + location.Modifier = directive.Parameters[0] + location.Match = directive.Parameters[1] + return location + } + return nil +} diff --git a/backend/utils/nginx/components/server.go b/backend/utils/nginx/components/server.go new file mode 100644 index 000000000..cf65f3b47 --- /dev/null +++ b/backend/utils/nginx/components/server.go @@ -0,0 +1,107 @@ +package components + +import ( + "errors" +) + +type Server struct { + Comment string + Listens []*ServerListen + Directives []IDirective +} + +func NewServer(directive IDirective) (*Server, error) { + server := &Server{} + if block := directive.GetBlock(); block != nil { + server.Comment = block.GetComment() + directives := block.GetDirectives() + for _, dir := range directives { + if dir.GetName() == "listen" { + server.Listens = append(server.Listens, NewServerListen(dir.GetParameters())) + } else { + server.Directives = append(server.Directives, dir) + } + } + return server, nil + } + return nil, errors.New("server directive must have a block") +} + +func (s *Server) GetName() string { + return "server" +} + +func (s *Server) GetParameters() []string { + return []string{} +} + +func (s *Server) GetBlock() IBlock { + return s +} + +func (s *Server) GetComment() string { + return s.Comment +} + +func (s *Server) GetDirectives() []IDirective { + directives := make([]IDirective, 0) + for _, ls := range s.Listens { + directives = append(directives, ls) + } + directives = append(directives, s.Directives...) + return directives +} + +func (s *Server) AddListen(bind string, defaultServer bool, params ...string) { + listen := &ServerListen{ + Bind: bind, + Parameters: params, + } + if defaultServer { + listen.DefaultServer = DefaultServer + } + s.Listens = append(s.Listens, listen) +} + +func (s *Server) RemoveListenByBind(bind string) { + index := 0 + listens := s.Listens + for _, listen := range s.Listens { + if listen.Bind != bind || len(listen.Parameters) > 0 { + listens[index] = listen + index++ + } + } + s.Listens = listens +} + +func (s *Server) FindDirectives(directiveName string) []IDirective { + directives := make([]IDirective, 0) + for _, directive := range s.Directives { + if directive.GetName() == directiveName { + directives = append(directives, directive) + } + if directive.GetBlock() != nil { + directives = append(directives, directive.GetBlock().FindDirectives(directiveName)...) + } + } + + return directives +} + +func (s *Server) UpdateDirectives(directiveName string, directive Directive) { + directives := make([]IDirective, 0) + for _, dir := range s.Directives { + if dir.GetName() == directiveName { + directives = append(directives, &directive) + } else { + directives = append(directives, dir) + } + } + s.Directives = directives +} + +func (s *Server) AddDirectives(directive Directive) { + directives := append(s.Directives, &directive) + s.Directives = directives +} diff --git a/backend/utils/nginx/components/server_listen.go b/backend/utils/nginx/components/server_listen.go new file mode 100644 index 000000000..472ed89ed --- /dev/null +++ b/backend/utils/nginx/components/server_listen.go @@ -0,0 +1,68 @@ +package components + +import ( + "github.com/1Panel-dev/1Panel/backend/utils/common" + "strings" +) + +const DefaultServer = "default_server" + +type ServerListen struct { + Bind string + DefaultServer string + Parameters []string + Comment string +} + +func NewServerListen(params []string) *ServerListen { + server := &ServerListen{ + Parameters: []string{}, + } + for _, param := range params { + if isBind(param) { + server.Bind = param + } else if param == DefaultServer { + server.DefaultServer = DefaultServer + } else { + server.Parameters = append(server.Parameters, param) + } + } + return server +} + +func isBind(param string) bool { + if common.IsNum(param) { + return true + } + if strings.Contains(param, "*") || strings.Contains(param, ":") || strings.Contains(param, ".") { + return true + } + return false +} + +func (sl *ServerListen) GetName() string { + return "listen" +} + +func (sl *ServerListen) GetBlock() IBlock { + return nil +} + +func (sl *ServerListen) GetParameters() []string { + params := []string{sl.Bind} + params = append(params, sl.DefaultServer) + params = append(params, sl.Parameters...) + return params +} + +func (sl *ServerListen) GetComment() string { + return sl.Comment +} + +func (sl *ServerListen) AddDefaultServer() { + sl.DefaultServer = DefaultServer +} + +func (sl *ServerListen) RemoveDefaultServe() { + sl.DefaultServer = "" +} diff --git a/backend/utils/nginx/components/statement.go b/backend/utils/nginx/components/statement.go new file mode 100644 index 000000000..2fbee6f71 --- /dev/null +++ b/backend/utils/nginx/components/statement.go @@ -0,0 +1,24 @@ +package components + +type IBlock interface { + GetDirectives() []IDirective + FindDirectives(directiveName string) []IDirective + UpdateDirectives(directiveName string, directive Directive) + AddDirectives(directive Directive) + GetComment() string +} + +type IDirective interface { + GetName() string + GetParameters() []string + GetBlock() IBlock + GetComment() string +} + +type FileDirective interface { + isFileDirective() +} + +type IncludeDirective interface { + FileDirective +} diff --git a/backend/utils/nginx/components/upstream.go b/backend/utils/nginx/components/upstream.go new file mode 100644 index 000000000..0f1c7327c --- /dev/null +++ b/backend/utils/nginx/components/upstream.go @@ -0,0 +1,94 @@ +package components + +import ( + "errors" +) + +type Upstream struct { + UpstreamName string + UpstreamServers []*UpstreamServer + Directives []IDirective + Comment string +} + +func (us *Upstream) GetName() string { + return "upstream" +} + +func (us *Upstream) GetParameters() []string { + return []string{us.UpstreamName} +} + +func (us *Upstream) GetBlock() IBlock { + return us +} + +func (us *Upstream) GetComment() string { + return us.Comment +} + +func (us *Upstream) GetDirectives() []IDirective { + directives := make([]IDirective, 0) + directives = append(directives, us.Directives...) + for _, uss := range us.UpstreamServers { + directives = append(directives, uss) + } + + return directives +} + +func NewUpstream(directive IDirective) (*Upstream, error) { + parameters := directive.GetParameters() + us := &Upstream{ + UpstreamName: parameters[0], + } + + if block := directive.GetBlock(); block != nil { + us.Comment = block.GetComment() + for _, d := range block.GetDirectives() { + if d.GetName() == "server" { + us.UpstreamServers = append(us.UpstreamServers, NewUpstreamServer(d)) + } else { + us.Directives = append(us.Directives, d) + } + } + return us, nil + } + + return nil, errors.New("missing upstream block") +} + +func (us *Upstream) AddServer(server *UpstreamServer) { + us.UpstreamServers = append(us.UpstreamServers, server) +} + +func (us *Upstream) FindDirectives(directiveName string) []IDirective { + directives := make([]IDirective, 0) + for _, directive := range us.Directives { + if directive.GetName() == directiveName { + directives = append(directives, directive) + } + if directive.GetBlock() != nil { + directives = append(directives, directive.GetBlock().FindDirectives(directiveName)...) + } + } + + return directives +} + +func (us *Upstream) UpdateDirectives(directiveName string, directive Directive) { + directives := make([]IDirective, 0) + for _, dir := range us.GetDirectives() { + if dir.GetName() == directiveName { + directives = append(directives, &directive) + } else { + directives = append(directives, dir) + } + } + us.Directives = directives +} + +func (us *Upstream) AddDirectives(directive Directive) { + directives := append(us.GetDirectives(), &directive) + us.Directives = directives +} diff --git a/backend/utils/nginx/components/upstream_server.go b/backend/utils/nginx/components/upstream_server.go new file mode 100644 index 000000000..4d0959891 --- /dev/null +++ b/backend/utils/nginx/components/upstream_server.go @@ -0,0 +1,77 @@ +package components + +import ( + "fmt" + "sort" + "strings" +) + +type UpstreamServer struct { + Comment string + Address string + Flags []string + Parameters map[string]string +} + +func (uss *UpstreamServer) GetName() string { + return "server" +} + +func (uss *UpstreamServer) GetBlock() IBlock { + return nil +} + +func (uss *UpstreamServer) GetParameters() []string { + return uss.GetDirective().Parameters +} + +func (uss *UpstreamServer) GetComment() string { + return uss.Comment +} + +func (uss *UpstreamServer) GetDirective() *Directive { + directive := &Directive{ + Name: "server", + Parameters: make([]string, 0), + Block: nil, + } + + directive.Parameters = append(directive.Parameters, uss.Address) + + paramNames := make([]string, 0) + for k := range uss.Parameters { + paramNames = append(paramNames, k) + } + sort.Strings(paramNames) + + for _, k := range paramNames { + directive.Parameters = append(directive.Parameters, fmt.Sprintf("%s=%s", k, uss.Parameters[k])) + } + + directive.Parameters = append(directive.Parameters, uss.Flags...) + + return directive +} + +func NewUpstreamServer(directive IDirective) *UpstreamServer { + uss := &UpstreamServer{ + Comment: directive.GetComment(), + Flags: make([]string, 0), + Parameters: make(map[string]string, 0), + } + + for i, parameter := range directive.GetParameters() { + if i == 0 { + uss.Address = parameter + continue + } + if strings.Contains(parameter, "=") { + s := strings.SplitN(parameter, "=", 2) + uss.Parameters[s[0]] = s[1] + } else { + uss.Flags = append(uss.Flags, parameter) + } + } + + return uss +} diff --git a/backend/utils/nginx/dumper.go b/backend/utils/nginx/dumper.go new file mode 100644 index 000000000..c7e9b3220 --- /dev/null +++ b/backend/utils/nginx/dumper.go @@ -0,0 +1,132 @@ +package nginx + +import ( + "bytes" + "fmt" + components "github.com/1Panel-dev/1Panel/backend/utils/nginx/components" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" +) + +var ( + //NoIndentStyle default style + NoIndentStyle = &Style{ + SortDirectives: false, + StartIndent: 0, + Indent: 0, + } + + //IndentedStyle default style + IndentedStyle = &Style{ + SortDirectives: false, + StartIndent: 0, + Indent: 4, + } + + //NoIndentSortedStyle default style + NoIndentSortedStyle = &Style{ + SortDirectives: true, + StartIndent: 0, + Indent: 0, + } + + //NoIndentSortedSpaceStyle default style + NoIndentSortedSpaceStyle = &Style{ + SortDirectives: true, + SpaceBeforeBlocks: true, + StartIndent: 0, + Indent: 0, + } +) + +type Style struct { + SortDirectives bool + SpaceBeforeBlocks bool + StartIndent int + Indent int +} + +func NewStyle() *Style { + style := &Style{ + SortDirectives: false, + StartIndent: 0, + Indent: 4, + } + return style +} + +func (s *Style) Iterate() *Style { + newStyle := &Style{ + SortDirectives: s.SortDirectives, + SpaceBeforeBlocks: s.SpaceBeforeBlocks, + StartIndent: s.StartIndent + s.Indent, + Indent: s.Indent, + } + return newStyle +} + +func DumpDirective(d components.IDirective, style *Style) string { + var buf bytes.Buffer + + if style.SpaceBeforeBlocks && d.GetBlock() != nil { + buf.WriteString("\n") + } + buf.WriteString(fmt.Sprintf("%s%s", strings.Repeat(" ", style.StartIndent), d.GetName())) + if len(d.GetParameters()) > 0 { + buf.WriteString(fmt.Sprintf(" %s", strings.Join(d.GetParameters(), " "))) + } + if d.GetBlock() == nil { + if d.GetName() != "" { + buf.WriteRune(';') + buf.WriteString(" ") + } + if d.GetComment() != "" { + buf.WriteString(d.GetComment()) + } + } else { + buf.WriteString(" {") + if d.GetComment() != "" { + buf.WriteString(" ") + buf.WriteString(d.GetComment()) + } + buf.WriteString("\n") + buf.WriteString(DumpBlock(d.GetBlock(), style.Iterate())) + buf.WriteString(fmt.Sprintf("\n%s}", strings.Repeat(" ", style.StartIndent))) + } + return buf.String() +} + +func DumpBlock(b components.IBlock, style *Style) string { + var buf bytes.Buffer + + directives := b.GetDirectives() + if style.SortDirectives { + sort.SliceStable(directives, func(i, j int) bool { + return directives[i].GetName() < directives[j].GetName() + }) + } + + for i, directive := range directives { + buf.WriteString(DumpDirective(directive, style)) + if i != len(directives)-1 { + buf.WriteString("\n") + } + } + return buf.String() +} + +func DumpConfig(c *components.Config, style *Style) string { + return DumpBlock(c.Block, style) +} + +func WriteConfig(c *components.Config, style *Style) error { + dir, _ := filepath.Split(c.FilePath) + err := os.MkdirAll(dir, 0755) + if err != nil { + return err + } + return ioutil.WriteFile(c.FilePath, []byte(DumpConfig(c, style)), 0644) +} diff --git a/backend/utils/nginx/nginx.go b/backend/utils/nginx/nginx.go new file mode 100644 index 000000000..0ba7706dd --- /dev/null +++ b/backend/utils/nginx/nginx.go @@ -0,0 +1,14 @@ +package nginx + +import ( + "github.com/1Panel-dev/1Panel/backend/utils/nginx/components" + "github.com/1Panel-dev/1Panel/backend/utils/nginx/parser" +) + +func GetConfig(path string) (*components.Config, error) { + p, err := parser.NewParser(path) + if err != nil { + return nil, err + } + return p.Parse(), nil +} diff --git a/backend/utils/nginx/parser/flag/flag.go b/backend/utils/nginx/parser/flag/flag.go new file mode 100644 index 000000000..4406bc674 --- /dev/null +++ b/backend/utils/nginx/parser/flag/flag.go @@ -0,0 +1,82 @@ +package flag + +import ( + "fmt" +) + +type Type int + +const ( + EOF Type = iota + Eol + Keyword + QuotedString + Variable + BlockStart + BlockEnd + Semicolon + Comment + Illegal + Regex +) + +var ( + FlagName = map[Type]string{ + QuotedString: "QuotedString", + EOF: "Eof", + Keyword: "Keyword", + Variable: "Variable", + BlockStart: "BlockStart", + BlockEnd: "BlockEnd", + Semicolon: "Semicolon", + Comment: "Comment", + Illegal: "Illegal", + Regex: "Regex", + } +) + +func (tt Type) String() string { + return FlagName[tt] +} + +type Flag struct { + Type Type + Literal string + Line int + Column int +} + +func (t Flag) String() string { + return fmt.Sprintf("{Type:%s,Literal:\"%s\",Line:%d,Column:%d}", t.Type, t.Literal, t.Line, t.Column) +} + +func (t Flag) Lit(literal string) Flag { + t.Literal = literal + return t +} + +func (t Flag) EqualTo(t2 Flag) bool { + return t.Type == t2.Type && t.Literal == t2.Literal +} + +type Flags []Flag + +func (fs Flags) EqualTo(flags Flags) bool { + if len(fs) != len(flags) { + return false + } + for i, t := range fs { + if !t.EqualTo(flags[i]) { + return false + } + } + return true +} + +func (t Flag) Is(typ Type) bool { + return t.Type == typ +} + +func (t Flag) IsParameterEligible() bool { + return t.Is(Keyword) || t.Is(QuotedString) || t.Is(Variable) || t.Is(Regex) +} diff --git a/backend/utils/nginx/parser/lexer.go b/backend/utils/nginx/parser/lexer.go new file mode 100644 index 000000000..45dd7204b --- /dev/null +++ b/backend/utils/nginx/parser/lexer.go @@ -0,0 +1,209 @@ +package parser + +import ( + "bufio" + "bytes" + "github.com/1Panel-dev/1Panel/backend/utils/nginx/parser/flag" + "io" +) + +type lexer struct { + reader *bufio.Reader + file string + line int + column int + Latest flag.Flag +} + +func lex(content string) *lexer { + return newLexer(bytes.NewBuffer([]byte(content))) +} + +func newLexer(r io.Reader) *lexer { + return &lexer{ + line: 1, + reader: bufio.NewReader(r), + } +} + +func (s *lexer) scan() flag.Flag { + s.Latest = s.getNextFlag() + return s.Latest +} + +//func (s *lexer) all() flag.Flags { +// tokens := make([]flag.Flag, 0) +// for { +// v := s.scan() +// if v.Type == flag.EOF || v.Type == -1 { +// break +// } +// tokens = append(tokens, v) +// } +// return tokens +//} + +func (s *lexer) getNextFlag() flag.Flag { +retoFlag: + ch := s.peek() + switch { + case isSpace(ch): + s.skipWhitespace() + goto retoFlag + case isEOF(ch): + return s.NewToken(flag.EOF).Lit(string(s.read())) + case ch == ';': + return s.NewToken(flag.Semicolon).Lit(string(s.read())) + case ch == '{': + return s.NewToken(flag.BlockStart).Lit(string(s.read())) + case ch == '}': + return s.NewToken(flag.BlockEnd).Lit(string(s.read())) + case ch == '#': + return s.scanComment() + case ch == '$': + return s.scanVariable() + case isQuote(ch): + return s.scanQuotedString(ch) + default: + return s.scanKeyword() + } +} + +func (s *lexer) peek() rune { + r, _, _ := s.reader.ReadRune() + _ = s.reader.UnreadRune() + return r +} + +type runeCheck func(rune) bool + +func (s *lexer) readUntil(until runeCheck) string { + var buf bytes.Buffer + buf.WriteRune(s.read()) + + for { + if ch := s.peek(); isEOF(ch) { + break + } else if until(ch) { + break + } else { + buf.WriteRune(s.read()) + } + } + + return buf.String() +} + +func (s *lexer) NewToken(tokenType flag.Type) flag.Flag { + return flag.Flag{ + Type: tokenType, + Line: s.line, + Column: s.column, + } +} + +func (s *lexer) readWhile(while runeCheck) string { + var buf bytes.Buffer + buf.WriteRune(s.read()) + + for { + if ch := s.peek(); while(ch) { + buf.WriteRune(s.read()) + } else { + break + } + } + return buf.String() +} + +func (s *lexer) skipWhitespace() { + s.readWhile(isSpace) +} + +func (s *lexer) scanComment() flag.Flag { + return s.NewToken(flag.Comment).Lit(s.readUntil(isEndOfLine)) +} + +func (s *lexer) scanQuotedString(delimiter rune) flag.Flag { + var buf bytes.Buffer + tok := s.NewToken(flag.QuotedString) + buf.WriteRune(s.read()) //consume delimiter + for { + ch := s.read() + + if ch == rune(flag.EOF) { + panic("unexpected end of file while scanning a string, maybe an unclosed quote?") + } + + if ch == '\\' { + if needsEscape(s.peek(), delimiter) { + switch s.read() { + case 'n': + buf.WriteRune('\n') + case 'r': + buf.WriteRune('\r') + case 't': + buf.WriteRune('\t') + case '\\': + buf.WriteRune('\\') + case delimiter: + buf.WriteRune(delimiter) + } + continue + } + } + buf.WriteRune(ch) + if ch == delimiter { + break + } + } + + return tok.Lit(buf.String()) +} + +func (s *lexer) scanKeyword() flag.Flag { + return s.NewToken(flag.Keyword).Lit(s.readUntil(isKeywordTerminator)) +} + +func (s *lexer) scanVariable() flag.Flag { + return s.NewToken(flag.Variable).Lit(s.readUntil(isKeywordTerminator)) +} + +func (s *lexer) read() rune { + ch, _, err := s.reader.ReadRune() + if err != nil { + return rune(flag.EOF) + } + + if ch == '\n' { + s.column = 1 + s.line++ + } else { + s.column++ + } + return ch +} + +func isQuote(ch rune) bool { + return ch == '"' || ch == '\'' || ch == '`' +} + +func isKeywordTerminator(ch rune) bool { + return isSpace(ch) || isEndOfLine(ch) || ch == '{' || ch == ';' +} + +func needsEscape(ch, delimiter rune) bool { + return ch == delimiter || ch == 'n' || ch == 't' || ch == '\\' || ch == 'r' +} + +func isSpace(ch rune) bool { + return ch == ' ' || ch == '\t' || isEndOfLine(ch) +} + +func isEOF(ch rune) bool { + return ch == rune(flag.EOF) +} + +func isEndOfLine(ch rune) bool { + return ch == '\r' || ch == '\n' +} diff --git a/backend/utils/nginx/parser/parser.go b/backend/utils/nginx/parser/parser.go new file mode 100644 index 000000000..e5b1bd7ed --- /dev/null +++ b/backend/utils/nginx/parser/parser.go @@ -0,0 +1,172 @@ +package parser + +import ( + "bufio" + "fmt" + components "github.com/1Panel-dev/1Panel/backend/utils/nginx/components" + "github.com/1Panel-dev/1Panel/backend/utils/nginx/parser/flag" + "os" +) + +type Parser struct { + lexer *lexer + currentToken flag.Flag + followingToken flag.Flag + blockWrappers map[string]func(*components.Directive) components.IDirective + directiveWrappers map[string]func(*components.Directive) components.IDirective +} + +func NewStringParser(str string) *Parser { + return NewParserFromLexer(lex(str)) +} + +func NewParser(filePath string) (*Parser, error) { + f, err := os.Open(filePath) + if err != nil { + return nil, err + } + l := newLexer(bufio.NewReader(f)) + l.file = filePath + p := NewParserFromLexer(l) + return p, nil +} + +func NewParserFromLexer(lexer *lexer) *Parser { + parser := &Parser{ + lexer: lexer, + } + + parser.nextToken() + parser.nextToken() + + parser.blockWrappers = map[string]func(*components.Directive) components.IDirective{ + "http": func(directive *components.Directive) components.IDirective { + return parser.wrapHttp(directive) + }, + "server": func(directive *components.Directive) components.IDirective { + return parser.wrapServer(directive) + }, + "location": func(directive *components.Directive) components.IDirective { + return parser.wrapLocation(directive) + }, + "upstream": func(directive *components.Directive) components.IDirective { + return parser.wrapUpstream(directive) + }, + } + + parser.directiveWrappers = map[string]func(*components.Directive) components.IDirective{ + "server": func(directive *components.Directive) components.IDirective { + return parser.parseUpstreamServer(directive) + }, + } + + return parser +} + +func (p *Parser) nextToken() { + p.currentToken = p.followingToken + p.followingToken = p.lexer.scan() +} + +func (p *Parser) curTokenIs(t flag.Type) bool { + return p.currentToken.Type == t +} + +func (p *Parser) followingTokenIs(t flag.Type) bool { + return p.followingToken.Type == t +} + +func (p *Parser) Parse() *components.Config { + return &components.Config{ + FilePath: p.lexer.file, + Block: p.parseBlock(), + } +} + +func (p *Parser) parseBlock() *components.Block { + + context := &components.Block{ + Comment: "", + Directives: make([]components.IDirective, 0), + } + +parsingloop: + for { + switch { + case p.curTokenIs(flag.EOF) || p.curTokenIs(flag.BlockEnd): + break parsingloop + case p.curTokenIs(flag.Keyword): + context.Directives = append(context.Directives, p.parseStatement()) + case p.curTokenIs(flag.Comment): + context.Directives = append(context.Directives, &components.Comment{ + Detail: p.currentToken.Literal, + }) + } + p.nextToken() + } + + return context +} + +func (p *Parser) parseStatement() components.IDirective { + d := &components.Directive{ + Name: p.currentToken.Literal, + } + + for p.nextToken(); p.currentToken.IsParameterEligible(); p.nextToken() { + d.Parameters = append(d.Parameters, p.currentToken.Literal) + } + + if p.curTokenIs(flag.Semicolon) { + if dw, ok := p.directiveWrappers[d.Name]; ok { + return dw(d) + } + if p.followingTokenIs(flag.Comment) && p.currentToken.Line == p.followingToken.Line { + d.Comment = p.followingToken.Literal + p.nextToken() + } + return d + } + + if p.curTokenIs(flag.BlockStart) { + + inLineComment := "" + if p.followingTokenIs(flag.Comment) && p.currentToken.Line == p.followingToken.Line { + inLineComment = p.followingToken.Literal + p.nextToken() + p.nextToken() + } + block := p.parseBlock() + block.Comment = inLineComment + d.Block = block + if bw, ok := p.blockWrappers[d.Name]; ok { + return bw(d) + } + return d + } + + panic(fmt.Errorf("unexpected token %s (%s) on line %d, column %d", p.currentToken.Type.String(), p.currentToken.Literal, p.currentToken.Line, p.currentToken.Column)) +} + +func (p *Parser) wrapLocation(directive *components.Directive) *components.Location { + return components.NewLocation(directive) +} + +func (p *Parser) wrapServer(directive *components.Directive) *components.Server { + s, _ := components.NewServer(directive) + return s +} + +func (p *Parser) wrapUpstream(directive *components.Directive) *components.Upstream { + s, _ := components.NewUpstream(directive) + return s +} + +func (p *Parser) wrapHttp(directive *components.Directive) *components.Http { + h, _ := components.NewHttp(directive) + return h +} + +func (p *Parser) parseUpstreamServer(directive *components.Directive) *components.UpstreamServer { + return components.NewUpstreamServer(directive) +}