Skip to content
This repository has been archived by the owner on Aug 1, 2023. It is now read-only.

Version 0.14.0 #12

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 28 additions & 34 deletions blockstack_zones/parse_zone_file.py
Original file line number Diff line number Diff line change
@@ -36,6 +36,7 @@ def make_rr_subparser(subparsers, rec_type, args_and_types):

sp.add_argument("name", type=str)
sp.add_argument("ttl", type=int, nargs='?')
sp.add_argument("class", type=str)
sp.add_argument(rec_type, type=str)

for (argname, argtype) in args_and_types:
@@ -227,34 +228,6 @@ def flatten(text):
return "\n".join(flattened)


def remove_class(text):
"""
Remove the CLASS from each DNS record, if present.
The only class that gets used today (for all intents
and purposes) is 'IN'.
"""

# see RFC 1035 for list of classes
lines = text.split("\n")
ret = []
for line in lines:
tokens = tokenize_line(line)
tokens_upper = [t.upper() for t in tokens]

if "IN" in tokens_upper:
tokens.remove("IN")
elif "CS" in tokens_upper:
tokens.remove("CS")
elif "CH" in tokens_upper:
tokens.remove("CH")
elif "HS" in tokens_upper:
tokens.remove("HS")

ret.append(serialize(tokens))

return "\n".join(ret)


def add_default_name(text):
"""
Go through each line of the text and ensure that
@@ -290,14 +263,32 @@ def parse_line(parser, record_token, parsed_records):
global SUPPORTED_RECORDS

line = " ".join(record_token)
class_inferred = False

# match parser to record type
# match parser to record type, add record type as first element
if len(record_token) >= 2 and record_token[1] in SUPPORTED_RECORDS:
# with no ttl
# RR has no TTL or CLASS
record_token = [record_token[1]] + record_token
record_token.insert(2, "IN")
class_inferred = True

elif len(record_token) >= 3 and record_token[2] in SUPPORTED_RECORDS:
# with ttl
# RR has a TTL or CLASS
record_token = [record_token[2]] + record_token
if record_token[2][0].isdigit():
# First character of token is numeric, must be TTL value and not a CLASS.
# Add default IN class
record_token.insert(3, "IN")
class_inferred = True

elif len(record_token) >= 4 and record_token[3] in SUPPORTED_RECORDS:
# RR has a TTL and CLASS
record_token = [record_token[3]] + record_token

# Class and TTL can be provided in either order, always place TTL before CLASS
if not record_token[2][0].isdigit():
record_token[2], record_token[3] = record_token[3], record_token[2]


try:
rr, unmatched = parser.parse_known_args(record_token)
@@ -324,12 +315,16 @@ def parse_line(parser, record_token, parsed_records):
if record_dict[field] is None:
del record_dict[field]

current_origin = record_dict.get('$ORIGIN', parsed_records.get('$ORIGIN', None))
current_origin = record_dict.get('$origin', parsed_records.get('$origin', None))

# special record-specific fix-ups
if record_type == 'PTR':
record_dict['fullname'] = record_dict['name'] + '.' + current_origin


# Add a hint that no CLASS was provide for this record, defaulted to IN
if class_inferred:
record_dict['_missing_class'] = True

if len(record_dict) > 0:
if record_type.startswith("$"):
# put the value directly
@@ -371,7 +366,6 @@ def parse_zone_file(text, ignore_invalid=False):
"""
text = remove_comments(text)
text = flatten(text)
text = remove_class(text)
text = add_default_name(text)
json_zone_file = parse_lines(text, ignore_invalid=ignore_invalid)
return json_zone_file
6 changes: 6 additions & 0 deletions blockstack_zones/record_processors.py
Original file line number Diff line number Diff line change
@@ -117,6 +117,12 @@ def process_rr(data, record_type, record_keys, field, template):
if data[i].get('ttl') is not None:
record_data.append( str(data[i]['ttl']) )

