pvl.dhcp.config: stricter quoting of e.g. include file paths
--- a/pvl/dhcp/config.py Sun Mar 01 23:32:57 2015 +0200
+++ b/pvl/dhcp/config.py Mon Mar 02 00:11:43 2015 +0200
@@ -1,5 +1,10 @@
import logging; log = logging.getLogger('pvl.dhcp.config')
import shlex
+import string
+
+# simplified model of lexer chars
+TOKEN_START = string.ascii_letters
+TOKEN = TOKEN_START + string.digits + '-_.'
class DHCPConfigError(Exception):
def __init__ (self, parser, error, line=None):
@@ -32,6 +37,10 @@
[]
>>> list(split(''))
[]
+ >>> list(split('next-server foo'))
+ ['next-server', 'foo']
+ >>> list(split('include "foo/bar.conf";'))
+ ['include', 'foo/bar.conf', ';']
"""
if line is None:
@@ -39,7 +48,7 @@
lexer = shlex.shlex(line, posix=True)
lexer.commenters = '#'
- lexer.wordchars += '-./'
+ lexer.wordchars = TOKEN
while True:
item = lexer.get_token()
@@ -49,6 +58,32 @@
yield item
+def quote (value):
+ """
+ Build a single field as part of a dhcp.conf line.
+
+ >>> print quote('foo')
+ foo
+ >>> print quote('foo bar')
+ "foo bar"
+ >>> print quote('foo/bar.conf')
+ "foo/bar.conf"
+ >>> print quote(5)
+ 5
+ >>> print quote('5')
+ "5"
+ """
+
+ if isinstance(value, int):
+ return str(value)
+ elif not value:
+ return ''
+ elif value[0] in TOKEN_START and all(c in TOKEN for c in value):
+ return str(value)
+ else:
+ # quoted
+ return '"{value}"'.format(value=value)
+
class Block (object):
"""
A block in a dhcp conf includes parameters and sub-blocks.
@@ -84,7 +119,7 @@
"""
Parse an complete file, returning the top-level Block.
- >>> DHCPConfigParser.load(['foo;', 'bar {', '\tasdf "quux";', '}'])
+ >>> DHCPConfigParser.load(['foo;', 'bar {', '\tasdf "quux";', '}'], name='test')
Block(None, items=[('foo', )], blocks=[Block(('bar', ), items=[('asdf', 'quux')], blocks=[])])
"""
@@ -207,22 +242,6 @@
for block in self.parse_line(line) :
yield block
-def build_field (value):
- """
- Build a single field as part of a dhcp.conf line.
-
- >>> print build_field('foo')
- foo
- >>> print build_field('foo bar')
- "foo bar"
- """
-
- if any(c.isspace() for c in value):
- # quoted
- return '"{value}"'.format(value=value)
- else:
- return value
-
def build_line (item, end, indent=0):
"""
Build a structured line.
@@ -235,7 +254,7 @@
foo "bar quux";
"""
- return ' '*indent + ' '.join(build_field(field) for field in item) + end
+ return ' '*indent + ' '.join(quote(field) for field in item) + end
def build_item (item, **opts):
"""