/framework/cfgparser/imports.go

https://gitlab.com/jasonkumpf/maddy · Go · 173 lines · 122 code · 25 blank · 26 comment · 45 complexity · 2f943c347189de1ff3bf015cc0ea03fc MD5 · raw file

  1. /*
  2. Maddy Mail Server - Composable all-in-one email server.
  3. Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. */
  15. package parser
  16. import (
  17. "os"
  18. "path/filepath"
  19. "regexp"
  20. "strings"
  21. )
  22. func (ctx *parseContext) expandImports(node Node, expansionDepth int) (Node, error) {
  23. // Leave nil value as is because it is used as non-existent block indicator
  24. // (vs empty slice - empty block).
  25. if node.Children == nil {
  26. return node, nil
  27. }
  28. newChildrens := make([]Node, 0, len(node.Children))
  29. containsImports := false
  30. for _, child := range node.Children {
  31. child, err := ctx.expandImports(child, expansionDepth+1)
  32. if err != nil {
  33. return node, err
  34. }
  35. if child.Name == "import" {
  36. // We check it here instead of function start so we can
  37. // use line information from import directive that is likely
  38. // caused this error.
  39. if expansionDepth > 255 {
  40. return node, NodeErr(child, "hit import expansion limit")
  41. }
  42. containsImports = true
  43. if len(child.Args) != 1 {
  44. return node, ctx.Err("import directive requires exactly 1 argument")
  45. }
  46. subtree, err := ctx.resolveImport(child, child.Args[0], expansionDepth)
  47. if err != nil {
  48. return node, err
  49. }
  50. newChildrens = append(newChildrens, subtree...)
  51. } else {
  52. newChildrens = append(newChildrens, child)
  53. }
  54. }
  55. node.Children = newChildrens
  56. // We need to do another pass to expand any imports added by snippets we
  57. // just expanded.
  58. if containsImports {
  59. return ctx.expandImports(node, expansionDepth+1)
  60. }
  61. return node, nil
  62. }
  63. func (ctx *parseContext) resolveImport(node Node, name string, expansionDepth int) ([]Node, error) {
  64. if subtree, ok := ctx.snippets[name]; ok {
  65. return subtree, nil
  66. }
  67. file := filepath.Join(filepath.Dir(ctx.fileLocation), name)
  68. src, err := os.Open(file)
  69. if err != nil {
  70. if os.IsNotExist(err) {
  71. src, err = os.Open(file + ".conf")
  72. if err != nil {
  73. if os.IsNotExist(err) {
  74. return nil, NodeErr(node, "unknown import: "+name)
  75. }
  76. return nil, err
  77. }
  78. } else {
  79. return nil, err
  80. }
  81. }
  82. nodes, snips, macros, err := readTree(src, file, expansionDepth+1)
  83. if err != nil {
  84. return nodes, err
  85. }
  86. for k, v := range snips {
  87. ctx.snippets[k] = v
  88. }
  89. for k, v := range macros {
  90. ctx.macros[k] = v
  91. }
  92. return nodes, nil
  93. }
  94. func (ctx *parseContext) expandMacros(node *Node) error {
  95. if strings.HasPrefix(node.Name, "$(") && strings.HasSuffix(node.Name, ")") {
  96. return ctx.Err("can't use macro argument as directive name")
  97. }
  98. newArgs := make([]string, 0, len(node.Args))
  99. for _, arg := range node.Args {
  100. if !strings.HasPrefix(arg, "$(") || !strings.HasSuffix(arg, ")") {
  101. if strings.Contains(arg, "$(") && strings.Contains(arg, ")") {
  102. var err error
  103. arg, err = ctx.expandSingleValueMacro(arg)
  104. if err != nil {
  105. return err
  106. }
  107. }
  108. newArgs = append(newArgs, arg)
  109. continue
  110. }
  111. macroName := arg[2 : len(arg)-1]
  112. replacement, ok := ctx.macros[macroName]
  113. if !ok {
  114. // Undefined macros are expanded to zero arguments.
  115. continue
  116. }
  117. newArgs = append(newArgs, replacement...)
  118. }
  119. node.Args = newArgs
  120. if node.Children != nil {
  121. for i := range node.Children {
  122. if err := ctx.expandMacros(&node.Children[i]); err != nil {
  123. return err
  124. }
  125. }
  126. }
  127. return nil
  128. }
  129. var macroRe = regexp.MustCompile(`\$\(([^\$]+)\)`)
  130. func (ctx *parseContext) expandSingleValueMacro(arg string) (string, error) {
  131. matches := macroRe.FindAllStringSubmatch(arg, -1)
  132. for _, match := range matches {
  133. macroName := match[1]
  134. if len(ctx.macros[macroName]) > 1 {
  135. return "", ctx.Err("can't expand macro with multiple arguments inside a string")
  136. }
  137. var value string
  138. if ctx.macros[macroName] != nil {
  139. // Macros have at least one argument.
  140. value = ctx.macros[macroName][0]
  141. }
  142. arg = strings.Replace(arg, "$("+macroName+")", value, -1)
  143. }
  144. return arg, nil
  145. }