# Do not output a CLASS field if the '_missing_class' flag indicates
# that it was not included in the original zone file.
# Records which did not have a CLASS should be serialized without a CLASS
if not data[i].get('_missing_class'):
record_data.append(str(data[i].get('class', 'IN')))

record_data.append(record_type)
record_data += [str(data[i][record_key]) for record_key in record_keys]
record += " ".join(record_data) + "\n"
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@

setup(
name='blockstack-zones',
version='0.1.6',
version='0.14.0',
url='https://github.com/blockstack/dns-zone-file-py',
license='MIT',
author='Blockstack Developers',
62 changes: 32 additions & 30 deletions test_sample_data.py
Original file line number Diff line number Diff line change
@@ -15,10 +15,11 @@
$ORIGIN example.com
$TTL 86400

server1 IN A 10.0.1.5
server1 A 10.0.1.5
server2 IN A 10.0.1.7
dns1 IN A 10.0.1.2
dns2 IN A 10.0.1.3
server3 3600 A 10.0.1.7
dns1 3600 IN A 10.0.1.2
dns2 IN 3600 A 10.0.1.3

ftp IN CNAME server1
mail IN CNAME server1
@@ -59,9 +60,10 @@
"uri": [{
"name": "@",
"ttl": "1D",
"class": "IN",
"priority": 1,
"weight": 10,
"target": "https://mq9.s3.amazonaws.com/naval.id/profile.json"
"target": "https://mq9.s3.amazonaws.com/naval.id/profile.json",
}]
},
"sample_2": {
@@ -77,33 +79,33 @@
"minimum": 86400
},
"ns": [
{ "host": "NS1.NAMESERVER.NET." },
{ "host": "NS2.NAMESERVER.NET." }
{ "host": "NS1.NAMESERVER.NET.", "class": "IN" },
{ "host": "NS2.NAMESERVER.NET.", "class": "IN" }
],
"a": [
{ "name": "@", "ip": "127.0.0.1" },
{ "name": "www", "ip": "127.0.0.1" },
{ "name": "mail", "ip": "127.0.0.1" }
{ "name": "@", "ip": "127.0.0.1", "class": "IN", },
{ "name": "www", "ip": "127.0.0.1", "class": "IN", "ttl": 3600 },
{ "name": "mail", "ip": "127.0.0.1", "ttl": 3600, "_missing_class": True }
],
"aaaa": [
{ "ip": "::1" },
{ "name": "mail", "ip": "2001:db8::1" }
{ "ip": "::1", "class": "IN" },
{ "name": "mail", "ip": "2001:db8::1", "class": "IN" }
],
"cname": [
{ "name": "mail1", "alias": "mail" },
{ "name": "mail2", "alias": "mail" }
{ "name": "mail1", "alias": "mail", "class": "IN" },
{ "name": "mail2", "alias": "mail", "class": "IN" }
],
"mx": [
{ "preference": 0, "host": "mail1" },
{ "preference": 10, "host": "mail2" }
{ "preference": 0, "host": "mail1", "class": "IN" },
{ "preference": 10, "host": "mail2", "class": "IN" }
],
"txt": [
{ "name": "txt1", "txt": "hello" },
{ "name": "txt2", "txt": "world" }
{ "name": "txt1", "txt": "hello", "class": "IN" },
{ "name": "txt2", "txt": "world", "class": "IN" }
],
"srv": [
{ "name": "_xmpp-client._tcp", "target": "jabber", "priority": 10, "weight": 0, "port": 5222 },
{ "name": "_xmpp-server._tcp", "target": "jabber", "priority": 10, "weight": 0, "port": 5269 }
{ "name": "_xmpp-client._tcp", "class": "IN", "target": "jabber", "priority": 10, "weight": 0, "port": 5222 },
{ "name": "_xmpp-server._tcp", "class": "IN", "target": "jabber", "priority": 10, "weight": 0, "port": 5269 }
]
},
"sample_3": {
@@ -119,25 +121,25 @@
"minimum": 86400
},
"ns": [
{ "host": "NS1.NAMESERVER.NET." },
{ "host": "NS2.NAMESERVER.NET." }
{ "host": "NS1.NAMESERVER.NET.", "class": "IN" },
{ "host": "NS2.NAMESERVER.NET.", "class": "IN" }
],
"a": [
{ "name": "@", "ip": "127.0.0.1" },
{ "name": "www", "ip": "127.0.0.1" },
{ "name": "mail", "ip": "127.0.0.1" }
{ "name": "@", "ip": "127.0.0.1", "class": "IN" },
{ "name": "www", "ip": "127.0.0.1", "class": "IN" },
{ "name": "mail", "ip": "127.0.0.1", "class": "IN" }
],
"aaaa": [
{ "ip": "::1" },
{ "name": "mail", "ip": "2001:db8::1" }
{ "ip": "::1", "class": "IN" },
{ "name": "mail", "ip": "2001:db8::1", "class": "IN" }
],
"cname":[
{ "name": "mail1", "alias": "mail" },
{ "name": "mail2", "alias": "mail" }
{ "name": "mail1", "alias": "mail", "class": "IN" },
{ "name": "mail2", "alias": "mail", "class": "IN" }
],
"mx":[
{ "preference": 0, "host": "mail1" },
{ "preference": 10, "host": "mail2" }
{ "preference": 0, "host": "mail1", "class": "IN" },
{ "preference": 10, "host": "mail2", "class": "IN" }
]
}
}
23 changes: 22 additions & 1 deletion unit_tests.py
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@ def test_zone_file_creation_1(self):
self.assertTrue(isinstance(zone_file, (unicode, str)))
self.assertTrue("$ORIGIN" in zone_file)
self.assertTrue("$TTL" in zone_file)
self.assertTrue("@ 1D URI" in zone_file)
self.assertTrue("@ 1D IN URI" in zone_file)

