[svn] commit: r906 - /experiments/each-zoneload/parse.py

BIND 10 source code commits bind10-changes at lists.isc.org
Mon Feb 22 04:08:43 UTC 2010


Author: each
Date: Mon Feb 22 04:08:42 2010
New Revision: 906

Log:
cleaned up, added comments

Modified:
    experiments/each-zoneload/parse.py

Modified: experiments/each-zoneload/parse.py
==============================================================================
--- experiments/each-zoneload/parse.py (original)
+++ experiments/each-zoneload/parse.py Mon Feb 22 04:08:42 2010
@@ -1,84 +1,48 @@
 #!/usr/bin/python3
 import re, string;
 
+#########################################################################
+# define exceptions
+#########################################################################
 class ParseError(Exception):
     def __init__(self, value):
         self.value = value
     def __str__(self):
         return repr(self.value)
 
+#########################################################################
 # global variables
-decomment = re.compile('\s*(?:;.*)+')
-
-origin='.'
-isname = re.compile('[-\w\$\d\/*]+(?:\.[-\w\$\d\/]+)*\.?')
-
-isttl = re.compile('[0-9]+[wdhms]?', re.I)
+#########################################################################
 maxttl = 0x7fffffff;
 defttl = -1
-
-rrtypes = set(['a', 'aaaa', 'afsdb', 'apl', 'cert', 'cname', 'dhcid',
-               'dlv', 'dname', 'dnskey', 'ds', 'gpos', 'hinfo', 'hip',
-               'ipseckey', 'isdn', 'key', 'kx', 'loc', 'mb', 'md',
-               'mf', 'mg', 'minfo', 'mr', 'mx', 'naptr', 'ns', 'nsap',
-               'nsap-ptr', 'nsec', 'nsec3', 'nsec3param', 'null',
-               'nxt', 'opt', 'ptr', 'px', 'rp', 'rrsig', 'rt', 'sig',
-               'soa', 'spf', 'srv', 'sshfp', 'tkey', 'tsig', 'txt',
-               'x25', 'wks'])
-rrclasses = set(['in', 'ch', 'chaos', 'hs', 'hesiod'])
+origin='.'
 defclass = 'IN'
 
+#########################################################################
+# cleanup: removes excess content from zone files, including comments
+# and extra whitespace
+# input:
+#   a line of text
+# returns:
+#   the same line, with comments removed, leading and trailing
+#   whitespace removed, and all other whitespace compressed to
+#   single spaces
+#########################################################################
+decomment = re.compile('\s*(?:;.*)+')
 def cleanup(s):
     global decomment
     s = s.strip().expandtabs()
     s = decomment.sub('', s)
     return ' '.join(s.split())
 
-def parse_ttl(s):
-    m = re.match('([0-9]+)(.*)', s)
-    if not m:
-        raise ParseError('Invalid TTL: ' + s)
-    ttl, suffix = int(m.group(1)), m.group(2)
-    if suffix.lower() == 'w':
-        ttl *= 604800
-    elif suffix.lower() == 'd':
-        ttl *= 86400
-    elif suffix.lower() == 'h':
-        ttl *= 3600
-    elif suffix.lower() == 'm':
-        ttl *= 60
-    return ttl
-
-def isdirective(s):
-    global origin, defttl, maxttl, isname
-    first, xx, more = s.partition(' ')
-    second, xx, more = more.partition(' ')
-    if re.match('\$origin', first, re.I):
-        if not isname.match(second):
-            raise ParseError('Invalid $ORIGIN')
-        if more:
-            raise ParseError('Invalid $ORIGIN')
-        if second[-1] == '.':
-            origin = second
-        else:
-            origin = second + '.' + origin
-        return True
-    elif re.match('\$ttl', first, re.I):
-        if not isttl.match(second):
-            raise ParseError('Invalid $TTL: ' + second)
-        if more:
-            raise ParseError('Invalid $TTL statement')
-        defttl = parse_ttl(second)
-        if defttl > maxttl:
-            raise ParseError('TTL too high: ' + second)
-        return True
-    elif re.match('\$include', first, re.I):
-        raise ParseError('$INCLUDE not yet implemented')
-    elif re.match('\$generate', first, re.I):
-        raise ParseError('$GENERATE not yet implemented')
-    else:
-        return False
-
+#########################################################################
+# records: generator function to return complete RRs from the zone file,
+# combining lines when necessary because of parentheses
+# input:
+#   zonedata as an array of lines
+# yields:
+#   complete RR
+#########################################################################
 def records(data):
     record = []
     complete = True
@@ -110,15 +74,29 @@
         record = []
         yield ret
 
