2018-09-20 09:28:18 +03:00
|
|
|
#
|
2023-11-21 23:29:58 +03:00
|
|
|
# This file is licensed under the Affero General Public License (AGPL) version 3.
|
|
|
|
#
|
|
|
|
# Copyright (C) 2023 New Vector, Ltd
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
|
|
# License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# See the GNU Affero General Public License for more details:
|
|
|
|
# <https://www.gnu.org/licenses/agpl-3.0.html>.
|
|
|
|
#
|
|
|
|
# Originally licensed under the Apache License, Version 2.0:
|
|
|
|
# <http://www.apache.org/licenses/LICENSE-2.0>.
|
|
|
|
#
|
|
|
|
# [This file includes modifications made by New Vector Limited]
|
2018-09-20 09:28:18 +03:00
|
|
|
#
|
|
|
|
#
|
|
|
|
|
2018-06-27 12:37:24 +03:00
|
|
|
import re
|
2022-05-11 14:24:48 +03:00
|
|
|
from http import HTTPStatus
|
2022-06-28 15:12:17 +03:00
|
|
|
from typing import Awaitable, Callable, Dict, NoReturn, Optional, Tuple
|
2018-09-20 09:28:18 +03:00
|
|
|
|
2018-06-27 12:37:24 +03:00
|
|
|
from twisted.internet.defer import Deferred
|
2018-09-20 09:28:18 +03:00
|
|
|
from twisted.web.resource import Resource
|
2018-06-27 12:37:24 +03:00
|
|
|
|
2020-01-15 18:58:55 +03:00
|
|
|
from synapse.api.errors import Codes, RedirectException, SynapseError
|
2020-06-16 14:44:07 +03:00
|
|
|
from synapse.config.server import parse_listener_def
|
2022-05-11 14:24:48 +03:00
|
|
|
from synapse.http.server import (
|
|
|
|
DirectServeHtmlResource,
|
|
|
|
DirectServeJsonResource,
|
|
|
|
JsonResource,
|
|
|
|
OptionsResource,
|
|
|
|
)
|
|
|
|
from synapse.http.site import SynapseRequest, SynapseSite
|
2019-07-03 17:07:04 +03:00
|
|
|
from synapse.logging.context import make_deferred_yieldable
|
2022-05-11 14:24:48 +03:00
|
|
|
from synapse.types import JsonDict
|
2018-07-09 09:09:20 +03:00
|
|
|
from synapse.util import Clock
|
2022-08-31 14:16:05 +03:00
|
|
|
from synapse.util.cancellation import cancellable
|
2018-07-09 09:09:20 +03:00
|
|
|
|
2018-06-27 12:37:24 +03:00
|
|
|
from tests import unittest
|
2022-06-07 20:17:32 +03:00
|
|
|
from tests.http.server._base import test_disconnect
|
2019-01-24 13:31:54 +03:00
|
|
|
from tests.server import (
|
2022-06-28 15:12:17 +03:00
|
|
|
FakeChannel,
|
2020-11-14 01:39:09 +03:00
|
|
|
FakeSite,
|
2023-07-18 11:49:21 +03:00
|
|
|
get_clock,
|
2019-01-24 13:31:54 +03:00
|
|
|
make_request,
|
|
|
|
setup_test_homeserver,
|
|
|
|
)
|
2018-06-27 12:37:24 +03:00
|
|
|
|
|
|
|
|
|
|
|
class JsonResourceTests(unittest.TestCase):
|
2022-06-28 15:12:17 +03:00
|
|
|
def setUp(self) -> None:
|
2023-07-18 11:49:21 +03:00
|
|
|
reactor, clock = get_clock()
|
|
|
|
self.reactor = reactor
|
2018-06-27 12:37:24 +03:00
|
|
|
self.homeserver = setup_test_homeserver(
|
2020-12-02 19:09:24 +03:00
|
|
|
self.addCleanup,
|
2023-07-18 11:49:21 +03:00
|
|
|
clock=clock,
|
2020-12-02 19:09:24 +03:00
|
|
|
reactor=self.reactor,
|
2018-06-27 12:37:24 +03:00
|
|
|
)
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def test_handler_for_request(self) -> None:
|
2018-06-27 12:37:24 +03:00
|
|
|
"""
|
|
|
|
JsonResource.handler_for_request gives correctly decoded URL args to
|
|
|
|
the callback, while Twisted will give the raw bytes of URL query
|
|
|
|
arguments.
|
|
|
|
"""
|
|
|
|
got_kwargs = {}
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def _callback(
|
|
|
|
request: SynapseRequest, **kwargs: object
|
|
|
|
) -> Tuple[int, Dict[str, object]]:
|
2018-06-27 12:37:24 +03:00
|
|
|
got_kwargs.update(kwargs)
|
2019-08-30 18:28:26 +03:00
|
|
|
return 200, kwargs
|
2018-06-27 12:37:24 +03:00
|
|
|
|
|
|
|
res = JsonResource(self.homeserver)
|
2018-07-17 13:43:18 +03:00
|
|
|
res.register_paths(
|
2019-07-24 15:07:35 +03:00
|
|
|
"GET",
|
|
|
|
[re.compile("^/_matrix/foo/(?P<room_id>[^/]*)$")],
|
|
|
|
_callback,
|
|
|
|
"test_servlet",
|
2018-07-17 13:43:18 +03:00
|
|
|
)
|
2018-06-27 12:37:24 +03:00
|
|
|
|
2020-12-15 17:54:41 +03:00
|
|
|
make_request(
|
2021-09-24 13:01:25 +03:00
|
|
|
self.reactor,
|
|
|
|
FakeSite(res, self.reactor),
|
|
|
|
b"GET",
|
|
|
|
b"/_matrix/foo/%E2%98%83?a=%E2%98%83",
|
2018-11-06 19:00:00 +03:00
|
|
|
)
|
2018-06-27 12:37:24 +03:00
|
|
|
|
|
|
|
self.assertEqual(got_kwargs, {"room_id": "\N{SNOWMAN}"})
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def test_callback_direct_exception(self) -> None:
|
2018-06-27 12:37:24 +03:00
|
|
|
"""
|
|
|
|
If the web callback raises an uncaught exception, it will be translated
|
|
|
|
into a 500.
|
|
|
|
"""
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def _callback(request: SynapseRequest, **kwargs: object) -> NoReturn:
|
2018-06-27 12:37:24 +03:00
|
|
|
raise Exception("boo")
|
|
|
|
|
|
|
|
res = JsonResource(self.homeserver)
|
2019-07-24 15:07:35 +03:00
|
|
|
res.register_paths(
|
|
|
|
"GET", [re.compile("^/_matrix/foo$")], _callback, "test_servlet"
|
|
|
|
)
|
2018-06-27 12:37:24 +03:00
|
|
|
|
2021-09-24 13:01:25 +03:00
|
|
|
channel = make_request(
|
|
|
|
self.reactor, FakeSite(res, self.reactor), b"GET", b"/_matrix/foo"
|
|
|
|
)
|
2018-06-27 12:37:24 +03:00
|
|
|
|
2022-08-05 17:59:09 +03:00
|
|
|
self.assertEqual(channel.code, 500)
|
2018-06-27 12:37:24 +03:00
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def test_callback_indirect_exception(self) -> None:
|
2018-06-27 12:37:24 +03:00
|
|
|
"""
|
|
|
|
If the web callback raises an uncaught exception in a Deferred, it will
|
|
|
|
be translated into a 500.
|
|
|
|
"""
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def _throw(*args: object) -> NoReturn:
|
2018-06-27 12:37:24 +03:00
|
|
|
raise Exception("boo")
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def _callback(request: SynapseRequest, **kwargs: object) -> "Deferred[None]":
|
|
|
|
d: "Deferred[None]" = Deferred()
|
2018-06-27 12:37:24 +03:00
|
|
|
d.addCallback(_throw)
|
2021-09-24 13:01:25 +03:00
|
|
|
self.reactor.callLater(0.5, d.callback, True)
|
2018-11-16 01:46:51 +03:00
|
|
|
return make_deferred_yieldable(d)
|
2018-06-27 12:37:24 +03:00
|
|
|
|
|
|
|
res = JsonResource(self.homeserver)
|
2019-07-24 15:07:35 +03:00
|
|
|
res.register_paths(
|
|
|
|
"GET", [re.compile("^/_matrix/foo$")], _callback, "test_servlet"
|
|
|
|
)
|
2018-06-27 12:37:24 +03:00
|
|
|
|
2021-09-24 13:01:25 +03:00
|
|
|
channel = make_request(
|
|
|
|
self.reactor, FakeSite(res, self.reactor), b"GET", b"/_matrix/foo"
|
|
|
|
)
|
2018-06-27 12:37:24 +03:00
|
|
|
|
2022-08-05 17:59:09 +03:00
|
|
|
self.assertEqual(channel.code, 500)
|
2018-06-27 12:37:24 +03:00
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def test_callback_synapseerror(self) -> None:
|
2018-06-27 12:37:24 +03:00
|
|
|
"""
|
|
|
|
If the web callback raises a SynapseError, it returns the appropriate
|
|
|
|
status code and message set in it.
|
|
|
|
"""
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def _callback(request: SynapseRequest, **kwargs: object) -> NoReturn:
|
2018-06-27 12:37:24 +03:00
|
|
|
raise SynapseError(403, "Forbidden!!one!", Codes.FORBIDDEN)
|
|
|
|
|
|
|
|
res = JsonResource(self.homeserver)
|
2019-07-24 15:07:35 +03:00
|
|
|
res.register_paths(
|
|
|
|
"GET", [re.compile("^/_matrix/foo$")], _callback, "test_servlet"
|
|
|
|
)
|
2018-06-27 12:37:24 +03:00
|
|
|
|
2021-09-24 13:01:25 +03:00
|
|
|
channel = make_request(
|
|
|
|
self.reactor, FakeSite(res, self.reactor), b"GET", b"/_matrix/foo"
|
|
|
|
)
|
2018-06-27 12:37:24 +03:00
|
|
|
|
2022-08-05 17:59:09 +03:00
|
|
|
self.assertEqual(channel.code, 403)
|
2018-08-09 05:22:01 +03:00
|
|
|
self.assertEqual(channel.json_body["error"], "Forbidden!!one!")
|
|
|
|
self.assertEqual(channel.json_body["errcode"], "M_FORBIDDEN")
|
2018-06-27 12:37:24 +03:00
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def test_no_handler(self) -> None:
|
2018-06-27 12:37:24 +03:00
|
|
|
"""
|
|
|
|
If there is no handler to process the request, Synapse will return 400.
|
|
|
|
"""
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def _callback(request: SynapseRequest, **kwargs: object) -> None:
|
2018-06-27 12:37:24 +03:00
|
|
|
"""
|
|
|
|
Not ever actually called!
|
|
|
|
"""
|
|
|
|
self.fail("shouldn't ever get here")
|
|
|
|
|
|
|
|
res = JsonResource(self.homeserver)
|
2019-07-24 15:07:35 +03:00
|
|
|
res.register_paths(
|
|
|
|
"GET", [re.compile("^/_matrix/foo$")], _callback, "test_servlet"
|
|
|
|
)
|
2018-06-27 12:37:24 +03:00
|
|
|
|
2021-09-24 13:01:25 +03:00
|
|
|
channel = make_request(
|
|
|
|
self.reactor, FakeSite(res, self.reactor), b"GET", b"/_matrix/foobar"
|
|
|
|
)
|
2018-06-27 12:37:24 +03:00
|
|
|
|
2022-12-08 19:37:05 +03:00
|
|
|
self.assertEqual(channel.code, 404)
|
2018-08-09 05:22:01 +03:00
|
|
|
self.assertEqual(channel.json_body["error"], "Unrecognized request")
|
|
|
|
self.assertEqual(channel.json_body["errcode"], "M_UNRECOGNIZED")
|
2018-09-20 09:28:18 +03:00
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def test_head_request(self) -> None:
|
2020-08-03 15:45:42 +03:00
|
|
|
"""
|
|
|
|
JsonResource.handler_for_request gives correctly decoded URL args to
|
|
|
|
the callback, while Twisted will give the raw bytes of URL query
|
|
|
|
arguments.
|
|
|
|
"""
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def _callback(
|
|
|
|
request: SynapseRequest, **kwargs: object
|
|
|
|
) -> Tuple[int, Dict[str, object]]:
|
2020-08-03 15:45:42 +03:00
|
|
|
return 200, {"result": True}
|
|
|
|
|
|
|
|
res = JsonResource(self.homeserver)
|
|
|
|
res.register_paths(
|
|
|
|
"GET",
|
|
|
|
[re.compile("^/_matrix/foo$")],
|
|
|
|
_callback,
|
|
|
|
"test_servlet",
|
|
|
|
)
|
|
|
|
|
|
|
|
# The path was registered as GET, but this is a HEAD request.
|
2021-09-24 13:01:25 +03:00
|
|
|
channel = make_request(
|
|
|
|
self.reactor, FakeSite(res, self.reactor), b"HEAD", b"/_matrix/foo"
|
|
|
|
)
|
2020-08-03 15:45:42 +03:00
|
|
|
|
2022-08-05 17:59:09 +03:00
|
|
|
self.assertEqual(channel.code, 200)
|
2020-08-03 15:45:42 +03:00
|
|
|
self.assertNotIn("body", channel.result)
|
|
|
|
|
2018-09-20 09:28:18 +03:00
|
|
|
|
2020-05-22 16:30:07 +03:00
|
|
|
class OptionsResourceTests(unittest.TestCase):
|
2022-06-28 15:12:17 +03:00
|
|
|
def setUp(self) -> None:
|
2023-07-18 11:49:21 +03:00
|
|
|
reactor, clock = get_clock()
|
|
|
|
self.reactor = reactor
|
|
|
|
self.homeserver = setup_test_homeserver(
|
|
|
|
self.addCleanup,
|
|
|
|
clock=clock,
|
|
|
|
reactor=self.reactor,
|
|
|
|
)
|
2020-05-22 16:30:07 +03:00
|
|
|
|
|
|
|
class DummyResource(Resource):
|
|
|
|
isLeaf = True
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def render(self, request: SynapseRequest) -> bytes:
|
|
|
|
# Type-ignore: mypy thinks request.path is Optional[Any], not bytes.
|
|
|
|
return request.path # type: ignore[return-value]
|
2020-05-22 16:30:07 +03:00
|
|
|
|
|
|
|
# Setup a resource with some children.
|
|
|
|
self.resource = OptionsResource()
|
|
|
|
self.resource.putChild(b"res", DummyResource())
|
|
|
|
|
2024-11-13 17:10:20 +03:00
|
|
|
def _make_request(self, method: bytes, path: bytes) -> FakeChannel:
|
2020-05-22 16:30:07 +03:00
|
|
|
"""Create a request from the method/path and return a channel with the response."""
|
|
|
|
# Create a site and query for the resource.
|
2020-06-16 14:44:07 +03:00
|
|
|
site = SynapseSite(
|
|
|
|
"test",
|
|
|
|
"site_tag",
|
2022-10-18 18:52:25 +03:00
|
|
|
parse_listener_def(
|
|
|
|
0,
|
|
|
|
{
|
|
|
|
"type": "http",
|
|
|
|
"port": 0,
|
|
|
|
},
|
|
|
|
),
|
2020-06-16 14:44:07 +03:00
|
|
|
self.resource,
|
|
|
|
"1.0",
|
2022-07-19 14:45:17 +03:00
|
|
|
max_request_body_size=4096,
|
2021-04-23 19:06:47 +03:00
|
|
|
reactor=self.reactor,
|
2023-07-18 11:49:21 +03:00
|
|
|
hs=self.homeserver,
|
2020-06-16 14:44:07 +03:00
|
|
|
)
|
2020-11-14 01:39:09 +03:00
|
|
|
|
2020-11-16 01:49:21 +03:00
|
|
|
# render the request and return the channel
|
2020-12-15 17:44:04 +03:00
|
|
|
channel = make_request(self.reactor, site, method, path, shorthand=False)
|
2020-05-22 16:30:07 +03:00
|
|
|
return channel
|
|
|
|
|
2022-10-18 18:52:25 +03:00
|
|
|
def _check_cors_standard_headers(self, channel: FakeChannel) -> None:
|
|
|
|
# Ensure the correct CORS headers have been added
|
|
|
|
# as per https://spec.matrix.org/v1.4/client-server-api/#web-browser-clients
|
|
|
|
self.assertEqual(
|
|
|
|
channel.headers.getRawHeaders(b"Access-Control-Allow-Origin"),
|
|
|
|
[b"*"],
|
|
|
|
"has correct CORS Origin header",
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
channel.headers.getRawHeaders(b"Access-Control-Allow-Methods"),
|
|
|
|
[b"GET, HEAD, POST, PUT, DELETE, OPTIONS"], # HEAD isn't in the spec
|
|
|
|
"has correct CORS Methods header",
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
channel.headers.getRawHeaders(b"Access-Control-Allow-Headers"),
|
|
|
|
[b"X-Requested-With, Content-Type, Authorization, Date"],
|
|
|
|
"has correct CORS Headers header",
|
|
|
|
)
|
2023-03-20 21:14:05 +03:00
|
|
|
self.assertEqual(
|
|
|
|
channel.headers.getRawHeaders(b"Access-Control-Expose-Headers"),
|
2023-07-11 11:18:50 +03:00
|
|
|
[b"Synapse-Trace-Id, Server"],
|
2023-03-20 21:14:05 +03:00
|
|
|
)
|
2022-10-18 18:52:25 +03:00
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def test_unknown_options_request(self) -> None:
|
2020-07-24 14:08:07 +03:00
|
|
|
"""An OPTIONS requests to an unknown URL still returns 204 No Content."""
|
2020-05-22 16:30:07 +03:00
|
|
|
channel = self._make_request(b"OPTIONS", b"/foo/")
|
2022-08-05 17:59:09 +03:00
|
|
|
self.assertEqual(channel.code, 204)
|
2020-07-24 14:08:07 +03:00
|
|
|
self.assertNotIn("body", channel.result)
|
2020-05-22 16:30:07 +03:00
|
|
|
|
2022-10-18 18:52:25 +03:00
|
|
|
self._check_cors_standard_headers(channel)
|
2020-05-22 19:42:39 +03:00
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def test_known_options_request(self) -> None:
|
2020-07-24 14:08:07 +03:00
|
|
|
"""An OPTIONS requests to an known URL still returns 204 No Content."""
|
2020-05-22 16:30:07 +03:00
|
|
|
channel = self._make_request(b"OPTIONS", b"/res/")
|
2022-08-05 17:59:09 +03:00
|
|
|
self.assertEqual(channel.code, 204)
|
2020-07-24 14:08:07 +03:00
|
|
|
self.assertNotIn("body", channel.result)
|
2020-05-22 16:30:07 +03:00
|
|
|
|
2022-10-18 18:52:25 +03:00
|
|
|
self._check_cors_standard_headers(channel)
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def test_unknown_request(self) -> None:
|
2020-05-22 16:30:07 +03:00
|
|
|
"""A non-OPTIONS request to an unknown URL should 404."""
|
|
|
|
channel = self._make_request(b"GET", b"/foo/")
|
2022-08-05 17:59:09 +03:00
|
|
|
self.assertEqual(channel.code, 404)
|
2020-05-22 16:30:07 +03:00
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def test_known_request(self) -> None:
|
2020-05-22 16:30:07 +03:00
|
|
|
"""A non-OPTIONS request to an known URL should query the proper resource."""
|
|
|
|
channel = self._make_request(b"GET", b"/res/")
|
2022-08-05 17:59:09 +03:00
|
|
|
self.assertEqual(channel.code, 200)
|
2020-05-22 16:30:07 +03:00
|
|
|
self.assertEqual(channel.result["body"], b"/res/")
|
|
|
|
|
|
|
|
|
2020-01-15 18:58:55 +03:00
|
|
|
class WrapHtmlRequestHandlerTests(unittest.TestCase):
|
2020-07-03 21:02:19 +03:00
|
|
|
class TestResource(DirectServeHtmlResource):
|
2022-06-28 15:12:17 +03:00
|
|
|
callback: Optional[Callable[..., Awaitable[None]]]
|
2020-01-15 18:58:55 +03:00
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
async def _async_render_GET(self, request: SynapseRequest) -> None:
|
|
|
|
assert self.callback is not None
|
2020-07-03 21:02:19 +03:00
|
|
|
await self.callback(request)
|
2020-01-15 18:58:55 +03:00
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def setUp(self) -> None:
|
2023-07-18 11:49:21 +03:00
|
|
|
reactor, _ = get_clock()
|
|
|
|
self.reactor = reactor
|
2020-01-15 18:58:55 +03:00
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def test_good_response(self) -> None:
|
|
|
|
async def callback(request: SynapseRequest) -> None:
|
2020-01-15 18:58:55 +03:00
|
|
|
request.write(b"response")
|
|
|
|
request.finish()
|
|
|
|
|
|
|
|
res = WrapHtmlRequestHandlerTests.TestResource()
|
|
|
|
res.callback = callback
|
|
|
|
|
2021-09-24 13:01:25 +03:00
|
|
|
channel = make_request(
|
|
|
|
self.reactor, FakeSite(res, self.reactor), b"GET", b"/path"
|
|
|
|
)
|
2020-01-15 18:58:55 +03:00
|
|
|
|
2022-08-05 17:59:09 +03:00
|
|
|
self.assertEqual(channel.code, 200)
|
2020-01-15 18:58:55 +03:00
|
|
|
body = channel.result["body"]
|
|
|
|
self.assertEqual(body, b"response")
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def test_redirect_exception(self) -> None:
|
2020-01-15 18:58:55 +03:00
|
|
|
"""
|
|
|
|
If the callback raises a RedirectException, it is turned into a 30x
|
|
|
|
with the right location.
|
|
|
|
"""
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
async def callback(request: SynapseRequest, **kwargs: object) -> None:
|
2020-01-15 18:58:55 +03:00
|
|
|
raise RedirectException(b"/look/an/eagle", 301)
|
|
|
|
|
|
|
|
res = WrapHtmlRequestHandlerTests.TestResource()
|
|
|
|
res.callback = callback
|
|
|
|
|
2021-09-24 13:01:25 +03:00
|
|
|
channel = make_request(
|
|
|
|
self.reactor, FakeSite(res, self.reactor), b"GET", b"/path"
|
|
|
|
)
|
2020-01-15 18:58:55 +03:00
|
|
|
|
2022-08-05 17:59:09 +03:00
|
|
|
self.assertEqual(channel.code, 301)
|
2024-07-30 17:23:23 +03:00
|
|
|
location_headers = channel.headers.getRawHeaders(b"Location", [])
|
2020-01-15 18:58:55 +03:00
|
|
|
self.assertEqual(location_headers, [b"/look/an/eagle"])
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def test_redirect_exception_with_cookie(self) -> None:
|
2020-01-15 18:58:55 +03:00
|
|
|
"""
|
|
|
|
If the callback raises a RedirectException which sets a cookie, that is
|
|
|
|
returned too
|
|
|
|
"""
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
async def callback(request: SynapseRequest, **kwargs: object) -> NoReturn:
|
2020-01-15 18:58:55 +03:00
|
|
|
e = RedirectException(b"/no/over/there", 304)
|
|
|
|
e.cookies.append(b"session=yespls")
|
|
|
|
raise e
|
|
|
|
|
|
|
|
res = WrapHtmlRequestHandlerTests.TestResource()
|
|
|
|
res.callback = callback
|
|
|
|
|
2021-09-24 13:01:25 +03:00
|
|
|
channel = make_request(
|
|
|
|
self.reactor, FakeSite(res, self.reactor), b"GET", b"/path"
|
|
|
|
)
|
2020-01-15 18:58:55 +03:00
|
|
|
|
2022-08-05 17:59:09 +03:00
|
|
|
self.assertEqual(channel.code, 304)
|
2024-07-30 17:23:23 +03:00
|
|
|
headers = channel.headers
|
|
|
|
location_headers = headers.getRawHeaders(b"Location", [])
|
2020-01-15 18:58:55 +03:00
|
|
|
self.assertEqual(location_headers, [b"/no/over/there"])
|
2024-07-30 17:23:23 +03:00
|
|
|
cookies_headers = headers.getRawHeaders(b"Set-Cookie", [])
|
2020-01-15 18:58:55 +03:00
|
|
|
self.assertEqual(cookies_headers, [b"session=yespls"])
|
2020-08-03 15:45:42 +03:00
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def test_head_request(self) -> None:
|
2020-08-03 15:45:42 +03:00
|
|
|
"""A head request should work by being turned into a GET request."""
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
async def callback(request: SynapseRequest) -> None:
|
2020-08-03 15:45:42 +03:00
|
|
|
request.write(b"response")
|
|
|
|
request.finish()
|
|
|
|
|
|
|
|
res = WrapHtmlRequestHandlerTests.TestResource()
|
|
|
|
res.callback = callback
|
|
|
|
|
2021-09-24 13:01:25 +03:00
|
|
|
channel = make_request(
|
|
|
|
self.reactor, FakeSite(res, self.reactor), b"HEAD", b"/path"
|
|
|
|
)
|
2020-08-03 15:45:42 +03:00
|
|
|
|
2022-08-05 17:59:09 +03:00
|
|
|
self.assertEqual(channel.code, 200)
|
2020-08-03 15:45:42 +03:00
|
|
|
self.assertNotIn("body", channel.result)
|
2022-05-11 14:24:48 +03:00
|
|
|
|
|
|
|
|
|
|
|
class CancellableDirectServeJsonResource(DirectServeJsonResource):
|
|
|
|
def __init__(self, clock: Clock):
|
|
|
|
super().__init__()
|
|
|
|
self.clock = clock
|
|
|
|
|
|
|
|
@cancellable
|
|
|
|
async def _async_render_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
|
|
|
|
await self.clock.sleep(1.0)
|
|
|
|
return HTTPStatus.OK, {"result": True}
|
|
|
|
|
|
|
|
async def _async_render_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
|
|
|
|
await self.clock.sleep(1.0)
|
|
|
|
return HTTPStatus.OK, {"result": True}
|
|
|
|
|
|
|
|
|
|
|
|
class CancellableDirectServeHtmlResource(DirectServeHtmlResource):
|
|
|
|
ERROR_TEMPLATE = "{code} {msg}"
|
|
|
|
|
|
|
|
def __init__(self, clock: Clock):
|
|
|
|
super().__init__()
|
|
|
|
self.clock = clock
|
|
|
|
|
|
|
|
@cancellable
|
|
|
|
async def _async_render_GET(self, request: SynapseRequest) -> Tuple[int, bytes]:
|
|
|
|
await self.clock.sleep(1.0)
|
|
|
|
return HTTPStatus.OK, b"ok"
|
|
|
|
|
|
|
|
async def _async_render_POST(self, request: SynapseRequest) -> Tuple[int, bytes]:
|
|
|
|
await self.clock.sleep(1.0)
|
|
|
|
return HTTPStatus.OK, b"ok"
|
|
|
|
|
|
|
|
|
2022-06-07 20:17:32 +03:00
|
|
|
class DirectServeJsonResourceCancellationTests(unittest.TestCase):
|
2022-05-11 14:24:48 +03:00
|
|
|
"""Tests for `DirectServeJsonResource` cancellation."""
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def setUp(self) -> None:
|
2023-07-18 11:49:21 +03:00
|
|
|
reactor, clock = get_clock()
|
|
|
|
self.reactor = reactor
|
|
|
|
self.resource = CancellableDirectServeJsonResource(clock)
|
2022-05-11 14:24:48 +03:00
|
|
|
self.site = FakeSite(self.resource, self.reactor)
|
|
|
|
|
|
|
|
def test_cancellable_disconnect(self) -> None:
|
|
|
|
"""Test that handlers with the `@cancellable` flag can be cancelled."""
|
|
|
|
channel = make_request(
|
|
|
|
self.reactor, self.site, "GET", "/sleep", await_result=False
|
|
|
|
)
|
2022-06-07 20:17:32 +03:00
|
|
|
test_disconnect(
|
2022-05-11 14:24:48 +03:00
|
|
|
self.reactor,
|
|
|
|
channel,
|
|
|
|
expect_cancellation=True,
|
|
|
|
expected_body={"error": "Request cancelled", "errcode": Codes.UNKNOWN},
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_uncancellable_disconnect(self) -> None:
|
|
|
|
"""Test that handlers without the `@cancellable` flag cannot be cancelled."""
|
|
|
|
channel = make_request(
|
|
|
|
self.reactor, self.site, "POST", "/sleep", await_result=False
|
|
|
|
)
|
2022-06-07 20:17:32 +03:00
|
|
|
test_disconnect(
|
2022-05-11 14:24:48 +03:00
|
|
|
self.reactor,
|
|
|
|
channel,
|
|
|
|
expect_cancellation=False,
|
|
|
|
expected_body={"result": True},
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2022-06-07 20:17:32 +03:00
|
|
|
class DirectServeHtmlResourceCancellationTests(unittest.TestCase):
|
2022-05-11 14:24:48 +03:00
|
|
|
"""Tests for `DirectServeHtmlResource` cancellation."""
|
|
|
|
|
2022-06-28 15:12:17 +03:00
|
|
|
def setUp(self) -> None:
|
2023-07-18 11:49:21 +03:00
|
|
|
reactor, clock = get_clock()
|
|
|
|
self.reactor = reactor
|
|
|
|
self.resource = CancellableDirectServeHtmlResource(clock)
|
2022-05-11 14:24:48 +03:00
|
|
|
self.site = FakeSite(self.resource, self.reactor)
|
|
|
|
|
|
|
|
def test_cancellable_disconnect(self) -> None:
|
|
|
|
"""Test that handlers with the `@cancellable` flag can be cancelled."""
|
|
|
|
channel = make_request(
|
|
|
|
self.reactor, self.site, "GET", "/sleep", await_result=False
|
|
|
|
)
|
2022-06-07 20:17:32 +03:00
|
|
|
test_disconnect(
|
2022-05-11 14:24:48 +03:00
|
|
|
self.reactor,
|
|
|
|
channel,
|
|
|
|
expect_cancellation=True,
|
|
|
|
expected_body=b"499 Request cancelled",
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_uncancellable_disconnect(self) -> None:
|
|
|
|
"""Test that handlers without the `@cancellable` flag cannot be cancelled."""
|
|
|
|
channel = make_request(
|
|
|
|
self.reactor, self.site, "POST", "/sleep", await_result=False
|
|
|
|
)
|
2022-06-07 20:17:32 +03:00
|
|
|
test_disconnect(
|
2022-05-11 14:24:48 +03:00
|
|
|
self.reactor, channel, expect_cancellation=False, expected_body=b"ok"
|
|
|
|
)
|