# HG changeset patch # User Goffi # Date 1676046002 -3600 # Node ID 69d970f974ff18183a7b384ce94acb8b7813d5e8 # Parent 722c2581877828eb53ad3c7f3b38fbb82eb379ee component AP gateway: don't percent-encode `@`: Mastodon doesn't like when "@" is percent-encoded (because it doesn't do it, and it checks that the URL matches the ID without taking care of percent-encoding). diff -r 722c25818778 -r 69d970f974ff sat/plugins/plugin_comp_ap_gateway/__init__.py --- a/sat/plugins/plugin_comp_ap_gateway/__init__.py Mon Dec 05 11:54:47 2022 +0100 +++ b/sat/plugins/plugin_comp_ap_gateway/__init__.py Fri Feb 10 17:20:02 2023 +0100 @@ -838,7 +838,7 @@ """ return parse.urljoin( self.base_ap_url, - str(Path(type_).joinpath(*(parse.quote_plus(a) for a in args))) + str(Path(type_).joinpath(*(parse.quote_plus(a, safe="@") for a in args))) ) def isLocalURL(self, url: str) -> bool: diff -r 722c25818778 -r 69d970f974ff sat/plugins/plugin_comp_ap_gateway/http_server.py --- a/sat/plugins/plugin_comp_ap_gateway/http_server.py Mon Dec 05 11:54:47 2022 +0100 +++ b/sat/plugins/plugin_comp_ap_gateway/http_server.py Fri Feb 10 17:20:02 2023 +0100 @@ -592,7 +592,9 @@ "https://w3id.org/security/v1" ], - "id": ap_url, + # XXX: Mastodon doesn't like percent-encode arobas, so we have to unescape it + # if it is escaped + "id": ap_url.replace("%40", "@"), "type": "Person", "preferredUsername": preferred_username, "inbox": inbox, @@ -637,7 +639,8 @@ return parse.urljoin( f"https://{self.apg.public_url}", request.path.decode().rstrip("/") - ) + # we unescape "@" for the same reason as in [APActorRequest] + ).replace("%40", "@") def queryData2RSMRequest( self, diff -r 722c25818778 -r 69d970f974ff tests/unit/test_ap-gateway.py --- a/tests/unit/test_ap-gateway.py Mon Dec 05 11:54:47 2022 +0100 +++ b/tests/unit/test_ap-gateway.py Fri Feb 10 17:20:02 2023 +0100 @@ -255,7 +255,7 @@ "id": f"{TEST_BASE_URL}/users/{TEST_USER}/following?page=1", "orderedItems": [ f"{TEST_BASE_URL}/users/ext_user", - f"https://{PUBLIC_URL}/_ap/{TYPE_ACTOR}/local_user%40{PUBLIC_URL}", + f"https://{PUBLIC_URL}/_ap/{TYPE_ACTOR}/local_user@{PUBLIC_URL}", ], "partOf": "{TEST_BASE_URL}/users/{TEST_USER}/following", "totalItems": 2, @@ -273,7 +273,7 @@ "id": f"{TEST_BASE_URL}/users/{TEST_USER}/followers?page=1", "orderedItems": [ f"{TEST_BASE_URL}/users/ext_user", - f"https://{PUBLIC_URL}/_ap/{TYPE_ACTOR}/local_user%40{PUBLIC_URL}", + f"https://{PUBLIC_URL}/_ap/{TYPE_ACTOR}/local_user@{PUBLIC_URL}", ], "partOf": "{TEST_BASE_URL}/users/{TEST_USER}/followers", "totalItems": 2, @@ -643,7 +643,7 @@ """ assert type_ and url is None or url and type_ is None if type_ is not None: - path = f"_ap/{type_}/some_user%40test.example" + path = f"_ap/{type_}/some_user@test.example" else: url_parsed = parse.urlparse(url) path = url_parsed.path.lstrip("/") @@ -685,7 +685,7 @@ **self.ap_request_params(ap_gateway, "outbox") ) assert outbox["@context"] == ["https://www.w3.org/ns/activitystreams"] - assert outbox["id"] == "https://test.example/_ap/outbox/some_user%40test.example" + assert outbox["id"] == "https://test.example/_ap/outbox/some_user@test.example" assert outbox["totalItems"] == len(XMPP_ITEMS) assert outbox["type"] == "OrderedCollection" assert outbox["first"] @@ -697,7 +697,7 @@ assert first_page["@context"] == ["https://www.w3.org/ns/activitystreams"] assert ( first_page["id"] - == "https://test.example/_ap/outbox/some_user%40test.example?page=first" + == "https://test.example/_ap/outbox/some_user@test.example?page=first" ) assert first_page["type"] == "OrderedCollectionPage" assert first_page["partOf"] == outbox["id"] @@ -705,11 +705,11 @@ first_item = first_page["orderedItems"][0] assert first_item["@context"] == ["https://www.w3.org/ns/activitystreams"] assert ( - first_item["id"] == "https://test.example/_ap/item/some_user%40test.example/4" + first_item["id"] == "https://test.example/_ap/item/some_user@test.example/4" ) assert ( first_item["actor"] - == "https://test.example/_ap/actor/some_user%40test.example" + == "https://test.example/_ap/actor/some_user@test.example" ) assert first_item["type"] == "Create" first_item_obj = first_item["object"] @@ -794,7 +794,7 @@ assert following["@context"] == ["https://www.w3.org/ns/activitystreams"] assert ( following["id"] - == "https://test.example/_ap/following/some_user%40test.example" + == "https://test.example/_ap/following/some_user@test.example" ) assert following["totalItems"] == len(subscriptions) assert following["type"] == "OrderedCollection" @@ -832,7 +832,7 @@ assert followers["@context"] == ["https://www.w3.org/ns/activitystreams"] assert ( followers["id"] - == "https://test.example/_ap/followers/some_user%40test.example" + == "https://test.example/_ap/followers/some_user@test.example" ) assert followers["totalItems"] == len(subscribers) assert followers["type"] == "OrderedCollection" @@ -861,14 +861,14 @@ await ap_gateway.onMessage(ap_gateway.client, mess_data) url, actor_id, doc = signAndPost.call_args[0] assert url == "https://example.org/users/test_user/inbox" - assert actor_id == "https://test.example/_ap/actor/some_user%40test.example" + assert actor_id == "https://test.example/_ap/actor/some_user@test.example" obj = doc["object"] assert doc["@context"] == ["https://www.w3.org/ns/activitystreams"] - assert doc["actor"] == "https://test.example/_ap/actor/some_user%40test.example" + assert doc["actor"] == "https://test.example/_ap/actor/some_user@test.example" assert obj["type"] == "Note" assert obj["content"] == "This is a test message." assert obj["attributedTo"] == ( - "https://test.example/_ap/actor/some_user%40test.example" + "https://test.example/_ap/actor/some_user@test.example" ) # we must have a direct message, thus the item must be only addressed to destinee # ("to" attribute of the message), and the "Public" namespace must not be set @@ -1669,11 +1669,11 @@ ap_object = ap_item["object"] actor_id = ( "https://test.example/_ap/actor/" - "___urn.3Axmpp.3Aevents.3A0---some_user%40test.example" + "___urn.3Axmpp.3Aevents.3A0---some_user@test.example" ) event_id = ( "https://test.example/_ap/item/" - "___urn.3Axmpp.3Aevents.3A0---some_user%40test.example/event_123" + "___urn.3Axmpp.3Aevents.3A0---some_user@test.example/event_123" ) assert ap_object["name"] == "test event" assert ap_object["actor"] == actor_id @@ -1700,7 +1700,7 @@ @ed async def test_ap_event_2_xmpp_event(self, ap_gateway): """AP events are converted to XMPP events""" - test_actor = "___urn.3Axmpp.3Aevents.3A0---some_user%40test.example" + test_actor = "___urn.3Axmpp.3Aevents.3A0---some_user@test.example" ap_object = { "actor": f"https://test.example/_ap/actor/{test_actor}", "attachment": [