+#########################################################################
+# pop: remove the first word from a line
+# input: a line
+# returns: first word, rest of the line
+#########################################################################
 def pop(line):
     list = line.split()
     first = list[0]
     rest = ' '.join(list[1:])
     return first, rest
 
-def unpop(word, line):
-    return ' '.join(line.split().insert(0, word))
-
+#########################################################################
+# istype: check whether a string is a known RR type.
+# returns: boolean
+#########################################################################
+rrtypes = set(['a', 'aaaa', 'afsdb', 'apl', 'cert', 'cname', 'dhcid',
+               'dlv', 'dname', 'dnskey', 'ds', 'gpos', 'hinfo', 'hip',
+               'ipseckey', 'isdn', 'key', 'kx', 'loc', 'mb', 'md',
+               'mf', 'mg', 'minfo', 'mr', 'mx', 'naptr', 'ns', 'nsap',
+               'nsap-ptr', 'nsec', 'nsec3', 'nsec3param', 'null',
+               'nxt', 'opt', 'ptr', 'px', 'rp', 'rrsig', 'rt', 'sig',
+               'soa', 'spf', 'srv', 'sshfp', 'tkey', 'tsig', 'txt',
+               'x25', 'wks'])
 def istype(s):
     global rrtypes
     if s.lower() in rrtypes:
@@ -126,6 +104,12 @@
     else:
         return False
 
+#########################################################################
+# istype: check whether a string is a known RR class.  (only 'IN' is
+# supported, but the others must still be recognizable.)
+# returns: boolean
+#########################################################################
+rrclasses = set(['in', 'ch', 'chaos', 'hs', 'hesiod'])
 def isclass(s):
     global rrclasses
     if s.lower() in rrclasses:
@@ -133,37 +117,144 @@
     else:
         return False
 
+#########################################################################
+# isname: check whether a string is a valid DNS name.
+# returns: boolean
+#########################################################################
+name_regex = re.compile('[-\w\$\d\/*]+(?:\.[-\w\$\d\/]+)*\.?')
+def isname(s):
+    global name_regex
+    if name_regex.match(s):
+        return True
+    else:
+        return False
+
+#########################################################################
+# isname: check whether a string is a valid TTL specifier.
+# returns: boolean
+#########################################################################
+ttl_regex = re.compile('[0-9]+[wdhms]?', re.I)
+def isttl(s):
+    global ttl_regex
+    if ttl_regex.match(s):
+        return True
+    else:
+        return False
+
+#########################################################################
+# parse_ttl: convert a TTL field into an integer TTL value
+# (multiplying as needed for minutes, hours, etc.)
+# input:
+#   string
+# returns:
+#   int
+# throws:
+#   ParseError
+#########################################################################
+def parse_ttl(s):
+    m = re.match('([0-9]+)(.*)', s)
+    if not m:
+        raise ParseError('Invalid TTL: ' + s)
+    ttl, suffix = int(m.group(1)), m.group(2)
+    if suffix.lower() == 'w':
+        ttl *= 604800
+    elif suffix.lower() == 'd':
+        ttl *= 86400
+    elif suffix.lower() == 'h':
+        ttl *= 3600
+    elif suffix.lower() == 'm':
+        ttl *= 60
+    return ttl
+
+#########################################################################
+# directive: handle $ORIGIN, $TTL, $INCLUDE and $GENERATE directives
+# (currently only $ORIGIN and $TTL are implemented)
+# input:
+#   a line from a zone file
+# returns:
+#   a boolean indicating whether a directive was found
+# throws:
+#   ParseError
+#########################################################################
+def directive(s):
+    global origin, defttl, maxttl
+    first, xx, more = s.partition(' ')
+    second, xx, more = more.partition(' ')
+    if re.match('\$origin', first, re.I):
+        if not isname(second):
+            raise ParseError('Invalid $ORIGIN')
+        if more:
+            raise ParseError('Invalid $ORIGIN')
+        if second[-1] == '.':
+            origin = second
+        else:
+            origin = second + '.' + origin
+        return True
+    elif re.match('\$ttl', first, re.I):
+        if not isttl(second):
+            raise ParseError('Invalid $TTL: ' + second)
+        if more:
+            raise ParseError('Invalid $TTL statement')
+        defttl = parse_ttl(second)
+        if defttl > maxttl:
+            raise ParseError('TTL too high: ' + second)
+        return True
+    elif re.match('\$include', first, re.I):
+        raise ParseError('$INCLUDE not yet implemented')
+    elif re.match('\$generate', first, re.I):
+        raise ParseError('$GENERATE not yet implemented')
+    else:
+        return False
+
+#########################################################################
+# four: try parsing on the assumption that the RR type is specified in
+# field 4, and name, ttl and class are in fields 1-3
+# are all specified, with type in field 4
+# input:
+#   a record to parse, and the most recent name found in prior records
+# returns:
+#   empty list if parse failed, else name, ttl, class, type, rdata
+# throws:
+#   ParseError
+#########################################################################
 def four(record, curname):
     ret = ''
     list = record.split()
     if len(list) <= 4:
         return ret
     if istype(list[3]):
