Last active
October 11, 2018 08:12
-
-
Save vsajip/d1b135010a07b7cd35673544feb61cd8 to your computer and use it in GitHub Desktop.
Logging format string validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import re | |
import string | |
import sys | |
_sfmt = string.Formatter() | |
FIELD_SPEC = re.compile(r'^(\d+|\w+)(\.\w+|\[[^]]+\])*$') | |
FMT_SPEC = re.compile(r'^(.?[<>=^])?[+ -]?#?0?(\d+|{\w+})?[,_]?(\.(\d+|{\w+}))?[bcdefgnosx%]?$') | |
def validate_braces(fs): | |
fields = set() | |
try: | |
for _, fieldname, spec, conversion in _sfmt.parse(fs): | |
if fieldname: | |
if not FIELD_SPEC.match(fieldname): | |
raise ValueError('invalid field name/expression: %r' % fieldname) | |
fields.add(fieldname) | |
if conversion: | |
if conversion not in 'rsa': | |
raise ValueError('invalid conversion: %r' % conversion) | |
if spec: | |
if not FMT_SPEC.match(spec): | |
raise ValueError('bad specifier: %r' % spec) | |
except ValueError as e: | |
raise ValueError('invalid format: %s' % e) | |
if not fields: | |
raise ValueError('invalid format: no fields') | |
def validate_dollars(fs): | |
p = string.Template.pattern | |
fields = set() | |
for m in p.finditer(fs): | |
d = m.groupdict() | |
if d['named']: | |
fields.add(d['named']) | |
elif d['braced']: | |
fields.add(d['braced']) | |
elif m.group(0) == '$': | |
raise ValueError('invalid format: bare \'$\' not allowed') | |
if not fields: | |
raise ValueError('invalid format: no fields') | |
def main(): | |
# G -> good and valid | |
# E -> format causes errors in underlying engine | |
# F -> cases which the underlying engine wll be happy with, but we're not | |
brace_cases = ( | |
'G:{foo}', | |
'G:{foo.bar}', | |
'G:{foo[0].bar[1].baz}', | |
'G:{foo[k1].bar[k2].baz}', | |
'G:{12[k1].bar[k2].baz}', | |
'G:{foo!r}', | |
'G:{foo!s}', | |
'G:{foo!a}', | |
'G:{foo!r:4.2}', | |
'E:{foo!r:4.2', | |
'E:{{foo!r:4.2}', | |
'F:{{foo!r:4.2}}', | |
'G:{foo:{w}.{p}}', | |
'G:{foo:12.{p}}', | |
'G:{foo:{w}.6}', | |
'F:{foo:{{w}}.{{p}}}', | |
'F:{foo/bar}', | |
'E:{foo:{{w}}.{{p}}}}', | |
'F:{foo!X:{{w}}.{{p}}}', | |
'F:{foo!a:random}', | |
'E:{foo!a:ran{dom}', | |
'F:{foo!a:ran{d}om}', | |
'F:{foo.!a:d}', | |
) | |
for case in brace_cases: | |
try: | |
validate_braces(case) | |
print('%-30s -> ok' % case) | |
except ValueError as e: | |
print('%-30s -> %s' % (case, e)) | |
dollar_cases = ( | |
'G:$foo', | |
'G:${bar}', | |
'G:$bar $$', | |
'E:$bar $$$', | |
'G:$bar $$$$', | |
'E:foo $', | |
'F:foo', | |
'E:foo $.', | |
) | |
print('-' * 80) | |
for case in dollar_cases: | |
try: | |
validate_dollars(case) | |
print('%-30s -> ok' % case) | |
except ValueError as e: | |
print('%-30s -> %s' % (case, e)) | |
if __name__ == '__main__': | |
try: | |
rc = main() | |
except Exception as e: | |
sys.stderr.write('Failed: %s\n' % e) | |
import traceback; traceback.print_exc() | |
rc = 1 | |
sys.exit(rc) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment