Back to index

moin  1.9.0~rc2
http.py
Go to the documentation of this file.
00001 # -*- coding: utf-8 -*-
00002 """
00003     werkzeug.http
00004     ~~~~~~~~~~~~~
00005 
00006     Werkzeug comes with a bunch of utilties that help Werkzeug to deal with
00007     HTTP data.  Most of the classes and functions provided by this module are
00008     used by the wrappers, but they are useful on their own, too, especially if
00009     the response and request objects are not used.
00010 
00011     This covers some of the more HTTP centric features of WSGI, some other
00012     utilities such as cookie handling are documented in the `werkzeug.utils`
00013     module.
00014 
00015 
00016     :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details.
00017     :license: BSD, see LICENSE for more details.
00018 """
00019 import re
00020 import inspect
00021 try:
00022     from email.utils import parsedate_tz, mktime_tz
00023 except ImportError:
00024     from email.Utils import parsedate_tz, mktime_tz
00025 from cStringIO import StringIO
00026 from tempfile import TemporaryFile
00027 from urllib2 import parse_http_list as _parse_list_header
00028 from datetime import datetime
00029 from itertools import chain, repeat
00030 try:
00031     from hashlib import md5
00032 except ImportError:
00033     from md5 import new as md5
00034 from werkzeug._internal import _decode_unicode, HTTP_STATUS_CODES
00035 
00036 
00037 _accept_re = re.compile(r'([^\s;,]+)(?:[^,]*?;\s*q=(\d*(?:\.\d+)?))?')
00038 _token_chars = frozenset("!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
00039                          '^_`abcdefghijklmnopqrstuvwxyz|~')
00040 _etag_re = re.compile(r'([Ww]/)?(?:"(.*?)"|(.*?))(?:\s*,\s*|$)')
00041 _multipart_boundary_re = re.compile('^[ -~]{0,200}[!-~]$')
00042 
00043 _entity_headers = frozenset([
00044     'allow', 'content-encoding', 'content-language', 'content-length',
00045     'content-location', 'content-md5', 'content-range', 'content-type',
00046     'expires', 'last-modified'
00047 ])
00048 _hop_by_pop_headers = frozenset([
00049     'connection', 'keep-alive', 'proxy-authenticate',
00050     'proxy-authorization', 'te', 'trailers', 'transfer-encoding',
00051     'upgrade'
00052 ])
00053 
00054 #: supported http encodings that are also available in python we support
00055 #: for multipart messages.
00056 _supported_multipart_encodings = frozenset(['base64', 'quoted-printable'])
00057 
00058 
00059 def quote_header_value(value, extra_chars='', allow_token=True):
00060     """Quote a header value if necessary.
00061 
00062     .. versionadded:: 0.5
00063 
00064     :param value: the value to quote.
00065     :param extra_chars: a list of extra characters to skip quoting.
00066     :param allow_token: if this is enabled token values are returned
00067                         unchanged.
00068     """
00069     value = str(value)
00070     if allow_token:
00071         token_chars = _token_chars | set(extra_chars)
00072         if set(value).issubset(token_chars):
00073             return value
00074     return '"%s"' % value.replace('\\', '\\\\').replace('"', '\\"')
00075 
00076 
00077 def unquote_header_value(value):
00078     r"""Unquotes a header value.  (Reversal of :func:`quote_header_value`).
00079     This does not use the real unquoting but what browsers are actually
00080     using for quoting.
00081 
00082     .. versionadded:: 0.5
00083 
00084     :param value: the header value to unquote.
00085     """
00086     if value and value[0] == value[-1] == '"':
00087         # this is not the real unquoting, but fixing this so that the
00088         # RFC is met will result in bugs with internet explorer and
00089         # probably some other browsers as well.  IE for example is
00090         # uploading files with "C:\foo\bar.txt" as filename
00091         value = value[1:-1].replace('\\\\', '\\').replace('\\"', '"')
00092     return value
00093 
00094 
00095 def dump_options_header(header, options):
00096     """The reverse function to :func:`parse_options_header`.
00097 
00098     :param header: the header to dump
00099     :param options: a dict of options to append.
00100     """
00101     segments = []
00102     if header is not None:
00103         segments.append(header)
00104     for key, value in options.iteritems():
00105         if value is None:
00106             segments.append(key)
00107         else:
00108             segments.append('%s=%s' % (key, quote_header_value(value)))
00109     return '; '.join(segments)
00110 
00111 
00112 def dump_header(iterable, allow_token=True):
00113     """Dump an HTTP header again.  This is the reversal of
00114     :func:`parse_list_header`, :func:`parse_set_header` and
00115     :func:`parse_dict_header`.  This also quotes strings that include an
00116     equals sign unless you pass it as dict of key, value pairs.
00117 
00118     :param iterable: the iterable or dict of values to quote.
00119     :param allow_token: if set to `False` tokens as values are disallowed.
00120                         See :func:`quote_header_value` for more details.
00121     """
00122     if isinstance(iterable, dict):
00123         items = []
00124         for key, value in iterable.iteritems():
00125             if value is None:
00126                 items.append(key)
00127             else:
00128                 items.append('%s=%s' % (
00129                     key,
00130                     quote_header_value(value, allow_token=allow_token)
00131                 ))
00132     else:
00133         items = [quote_header_value(x, allow_token=allow_token)
00134                  for x in iterable]
00135     return ', '.join(items)
00136 
00137 
00138 def parse_list_header(value):
00139     """Parse lists as described by RFC 2068 Section 2.
00140 
00141     In particular, parse comma-separated lists where the elements of
00142     the list may include quoted-strings.  A quoted-string could
00143     contain a comma.  A non-quoted string could have quotes in the
00144     middle.  Quotes are removed automatically after parsing.
00145 
00146     :param value: a string with a list header.
00147     :return: list
00148     """
00149     result = []
00150     for item in _parse_list_header(value):
00151         if item[:1] == item[-1:] == '"':
00152             item = unquote_header_value(item[1:-1])
00153         result.append(item)
00154     return result
00155 
00156 
00157 def parse_dict_header(value):
00158     """Parse lists of key, value pairs as described by RFC 2068 Section 2 and
00159     convert them into a python dict.  If there is no value for a key it will
00160     be `None`.
00161 
00162     :param value: a string with a dict header.
00163     :return: dict
00164     """
00165     result = {}
00166     for item in _parse_list_header(value):
00167         if '=' not in item:
00168             result[item] = None
00169             continue
00170         name, value = item.split('=', 1)
00171         if value[:1] == value[-1:] == '"':
00172             value = unquote_header_value(value[1:-1])
00173         result[name] = value
00174     return result
00175 
00176 
00177 def parse_options_header(value):
00178     """Parse a ``Content-Type`` like header into a tuple with the content
00179     type and the options:
00180 
00181     >>> parse_options_header('Content-Type: text/html; mimetype=text/html')
00182     ('Content-Type: text/html', {'mimetype': 'text/html'})
00183 
00184     This should not be used to parse ``Cache-Control`` like headers that use
00185     a slightly different format.  For these headers use the
00186     :func:`parse_dict_header` function.
00187 
00188     .. versionadded:: 0.5
00189 
00190     :param value: the header to parse.
00191     :return: (str, options)
00192     """
00193     def _tokenize(string):
00194         while string[:1] == ';':
00195             string = string[1:]
00196             end = string.find(';')
00197             while end > 0 and string.count('"', 0, end) % 2:
00198                 end = string.find(';', end + 1)
00199             if end < 0:
00200                 end = len(string)
00201             value = string[:end]
00202             yield value.strip()
00203             string = string[end:]
00204 
00205     parts = _tokenize(';' + value)
00206     name = parts.next()
00207     extra = {}
00208     for part in parts:
00209         if '=' in part:
00210             key, value = part.split('=', 1)
00211             extra[key.strip().lower()] = unquote_header_value(value.strip())
00212         else:
00213             extra[part.strip()] = None
00214     return name, extra
00215 
00216 
00217 def parse_accept_header(value, cls=None):
00218     """Parses an HTTP Accept-* header.  This does not implement a complete
00219     valid algorithm but one that supports at least value and quality
00220     extraction.
00221 
00222     Returns a new :class:`Accept` object (basically a list of ``(value, quality)``
00223     tuples sorted by the quality with some additional accessor methods).
00224 
00225     The second parameter can be a subclass of :class:`Accept` that is created
00226     with the parsed values and returned.
00227 
00228     :param value: the accept header string to be parsed.
00229     :param cls: the wrapper class for the return value (can be
00230                          :class:`Accept` or a subclass thereof)
00231     :return: an instance of `cls`.
00232     """
00233     if cls is None:
00234         cls = Accept
00235 
00236     if not value:
00237         return cls(None)
00238 
00239     result = []
00240     for match in _accept_re.finditer(value):
00241         quality = match.group(2)
00242         if not quality:
00243             quality = 1
00244         else:
00245             quality = max(min(float(quality), 1), 0)
00246         result.append((match.group(1), quality))
00247     return cls(result)
00248 
00249 
00250 def parse_cache_control_header(value, on_update=None, cls=None):
00251     """Parse a cache control header.  The RFC differs between response and
00252     request cache control, this method does not.  It's your responsibility
00253     to not use the wrong control statements.
00254 
00255     .. versionadded:: 0.5
00256        The `cls` was added.  If not specified an immutable
00257        :class:`RequestCacheControl` is returned.
00258 
00259     :param value: a cache control header to be parsed.
00260     :param on_update: an optional callable that is called every time a
00261                       value on the :class:`CacheControl` object is changed.
00262     :param cls: the class for the returned object.  By default
00263                                 :class:`RequestCacheControl` is used.
00264     :return: a `cls` object.
00265     """
00266     if cls is None:
00267         cls = RequestCacheControl
00268     if not value:
00269         return cls(None, on_update)
00270     return cls(parse_dict_header(value), on_update)
00271 
00272 
00273 def parse_set_header(value, on_update=None):
00274     """Parse a set-like header and return a :class:`HeaderSet` object.  The
00275     return value is an object that treats the items case-insensitively and
00276     keeps the order of the items.
00277 
00278     :param value: a set header to be parsed.
00279     :param on_update: an optional callable that is called every time a
00280                       value on the :class:`HeaderSet` object is changed.
00281     :return: a :class:`HeaderSet`
00282     """
00283     if not value:
00284         return HeaderSet(None, on_update)
00285     return HeaderSet(parse_list_header(value), on_update)
00286 
00287 
00288 def parse_authorization_header(value):
00289     """Parse an HTTP basic/digest authorization header transmitted by the web
00290     browser.  The return value is either `None` if the header was invalid or
00291     not given, otherwise an :class:`Authorization` object.
00292 
00293     :param value: the authorization header to parse.
00294     :return: a :class:`Authorization` object or `None`.
00295     """
00296     if not value:
00297         return
00298     try:
00299         auth_type, auth_info = value.split(None, 1)
00300         auth_type = auth_type.lower()
00301     except ValueError:
00302         return
00303     if auth_type == 'basic':
00304         try:
00305             username, password = auth_info.decode('base64').split(':', 1)
00306         except Exception, e:
00307             return
00308         return Authorization('basic', {'username': username,
00309                                        'password': password})
00310     elif auth_type == 'digest':
00311         auth_map = parse_dict_header(auth_info)
00312         for key in 'username', 'realm', 'nonce', 'uri', 'nc', 'cnonce', \
00313                    'response':
00314             if not key in auth_map:
00315                 return
00316         return Authorization('digest', auth_map)
00317 
00318 
00319 def parse_www_authenticate_header(value, on_update=None):
00320     """Parse an HTTP WWW-Authenticate header into a :class:`WWWAuthenticate`
00321     object.
00322 
00323     :param value: a WWW-Authenticate header to parse.
00324     :param on_update: an optional callable that is called every time a
00325                       value on the :class:`WWWAuthenticate` object is changed.
00326     :return: a :class:`WWWAuthenticate` object.
00327     """
00328     if not value:
00329         return WWWAuthenticate(on_update=on_update)
00330     try:
00331         auth_type, auth_info = value.split(None, 1)
00332         auth_type = auth_type.lower()
00333     except (ValueError, AttributeError):
00334         return WWWAuthenticate(value.lower(), on_update=on_update)
00335     return WWWAuthenticate(auth_type, parse_dict_header(auth_info),
00336                            on_update)
00337 
00338 
00339 def quote_etag(etag, weak=False):
00340     """Quote an etag.
00341 
00342     :param etag: the etag to quote.
00343     :param weak: set to `True` to tag it "weak".
00344     """
00345     if '"' in etag:
00346         raise ValueError('invalid etag')
00347     etag = '"%s"' % etag
00348     if weak:
00349         etag = 'w/' + etag
00350     return etag
00351 
00352 
00353 def unquote_etag(etag):
00354     """Unquote a single etag:
00355 
00356     >>> unquote_etag('w/"bar"')
00357     ('bar', True)
00358     >>> unquote_etag('"bar"')
00359     ('bar', False)
00360 
00361     :param etag: the etag identifier to unquote.
00362     :return: a ``(etag, weak)`` tuple.
00363     """
00364     if not etag:
00365         return None, None
00366     etag = etag.strip()
00367     weak = False
00368     if etag[:2] in ('w/', 'W/'):
00369         weak = True
00370         etag = etag[2:]
00371     if etag[:1] == etag[-1:] == '"':
00372         etag = etag[1:-1]
00373     return etag, weak
00374 
00375 
00376 def parse_etags(value):
00377     """Parse an etag header.
00378 
00379     :param value: the tag header to parse
00380     :return: an :class:`ETags` object.
00381     """
00382     if not value:
00383         return ETags()
00384     strong = []
00385     weak = []
00386     end = len(value)
00387     pos = 0
00388     while pos < end:
00389         match = _etag_re.match(value, pos)
00390         if match is None:
00391             break
00392         is_weak, quoted, raw = match.groups()
00393         if raw == '*':
00394             return ETags(star_tag=True)
00395         elif quoted:
00396             raw = quoted
00397         if is_weak:
00398             weak.append(raw)
00399         else:
00400             strong.append(raw)
00401         pos = match.end()
00402     return ETags(strong, weak)
00403 
00404 
00405 def generate_etag(data):
00406     """Generate an etag for some data."""
00407     return md5(data).hexdigest()
00408 
00409 
00410 def parse_date(value):
00411     """Parse one of the following date formats into a datetime object:
00412 
00413     .. sourcecode:: text
00414 
00415         Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
00416         Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
00417         Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
00418 
00419     If parsing fails the return value is `None`.
00420 
00421     :param value: a string with a supported date format.
00422     :return: a :class:`datetime.datetime` object.
00423     """
00424     if value:
00425         t = parsedate_tz(value.strip())
00426         if t is not None:
00427             # if no timezone is part of the string we assume UTC
00428             if t[-1] is None:
00429                 t = t[:-1] + (0,)
00430             try:
00431                 return datetime.utcfromtimestamp(mktime_tz(t))
00432             except (OverflowError, ValueError):
00433                 # XXX Exception handler added by MoinMoin development:
00434                 # catch exceptions raised by the stdlib functions if they receive far-off or invalid values.
00435                 # This can happen if a user agent transmits a broken if-modified-since header:
00436                 # 'HTTP_IF_MODIFIED_SINCE': 'Mon, 23 Jan 3115 29:41:44 GMT'
00437                 # 'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5'
00438                 # TODO: remove this patch if we require a werkzeug version > 0.5.1
00439                 # that has an official fix for this problem.
00440                 # related werkzeug ticket: http://dev.pocoo.org/projects/werkzeug/ticket/432
00441                 return None  # just tell we can't parse this
00442 
00443 
00444 def default_stream_factory(total_content_length, filename, content_type,
00445                            content_length=None):
00446     """The stream factory that is used per default."""
00447     if total_content_length > 1024 * 500:
00448         return TemporaryFile('wb+')
00449     return StringIO()
00450 
00451 
00452 def _make_stream_factory(factory):
00453     """this exists for backwards compatibility!, will go away in 0.6."""
00454     args, _, _, defaults = inspect.getargspec(factory)
00455     required_args = len(args) - len(defaults or ())
00456     if inspect.ismethod(factory):
00457         required_args -= 1
00458     if required_args != 0:
00459         return factory
00460     from warnings import warn
00461     warn(DeprecationWarning('stream factory passed to `parse_form_data` '
00462                             'uses deprecated invokation API.'), stacklevel=4)
00463     return lambda *a: factory()
00464 
00465 
00466 def _fix_ie_filename(filename):
00467     """Internet Explorer 6 transmits the full file name if a file is
00468     uploaded.  This function strips the full path if it thinks the
00469     filename is Windows-like absolute.
00470     """
00471     if filename[1:3] == ':\\' or filename[:2] == '\\\\':
00472         return filename.split('\\')[-1]
00473     return filename
00474 
00475 
00476 def _line_parse(line):
00477     """Removes line ending characters and returns a tuple (`stripped_line`,
00478     `is_terminated`).
00479     """
00480     if line[-2:] == '\r\n':
00481         return line[:-2], True
00482     elif line[-1:] in '\r\n':
00483         return line[:-1], True
00484     return line, False
00485 
00486 
00487 def parse_multipart(file, boundary, content_length, stream_factory=None,
00488                     charset='utf-8', errors='ignore', buffer_size=10 * 1024,
00489                     max_form_memory_size=None):
00490     """Parse a multipart/form-data stream.  This is invoked by
00491     :func:`utils.parse_form_data` if the content type matches.  Currently it
00492     exists for internal usage only, but could be exposed as separate
00493     function if it turns out to be useful and if we consider the API stable.
00494     """
00495     # XXX: this function does not support multipart/mixed.  I don't know of
00496     #      any browser that supports this, but it should be implemented
00497     #      nonetheless.
00498 
00499     # make sure the buffer size is divisible by four so that we can base64
00500     # decode chunk by chunk
00501     assert buffer_size % 4 == 0, 'buffer size has to be divisible by 4'
00502     # also the buffer size has to be at least 1024 bytes long or long headers
00503     # will freak out the system
00504     assert buffer_size >= 1024, 'buffer size has to be at least 1KB'
00505 
00506     if stream_factory is None:
00507         stream_factory = default_stream_factory
00508     else:
00509         stream_factory = _make_stream_factory(stream_factory)
00510 
00511     if not boundary:
00512         raise ValueError('Missing boundary')
00513     if not is_valid_multipart_boundary(boundary):
00514         raise ValueError('Invalid boundary: %s' % boundary)
00515     if len(boundary) > buffer_size:
00516         raise ValueError('Boundary longer than buffer size')
00517 
00518     total_content_length = content_length
00519     next_part = '--' + boundary
00520     last_part = next_part + '--'
00521 
00522     form = []
00523     files = []
00524     in_memory = 0
00525 
00526     # convert the file into a limited stream with iteration capabilities
00527     file = LimitedStream(file, content_length)
00528     iterator = chain(make_line_iter(file, buffer_size=buffer_size),
00529                      repeat(''))
00530 
00531     def _find_terminator():
00532         """The terminator might have some additional newlines before it.
00533         There is at least one application that sends additional newlines
00534         before headers (the python setuptools package).
00535         """
00536         for line in iterator:
00537             if not line:
00538                 break
00539             line = line.strip()
00540             if line:
00541                 return line
00542         return ''
00543 
00544     try:
00545         terminator = _find_terminator()
00546         if terminator != next_part:
00547             raise ValueError('Expected boundary at start of multipart data')
00548 
00549         while terminator != last_part:
00550             headers = parse_multipart_headers(iterator)
00551             disposition = headers.get('content-disposition')
00552             if disposition is None:
00553                 raise ValueError('Missing Content-Disposition header')
00554             disposition, extra = parse_options_header(disposition)
00555             filename = extra.get('filename')
00556             name = extra.get('name')
00557             transfer_encoding = headers.get('content-transfer-encoding')
00558 
00559             content_type = headers.get('content-type')
00560             if content_type is None:
00561                 is_file = False
00562             else:
00563                 content_type = parse_options_header(content_type)[0]
00564                 is_file = True
00565 
00566             if is_file:
00567                 if filename is not None:
00568                     filename = _fix_ie_filename(_decode_unicode(filename,
00569                                                                 charset,
00570                                                                 errors))
00571                 try:
00572                     content_length = int(headers['content-length'])
00573                 except (KeyError, ValueError):
00574                     content_length = 0
00575                 stream = stream_factory(total_content_length, content_type,
00576                                         filename, content_length)
00577             else:
00578                 stream = StringIO()
00579 
00580             buf = ''
00581             for line in iterator:
00582                 if not line:
00583                     raise ValueError('unexpected end of stream')
00584                 if line[:2] == '--':
00585                     terminator = line.rstrip()
00586                     if terminator in (next_part, last_part):
00587                         break
00588                 if transfer_encoding in _supported_multipart_encodings:
00589                     try:
00590                         line = line.decode(transfer_encoding)
00591                     except:
00592                         raise ValueError('could not base 64 decode chunk')
00593                 # we have something in the buffer from the last iteration.
00594                 # write that value to the output stream now and clear the buffer.
00595                 if buf:
00596                     stream.write(buf)
00597                     buf = ''
00598 
00599                 # If the line ends with windows CRLF we write everything except
00600                 # the last two bytes.  In all other cases however we write everything
00601                 # except the last byte.  If it was a newline, that's fine, otherwise
00602                 # it does not matter because we write it the last iteration.  If the
00603                 # loop aborts early because the end of a part was reached, the last
00604                 # newline is not written which is exactly what we want.
00605                 newline_length = line[-2:] == '\r\n' and 2 or 1
00606                 stream.write(line[:-newline_length])
00607                 buf = line[-newline_length:]
00608                 if not is_file and max_form_memory_size is not None:
00609                     in_memory += len(line)
00610                     if in_memory > max_form_memory_size:
00611                         from werkzeug.exceptions import RequestEntityTooLarge
00612                         raise RequestEntityTooLarge()
00613             else:
00614                 raise ValueError('unexpected end of part')
00615 
00616             # rewind the stream
00617             stream.seek(0)
00618 
00619             if is_file:
00620                 files.append((name, FileStorage(stream, filename, name,
00621                                                 content_type,
00622                                                 content_length)))
00623             else:
00624                 form.append((name, _decode_unicode(stream.read(),
00625                                                    charset, errors)))
00626     finally:
00627         # make sure the whole input stream is read
00628         file.exhaust()
00629 
00630     return form, files
00631 
00632 
00633 def parse_multipart_headers(iterable):
00634     """Parses multipart headers from an iterable that yields lines (including
00635     the trailing newline symbol.
00636     """
00637     result = []
00638     for line in iterable:
00639         line, line_terminated = _line_parse(line)
00640         if not line_terminated:
00641             raise ValueError('unexpected end of line in multipart header')
00642         if not line:
00643             break
00644         elif line[0] in ' \t' and result:
00645             key, value = result[-1]
00646             result[-1] = (key, value + '\n ' + line[1:])
00647         else:
00648             parts = line.split(':', 1)
00649             if len(parts) == 2:
00650                 result.append((parts[0].strip(), parts[1].strip()))
00651     return Headers(result)
00652 
00653 
00654 def is_resource_modified(environ, etag=None, data=None, last_modified=None):
00655     """Convenience method for conditional requests.
00656 
00657     :param environ: the WSGI environment of the request to be checked.
00658     :param etag: the etag for the response for comparision.
00659     :param data: or alternatively the data of the response to automatically
00660                  generate an etag using :func:`generate_etag`.
00661     :param last_modified: an optional date of the last modification.
00662     :return: `True` if the resource was modified, otherwise `False`.
00663     """
00664     if etag is None and data is not None:
00665         etag = generate_etag(data)
00666     elif data is not None:
00667         raise TypeError('both data and etag given')
00668     if environ['REQUEST_METHOD'] not in ('GET', 'HEAD'):
00669         return False
00670 
00671     unmodified = False
00672     if isinstance(last_modified, basestring):
00673         last_modified = parse_date(last_modified)
00674     modified_since = parse_date(environ.get('HTTP_IF_MODIFIED_SINCE'))
00675 
00676     if modified_since and last_modified and last_modified <= modified_since:
00677         unmodified = True
00678     if etag:
00679         if_none_match = parse_etags(environ.get('HTTP_IF_NONE_MATCH'))
00680         if if_none_match:
00681             unmodified = if_none_match.contains_raw(etag)
00682 
00683     return not unmodified
00684 
00685 
00686 def remove_entity_headers(headers, allowed=('expires', 'content-location')):
00687     """Remove all entity headers from a list or :class:`Headers` object.  This
00688     operation works in-place.  `Expires` and `Content-Location` headers are
00689     by default not removed.  The reason for this is :rfc:`2616` section
00690     10.3.5 which specifies some entity headers that should be sent.
00691 
00692     .. versionchanged:: 0.5
00693        added `allowed` parameter.
00694 
00695     :param headers: a list or :class:`Headers` object.
00696     :param allowed: a list of headers that should still be allowed even though
00697                     they are entity headers.
00698     """
00699     allowed = set(x.lower() for x in allowed)
00700     headers[:] = [(key, value) for key, value in headers if
00701                   not is_entity_header(key) or key.lower() in allowed]
00702 
00703 
00704 def remove_hop_by_hop_headers(headers):
00705     """Remove all HTTP/1.1 "Hop-by-Hop" headers from a list or
00706     :class:`Headers` object.  This operation works in-place.
00707 
00708     .. versionadded:: 0.5
00709 
00710     :param headers: a list or :class:`Headers` object.
00711     """
00712     headers[:] = [(key, value) for key, value in headers if
00713                   not is_hop_by_hop_header(key)]
00714 
00715 
00716 def is_entity_header(header):
00717     """Check if a header is an entity header.
00718 
00719     .. versionadded:: 0.5
00720 
00721     :param header: the header to test.
00722     :return: `True` if it's an entity header, `False` otherwise.
00723     """
00724     return header.lower() in _entity_headers
00725 
00726 
00727 def is_hop_by_hop_header(header):
00728     """Check if a header is an HTTP/1.1 "Hop-by-Hop" header.
00729 
00730     .. versionadded:: 0.5
00731 
00732     :param header: the header to test.
00733     :return: `True` if it's an entity header, `False` otherwise.
00734     """
00735     return header.lower() in _hop_by_pop_headers
00736 
00737 
00738 def is_valid_multipart_boundary(boundary):
00739     """Checks if the string given is a valid multipart boundary."""
00740     return _multipart_boundary_re.match(boundary) is not None
00741 
00742 
00743 # circular dependency fun
00744 from werkzeug.utils import make_line_iter, FileStorage, LimitedStream
00745 from werkzeug.datastructures import Headers, Accept, RequestCacheControl, \
00746      ResponseCacheControl, HeaderSet, ETags, Authorization, \
00747      WWWAuthenticate
00748 
00749 
00750 # DEPRECATED
00751 # backwards compatibible imports
00752 from werkzeug.datastructures import MIMEAccept, CharsetAccept, LanguageAccept