| package dns |
| |
| import ( |
| "bytes" |
| "fmt" |
| "io" |
| "strconv" |
| "strings" |
| ) |
| |
| // Parse the $GENERATE statement as used in BIND9 zones. |
| // See http://www.zytrax.com/books/dns/ch8/generate.html for instance. |
| // We are called after '$GENERATE '. After which we expect: |
| // * the range (12-24/2) |
| // * lhs (ownername) |
| // * [[ttl][class]] |
| // * type |
| // * rhs (rdata) |
| // But we are lazy here, only the range is parsed *all* occurrences |
| // of $ after that are interpreted. |
| func (zp *ZoneParser) generate(l lex) (RR, bool) { |
| token := l.token |
| step := 1 |
| if i := strings.IndexByte(token, '/'); i >= 0 { |
| if i+1 == len(token) { |
| return zp.setParseError("bad step in $GENERATE range", l) |
| } |
| |
| s, err := strconv.Atoi(token[i+1:]) |
| if err != nil || s <= 0 { |
| return zp.setParseError("bad step in $GENERATE range", l) |
| } |
| |
| step = s |
| token = token[:i] |
| } |
| |
| sx := strings.SplitN(token, "-", 2) |
| if len(sx) != 2 { |
| return zp.setParseError("bad start-stop in $GENERATE range", l) |
| } |
| |
| start, err := strconv.Atoi(sx[0]) |
| if err != nil { |
| return zp.setParseError("bad start in $GENERATE range", l) |
| } |
| |
| end, err := strconv.Atoi(sx[1]) |
| if err != nil { |
| return zp.setParseError("bad stop in $GENERATE range", l) |
| } |
| if end < 0 || start < 0 || end < start || (end-start)/step > 65535 { |
| return zp.setParseError("bad range in $GENERATE range", l) |
| } |
| |
| // _BLANK |
| l, ok := zp.c.Next() |
| if !ok || l.value != zBlank { |
| return zp.setParseError("garbage after $GENERATE range", l) |
| } |
| |
| // Create a complete new string, which we then parse again. |
| var s string |
| for l, ok := zp.c.Next(); ok; l, ok = zp.c.Next() { |
| if l.err { |
| return zp.setParseError("bad data in $GENERATE directive", l) |
| } |
| if l.value == zNewline { |
| break |
| } |
| |
| s += l.token |
| } |
| |
| r := &generateReader{ |
| s: s, |
| |
| cur: start, |
| start: start, |
| end: end, |
| step: step, |
| |
| file: zp.file, |
| lex: &l, |
| } |
| zp.sub = NewZoneParser(r, zp.origin, zp.file) |
| zp.sub.includeDepth, zp.sub.includeAllowed = zp.includeDepth, zp.includeAllowed |
| zp.sub.generateDisallowed = true |
| zp.sub.SetDefaultTTL(defaultTtl) |
| return zp.subNext() |
| } |
| |
| type generateReader struct { |
| s string |
| si int |
| |
| cur int |
| start int |
| end int |
| step int |
| |
| mod bytes.Buffer |
| |
| escape bool |
| |
| eof bool |
| |
| file string |
| lex *lex |
| } |
| |
| func (r *generateReader) parseError(msg string, end int) *ParseError { |
| r.eof = true // Make errors sticky. |
| |
| l := *r.lex |
| l.token = r.s[r.si-1 : end] |
| l.column += r.si // l.column starts one zBLANK before r.s |
| |
| return &ParseError{r.file, msg, l} |
| } |
| |
| func (r *generateReader) Read(p []byte) (int, error) { |
| // NewZLexer, through NewZoneParser, should use ReadByte and |
| // not end up here. |
| |
| panic("not implemented") |
| } |
| |
| func (r *generateReader) ReadByte() (byte, error) { |
| if r.eof { |
| return 0, io.EOF |
| } |
| if r.mod.Len() > 0 { |
| return r.mod.ReadByte() |
| } |
| |
| if r.si >= len(r.s) { |
| r.si = 0 |
| r.cur += r.step |
| |
| r.eof = r.cur > r.end || r.cur < 0 |
| return '\n', nil |
| } |
| |
| si := r.si |
| r.si++ |
| |
| switch r.s[si] { |
| case '\\': |
| if r.escape { |
| r.escape = false |
| return '\\', nil |
| } |
| |
| r.escape = true |
| return r.ReadByte() |
| case '$': |
| if r.escape { |
| r.escape = false |
| return '$', nil |
| } |
| |
| mod := "%d" |
| |
| if si >= len(r.s)-1 { |
| // End of the string |
| fmt.Fprintf(&r.mod, mod, r.cur) |
| return r.mod.ReadByte() |
| } |
| |
| if r.s[si+1] == '$' { |
| r.si++ |
| return '$', nil |
| } |
| |
| var offset int |
| |
| // Search for { and } |
| if r.s[si+1] == '{' { |
| // Modifier block |
| sep := strings.Index(r.s[si+2:], "}") |
| if sep < 0 { |
| return 0, r.parseError("bad modifier in $GENERATE", len(r.s)) |
| } |
| |
| var errMsg string |
| mod, offset, errMsg = modToPrintf(r.s[si+2 : si+2+sep]) |
| if errMsg != "" { |
| return 0, r.parseError(errMsg, si+3+sep) |
| } |
| if r.start+offset < 0 || r.end+offset > 1<<31-1 { |
| return 0, r.parseError("bad offset in $GENERATE", si+3+sep) |
| } |
| |
| r.si += 2 + sep // Jump to it |
| } |
| |
| fmt.Fprintf(&r.mod, mod, r.cur+offset) |
| return r.mod.ReadByte() |
| default: |
| if r.escape { // Pretty useless here |
| r.escape = false |
| return r.ReadByte() |
| } |
| |
| return r.s[si], nil |
| } |
| } |
| |
| // Convert a $GENERATE modifier 0,0,d to something Printf can deal with. |
| func modToPrintf(s string) (string, int, string) { |
| // Modifier is { offset [ ,width [ ,base ] ] } - provide default |
| // values for optional width and type, if necessary. |
| var offStr, widthStr, base string |
| switch xs := strings.Split(s, ","); len(xs) { |
| case 1: |
| offStr, widthStr, base = xs[0], "0", "d" |
| case 2: |
| offStr, widthStr, base = xs[0], xs[1], "d" |
| case 3: |
| offStr, widthStr, base = xs[0], xs[1], xs[2] |
| default: |
| return "", 0, "bad modifier in $GENERATE" |
| } |
| |
| switch base { |
| case "o", "d", "x", "X": |
| default: |
| return "", 0, "bad base in $GENERATE" |
| } |
| |
| offset, err := strconv.Atoi(offStr) |
| if err != nil { |
| return "", 0, "bad offset in $GENERATE" |
| } |
| |
| width, err := strconv.Atoi(widthStr) |
| if err != nil || width < 0 || width > 255 { |
| return "", 0, "bad width in $GENERATE" |
| } |
| |
| if width == 0 { |
| return "%" + base, offset, "" |
| } |
| |
| return "%0" + widthStr + base, offset, "" |
| } |