1package dns
2
3import (
4	"bytes"
5	"fmt"
6	"io"
7	"strconv"
8	"strings"
9)
10
11// Parse the $GENERATE statement as used in BIND9 zones.
12// See http://www.zytrax.com/books/dns/ch8/generate.html for instance.
13// We are called after '$GENERATE '. After which we expect:
14// * the range (12-24/2)
15// * lhs (ownername)
16// * [[ttl][class]]
17// * type
18// * rhs (rdata)
19// But we are lazy here, only the range is parsed *all* occurrences
20// of $ after that are interpreted.
21func (zp *ZoneParser) generate(l lex) (RR, bool) {
22	token := l.token
23	step := int64(1)
24	if i := strings.IndexByte(token, '/'); i >= 0 {
25		if i+1 == len(token) {
26			return zp.setParseError("bad step in $GENERATE range", l)
27		}
28
29		s, err := strconv.ParseInt(token[i+1:], 10, 64)
30		if err != nil || s <= 0 {
31			return zp.setParseError("bad step in $GENERATE range", l)
32		}
33
34		step = s
35		token = token[:i]
36	}
37
38	sx := strings.SplitN(token, "-", 2)
39	if len(sx) != 2 {
40		return zp.setParseError("bad start-stop in $GENERATE range", l)
41	}
42
43	start, err := strconv.ParseInt(sx[0], 10, 64)
44	if err != nil {
45		return zp.setParseError("bad start in $GENERATE range", l)
46	}
47
48	end, err := strconv.ParseInt(sx[1], 10, 64)
49	if err != nil {
50		return zp.setParseError("bad stop in $GENERATE range", l)
51	}
52	if end < 0 || start < 0 || end < start || (end-start)/step > 65535 {
53		return zp.setParseError("bad range in $GENERATE range", l)
54	}
55
56	// _BLANK
57	l, ok := zp.c.Next()
58	if !ok || l.value != zBlank {
59		return zp.setParseError("garbage after $GENERATE range", l)
60	}
61
62	// Create a complete new string, which we then parse again.
63	var s string
64	for l, ok := zp.c.Next(); ok; l, ok = zp.c.Next() {
65		if l.err {
66			return zp.setParseError("bad data in $GENERATE directive", l)
67		}
68		if l.value == zNewline {
69			break
70		}
71
72		s += l.token
73	}
74
75	r := &generateReader{
76		s: s,
77
78		cur:   start,
79		start: start,
80		end:   end,
81		step:  step,
82
83		file: zp.file,
84		lex:  &l,
85	}
86	zp.sub = NewZoneParser(r, zp.origin, zp.file)
87	zp.sub.includeDepth, zp.sub.includeAllowed = zp.includeDepth, zp.includeAllowed
88	zp.sub.generateDisallowed = true
89	zp.sub.SetDefaultTTL(defaultTtl)
90	return zp.subNext()
91}
92
93type generateReader struct {
94	s  string
95	si int
96
97	cur   int64
98	start int64
99	end   int64
100	step  int64
101
102	mod bytes.Buffer
103
104	escape bool
105
106	eof bool
107
108	file string
109	lex  *lex
110}
111
112func (r *generateReader) parseError(msg string, end int) *ParseError {
113	r.eof = true // Make errors sticky.
114
115	l := *r.lex
116	l.token = r.s[r.si-1 : end]
117	l.column += r.si // l.column starts one zBLANK before r.s
118
119	return &ParseError{r.file, msg, l}
120}
121
122func (r *generateReader) Read(p []byte) (int, error) {
123	// NewZLexer, through NewZoneParser, should use ReadByte and
124	// not end up here.
125
126	panic("not implemented")
127}
128
129func (r *generateReader) ReadByte() (byte, error) {
130	if r.eof {
131		return 0, io.EOF
132	}
133	if r.mod.Len() > 0 {
134		return r.mod.ReadByte()
135	}
136
137	if r.si >= len(r.s) {
138		r.si = 0
139		r.cur += r.step
140
141		r.eof = r.cur > r.end || r.cur < 0
142		return '\n', nil
143	}
144
145	si := r.si
146	r.si++
147
148	switch r.s[si] {
149	case '\\':
150		if r.escape {
151			r.escape = false
152			return '\\', nil
153		}
154
155		r.escape = true
156		return r.ReadByte()
157	case '$':
158		if r.escape {
159			r.escape = false
160			return '$', nil
161		}
162
163		mod := "%d"
164
165		if si >= len(r.s)-1 {
166			// End of the string
167			fmt.Fprintf(&r.mod, mod, r.cur)
168			return r.mod.ReadByte()
169		}
170
171		if r.s[si+1] == '$' {
172			r.si++
173			return '$', nil
174		}
175
176		var offset int64
177
178		// Search for { and }
179		if r.s[si+1] == '{' {
180			// Modifier block
181			sep := strings.Index(r.s[si+2:], "}")
182			if sep < 0 {
183				return 0, r.parseError("bad modifier in $GENERATE", len(r.s))
184			}
185
186			var errMsg string
187			mod, offset, errMsg = modToPrintf(r.s[si+2 : si+2+sep])
188			if errMsg != "" {
189				return 0, r.parseError(errMsg, si+3+sep)
190			}
191			if r.start+offset < 0 || r.end+offset > 1<<31-1 {
192				return 0, r.parseError("bad offset in $GENERATE", si+3+sep)
193			}
194
195			r.si += 2 + sep // Jump to it
196		}
197
198		fmt.Fprintf(&r.mod, mod, r.cur+offset)
199		return r.mod.ReadByte()
200	default:
201		if r.escape { // Pretty useless here
202			r.escape = false
203			return r.ReadByte()
204		}
205
206		return r.s[si], nil
207	}
208}
209
210// Convert a $GENERATE modifier 0,0,d to something Printf can deal with.
211func modToPrintf(s string) (string, int64, string) {
212	// Modifier is { offset [ ,width [ ,base ] ] } - provide default
213	// values for optional width and type, if necessary.
214	var offStr, widthStr, base string
215	switch xs := strings.Split(s, ","); len(xs) {
216	case 1:
217		offStr, widthStr, base = xs[0], "0", "d"
218	case 2:
219		offStr, widthStr, base = xs[0], xs[1], "d"
220	case 3:
221		offStr, widthStr, base = xs[0], xs[1], xs[2]
222	default:
223		return "", 0, "bad modifier in $GENERATE"
224	}
225
226	switch base {
227	case "o", "d", "x", "X":
228	default:
229		return "", 0, "bad base in $GENERATE"
230	}
231
232	offset, err := strconv.ParseInt(offStr, 10, 64)
233	if err != nil {
234		return "", 0, "bad offset in $GENERATE"
235	}
236
237	width, err := strconv.ParseInt(widthStr, 10, 64)
238	if err != nil || width < 0 || width > 255 {
239		return "", 0, "bad width in $GENERATE"
240	}
241
242	if width == 0 {
243		return "%" + base, offset, ""
244	}
245
246	return "%0" + widthStr + base, offset, ""
247}
248