-        if isclass(list[2]) and isttl.match(list[1]) \
-                            and isname.match(list[0]):
+        if isclass(list[2]) and isttl(list[1]) and isname(list[0]):
             name, ttl, rrclass, rrtype = list[0:3]
             rdata = ' '.join(list[4:])
             ret = name, ttl, rrclass, rrtype, rdata
     return ret
 
+#########################################################################
+# three: try parsing on the assumption that the RR type is specified in
+# field 3, and one of name, ttl, or class has been omitted
+# input:
+#   a record to parse, and the most recent name found in prior records
+# returns:
+#   empty list if parse failed, else name, ttl, class, type, rdata
+# throws:
+#   ParseError
+#########################################################################
 def three(record, curname):
-    global isttl, defttl, defclass
+    global defttl, defclass
     ret = ''
     list = record.split()
     if len(list) <= 3:
         return ret
     if istype(list[2]):
-        if isclass(list[1]) and not isttl.match(list[0]) \
-                            and isname.match(list[0]):
+        if isclass(list[1]) and not isttl(list[0]) and isname(list[0]):
             rrclass = list[1]
             ttl = defttl
             name = list[0]
-        elif not isclass(list[1]) and isttl.match(list[1]) \
-                                  and isname.match(list[0]):
+        elif not isclass(list[1]) and isttl(list[1]) and isname(list[0]):
             rrclass = defclass
             ttl = parse_ttl(list[1])
             name = list[0]
-        elif curname and isclass(list[1]) and isttl.match(list[0]):
+        elif curname and isclass(list[1]) and isttl(list[0]):
             rrclass = defclass
             ttl = parse_ttl(list[1])
             name = curname
@@ -175,14 +266,24 @@
         ret = name, ttl, rrclass, rrtype, rdata
     return ret
 
+#########################################################################
+# two: try parsing on the assumption that the RR type is specified in
+# field 2, and field 1 is name, with ttl and class omitted.
+# input:
+#   a record to parse, and the most recent name found in prior records
+# returns:
+#   empty list if parse failed, else name, ttl, class, type, rdata
+# throws:
+#   ParseError
+#########################################################################
 def two(record, curname):
-    global isttl, defttl, defclass
+    global defttl, defclass
     ret = ''
     list = record.split()
     if len(list) <= 2:
         return ret
     if istype(list[1]):
-        if isname.match(list[0]):
+        if isname(list[0]):
             name = list[0]
         else:
             raise ParseError("Cannot parse RR: " + record)
@@ -194,18 +295,18 @@
         ret = name, ttl, rrclass, rrtype, rdata
     return ret
 
-def main():
+#########################################################################
+# parse_zonefile: parse a zone master file and return it as an array of
+# tuples
+#########################################################################
+def parse_zonefile(file):
     global defttl, defclass
-    data = open('testfile').read().splitlines()
+    data = open(file).read().splitlines()
+    zone = []
     name = ''
 
-    print ('---------------------')
-
     for record in records(data):
-        if isdirective(record):
-            print('ORIGIN: ' + origin)
-            print('TTL: ' + str(defttl))
-            print ('---------------------')
+        if directive(record):
             continue;
 
         first = record.split()[0]
@@ -238,11 +339,23 @@
 
         # add origin to rdata if necessary
         if rrtype.lower() in ('cname', 'dname', 'ns'):
-            if not isname.match(rdata):
+            if not isname(rdata):
                 raise ParseError("Invalid " + rrtype + ": " + rdata)
             if rdata[-1] != '.':
                 rdata += '.' + origin
 
+        zone.append((name, ttl, rrclass, rrtype, rdata))
+
+    return zone
+
+#########################################################################
+# main: used for testing; parse a zone file and print out each record
+# broken up into separate name, ttl, class, type, and rdata files
+#########################################################################
+def main():
+    print ('---------------------')
+    zone = parse_zonefile('testfile')
+    for name, ttl, rrclass, rrtype, rdata in zone:
         print ('name: ' + name)
         print ('ttl: ' + str(ttl))
         print ('rrclass: ' + rrclass)




More information about the bind10-changes mailing list