Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserve blank form values for urlencoded forms (option) #2439

Merged
merged 10 commits into from Apr 24, 2022
50 changes: 29 additions & 21 deletions sanic/request.py
Expand Up @@ -152,8 +152,8 @@ def __init__(
self.parsed_accept: Optional[AcceptContainer] = None
self.parsed_credentials: Optional[Credentials] = None
self.parsed_json = None
self.parsed_form = None
self.parsed_files = None
self.parsed_form: Optional[RequestParameters] = None
self.parsed_files: Optional[RequestParameters] = None
self.parsed_token: Optional[str] = None
self.parsed_args: DefaultDict[
Tuple[bool, bool, str, str], RequestParameters
Expand Down Expand Up @@ -426,28 +426,36 @@ def credentials(self) -> Optional[Credentials]:
pass
return self.parsed_credentials

def get_form(self, keep_blank_values: bool = False):
ahopkins marked this conversation as resolved.
Show resolved Hide resolved
self.parsed_form = RequestParameters()
self.parsed_files = RequestParameters()
content_type = self.headers.getone(
"content-type", DEFAULT_HTTP_CONTENT_TYPE
)
content_type, parameters = parse_content_header(content_type)
try:
if content_type == "application/x-www-form-urlencoded":
self.parsed_form = RequestParameters(
parse_qs(
self.body.decode("utf-8"),
keep_blank_values=keep_blank_values,
)
)
ahopkins marked this conversation as resolved.
Show resolved Hide resolved
elif content_type == "multipart/form-data":
# TODO: Stream this instead of reading to/from memory
boundary = parameters["boundary"].encode( # type: ignore
"utf-8"
) # type: ignore
self.parsed_form, self.parsed_files = parse_multipart_form(
self.body, boundary
)
except Exception:
error_logger.exception("Failed when parsing form")

@property
def form(self):
if self.parsed_form is None:
self.parsed_form = RequestParameters()
self.parsed_files = RequestParameters()
content_type = self.headers.getone(
"content-type", DEFAULT_HTTP_CONTENT_TYPE
)
content_type, parameters = parse_content_header(content_type)
try:
if content_type == "application/x-www-form-urlencoded":
self.parsed_form = RequestParameters(
parse_qs(self.body.decode("utf-8"))
)
elif content_type == "multipart/form-data":
# TODO: Stream this instead of reading to/from memory
boundary = parameters["boundary"].encode("utf-8")
self.parsed_form, self.parsed_files = parse_multipart_form(
self.body, boundary
)
except Exception:
error_logger.exception("Failed when parsing form")
self.get_form()

return self.parsed_form

Expand Down
66 changes: 66 additions & 0 deletions tests/test_requests.py
Expand Up @@ -1016,6 +1016,72 @@ async def handler(request):
assert request.form.get("test") == "OK" # For request.parsed_form


def test_post_form_urlencoded_keep_blanks(app):
@app.route("/", methods=["POST"])
async def handler(request):
request.get_form(keep_blank_values=True)
return text("OK")

payload = "test="
headers = {"content-type": "application/x-www-form-urlencoded"}

request, response = app.test_client.post(
"/", data=payload, headers=headers
)

assert request.form.get("test") == ""
assert request.form.get("test") == "" # For request.parsed_form


@pytest.mark.asyncio
async def test_post_form_urlencoded_keep_blanks_asgi(app):
@app.route("/", methods=["POST"])
async def handler(request):
request.get_form(keep_blank_values=True)
return text("OK")

payload = "test="
headers = {"content-type": "application/x-www-form-urlencoded"}

request, response = await app.asgi_client.post(
"/", data=payload, headers=headers
)

assert request.form.get("test") == ""
assert request.form.get("test") == "" # For request.parsed_form



def test_post_form_urlencoded_drop_blanks(app):
@app.route("/", methods=["POST"])
async def handler(request):
return text("OK")

payload = "test="
headers = {"content-type": "application/x-www-form-urlencoded"}

request, response = app.test_client.post(
"/", data=payload, headers=headers
)

assert "test" not in request.form.keys()

@pytest.mark.asyncio
async def test_post_form_urlencoded_drop_blanks_asgi(app):
@app.route("/", methods=["POST"])
async def handler(request):
return text("OK")

payload = "test="
headers = {"content-type": "application/x-www-form-urlencoded"}

request, response = await app.asgi_client.post(
"/", data=payload, headers=headers
)

assert "test" not in request.form.keys()


@pytest.mark.parametrize(
"payload",
[
Expand Down