diff options
author | Marc Abramowitz <marc@marc-abramowitz.com> | 2015-04-30 16:42:17 -0700 |
---|---|---|
committer | Marc Abramowitz <marc@marc-abramowitz.com> | 2015-04-30 16:42:17 -0700 |
commit | 12a3f1f4cfa7f88478dc1b0e949fcc095b9fc804 (patch) | |
tree | ddb8079523d846f0b074437fc33fa5e28b508183 /paste/response.py | |
download | paste-git-eliminate_cgi_parse_qsl_2.tar.gz |
Replace cgi.parse_qsl w/ six.moves.urllib.parse.parse_sqleliminate_cgi_parse_qsl_2eliminate_cgi_parse_qsl
because `cgi.parse_qsl` is deprecated, according to
https://docs.python.org/2/library/cgi.html#cgi.parse_qsl
Diffstat (limited to 'paste/response.py')
-rw-r--r-- | paste/response.py | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/paste/response.py b/paste/response.py new file mode 100644 index 0000000..5ce0320 --- /dev/null +++ b/paste/response.py @@ -0,0 +1,240 @@ +# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) +# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php +"""Routines to generate WSGI responses""" + +############################################################ +## Headers +############################################################ +import warnings + +class HeaderDict(dict): + + """ + This represents response headers. It handles the headers as a + dictionary, with case-insensitive keys. + + Also there is an ``.add(key, value)`` method, which sets the key, + or adds the value to the current value (turning it into a list if + necessary). + + For passing to WSGI there is a ``.headeritems()`` method which is + like ``.items()`` but unpacks value that are lists. It also + handles encoding -- all headers are encoded in ASCII (if they are + unicode). + + @@: Should that encoding be ISO-8859-1 or UTF-8? I'm not sure + what the spec says. + """ + + def __getitem__(self, key): + return dict.__getitem__(self, self.normalize(key)) + + def __setitem__(self, key, value): + dict.__setitem__(self, self.normalize(key), value) + + def __delitem__(self, key): + dict.__delitem__(self, self.normalize(key)) + + def __contains__(self, key): + return dict.__contains__(self, self.normalize(key)) + + has_key = __contains__ + + def get(self, key, failobj=None): + return dict.get(self, self.normalize(key), failobj) + + def setdefault(self, key, failobj=None): + return dict.setdefault(self, self.normalize(key), failobj) + + def pop(self, key, *args): + return dict.pop(self, self.normalize(key), *args) + + def update(self, other): + for key in other: + self[self.normalize(key)] = other[key] + + def normalize(self, key): + return str(key).lower().strip() + + def add(self, key, value): + key = self.normalize(key) + if key in self: + if isinstance(self[key], list): + self[key].append(value) + else: + self[key] = [self[key], value] + else: + self[key] = value + + def headeritems(self): + result = [] + for key, value in self.items(): + if isinstance(value, list): + for v in value: + result.append((key, str(v))) + else: + result.append((key, str(value))) + return result + + #@classmethod + def fromlist(cls, seq): + self = cls() + for name, value in seq: + self.add(name, value) + return self + + fromlist = classmethod(fromlist) + +def has_header(headers, name): + """ + Is header named ``name`` present in headers? + """ + name = name.lower() + for header, value in headers: + if header.lower() == name: + return True + return False + +def header_value(headers, name): + """ + Returns the header's value, or None if no such header. If a + header appears more than once, all the values of the headers + are joined with ','. Note that this is consistent /w RFC 2616 + section 4.2 which states: + + It MUST be possible to combine the multiple header fields + into one "field-name: field-value" pair, without changing + the semantics of the message, by appending each subsequent + field-value to the first, each separated by a comma. + + However, note that the original netscape usage of 'Set-Cookie', + especially in MSIE which contains an 'expires' date will is not + compatible with this particular concatination method. + """ + name = name.lower() + result = [value for header, value in headers + if header.lower() == name] + if result: + return ','.join(result) + else: + return None + +def remove_header(headers, name): + """ + Removes the named header from the list of headers. Returns the + value of that header, or None if no header found. If multiple + headers are found, only the last one is returned. + """ + name = name.lower() + i = 0 + result = None + while i < len(headers): + if headers[i][0].lower() == name: + result = headers[i][1] + del headers[i] + continue + i += 1 + return result + +def replace_header(headers, name, value): + """ + Updates the headers replacing the first occurance of the given name + with the value provided; asserting that no further occurances + happen. Note that this is _not_ the same as remove_header and then + append, as two distinct operations (del followed by an append) are + not atomic in a threaded environment. Returns the previous header + value for the provided name, if any. Clearly one should not use + this function with ``set-cookie`` or other names that may have more + than one occurance in the headers. + """ + name = name.lower() + i = 0 + result = None + while i < len(headers): + if headers[i][0].lower() == name: + assert not result, "two values for the header '%s' found" % name + result = headers[i][1] + headers[i] = (name, value) + i += 1 + if not result: + headers.append((name, value)) + return result + + +############################################################ +## Deprecated methods +############################################################ + +def error_body_response(error_code, message, __warn=True): + """ + Returns a standard HTML response page for an HTTP error. + **Note:** Deprecated + """ + if __warn: + warnings.warn( + 'wsgilib.error_body_response is deprecated; use the ' + 'wsgi_application method on an HTTPException object ' + 'instead', DeprecationWarning, 2) + return '''\ +<html> + <head> + <title>%(error_code)s</title> + </head> + <body> + <h1>%(error_code)s</h1> + %(message)s + </body> +</html>''' % { + 'error_code': error_code, + 'message': message, + } + + +def error_response(environ, error_code, message, + debug_message=None, __warn=True): + """ + Returns the status, headers, and body of an error response. + + Use like: + + .. code-block:: python + + status, headers, body = wsgilib.error_response( + '301 Moved Permanently', 'Moved to <a href="%s">%s</a>' + % (url, url)) + start_response(status, headers) + return [body] + + **Note:** Deprecated + """ + if __warn: + warnings.warn( + 'wsgilib.error_response is deprecated; use the ' + 'wsgi_application method on an HTTPException object ' + 'instead', DeprecationWarning, 2) + if debug_message and environ.get('paste.config', {}).get('debug'): + message += '\n\n<!-- %s -->' % debug_message + body = error_body_response(error_code, message, __warn=False) + headers = [('content-type', 'text/html'), + ('content-length', str(len(body)))] + return error_code, headers, body + +def error_response_app(error_code, message, debug_message=None, + __warn=True): + """ + An application that emits the given error response. + + **Note:** Deprecated + """ + if __warn: + warnings.warn( + 'wsgilib.error_response_app is deprecated; use the ' + 'wsgi_application method on an HTTPException object ' + 'instead', DeprecationWarning, 2) + def application(environ, start_response): + status, headers, body = error_response( + environ, error_code, message, + debug_message=debug_message, __warn=False) + start_response(status, headers) + return [body] + return application |