1package dns
2
3import (
4	"bytes"
5	"fmt"
6	"strconv"
7	"strings"
8)
9
10// Parse the $GENERATE statement as used in BIND9 zones.
11// See http://www.zytrax.com/books/dns/ch8/generate.html for instance.
12// We are called after '$GENERATE '. After which we expect:
13// * the range (12-24/2)
14// * lhs (ownername)
15// * [[ttl][class]]
16// * type
17// * rhs (rdata)
18// But we are lazy here, only the range is parsed *all* occurrences
19// of $ after that are interpreted.
20// Any error are returned as a string value, the empty string signals
21// "no error".
22func generate(l lex, c chan lex, t chan *Token, o string) string {
23	step := 1
24	if i := strings.IndexAny(l.token, "/"); i != -1 {
25		if i+1 == len(l.token) {
26			return "bad step in $GENERATE range"
27		}
28		if s, e := strconv.Atoi(l.token[i+1:]); e == nil {
29			if s < 0 {
30				return "bad step in $GENERATE range"
31			}
32			step = s
33		} else {
34			return "bad step in $GENERATE range"
35		}
36		l.token = l.token[:i]
37	}
38	sx := strings.SplitN(l.token, "-", 2)
39	if len(sx) != 2 {
40		return "bad start-stop in $GENERATE range"
41	}
42	start, err := strconv.Atoi(sx[0])
43	if err != nil {
44		return "bad start in $GENERATE range"
45	}
46	end, err := strconv.Atoi(sx[1])
47	if err != nil {
48		return "bad stop in $GENERATE range"
49	}
50	if end < 0 || start < 0 || end < start {
51		return "bad range in $GENERATE range"
52	}
53
54	<-c // _BLANK
55	// Create a complete new string, which we then parse again.
56	s := ""
57BuildRR:
58	l = <-c
59	if l.value != zNewline && l.value != zEOF {
60		s += l.token
61		goto BuildRR
62	}
63	for i := start; i <= end; i += step {
64		var (
65			escape bool
66			dom    bytes.Buffer
67			mod    string
68			err    string
69			offset int
70		)
71
72		for j := 0; j < len(s); j++ { // No 'range' because we need to jump around
73			switch s[j] {
74			case '\\':
75				if escape {
76					dom.WriteByte('\\')
77					escape = false
78					continue
79				}
80				escape = true
81			case '$':
82				mod = "%d"
83				offset = 0
84				if escape {
85					dom.WriteByte('$')
86					escape = false
87					continue
88				}
89				escape = false
90				if j+1 >= len(s) { // End of the string
91					dom.WriteString(fmt.Sprintf(mod, i+offset))
92					continue
93				} else {
94					if s[j+1] == '$' {
95						dom.WriteByte('$')
96						j++
97						continue
98					}
99				}
100				// Search for { and }
101				if s[j+1] == '{' { // Modifier block
102					sep := strings.Index(s[j+2:], "}")
103					if sep == -1 {
104						return "bad modifier in $GENERATE"
105					}
106					mod, offset, err = modToPrintf(s[j+2 : j+2+sep])
107					if err != "" {
108						return err
109					}
110					j += 2 + sep // Jump to it
111				}
112				dom.WriteString(fmt.Sprintf(mod, i+offset))
113			default:
114				if escape { // Pretty useless here
115					escape = false
116					continue
117				}
118				dom.WriteByte(s[j])
119			}
120		}
121		// Re-parse the RR and send it on the current channel t
122		rx, e := NewRR("$ORIGIN " + o + "\n" + dom.String())
123		if e != nil {
124			return e.(*ParseError).err
125		}
126		t <- &Token{RR: rx}
127		// Its more efficient to first built the rrlist and then parse it in
128		// one go! But is this a problem?
129	}
130	return ""
131}
132
133// Convert a $GENERATE modifier 0,0,d to something Printf can deal with.
134func modToPrintf(s string) (string, int, string) {
135	xs := strings.SplitN(s, ",", 3)
136	if len(xs) != 3 {
137		return "", 0, "bad modifier in $GENERATE"
138	}
139	// xs[0] is offset, xs[1] is width, xs[2] is base
140	if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" {
141		return "", 0, "bad base in $GENERATE"
142	}
143	offset, err := strconv.Atoi(xs[0])
144	if err != nil || offset > 255 {
145		return "", 0, "bad offset in $GENERATE"
146	}
147	width, err := strconv.Atoi(xs[1])
148	if err != nil || width > 255 {
149		return "", offset, "bad width in $GENERATE"
150	}
151	switch {
152	case width < 0:
153		return "", offset, "bad width in $GENERATE"
154	case width == 0:
155		return "%" + xs[1] + xs[2], offset, ""
156	}
157	return "%0" + xs[1] + xs[2], offset, ""
158}
159