Skip to content

Commit f9cb192

Browse files
committed
bluesky.to_as1: escape HTML characters in app.bsky.actor.profile description
for snarfed/bridgy-fed#1144 also includes a `@skip`ed test for doing the same thing in `app.bsky.feed.post` `text`, which is more difficult to implement because we'll have to shuffle facet/tag indices over to match every time we escape an HTML char.
1 parent 9434ce0 commit f9cb192

3 files changed

Lines changed: 27 additions & 2 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ _Non-breaking changes:_
315315
* Add hashtag facet support.
316316
* Convert blobs in embeds to `getBlob` image URLs.
317317
* `app.bsky.actor.profile`: add HTML links for URLs in `summary` ([snarfed/bridgy-fed#1065](https://github.com/snarfed/bridgy-fed/issues/1065)).
318+
* Escape HTML characters (`<`, `>`, `&`) in `app.bsky.actor.profile` `description` field.
318319
* Bug fix for `create`/`update` activities with bare string `object`.
319320
* `from_as1`:
320321
* Add hashtag, mention, block, and flag support. Interpret `tags` with missing `objectType` as hashtags.

‎granary/bluesky.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"""
77
import copy
88
from datetime import datetime, timezone
9+
import html
910
import json
1011
import logging
1112
from pathlib import Path
@@ -1053,11 +1054,12 @@ def to_as1(obj, type=None, uri=None, repo_did=None, repo_handle=None,
10531054
'username': obj.get('handle') or repo_handle,
10541055
}
10551056

1057+
desc = html.escape(obj.get('description') or '')
10561058
ret.update({
10571059
'url': util.dedupe_urls(urls),
10581060
'displayName': obj.get('displayName'),
10591061
# TODO: for app.bsky.feed.generator, use descriptionFacets
1060-
'summary': util.linkify(obj.get('description') or '', pretty=True),
1062+
'summary': util.linkify(desc, pretty=True),
10611063
'image': images,
10621064
'published': obj.get('createdAt'),
10631065
})
@@ -1085,7 +1087,10 @@ def to_as1(obj, type=None, uri=None, repo_did=None, repo_handle=None,
10851087
}]
10861088

10871089
elif type == 'app.bsky.feed.post':
1090+
# TODO: escape HTML chars. difficult because we have to shuffle over facet
1091+
# indices to match.
10881092
text = obj.get('text', '')
1093+
text_encoded = text.encode()
10891094

10901095
# convert facets to tags
10911096
tags = []
@@ -1117,7 +1122,6 @@ def to_as1(obj, type=None, uri=None, repo_did=None, repo_handle=None,
11171122

11181123
try:
11191124
if byte_start is not None:
1120-
text_encoded = text.encode()
11211125
tag['startIndex'] = len(text_encoded[:byte_start].decode())
11221126
if byte_end is not None:
11231127
name = text_encoded[byte_start:byte_end].decode()

‎granary/tests/test_bluesky.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""
55
import copy
66
from io import BytesIO
7+
from unittest import skip
78
from unittest.mock import ANY, patch
89

910
from oauth_dropins.webutil import testutil, util
@@ -1743,6 +1744,15 @@ def test_to_as1_profile_links_in_bio(self):
17431744
'description': 'one http://li.nk/foo two li.nk three https://www.li.nk/',
17441745
}))
17451746

1747+
def test_to_as1_profile_escape_html_chars(self):
1748+
self.assert_equals({
1749+
'objectType': 'person',
1750+
'summary': 'one &lt;two&gt; &lt;thr&amp;ee&gt;',
1751+
}, to_as1({
1752+
'$type': 'app.bsky.actor.profile',
1753+
'description': 'one <two> <thr&ee>',
1754+
}))
1755+
17461756
def test_to_as1_profile_bsky_social_handle_is_not_url(self):
17471757
self.assert_equals({
17481758
'objectType': 'person',
@@ -1821,6 +1831,16 @@ def test_to_as1_post_uri(self):
18211831
'published': '2007-07-07T03:04:05.000Z',
18221832
}, to_as1(POST_BSKY, uri='at://alice.com/app.bsky.feed.post/123'))
18231833

1834+
@skip
1835+
def test_to_as1_post_escape_html_chars(self):
1836+
self.assert_equals({
1837+
'objectType': 'note',
1838+
'summary': 'one &lt;two&gt; &lt;thr&amp;ee&gt;',
1839+
}, to_as1({
1840+
'$type': 'app.bsky.feed.post',
1841+
'text': 'one <two> <thr&ee>',
1842+
}))
1843+
18241844
def test_to_as1_post_view(self):
18251845
self.assert_equals(POST_AS['object'], to_as1(POST_VIEW_BSKY))
18261846

0 commit comments

Comments
 (0)