Skip to content

Commit

Permalink
Fixed #29186 -- Fixed pickling HttpRequest and subclasses.
Browse files Browse the repository at this point in the history
  • Loading branch information
Anv3sh authored and felixxm committed Sep 14, 2022
1 parent e14d08c commit 4ec4640
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 2 deletions.
10 changes: 10 additions & 0 deletions django/core/handlers/wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ def close(self):


class WSGIRequest(HttpRequest):
non_picklable_attrs = HttpRequest.non_picklable_attrs | frozenset(["environ"])
meta_non_picklable_attrs = frozenset(["wsgi.errors", "wsgi.input"])

def __init__(self, environ):
script_name = get_script_name(environ)
# If PATH_INFO is empty (e.g. accessing the SCRIPT_NAME URL without a
Expand All @@ -89,6 +92,13 @@ def __init__(self, environ):
self._read_started = False
self.resolver_match = None

def __getstate__(self):
state = super().__getstate__()
for attr in self.meta_non_picklable_attrs:
if attr in state["META"]:
del state["META"][attr]
return state

def _get_scheme(self):
return self.environ.get("wsgi.url_scheme")

Expand Down
17 changes: 17 additions & 0 deletions django/http/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class HttpRequest:
_encoding = None
_upload_handlers = []

non_picklable_attrs = frozenset(["resolver_match", "_stream"])

def __init__(self):
# WARNING: The `WSGIRequest` subclass doesn't call `super`.
# Any variable assignment made here should also happen in
Expand Down Expand Up @@ -78,6 +80,21 @@ def __repr__(self):
self.get_full_path(),
)

def __getstate__(self):
obj_dict = self.__dict__.copy()
for attr in self.non_picklable_attrs:
if attr in obj_dict:
del obj_dict[attr]
return obj_dict

def __deepcopy__(self, memo):
obj = copy.copy(self)
for attr in self.non_picklable_attrs:
if hasattr(self, attr):
setattr(obj, attr, copy.deepcopy(getattr(self, attr), memo))
memo[id(self)] = obj
return obj

@cached_property
def headers(self):
return HttpHeaders(self.META)
Expand Down
2 changes: 0 additions & 2 deletions django/http/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,12 +370,10 @@ class HttpResponse(HttpResponseBase):
[
"resolver_match",
# Non-picklable attributes added by test clients.
"asgi_request",
"client",
"context",
"json",
"templates",
"wsgi_request",
]
)

Expand Down
15 changes: 15 additions & 0 deletions tests/requests/tests.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import pickle
from io import BytesIO
from itertools import chain
from urllib.parse import urlencode
Expand Down Expand Up @@ -669,6 +670,20 @@ def read(self, len=0):
with self.assertRaises(UnreadablePostError):
request.FILES

def test_pickling_request(self):
request = HttpRequest()
request.method = "GET"
request.path = "/testpath/"
request.META = {
"QUERY_STRING": ";some=query&+query=string",
"SERVER_NAME:" "example.com",
"SERVER_PORT": 80,
}
request.COOKIES = {"post-key": "post-value"}
dump = pickle.dumps(request)
request_from_pickle = pickle.loads(dump)
self.assertEqual(repr(request), repr(request_from_pickle))


class HostValidationTests(SimpleTestCase):
poisoned_hosts = [
Expand Down

0 comments on commit 4ec4640

Please sign in to comment.