1/*2Maddy Mail Server - Composable all-in-one email server.3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors45This program is free software: you can redistribute it and/or modify6it under the terms of the GNU General Public License as published by7the Free Software Foundation, either version 3 of the License, or8(at your option) any later version.910This program is distributed in the hope that it will be useful,11but WITHOUT ANY WARRANTY; without even the implied warranty of12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13GNU General Public License for more details.1415You should have received a copy of the GNU General Public License16along with this program. If not, see <https://www.gnu.org/licenses/>.17*/1819package parser2021import (22 "os"23 "path/filepath"24 "regexp"25 "strings"26)2728func (ctx *parseContext) expandImports(node Node, expansionDepth int) (Node, error) {29 // Leave nil value as is because it is used as non-existent block indicator30 // (vs empty slice - empty block).31 if node.Children == nil {32 return node, nil33 }3435 newChildrens := make([]Node, 0, len(node.Children))36 containsImports := false37 for _, child := range node.Children {38 child, err := ctx.expandImports(child, expansionDepth+1)39 if err != nil {40 return node, err41 }4243 if child.Name == "import" {44 // We check it here instead of function start so we can45 // use line information from import directive that is likely46 // caused this error.47 if expansionDepth > 255 {48 return node, NodeErr(child, "hit import expansion limit")49 }5051 containsImports = true52 if len(child.Args) != 1 {53 return node, ctx.Err("import directive requires exactly 1 argument")54 }5556 subtree, err := ctx.resolveImport(child, child.Args[0], expansionDepth)57 if err != nil {58 return node, err59 }6061 newChildrens = append(newChildrens, subtree...)62 } else {63 newChildrens = append(newChildrens, child)64 }65 }66 node.Children = newChildrens6768 // We need to do another pass to expand any imports added by snippets we69 // just expanded.70 if containsImports {71 return ctx.expandImports(node, expansionDepth+1)72 }7374 return node, nil75}7677func (ctx *parseContext) resolveImport(node Node, name string, expansionDepth int) ([]Node, error) {78 if subtree, ok := ctx.snippets[name]; ok {79 return subtree, nil80 }8182 file := name83 if !filepath.IsAbs(name) {84 file = filepath.Join(filepath.Dir(ctx.fileLocation), name)85 }86 src, err := os.Open(file)87 if err != nil {88 if os.IsNotExist(err) {89 src, err = os.Open(file + ".conf")90 if err != nil {91 if os.IsNotExist(err) {92 return nil, NodeErr(node, "unknown import: %s", name)93 }94 return nil, err95 }96 } else {97 return nil, err98 }99 }100 nodes, snips, macros, err := readTree(src, file, expansionDepth+1)101 if err != nil {102 return nodes, err103 }104 for k, v := range snips {105 ctx.snippets[k] = v106 }107 for k, v := range macros {108 ctx.macros[k] = v109 }110111 return nodes, nil112}113114func (ctx *parseContext) expandMacros(node *Node) error {115 if strings.HasPrefix(node.Name, "$(") && strings.HasSuffix(node.Name, ")") {116 return ctx.Err("can't use macro argument as directive name")117 }118119 newArgs := make([]string, 0, len(node.Args))120 for _, arg := range node.Args {121 if !strings.HasPrefix(arg, "$(") || !strings.HasSuffix(arg, ")") {122 if strings.Contains(arg, "$(") && strings.Contains(arg, ")") {123 var err error124 arg, err = ctx.expandSingleValueMacro(arg)125 if err != nil {126 return err127 }128 }129130 newArgs = append(newArgs, arg)131 continue132 }133134 macroName := arg[2 : len(arg)-1]135 replacement, ok := ctx.macros[macroName]136 if !ok {137 // Undefined macros are expanded to zero arguments.138 continue139 }140141 newArgs = append(newArgs, replacement...)142 }143 node.Args = newArgs144145 if node.Children != nil {146 for i := range node.Children {147 if err := ctx.expandMacros(&node.Children[i]); err != nil {148 return err149 }150 }151 }152153 return nil154}155156var macroRe = regexp.MustCompile(`\$\(([^\$]+)\)`)157158func (ctx *parseContext) expandSingleValueMacro(arg string) (string, error) {159 matches := macroRe.FindAllStringSubmatch(arg, -1)160 for _, match := range matches {161 macroName := match[1]162 if len(ctx.macros[macroName]) > 1 {163 return "", ctx.Err("can't expand macro with multiple arguments inside a string")164 }165166 var value string167 if ctx.macros[macroName] != nil {168 // Macros have at least one argument.169 value = ctx.macros[macroName][0]170 }171172 arg = strings.Replace(arg, "$("+macroName+")", value, -1)173 }174175 return arg, nil176}