parse.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. package nginx
  2. import (
  3. "bufio"
  4. "github.com/emirpasic/gods/stacks/linkedliststack"
  5. "github.com/pkg/errors"
  6. "os"
  7. "strings"
  8. "unicode"
  9. )
  10. const (
  11. Server = "server"
  12. Location = "location"
  13. Upstream = "upstream"
  14. CommentStart = "#"
  15. Empty = ""
  16. If = "if"
  17. )
  18. func matchParentheses(stack *linkedliststack.Stack, v int32) {
  19. if v == '{' {
  20. stack.Push(v)
  21. } else if v == '}' {
  22. // stack is not empty and the top is == '{'
  23. if top, ok := stack.Peek(); ok && top == '{' {
  24. stack.Pop()
  25. } else {
  26. // fail
  27. stack.Push(v)
  28. }
  29. }
  30. }
  31. func parseDirective(scanner *bufio.Scanner) (d NgxDirective) {
  32. text := strings.TrimSpace(scanner.Text())
  33. // escape empty line or comment line
  34. if len(text) < 1 {
  35. return
  36. }
  37. if text[0] == '#' {
  38. d.Directive = "#"
  39. d.Params = strings.TrimLeft(text, "#")
  40. return
  41. }
  42. if len(text) > 1 {
  43. sep := len(text) - 1
  44. for k, v := range text {
  45. if unicode.IsSpace(v) {
  46. sep = k
  47. break
  48. }
  49. }
  50. d.Directive = text[0:sep]
  51. d.Params = text[sep:]
  52. } else {
  53. d.Directive = text
  54. return
  55. }
  56. stack := linkedliststack.New()
  57. if d.Directive == Server || d.Directive == Upstream || d.Directive == Location || d.Directive == If {
  58. // { } in one line
  59. // location = /.well-known/carddav { return 301 /remote.php/dav/; }
  60. if strings.Contains(d.Params, "{") {
  61. for _, v := range d.Params {
  62. matchParentheses(stack, v)
  63. }
  64. if stack.Empty() {
  65. return
  66. }
  67. }
  68. // location ^~ /.well-known {
  69. // location ^~ /.well-known
  70. // {
  71. // location ^~ /.well-known
  72. //
  73. // {
  74. // { } not in one line
  75. for scanner.Scan() {
  76. text = strings.TrimSpace(scanner.Text())
  77. // escape empty line
  78. if text == "" {
  79. continue
  80. }
  81. d.Params += "\n" + scanner.Text()
  82. for _, v := range text {
  83. matchParentheses(stack, v)
  84. if stack.Empty() {
  85. break
  86. }
  87. }
  88. if stack.Empty() {
  89. break
  90. }
  91. }
  92. }
  93. d.Params = strings.TrimSpace(d.Params)
  94. return
  95. }
  96. func ParseNgxConfigByScanner(filename string, scanner *bufio.Scanner) (c *NgxConfig, err error) {
  97. c = NewNgxConfig(filename)
  98. for scanner.Scan() {
  99. d := parseDirective(scanner)
  100. paramsScanner := bufio.NewScanner(strings.NewReader(d.Params))
  101. switch d.Directive {
  102. case Server:
  103. c.parseServer(paramsScanner)
  104. case Upstream:
  105. c.parseUpstream(paramsScanner)
  106. case CommentStart:
  107. c.commentQueue.Enqueue(d.Params)
  108. case Empty:
  109. continue
  110. default:
  111. c.Custom += d.Orig() + "\n"
  112. }
  113. }
  114. if err = scanner.Err(); err != nil {
  115. return nil, errors.Wrap(err, "error scanner in ParseNgxConfig")
  116. }
  117. // Attach the rest of the comments to the last server
  118. if len(c.Servers) > 0 {
  119. c.Servers[len(c.Servers)-1].Comments += c.commentQueue.DequeueAllComments()
  120. }
  121. return c, nil
  122. }
  123. func ParseNgxConfig(filename string) (c *NgxConfig, err error) {
  124. file, err := os.Open(filename)
  125. if err != nil {
  126. return nil, errors.Wrap(err, "error open file in ParseNgxConfig")
  127. }
  128. defer file.Close()
  129. scanner := bufio.NewScanner(file)
  130. return ParseNgxConfigByScanner(filename, scanner)
  131. }