def test_zone_file_creation_2(self):
json_zone_file = zone_file_objects["sample_2"]
@@ -29,6 +29,10 @@ def test_zone_file_creation_2(self):
self.assertTrue("$ORIGIN" in zone_file)
self.assertTrue("$TTL" in zone_file)
self.assertTrue("@ IN SOA" in zone_file)
# www has a TTL and a class
self.assertTrue("www 3600 IN A 127.0.0.1" in zone_file)
# mail has "_missing_class" set, confirm no class is output
self.assertTrue("mail 3600 A 127.0.0.1" in zone_file)

def test_zone_file_creation_3(self):
json_zone_file = zone_file_objects["sample_3"]
@@ -58,6 +62,23 @@ def test_zone_file_parsing_2(self):
self.assertTrue("$ttl" in zone_file)
self.assertTrue("$origin" in zone_file)

a_records = {record["name"]: record for record in zone_file["a"]}
# Confirm that all records have class "IN"
self.assertTrue(all([(record["class"] == "IN") for record in a_records.values()]))
# TTL and no CLASS
self.assertEqual(a_records["server1"].get("_missing_class"), True)
# CLASS and no TTL
self.assertEqual(a_records["server2"].get("_missing_class"), None)
# TTL and no CLASS
self.assertEqual(a_records["server3"].get("ttl"), 3600)
self.assertEqual(a_records["server3"].get("_missing_class"), True)
# TTL and CLASS
self.assertEqual(a_records["dns1"].get("ttl"), 3600)
self.assertEqual(a_records["dns1"].get("_missing_class"), None)
# Reversed TTL and CLASS field order
self.assertEqual(a_records["dns2"].get("ttl"), 3600)
self.assertEqual(a_records["dns2"].get("_missing_class"), None)

def test_zone_file_parsing_3(self):
zone_file = parse_zone_file(zone_files["sample_3"])
#print json.dumps(zone_file, indent=2)