home *** CD-ROM | disk | FTP | other *** search
/ Enter 2004 April / enter-2004-04.iso / files / EVE_1424_100181.exe / _HeadersUtil.py < prev    next >
Encoding:
Python Source  |  2004-04-20  |  6.2 KB  |  178 lines

  1. """Header value parsing utility functions.
  2.  
  3.   from ClientCookie._HeadersUtil import split_header_words
  4.   values = split_header_words(h.headers["Content-Type"])
  5.  
  6. This module provides a few functions that helps parsing and
  7. construction of valid HTTP header values.
  8.  
  9.  
  10. Copyright 1997-1998, Gisle Aas
  11. Copyright 2002, John J. Lee
  12.  
  13. This code is free software; you can redistribute it and/or modify it under
  14. the terms of the MIT License (see the file COPYING included with the
  15. distribution).
  16.  
  17. """
  18.  
  19. # from Gisle Aas's CVS revision 1.9, libwww-perl 5.64
  20.  
  21. import re, string
  22. from types import StringType
  23. try:
  24.     from types import UnicodeType
  25.     STRING_TYPES = StringType, UnicodeType
  26. except:
  27.     STRING_TYPES = StringType,
  28.  
  29. from _Util import startswith
  30.  
  31. def pair_up(l):
  32.     """Return list of pairs, given a list.
  33.  
  34.     pair_up([1,2,3,4]) => [(1,2), (3,4)]
  35.  
  36.     """
  37.     assert len(l)%2 == 0
  38.     result = []
  39.     pair = [None, None]
  40.     for i in xrange(len(l)):
  41.         pair[i%2] = l[i]
  42.         if i%2 == 1:
  43.             result.append(tuple(pair))
  44.     return result
  45.  
  46. def unmatched(match):
  47.     """Return unmatched part of re.Match object."""
  48.     start, end = match.span(0)
  49.     return match.string[:start]+match.string[end:]
  50.  
  51. token_re = re.compile(r"^\s*(=*[^\s=;,]+)")
  52. quoted_value_re = re.compile(r"^\s*=\s*\"([^\"\\]*(?:\\.[^\"\\]*)*)\"")
  53. escape_re = re.compile(r"\\(.)")
  54. value_re = re.compile(r"^\s*=\s*([^;,\s]*)")
  55. def split_header_words(header_values):
  56.     r"""Parse header values into a list of lists containing key,value pairs.
  57.  
  58.     The function knows how to deal with ",", ";" and "=" as well as quoted
  59.     values after "=".  A list of space separated tokens are parsed as if they
  60.     were separated by ";".
  61.  
  62.     If the header_values passed as argument contains multiple values, then they
  63.     are treated as if they were a single value separated by comma ",".
  64.  
  65.     This means that this function is useful for parsing header fields that
  66.     follow this syntax (BNF as from the HTTP/1.1 specification, but we relax
  67.     the requirement for tokens).
  68.  
  69.       headers           = #header
  70.       header            = (token | parameter) *( [";"] (token | parameter))
  71.  
  72.       token             = 1*<any CHAR except CTLs or separators>
  73.       separators        = "(" | ")" | "<" | ">" | "@"
  74.                         | "," | ";" | ":" | "\" | <">
  75.                         | "/" | "[" | "]" | "?" | "="
  76.                         | "{" | "}" | SP | HT
  77.  
  78.       quoted-string     = ( <"> *(qdtext | quoted-pair ) <"> )
  79.       qdtext            = <any TEXT except <">>
  80.       quoted-pair       = "\" CHAR
  81.  
  82.       parameter         = attribute "=" value
  83.       attribute         = token
  84.       value             = token | quoted-string
  85.  
  86.     Each header is represented by an anonymous array of key/value pairs.  The
  87.     value for a simple token (not part of a parameter) is undef.  Syntactically
  88.     incorrect headers will not necessary be parsed as you would want.
  89.  
  90.     This is easier to describe with some examples:
  91.  
  92.     >>> split_header_words(['foo="bar"; port="80,81"; discard, bar=baz'])
  93.     [[('foo', 'bar'), ('port', '80,81'), ('discard', None)], [('bar', 'baz')]]
  94.     >>> split_header_words(['text/html; charset="iso-8859-1"'])
  95.     [[('text/html', None), ('charset', 'iso-8859-1')]]
  96.     >>> split_header_words([r'Basic realm="\"foo\bar\""'])
  97.     [[('Basic', None), ('realm', '"foobar"')]]
  98.  
  99.     """
  100.     # XXX yuck
  101.     assert type(header_values) not in STRING_TYPES
  102.     res = []
  103.     for thing in header_values:
  104.         cur = []
  105.         while len(thing) != 0:
  106.             matched = 0
  107.             m = token_re.search(thing)
  108.             if m:  # 'token' or parameter 'attribute'
  109.                 matched = 1
  110.                 thing = unmatched(m)
  111.                 cur.append(m.group(1))
  112.                 matched_inner = 0
  113.                 m = quoted_value_re.search(thing)
  114.                 if m:  # a quoted value
  115.                     matched_inner = 1
  116.                     thing = unmatched(m)
  117.                     val = m.group(1)
  118.                     val = escape_re.sub(r"\1", val)
  119.             cur.append(val)
  120.                 if not matched_inner:
  121.                     m = value_re.search(thing)
  122.                     if m:  # some unquoted value
  123.                         matched_inner = 1
  124.                         thing = unmatched(m)
  125.                         val = m.group(1)
  126.                         val = string.rstrip(val)
  127.                         cur.append(val)
  128.                 if not matched_inner:  # no value, a lone token
  129.                     cur.append(None)
  130.             if not matched:
  131.                 if startswith(string.lstrip(thing), ","):
  132.                     matched = 1
  133.                     thing = string.lstrip(thing)[1:]
  134.                     if cur: res.append(pair_up(cur))
  135.                     cur = []
  136.             if not matched:
  137.                 assert startswith(thing, " ") or startswith(thing, ";"), (
  138.                     "This should not happen: '%s'\n cur: %s" % (thing, cur))
  139.                 thing = string.lstrip(thing)
  140.                 if startswith(thing, ";"):
  141.                     thing = thing[1:]
  142.         if cur: res.append(pair_up(cur))
  143.     return res
  144.  
  145. join_escape_re = re.compile(r"([\"\\])")
  146. def join_header_words(lists):
  147.     """Do the inverse of the conversion done by split_header_words.
  148.  
  149.     Takes a list of lists of (key, value) pairs and produces a single header
  150.     value.  Attribute values are quoted if needed.
  151.  
  152.     >>> join_header_words([[("text/plain", None), ("charset", "iso-8859/1")]])
  153.     'text/plain; charset="iso-8859/1"'
  154.     >>> join_header_words([[("text/plain", None)], [("charset", "iso-8859/1")]])
  155.     'text/plain, charset="iso-8859/1"'
  156.  
  157.     """
  158.     res = []
  159.     for pairs in lists:
  160.         attr = []
  161.         for k, v in pairs:
  162.             if v is not None:
  163.                 if re.search(r"^\w+$", v):
  164.                     k = k + ("=%s" % (v,))
  165.                 else:
  166.                     v = join_escape_re.sub(r"\\\1", v)  # escape " and \
  167.                     k = k + ('="%s"' % (v,))
  168.             attr.append(k)
  169.         if attr: res.append(string.join(attr, "; "))
  170.     return string.join(res, ", ")
  171.  
  172. def _test():
  173.    import doctest, _HeadersUtil
  174.    return doctest.testmod(_HeadersUtil)
  175.  
  176. if __name__ == "__main__":
  177.    _test()
  178.