123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- package dns
- import (
- "bytes"
- "fmt"
- "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.
- // Any error are returned as a string value, the empty string signals
- // "no error".
- func generate(l lex, c chan lex, t chan *Token, o string) string {
- step := 1
- if i := strings.IndexAny(l.token, "/"); i != -1 {
- if i+1 == len(l.token) {
- return "bad step in $GENERATE range"
- }
- if s, e := strconv.Atoi(l.token[i+1:]); e == nil {
- if s < 0 {
- return "bad step in $GENERATE range"
- }
- step = s
- } else {
- return "bad step in $GENERATE range"
- }
- l.token = l.token[:i]
- }
- sx := strings.SplitN(l.token, "-", 2)
- if len(sx) != 2 {
- return "bad start-stop in $GENERATE range"
- }
- start, err := strconv.Atoi(sx[0])
- if err != nil {
- return "bad start in $GENERATE range"
- }
- end, err := strconv.Atoi(sx[1])
- if err != nil {
- return "bad stop in $GENERATE range"
- }
- if end < 0 || start < 0 || end < start {
- return "bad range in $GENERATE range"
- }
- <-c // _BLANK
- // Create a complete new string, which we then parse again.
- s := ""
- BuildRR:
- l = <-c
- if l.value != zNewline && l.value != zEOF {
- s += l.token
- goto BuildRR
- }
- for i := start; i <= end; i += step {
- var (
- escape bool
- dom bytes.Buffer
- mod string
- err string
- offset int
- )
- for j := 0; j < len(s); j++ { // No 'range' because we need to jump around
- switch s[j] {
- case '\\':
- if escape {
- dom.WriteByte('\\')
- escape = false
- continue
- }
- escape = true
- case '$':
- mod = "%d"
- offset = 0
- if escape {
- dom.WriteByte('$')
- escape = false
- continue
- }
- escape = false
- if j+1 >= len(s) { // End of the string
- dom.WriteString(fmt.Sprintf(mod, i+offset))
- continue
- } else {
- if s[j+1] == '$' {
- dom.WriteByte('$')
- j++
- continue
- }
- }
- // Search for { and }
- if s[j+1] == '{' { // Modifier block
- sep := strings.Index(s[j+2:], "}")
- if sep == -1 {
- return "bad modifier in $GENERATE"
- }
- mod, offset, err = modToPrintf(s[j+2 : j+2+sep])
- if err != "" {
- return err
- }
- j += 2 + sep // Jump to it
- }
- dom.WriteString(fmt.Sprintf(mod, i+offset))
- default:
- if escape { // Pretty useless here
- escape = false
- continue
- }
- dom.WriteByte(s[j])
- }
- }
- // Re-parse the RR and send it on the current channel t
- rx, e := NewRR("$ORIGIN " + o + "\n" + dom.String())
- if e != nil {
- return e.(*ParseError).err
- }
- t <- &Token{RR: rx}
- // Its more efficient to first built the rrlist and then parse it in
- // one go! But is this a problem?
- }
- return ""
- }
- // Convert a $GENERATE modifier 0,0,d to something Printf can deal with.
- func modToPrintf(s string) (string, int, string) {
- xs := strings.SplitN(s, ",", 3)
- if len(xs) != 3 {
- return "", 0, "bad modifier in $GENERATE"
- }
- // xs[0] is offset, xs[1] is width, xs[2] is base
- if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" {
- return "", 0, "bad base in $GENERATE"
- }
- offset, err := strconv.Atoi(xs[0])
- if err != nil || offset > 255 {
- return "", 0, "bad offset in $GENERATE"
- }
- width, err := strconv.Atoi(xs[1])
- if err != nil || width > 255 {
- return "", offset, "bad width in $GENERATE"
- }
- switch {
- case width < 0:
- return "", offset, "bad width in $GENERATE"
- case width == 0:
- return "%" + xs[1] + xs[2], offset, ""
- }
- return "%0" + xs[1] + xs[2], offset, ""
- }
|