aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy/models/http.py
diff options
context:
space:
mode:
Diffstat (limited to 'libmproxy/models/http.py')
-rw-r--r--libmproxy/models/http.py469
1 files changed, 0 insertions, 469 deletions
diff --git a/libmproxy/models/http.py b/libmproxy/models/http.py
deleted file mode 100644
index da9c430e..00000000
--- a/libmproxy/models/http.py
+++ /dev/null
@@ -1,469 +0,0 @@
-from __future__ import (absolute_import, print_function, division)
-import Cookie
-import copy
-import warnings
-from email.utils import parsedate_tz, formatdate, mktime_tz
-import time
-
-from libmproxy import utils
-from netlib import encoding
-from netlib.http import status_codes, Headers, Request, Response, decoded
-from netlib.tcp import Address
-from .. import version
-from .flow import Flow
-
-
-class MessageMixin(object):
-
- def get_decoded_content(self):
- """
- Returns the decoded content based on the current Content-Encoding
- header.
- Doesn't change the message iteself or its headers.
- """
- ce = self.headers.get("content-encoding")
- if not self.content or ce not in encoding.ENCODINGS:
- return self.content
- return encoding.decode(ce, self.content)
-
- def copy(self):
- c = copy.copy(self)
- if hasattr(self, "data"): # FIXME remove condition
- c.data = copy.copy(self.data)
-
- c.headers = self.headers.copy()
- return c
-
- def replace(self, pattern, repl, *args, **kwargs):
- """
- Replaces a regular expression pattern with repl in both the headers
- and the body of the message. Encoded body will be decoded
- before replacement, and re-encoded afterwards.
-
- Returns the number of replacements made.
- """
- count = 0
- if self.content:
- with decoded(self):
- self.content, count = utils.safe_subn(
- pattern, repl, self.content, *args, **kwargs
- )
- fields = []
- for name, value in self.headers.fields:
- name, c = utils.safe_subn(pattern, repl, name, *args, **kwargs)
- count += c
- value, c = utils.safe_subn(pattern, repl, value, *args, **kwargs)
- count += c
- fields.append([name, value])
- self.headers.fields = fields
- return count
-
-
-class HTTPRequest(MessageMixin, Request):
-
- """
- An HTTP request.
-
- Exposes the following attributes:
-
- method: HTTP method
-
- scheme: URL scheme (http/https)
-
- host: Target hostname of the request. This is not neccessarily the
- directy upstream server (which could be another proxy), but it's always
- the target server we want to reach at the end. This attribute is either
- inferred from the request itself (absolute-form, authority-form) or from
- the connection metadata (e.g. the host in reverse proxy mode).
-
- port: Destination port
-
- path: Path portion of the URL (not present in authority-form)
-
- http_version: HTTP version, e.g. "HTTP/1.1"
-
- headers: Headers object
-
- content: Content of the request, None, or CONTENT_MISSING if there
- is content associated, but not present. CONTENT_MISSING evaluates
- to False to make checking for the presence of content natural.
-
- form_in: The request form which mitmproxy has received. The following
- values are possible:
-
- - relative (GET /index.html, OPTIONS *) (covers origin form and
- asterisk form)
- - absolute (GET http://example.com:80/index.html)
- - authority-form (CONNECT example.com:443)
- Details: http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-25#section-5.3
-
- form_out: The request form which mitmproxy will send out to the
- destination
-
- timestamp_start: Timestamp indicating when request transmission started
-
- timestamp_end: Timestamp indicating when request transmission ended
- """
-
- def __init__(
- self,
- first_line_format,
- method,
- scheme,
- host,
- port,
- path,
- http_version,
- headers,
- content,
- timestamp_start=None,
- timestamp_end=None,
- form_out=None,
- is_replay=False,
- stickycookie=False,
- stickyauth=False,
- ):
- Request.__init__(
- self,
- first_line_format,
- method,
- scheme,
- host,
- port,
- path,
- http_version,
- headers,
- content,
- timestamp_start,
- timestamp_end,
- )
- self.form_out = form_out or first_line_format # FIXME remove
-
- # Have this request's cookies been modified by sticky cookies or auth?
- self.stickycookie = stickycookie
- self.stickyauth = stickyauth
-
- # Is this request replayed?
- self.is_replay = is_replay
-
- def get_state(self):
- state = super(HTTPRequest, self).get_state()
- state.update(
- stickycookie = self.stickycookie,
- stickyauth = self.stickyauth,
- is_replay = self.is_replay,
- )
- return state
-
- def set_state(self, state):
- self.stickycookie = state.pop("stickycookie")
- self.stickyauth = state.pop("stickyauth")
- self.is_replay = state.pop("is_replay")
- super(HTTPRequest, self).set_state(state)
-
- @classmethod
- def wrap(self, request):
- req = HTTPRequest(
- first_line_format=request.form_in,
- method=request.method,
- scheme=request.scheme,
- host=request.host,
- port=request.port,
- path=request.path,
- http_version=request.http_version,
- headers=request.headers,
- content=request.content,
- timestamp_start=request.timestamp_start,
- timestamp_end=request.timestamp_end,
- form_out=(request.form_out if hasattr(request, 'form_out') else None),
- )
- return req
-
- @property
- def form_out(self):
- warnings.warn(".form_out is deprecated, use .first_line_format instead.", DeprecationWarning)
- return self.first_line_format
-
- @form_out.setter
- def form_out(self, value):
- warnings.warn(".form_out is deprecated, use .first_line_format instead.", DeprecationWarning)
- self.first_line_format = value
-
- def __hash__(self):
- return id(self)
-
- def replace(self, pattern, repl, *args, **kwargs):
- """
- Replaces a regular expression pattern with repl in the headers, the
- request path and the body of the request. Encoded content will be
- decoded before replacement, and re-encoded afterwards.
-
- Returns the number of replacements made.
- """
- c = MessageMixin.replace(self, pattern, repl, *args, **kwargs)
- self.path, pc = utils.safe_subn(
- pattern, repl, self.path, *args, **kwargs
- )
- c += pc
- return c
-
-
-class HTTPResponse(MessageMixin, Response):
-
- """
- An HTTP response.
-
- Exposes the following attributes:
-
- http_version: HTTP version, e.g. "HTTP/1.1"
-
- status_code: HTTP response status code
-
- msg: HTTP response message
-
- headers: Headers object
-
- content: Content of the request, None, or CONTENT_MISSING if there
- is content associated, but not present. CONTENT_MISSING evaluates
- to False to make checking for the presence of content natural.
-
- timestamp_start: Timestamp indicating when request transmission started
-
- timestamp_end: Timestamp indicating when request transmission ended
- """
-
- def __init__(
- self,
- http_version,
- status_code,
- reason,
- headers,
- content,
- timestamp_start=None,
- timestamp_end=None,
- is_replay = False
- ):
- Response.__init__(
- self,
- http_version,
- status_code,
- reason,
- headers,
- content,
- timestamp_start=timestamp_start,
- timestamp_end=timestamp_end,
- )
-
- # Is this request replayed?
- self.is_replay = is_replay
- self.stream = False
-
- @classmethod
- def wrap(self, response):
- resp = HTTPResponse(
- http_version=response.http_version,
- status_code=response.status_code,
- reason=response.reason,
- headers=response.headers,
- content=response.content,
- timestamp_start=response.timestamp_start,
- timestamp_end=response.timestamp_end,
- )
- return resp
-
- def _refresh_cookie(self, c, delta):
- """
- Takes a cookie string c and a time delta in seconds, and returns
- a refreshed cookie string.
- """
- try:
- c = Cookie.SimpleCookie(str(c))
- except Cookie.CookieError:
- raise ValueError("Invalid Cookie")
- for i in c.values():
- if "expires" in i:
- d = parsedate_tz(i["expires"])
- if d:
- d = mktime_tz(d) + delta
- i["expires"] = formatdate(d)
- else:
- # This can happen when the expires tag is invalid.
- # reddit.com sends a an expires tag like this: "Thu, 31 Dec
- # 2037 23:59:59 GMT", which is valid RFC 1123, but not
- # strictly correct according to the cookie spec. Browsers
- # appear to parse this tolerantly - maybe we should too.
- # For now, we just ignore this.
- del i["expires"]
- ret = c.output(header="").strip()
- if not ret:
- raise ValueError("Invalid Cookie")
- return ret
-
- def refresh(self, now=None):
- """
- This fairly complex and heuristic function refreshes a server
- response for replay.
-
- - It adjusts date, expires and last-modified headers.
- - It adjusts cookie expiration.
- """
- if not now:
- now = time.time()
- delta = now - self.timestamp_start
- refresh_headers = [
- "date",
- "expires",
- "last-modified",
- ]
- for i in refresh_headers:
- if i in self.headers:
- d = parsedate_tz(self.headers[i])
- if d:
- new = mktime_tz(d) + delta
- self.headers[i] = formatdate(new)
- c = []
- for set_cookie_header in self.headers.get_all("set-cookie"):
- try:
- refreshed = self._refresh_cookie(set_cookie_header, delta)
- except ValueError:
- refreshed = set_cookie_header
- c.append(refreshed)
- if c:
- self.headers.set_all("set-cookie", c)
-
-
-class HTTPFlow(Flow):
-
- """
- A HTTPFlow is a collection of objects representing a single HTTP
- transaction.
-
- Attributes:
- request: HTTPRequest object
- response: HTTPResponse object
- error: Error object
- server_conn: ServerConnection object
- client_conn: ClientConnection object
- intercepted: Is this flow currently being intercepted?
- live: Does this flow have a live client connection?
-
- Note that it's possible for a Flow to have both a response and an error
- object. This might happen, for instance, when a response was received
- from the server, but there was an error sending it back to the client.
- """
-
- def __init__(self, client_conn, server_conn, live=None):
- super(HTTPFlow, self).__init__("http", client_conn, server_conn, live)
- self.request = None
- """@type: HTTPRequest"""
- self.response = None
- """@type: HTTPResponse"""
-
- _stateobject_attributes = Flow._stateobject_attributes.copy()
- _stateobject_attributes.update(
- request=HTTPRequest,
- response=HTTPResponse
- )
-
- @classmethod
- def from_state(cls, state):
- f = cls(None, None)
- f.set_state(state)
- return f
-
- def __repr__(self):
- s = "<HTTPFlow"
- for a in ("request", "response", "error", "client_conn", "server_conn"):
- if getattr(self, a, False):
- s += "\r\n %s = {flow.%s}" % (a, a)
- s += ">"
- return s.format(flow=self)
-
- def copy(self):
- f = super(HTTPFlow, self).copy()
- if self.request:
- f.request = self.request.copy()
- if self.response:
- f.response = self.response.copy()
- return f
-
- def match(self, f):
- """
- Match this flow against a compiled filter expression. Returns True
- if matched, False if not.
-
- If f is a string, it will be compiled as a filter expression. If
- the expression is invalid, ValueError is raised.
- """
- if isinstance(f, basestring):
- from .. import filt
-
- f = filt.parse(f)
- if not f:
- raise ValueError("Invalid filter expression.")
- if f:
- return f(self)
- return True
-
- def replace(self, pattern, repl, *args, **kwargs):
- """
- Replaces a regular expression pattern with repl in both request and
- response of the flow. Encoded content will be decoded before
- replacement, and re-encoded afterwards.
-
- Returns the number of replacements made.
- """
- c = self.request.replace(pattern, repl, *args, **kwargs)
- if self.response:
- c += self.response.replace(pattern, repl, *args, **kwargs)
- return c
-
-
-def make_error_response(status_code, message, headers=None):
- response = status_codes.RESPONSES.get(status_code, "Unknown")
- body = """
- <html>
- <head>
- <title>%d %s</title>
- </head>
- <body>%s</body>
- </html>
- """.strip() % (status_code, response, message)
-
- if not headers:
- headers = Headers(
- Server=version.NAMEVERSION,
- Connection="close",
- Content_Length=str(len(body)),
- Content_Type="text/html"
- )
-
- return HTTPResponse(
- b"HTTP/1.1",
- status_code,
- response,
- headers,
- body,
- )
-
-
-def make_connect_request(address):
- address = Address.wrap(address)
- return HTTPRequest(
- "authority", "CONNECT", None, address.host, address.port, None, b"HTTP/1.1",
- Headers(), ""
- )
-
-
-def make_connect_response(http_version):
- # Do not send any response headers as it breaks proxying non-80 ports on
- # Android emulators using the -http-proxy option.
- return HTTPResponse(
- http_version,
- 200,
- "Connection established",
- Headers(),
- "",
- )
-
-expect_continue_response = HTTPResponse(b"HTTP/1.1", 100, "Continue", Headers(), b"")