def comment(include_parens=False): inner = recursive() > named(u'comment', RFC(7230)) inner.rec = '(' + string(ctext | quoted_pair(sensible_for=u'()\\') | inner) + ')' if not include_parens: inner = (lambda s: s[1:-1]) << inner return inner > named(u'comment', RFC(7230))
def test_parser_edge_cases(): # Our parser implementation is general enough that # some of its branches are not being exercised by our regular tests, # so I had to come up with these contrived examples to test them. p = many(rfc7230.tchar) > named(u'p') p1 = '1' * p > named(u'p1') p2 = '11' * p * skip('\n') > named(u'p2') assert parse(p1 | p2, b'11abc') == (u'1', [u'1', u'a', u'b', u'c']) assert parse(p1 | p2, b'11abc\n') == (u'11', [u'a', u'b', u'c']) p = recursive() > named(u'p') p.rec = (rfc7230.tchar * p | subst(None) << empty) assert parse(p, b'abc') == (u'a', (u'b', (u'c', None))) p = literal('ab') > named(u'p') p0 = subst(u'') << empty | p > named(u'p0') p1 = 'xab' * p0 > named(u'p1') p2 = 'x' * string(p0) * '!' > named(u'p2') assert parse(p1 | p2, b'xabab') == (u'xab', u'ab') assert parse(p1 | p2, b'xabab!') == (u'x', u'abab', u'!') p = empty | literal('a') > named(u'p') p0 = p * 'x' > named(u'x') assert parse(p0, b'x') == u'x'
def preference_parameter(head=False): # The head (first) ``preference-parameter`` of a ``preference`` # contains the actual preference name, which we want to annotate. name_cls = Preference if head else CaseInsensitive return (_normalize_empty_value << (parameter(name_cls=name_cls) | name_cls << token)) > named( u'preference-parameter', RFC(7240, errata=4439), is_pivot=True)
def preference_parameter(head=False): # The head (first) ``preference-parameter`` of a ``preference`` # contains the actual preference name, which we want to annotate. name_cls = Preference if head else CaseInsensitive return ( _normalize_empty_value << (parameter(name_cls=name_cls) | name_cls << token) ) > named(u'preference-parameter', RFC(7240, errata=4439), is_pivot=True)
def transfer_coding(no_trailers=False, no_q=False): exclude = _built_in_codings if no_trailers: exclude = exclude + ['trailers'] r = transfer_extension(exclude, no_q) for name in _built_in_codings: r = r | _empty_params << (TransferCoding << literal(name)) return r > named(u'transfer-coding', RFC(7230), is_pivot=True)
def quoted_pair(sensible_for): # In RFC 7230, ``<quoted-pair>`` is a single rule, # but we parametrize it to report no. 1017 depending on the context. @can_complain def check_sensible(complain, c): if c not in sensible_for: complain(1017, char=c) return c return (check_sensible << skip('\\') * (HTAB | SP | VCHAR | obs_text) > named(u'quoted-pair', RFC(7230)))
def media_range(no_q=False): return Parametrized << ( ( literal('*/*') | type_ + '/' + '*' | MediaType << type_ + '/' + subtype ) * ( MultiDict << many( skip(OWS * ';' * OWS) * parameter(exclude=['q'] if no_q else []) ) ) ) > named(u'media-range', RFC(7231), is_pivot=True)
def link_extension(exclude_builtin): if exclude_builtin: exclude1 = [name for name in _builtin_params if not name.endswith('*')] exclude2 = [name.rstrip('*') for name in _builtin_params if name.endswith('*')] else: # pragma: no cover exclude1 = exclude2 = None return ( ( (CaseInsensitive << parmname__excluding(exclude1)) * maybe(skip(OWS * '=' * OWS) * (ptoken | quoted_string)) ) | ( (CaseInsensitive << ext_name_star__excluding(exclude2)) * skip(OWS * '=' * OWS) * ext_value ) ) > named(u'link-extension', RFC(5988), is_pivot=True)
def link_extension(exclude_builtin): if exclude_builtin: exclude1 = [name for name in _builtin_params if not name.endswith('*')] exclude2 = [name.rstrip('*') for name in _builtin_params if name.endswith('*')] else: exclude1 = exclude2 = None return ( ( (CaseInsensitive << parmname__excluding(exclude1)) * maybe(skip(OWS * '=' * OWS) * (ptoken | quoted_string)) ) | ( (CaseInsensitive << ext_name_star__excluding(exclude2)) * skip(OWS * '=' * OWS) * ext_value ) ) > named(u'link-extension', RFC(5988), is_pivot=True)
# RFC 7240 Section 2: "Empty or zero-length values on both # the preference token and within parameters are equivalent # to no value being specified at all." (name, value) = x if isinstance(x, tuple) else (x, None) return Parametrized(name, None if value == u'' else value) def preference_parameter(head=False): # The head (first) ``preference-parameter`` of a ``preference`` # contains the actual preference name, which we want to annotate. name_cls = Preference if head else CaseInsensitive return ( _normalize_empty_value << (parameter(name_cls=name_cls) | name_cls << token) ) > named(u'preference-parameter', RFC(7240, errata=4439), is_pivot=True) preference = Parametrized << ( preference_parameter(head=True) * many(skip(OWS * ';') * maybe(skip(OWS) * preference_parameter())) ) > named(u'preference', RFC(7240, errata=4439), is_pivot=True) Prefer = comma_list1(preference) > pivot Preference_Applied = comma_list1(preference_parameter(head=True)) > pivot return_ = CaseInsensitive << (literal('representation') | 'minimal') > pivot wait = delay_seconds > auto handling = CaseInsensitive << (literal('strict') | 'lenient') > pivot fill_names(globals(), RFC(7240))
def transfer_extension(exclude=None, no_q=False): return Parametrized << ( (TransferCoding << token__excluding(exclude or [])) * (MultiDict << many(skip(OWS * ';' * OWS) * transfer_parameter(no_q))) ) > named(u'transfer-extension', RFC(7230), is_pivot=True)
def transfer_parameter(no_q=False): return ( (token__excluding(['q']) if no_q else token) * skip(BWS * '=' * BWS) * (token | quoted_string) ) > named(u'transfer-parameter', RFC(7230), is_pivot=True)
def ext_name_star__excluding(exclude): return (parmname__excluding(exclude) + '*' > named(u'ext-name-star', RFC(5988)))
def comma_list1(element): return _collect_elements << ( many(subst(None) << ',' * OWS) + ((lambda x: [x]) << group(element)) + many(skip(OWS * ',') * maybe(skip(OWS) * element)) ) > named(u'1#rule', RFC(7230, section=(7,)))
def comma_list(element): # RFC Errata ID: 5257 return _collect_elements << (maybe(group(element) * skip(OWS)) % many( skip(literal(',') * OWS) * maybe(group(element) * skip(OWS)))) > named( u'#rule', RFC(7230, section=u'7'))
def comma_list(element): # RFC Errata ID: 5257 return _collect_elements << ( maybe(group(element) * skip(OWS)) % many(skip(literal(',') * OWS) * maybe(group(element) * skip(OWS))) ) > named(u'#rule', RFC(7230, section=u'7'))
def parmname__excluding(exclude): return (string_excluding(attr_char, [''] + exclude) > named(u'parmname', RFC(5987), is_pivot=True))
def parmname__excluding(exclude): return (string_excluding(attr_char, [''] + exclude) > named( u'parmname', RFC(5987), is_pivot=True))
def transfer_parameter(no_q=False): return ((token__excluding(['q']) if no_q else token) * skip(BWS * '=' * BWS) * (token | quoted_string)) > named( u'transfer-parameter', RFC(7230), is_pivot=True)
def comma_list1(element): return _collect_elements << (many(subst(None) << ',' * OWS) + ( (lambda x: [x]) << group(element)) + many( skip(OWS * ',') * maybe(skip(OWS) * element))) > named( u'1#rule', RFC(7230, section=u'7'))
def comma_list(element): return _collect_elements << maybe( (subst([None, None]) << literal(',') | (lambda x: [x]) << group(element)) + many(skip(OWS * ',') * maybe(skip(OWS) * element)) ) > named(u'#rule', RFC(7230, section=(7,)))
def media_range(no_q=False): return Parametrized << ( (literal('*/*') | type_ + '/' + '*' | _check_media_type << (MediaType << type_ + '/' + subtype)) * (MultiDict << many( skip(OWS * ';' * OWS) * parameter(exclude=['q'] if no_q else []))) ) > named(u'media-range', RFC(7231), is_pivot=True)
def comma_list(element): return _collect_elements << maybe( (subst([None, None]) << literal(',') | (lambda x: [x]) << group(element)) + many(skip(OWS * ',') * maybe(skip(OWS) * element))) > named( u'#rule', RFC(7230, section=(7, )))
def parameter(exclude=None): return ((CaseInsensitive << token__excluding(exclude or [])) * skip('=') * (token | quoted_string)) > named( u'parameter', RFC(7231), is_pivot=True)
def parameter(exclude=None): return ( (CaseInsensitive << token__excluding(exclude or [])) * skip('=') * (token | quoted_string) ) > named(u'parameter', RFC(7231), is_pivot=True)
def extension_pragma(exclude_no_cache=False): return Parametrized << ( (token__excluding(['no-cache']) if exclude_no_cache else token) * maybe(skip('=') * (token | quoted_string))) > named( u'extension-pragma', RFC(7234), is_pivot=True)
def extension_pragma(exclude_no_cache=False): return Parametrized << ( (token__excluding(['no-cache']) if exclude_no_cache else token) * maybe(skip('=') * (token | quoted_string)) ) > named(u'extension-pragma', RFC(7234), is_pivot=True)
def _normalize_empty_value(x): # RFC 7240 Section 2: "Empty or zero-length values on both # the preference token and within parameters are equivalent # to no value being specified at all." (name, value) = x if isinstance(x, tuple) else (x, None) return Parametrized(name, None if value == u'' else value) def preference_parameter(head=False): # The head (first) ``preference-parameter`` of a ``preference`` # contains the actual preference name, which we want to annotate. name_cls = Preference if head else CaseInsensitive return (_normalize_empty_value << (parameter(name_cls=name_cls) | name_cls << token)) > named( u'preference-parameter', RFC(7240, errata=4439), is_pivot=True) preference = Parametrized << (preference_parameter(head=True) * many( skip(OWS * ';') * maybe(skip(OWS) * preference_parameter()))) > named( u'preference', RFC(7240, errata=4439), is_pivot=True) Prefer = comma_list1(preference) > pivot Preference_Applied = comma_list1(preference_parameter(head=True)) > pivot return_ = CaseInsensitive << (literal('representation') | 'minimal') > pivot wait = delay_seconds > auto handling = CaseInsensitive << (literal('strict') | 'lenient') > pivot fill_names(globals(), RFC(7240))