forked from openkylin/python-httpretty
154 lines
4.6 KiB
Python
154 lines
4.6 KiB
Python
# #!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
# <HTTPretty - HTTP client mock for Python>
|
|
# Copyright (C) <2011-2021> Gabriel Falcão <gabriel@nacaolivre.org>
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person
|
|
# obtaining a copy of this software and associated documentation
|
|
# files (the "Software"), to deal in the Software without
|
|
# restriction, including without limitation the rights to use,
|
|
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the
|
|
# Software is furnished to do so, subject to the following
|
|
# conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be
|
|
# included in all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
# OTHER DEALINGS IN THE SOFTWARE.
|
|
from __future__ import unicode_literals
|
|
|
|
import re
|
|
from .compat import BaseClass
|
|
from .utils import decode_utf8
|
|
|
|
|
|
STATUSES = {
|
|
100: "Continue",
|
|
101: "Switching Protocols",
|
|
102: "Processing",
|
|
200: "OK",
|
|
201: "Created",
|
|
202: "Accepted",
|
|
203: "Non-Authoritative Information",
|
|
204: "No Content",
|
|
205: "Reset Content",
|
|
206: "Partial Content",
|
|
207: "Multi-Status",
|
|
208: "Already Reported",
|
|
226: "IM Used",
|
|
300: "Multiple Choices",
|
|
301: "Moved Permanently",
|
|
302: "Found",
|
|
303: "See Other",
|
|
304: "Not Modified",
|
|
305: "Use Proxy",
|
|
306: "Switch Proxy",
|
|
307: "Temporary Redirect",
|
|
308: "Permanent Redirect",
|
|
400: "Bad Request",
|
|
401: "Unauthorized",
|
|
402: "Payment Required",
|
|
403: "Forbidden",
|
|
404: "Not Found",
|
|
405: "Method Not Allowed",
|
|
406: "Not Acceptable",
|
|
407: "Proxy Authentication Required",
|
|
408: "Request a Timeout",
|
|
409: "Conflict",
|
|
410: "Gone",
|
|
411: "Length Required",
|
|
412: "Precondition Failed",
|
|
413: "Request Entity Too Large",
|
|
414: "Request-URI Too Long",
|
|
415: "Unsupported Media Type",
|
|
416: "Requested Range Not Satisfiable",
|
|
417: "Expectation Failed",
|
|
418: "I'm a teapot",
|
|
420: "Enhance Your Calm",
|
|
422: "Unprocessable Entity",
|
|
423: "Locked",
|
|
424: "Failed Dependency",
|
|
425: "Unordered Collection",
|
|
426: "Upgrade Required",
|
|
428: "Precondition Required",
|
|
429: "Too Many Requests",
|
|
431: "Request Header Fields Too Large",
|
|
444: "No Response",
|
|
449: "Retry With",
|
|
450: "Blocked by Windows Parental Controls",
|
|
451: "Unavailable For Legal Reasons",
|
|
494: "Request Header Too Large",
|
|
495: "Cert Error",
|
|
496: "No Cert",
|
|
497: "HTTP to HTTPS",
|
|
499: "Client Closed Request",
|
|
500: "Internal Server Error",
|
|
501: "Not Implemented",
|
|
502: "Bad Gateway",
|
|
503: "Service Unavailable",
|
|
504: "Gateway Timeout",
|
|
505: "HTTP Version Not Supported",
|
|
506: "Variant Also Negotiates",
|
|
507: "Insufficient Storage",
|
|
508: "Loop Detected",
|
|
509: "Bandwidth Limit Exceeded",
|
|
510: "Not Extended",
|
|
511: "Network Authentication Required",
|
|
598: "Network read timeout error",
|
|
599: "Network connect timeout error",
|
|
}
|
|
|
|
|
|
class HttpBaseClass(BaseClass):
|
|
GET = 'GET'
|
|
PUT = 'PUT'
|
|
POST = 'POST'
|
|
DELETE = 'DELETE'
|
|
HEAD = 'HEAD'
|
|
PATCH = 'PATCH'
|
|
OPTIONS = 'OPTIONS'
|
|
CONNECT = 'CONNECT'
|
|
METHODS = (GET, PUT, POST, DELETE, HEAD, PATCH, OPTIONS, CONNECT)
|
|
|
|
|
|
def parse_requestline(s):
|
|
"""
|
|
http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5
|
|
|
|
>>> parse_requestline('GET / HTTP/1.0')
|
|
('GET', '/', '1.0')
|
|
>>> parse_requestline('post /testurl htTP/1.1')
|
|
('POST', '/testurl', '1.1')
|
|
>>> parse_requestline('Im not a RequestLine')
|
|
Traceback (most recent call last):
|
|
...
|
|
ValueError: Not a Request-Line
|
|
"""
|
|
methods = '|'.join(HttpBaseClass.METHODS)
|
|
m = re.match(r'(' + methods + r')\s+(.*)\s+HTTP/(1.[0|1])', s, re.I)
|
|
if m:
|
|
return m.group(1).upper(), m.group(2), m.group(3)
|
|
else:
|
|
raise ValueError('Not a Request-Line')
|
|
|
|
|
|
def last_requestline(sent_data):
|
|
"""
|
|
Find the last line in sent_data that can be parsed with parse_requestline
|
|
"""
|
|
for line in reversed(sent_data):
|
|
try:
|
|
parse_requestline(decode_utf8(line))
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
return line
|