Example #1
0
File: zone.py Project: udoprog/bsa
    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"
Example #2
0
File: zone.py Project: udoprog/bsa
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)