def __init__(self, path, origin, root_directory=None, fake_root=None, custom_records=[], file_reader=None): if root_directory is None: self.root_directory = os.path.dirname(path) else: self.root_directory = root_directory if file_reader is None: self.file_reader = default_file_reader else: self.file_reader = file_reader self.path = path self.origin = origin self.records = [A, AAAA, CNAME, PTR, TXT, NS, MX, SRV, AFSDB, SOA] self.records += custom_records self.record_names = set(r.record_type for r in self.records) self.rb = RecordBuilder(path, origin, self.records) self.include_handler = IncludeHandler( self.root_directory, path, self, fake_root=fake_root, file_reader=self.file_reader, stack=self.rb) self.whitespace = "\t\r " self.newline = "\n"
class ZoneParser(object): PRAGMA = "P" RECORD = "R" def __init__(self, path, origin, root_directory=None, fake_root=None, custom_records=[], file_reader=None): if root_directory is None: self.root_directory = os.path.dirname(path) else: self.root_directory = root_directory if file_reader is None: self.file_reader = default_file_reader else: self.file_reader = file_reader self.path = path self.origin = origin self.records = [A, AAAA, CNAME, PTR, TXT, NS, MX, SRV, AFSDB, SOA] self.records += custom_records self.record_names = set(r.record_type for r in self.records) self.rb = RecordBuilder(path, origin, self.records) self.include_handler = IncludeHandler( self.root_directory, path, self, fake_root=fake_root, file_reader=self.file_reader, stack=self.rb) self.whitespace = "\t\r " self.newline = "\n" @classmethod def yield_file_characters(cls, f): while True: buf = f.read(4096) if not buf: return for c in buf: yield c def zone_tokenizer(self, generator): collected = "" quoted = False multiline = False comment = False escape = False first = False for c in generator: if first: first = False if c in self.whitespace: yield "" continue if c in self.newline: comment = False quoted = False if collected: yield collected if not multiline: first = True yield None collected = "" continue if escape: collected += c escape = False continue if c == ';': comment = True continue if comment: continue if not quoted and c in self.whitespace: if collected: yield collected collected = "" continue if c == '\\': escape = True continue if c == '"': quoted = not quoted continue if c == '(': multiline = True continue if c == ')': multiline = False continue collected += c if collected: yield collected yield None def generate_lines(self, generator): collected = [] for t in self.zone_tokenizer(generator): if t is None: if collected: yield tuple(collected) collected = [] continue collected.append(t) def parse_generator(self, generator): result = list() for line in self.generate_lines(generator): t, val = self.parse_zone_line(line) if t == self.PRAGMA: result.extend(self.handle_pragma(val)) continue record = self.rb.build_name_record(val) result.append(record) return result def parse_zone_line(self, line): l = line if l[0].startswith("$"): return self.PRAGMA, tuple(l) if len(l) >= 2 and l[1] in self.record_names: return self.RECORD, (l[0], None, None, (l[1], l[2:])) if len(l) >= 3 and l[2] in self.record_names: if l[1] in Record.VALID_CLASS_TYPES: return self.RECORD, (l[0], None, l[1], (l[2], l[3:])) return self.RECORD, (l[0], to_time(l[1]), None, (l[2], l[3:])) if len(l) >= 4 and l[3] in self.record_names: if l[2] in Record.VALID_CLASS_TYPES: return self.RECORD, (l[0], to_time(l[1]), l[2], (l[3], l[4:])) if l[1] in Record.VALID_CLASS_TYPES: return self.RECORD, (l[0], to_time(l[2]), l[1], (l[3], l[4:])) raise ValueError("Cannot handle: {0}".format(line)) def handle_pragma(self, val): name = val[0] if name == "$ORIGIN": self.rb.update_origin(val[1]) return [] if name == "$TTL": self.rb.update_ttl(int(val[1])) return [] if name == "$INCLUDE": path = val[1] return self.include_handler.include(path) if name == "$GENERATE": return self.handle_generate(*val[1:]) raise ValueError(val) def handle_generate(self, range_specifier, *rest): records = list() if '-' not in range_specifier: raise ValueError("invalid range: {0}".format(range_specifier)) from_range, to_range = range_specifier.split('-') from_range, to_range = int(from_range), int(to_range) for i in range(from_range, to_range + 1): current_rest = map(lambda s: str(i) if s == '$' else s, rest) t, val = self.parse_zone_line(current_rest) records.append(self.rb.build_name_record(val)) return records def parse_path(self, path): with self.file_reader(self.root_directory, path) as f: return self.parse_file(f) def parse_file(self, f): generator = self.yield_file_characters(f) return self.parse_generator(generator) def parse_string(self, string): generator = (c for c in string) return self.parse_generator(generator)