maddy

Fork https://github.com/foxcpp/maddy

git clone git://git.lin.moe/go/maddy.git

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