kopia lustrzana https://github.com/snarfed/bridgy-fed
noop: drop content type constants in common, use granary's instead
rodzic
7ac1006021
commit
aa0dcbb222
|
@ -41,7 +41,7 @@ def actor(domain):
|
|||
"""Fetches a domain's h-card and converts to AS2 actor."""
|
||||
actor = common.actor(domain)
|
||||
return (actor, {
|
||||
'Content-Type': common.CONTENT_TYPE_AS2,
|
||||
'Content-Type': as2.CONTENT_TYPE,
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
})
|
||||
|
||||
|
|
33
common.py
33
common.py
|
@ -32,23 +32,10 @@ TLD_BLOCKLIST = ('7z', 'asp', 'aspx', 'gif', 'html', 'ico', 'jpg', 'jpeg', 'js',
|
|||
XML_UTF8 = "<?xml version='1.0' encoding='UTF-8'?>\n"
|
||||
LINK_HEADER_RE = re.compile(r""" *< *([^ >]+) *> *; *rel=['"]([^'"]+)['"] *""")
|
||||
|
||||
# Content-Type values. All non-unicode strings because App Engine's wsgi.py
|
||||
# requires header values to be str, not unicode.
|
||||
#
|
||||
# ActivityPub Content-Type details:
|
||||
# https://www.w3.org/TR/activitypub/#retrieving-objects
|
||||
CONTENT_TYPE_LD = 'application/ld+json'
|
||||
CONTENT_TYPE_AS2_LD = f'{CONTENT_TYPE_LD};profile="https://www.w3.org/ns/activitystreams"'
|
||||
CONTENT_TYPE_AS2 = 'application/activity+json'
|
||||
CONTENT_TYPE_AS1 = 'application/stream+json'
|
||||
CONTENT_TYPE_LD_PLAIN = 'application/ld+json'
|
||||
CONTENT_TYPE_HTML = 'text/html; charset=utf-8'
|
||||
CONTENT_TYPE_ATOM = 'application/atom+xml'
|
||||
CONTENT_TYPE_MAGIC_ENVELOPE = 'application/magic-envelope+xml'
|
||||
|
||||
CONNEG_HEADERS_AS2 = {
|
||||
'Accept': '%s; q=0.9, %s; q=0.8' % (CONTENT_TYPE_AS2, CONTENT_TYPE_AS2_LD),
|
||||
}
|
||||
CONNEG_HEADERS_AS2_HTML = copy.deepcopy(CONNEG_HEADERS_AS2)
|
||||
CONNEG_HEADERS_AS2_HTML = copy.deepcopy(as2.CONNEG_HEADERS)
|
||||
CONNEG_HEADERS_AS2_HTML['Accept'] += f', {CONTENT_TYPE_HTML}; q=0.7'
|
||||
|
||||
SUPPORTED_VERBS = (
|
||||
|
@ -140,7 +127,7 @@ def signed_request(fn, url, data=None, user=None, headers=None, **kwargs):
|
|||
# required by Mastodon
|
||||
# https://github.com/tootsuite/mastodon/pull/14556#issuecomment-674077648
|
||||
'Host': util.domain_from_link(url, minimize=False),
|
||||
'Content-Type': CONTENT_TYPE_AS2,
|
||||
'Content-Type': as2.CONTENT_TYPE,
|
||||
# required for HTTP Signature and Mastodon
|
||||
'Digest': f'SHA-256={b64encode(sha256(data or b"").digest()).decode()}',
|
||||
})
|
||||
|
@ -203,18 +190,18 @@ def get_as2(url, user=None):
|
|||
raise err
|
||||
|
||||
resp = signed_get(url, user=user, headers=CONNEG_HEADERS_AS2_HTML)
|
||||
if content_type(resp) in (CONTENT_TYPE_AS2, CONTENT_TYPE_LD):
|
||||
if content_type(resp) in (as2.CONTENT_TYPE, CONTENT_TYPE_LD_PLAIN):
|
||||
return resp
|
||||
|
||||
parsed = util.parse_html(resp)
|
||||
as2 = parsed.find('link', rel=('alternate', 'self'), type=(
|
||||
CONTENT_TYPE_AS2, CONTENT_TYPE_AS2_LD))
|
||||
if not (as2 and as2['href']):
|
||||
obj = parsed.find('link', rel=('alternate', 'self'), type=(
|
||||
as2.CONTENT_TYPE, as2.CONTENT_TYPE_LD))
|
||||
if not (obj and obj['href']):
|
||||
_error(resp)
|
||||
|
||||
resp = signed_get(urllib.parse.urljoin(resp.url, as2['href']),
|
||||
headers=CONNEG_HEADERS_AS2)
|
||||
if content_type(resp) in (CONTENT_TYPE_AS2, CONTENT_TYPE_LD):
|
||||
resp = signed_get(urllib.parse.urljoin(resp.url, obj['href']),
|
||||
headers=as2.CONNEG_HEADERS)
|
||||
if content_type(resp) in (as2.CONTENT_TYPE, CONTENT_TYPE_LD_PLAIN):
|
||||
return resp
|
||||
|
||||
_error(resp)
|
||||
|
|
|
@ -27,8 +27,6 @@ from werkzeug.exceptions import abort
|
|||
from app import app, cache
|
||||
from common import (
|
||||
CACHE_TIME,
|
||||
CONTENT_TYPE_AS2,
|
||||
CONTENT_TYPE_AS2_LD,
|
||||
CONTENT_TYPE_HTML,
|
||||
postprocess_as2,
|
||||
)
|
||||
|
@ -38,8 +36,8 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
_negotiator = ContentNegotiator(acceptable=[
|
||||
AcceptParameters(ContentType(CONTENT_TYPE_HTML)),
|
||||
AcceptParameters(ContentType(CONTENT_TYPE_AS2)),
|
||||
AcceptParameters(ContentType(CONTENT_TYPE_AS2_LD)),
|
||||
AcceptParameters(ContentType(as2.CONTENT_TYPE)),
|
||||
AcceptParameters(ContentType(as2.CONTENT_TYPE_LD)),
|
||||
])
|
||||
|
||||
|
||||
|
@ -83,7 +81,7 @@ def redir(to):
|
|||
negotiated = None
|
||||
if negotiated:
|
||||
type = str(negotiated.content_type)
|
||||
if type in (CONTENT_TYPE_AS2, CONTENT_TYPE_AS2_LD):
|
||||
if type in (as2.CONTENT_TYPE, as2.CONTENT_TYPE_LD):
|
||||
return convert_to_as2(to, user), {
|
||||
'Content-Type': type,
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
|
|
|
@ -162,7 +162,7 @@ class ActivityPubTest(testutil.TestCase):
|
|||
self.assert_req(mock_get, 'https://foo.com/')
|
||||
self.assertEqual(200, got.status_code)
|
||||
type = got.headers['Content-Type']
|
||||
self.assertTrue(type.startswith(common.CONTENT_TYPE_AS2), type)
|
||||
self.assertTrue(type.startswith(as2.CONTENT_TYPE), type)
|
||||
self.assertEqual({
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
|
@ -296,7 +296,7 @@ class ActivityPubTest(testutil.TestCase):
|
|||
|
||||
mock_head.return_value = requests_response(url='http://target')
|
||||
mock_get.return_value = requests_response( # source actor
|
||||
ACTOR, headers={'Content-Type': common.CONTENT_TYPE_AS2})
|
||||
ACTOR, headers={'Content-Type': as2.CONTENT_TYPE})
|
||||
mock_post.return_value = requests_response()
|
||||
|
||||
with self.client:
|
||||
|
@ -318,7 +318,7 @@ class ActivityPubTest(testutil.TestCase):
|
|||
|
||||
mock_head.return_value = requests_response(url='http://target')
|
||||
mock_get.return_value = requests_response( # source actor
|
||||
ACTOR, headers={'Content-Type': common.CONTENT_TYPE_AS2})
|
||||
ACTOR, headers={'Content-Type': as2.CONTENT_TYPE})
|
||||
|
||||
not_public = copy.deepcopy(NOTE)
|
||||
del not_public['object']['to']
|
||||
|
@ -366,7 +366,7 @@ class ActivityPubTest(testutil.TestCase):
|
|||
mock_head.return_value = requests_response(url='http://or.ig/post')
|
||||
mock_get.side_effect = [
|
||||
# source actor
|
||||
requests_response(LIKE_WITH_ACTOR['actor'], headers={'Content-Type': common.CONTENT_TYPE_AS2}),
|
||||
requests_response(LIKE_WITH_ACTOR['actor'], headers={'Content-Type': as2.CONTENT_TYPE}),
|
||||
# target post webmention discovery
|
||||
requests_response(
|
||||
'<html><head><link rel="webmention" href="/webmention"></html>'),
|
||||
|
@ -401,7 +401,7 @@ class ActivityPubTest(testutil.TestCase):
|
|||
mock_get.side_effect = [
|
||||
# source actor
|
||||
requests_response(FOLLOW_WITH_ACTOR['actor'],
|
||||
content_type=common.CONTENT_TYPE_AS2),
|
||||
content_type=as2.CONTENT_TYPE),
|
||||
# target post webmention discovery
|
||||
requests_response(
|
||||
'<html><head><link rel="webmention" href="/webmention"></html>'),
|
||||
|
@ -448,7 +448,7 @@ class ActivityPubTest(testutil.TestCase):
|
|||
mock_head.return_value = requests_response(url='https://www.realize.be/')
|
||||
mock_get.side_effect = [
|
||||
# source actor
|
||||
requests_response(ACTOR, content_type=common.CONTENT_TYPE_AS2),
|
||||
requests_response(ACTOR, content_type=as2.CONTENT_TYPE),
|
||||
# target post webmention discovery
|
||||
requests_response('<html></html>'),
|
||||
]
|
||||
|
@ -484,7 +484,7 @@ class ActivityPubTest(testutil.TestCase):
|
|||
mock_get.side_effect = [
|
||||
# source actor
|
||||
requests_response(FOLLOW_WITH_ACTOR['actor'],
|
||||
content_type=common.CONTENT_TYPE_AS2),
|
||||
content_type=as2.CONTENT_TYPE),
|
||||
# target post webmention discovery
|
||||
requests_response(
|
||||
'<html><head><link rel="webmention" href="/webmention"></html>'),
|
||||
|
@ -525,7 +525,7 @@ class ActivityPubTest(testutil.TestCase):
|
|||
# https://console.cloud.google.com/errors/detail/CMKn7tqbq-GIRA;time=P30D?project=bridgy-federated
|
||||
mock_get.side_effect = [
|
||||
# source actor
|
||||
requests_response(ACTOR, content_type=common.CONTENT_TYPE_AS2),
|
||||
requests_response(ACTOR, content_type=as2.CONTENT_TYPE),
|
||||
]
|
||||
got = self.client.post('/foo.com/inbox', json={
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
|
@ -573,7 +573,7 @@ class ActivityPubTest(testutil.TestCase):
|
|||
mock_get.side_effect = [
|
||||
# source actor
|
||||
requests_response(LIKE_WITH_ACTOR['actor'],
|
||||
headers={'Content-Type': common.CONTENT_TYPE_AS2}),
|
||||
headers={'Content-Type': as2.CONTENT_TYPE}),
|
||||
# target post webmention discovery
|
||||
ReadTimeoutError(None, None, None),
|
||||
]
|
||||
|
@ -585,7 +585,7 @@ class ActivityPubTest(testutil.TestCase):
|
|||
mock_get.side_effect = [
|
||||
# source actor
|
||||
requests_response(LIKE_WITH_ACTOR['actor'],
|
||||
headers={'Content-Type': common.CONTENT_TYPE_AS2}),
|
||||
headers={'Content-Type': as2.CONTENT_TYPE}),
|
||||
# target post webmention discovery
|
||||
requests_response('<html><body>foo</body></html>'),
|
||||
]
|
||||
|
|
|
@ -25,7 +25,7 @@ HTML_WITH_AS2 = requests_response("""\
|
|||
})
|
||||
AS2_OBJ = {'foo': ['bar']}
|
||||
AS2 = requests_response(AS2_OBJ, headers={
|
||||
'Content-Type': common.CONTENT_TYPE_AS2,
|
||||
'Content-Type': as2.CONTENT_TYPE,
|
||||
})
|
||||
NOT_ACCEPTABLE = requests_response(status=406)
|
||||
|
||||
|
@ -54,7 +54,7 @@ class CommonTest(testutil.TestCase):
|
|||
self.assertEqual(AS2, resp)
|
||||
mock_get.assert_has_calls((
|
||||
self.as2_req('http://orig'),
|
||||
self.as2_req('http://as2', headers=common.CONNEG_HEADERS_AS2),
|
||||
self.as2_req('http://as2', headers=common.as2.CONNEG_HEADERS),
|
||||
))
|
||||
|
||||
@mock.patch('requests.get', return_value=HTML)
|
||||
|
|
|
@ -46,10 +46,10 @@ class RedirectTest(testutil.TestCase):
|
|||
self.assertEqual(404, got.status_code)
|
||||
|
||||
def test_as2(self):
|
||||
self._test_as2(common.CONTENT_TYPE_AS2)
|
||||
self._test_as2(as2.CONTENT_TYPE)
|
||||
|
||||
def test_as2_ld(self):
|
||||
self._test_as2(common.CONTENT_TYPE_AS2_LD)
|
||||
self._test_as2(as2.CONTENT_TYPE_LD)
|
||||
|
||||
def test_accept_header_cache_key(self):
|
||||
app.config['CACHE_TYPE'] = 'SimpleCache'
|
||||
|
@ -60,7 +60,7 @@ class RedirectTest(testutil.TestCase):
|
|||
self.assertEqual(301, got.status_code)
|
||||
self.assertEqual('https://foo.com/bar', got.headers['Location'])
|
||||
|
||||
self._test_as2(common.CONTENT_TYPE_AS2)
|
||||
self._test_as2(as2.CONTENT_TYPE)
|
||||
|
||||
got = self.client.get('/r/https://foo.com/bar',
|
||||
headers={'Accept': 'text/html'})
|
||||
|
|
|
@ -19,12 +19,8 @@ import requests
|
|||
|
||||
import activitypub
|
||||
from common import (
|
||||
CONNEG_HEADERS_AS2,
|
||||
CONNEG_HEADERS_AS2_HTML,
|
||||
CONTENT_TYPE_AS2,
|
||||
CONTENT_TYPE_ATOM,
|
||||
CONTENT_TYPE_HTML,
|
||||
CONTENT_TYPE_MAGIC_ENVELOPE,
|
||||
default_signature_user,
|
||||
)
|
||||
from models import Follower, User, Activity
|
||||
|
@ -95,7 +91,7 @@ class WebmentionTest(testutil.TestCase):
|
|||
<link rel="salmon" href="http://orig/salmon"/>
|
||||
<content type="html">baz ☕ baj</content>
|
||||
</entry>
|
||||
""", content_type=CONTENT_TYPE_ATOM)
|
||||
""", content_type=atom.CONTENT_TYPE)
|
||||
self.orig_as2_data = {
|
||||
'@context': ['https://www.w3.org/ns/activitystreams'],
|
||||
'type': 'Article',
|
||||
|
@ -107,7 +103,7 @@ class WebmentionTest(testutil.TestCase):
|
|||
}
|
||||
self.orig_as2 = requests_response(
|
||||
self.orig_as2_data, url='http://orig/as2',
|
||||
content_type=CONTENT_TYPE_AS2 + '; charset=utf-8')
|
||||
content_type=as2.CONTENT_TYPE + '; charset=utf-8')
|
||||
|
||||
self.reply_html = """\
|
||||
<html>
|
||||
|
@ -154,7 +150,7 @@ class WebmentionTest(testutil.TestCase):
|
|||
'displayName': 'Mrs. ☕ Foo',
|
||||
'url': 'https://foo.com/about-me',
|
||||
'inbox': 'https://foo.com/inbox',
|
||||
}, content_type=CONTENT_TYPE_AS2)
|
||||
}, content_type=as2.CONTENT_TYPE)
|
||||
|
||||
self.as2_create = {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
|
@ -437,7 +433,7 @@ class WebmentionTest(testutil.TestCase):
|
|||
self.assertEqual(self.as2_create, json_loads(kwargs['data']))
|
||||
|
||||
headers = kwargs['headers']
|
||||
self.assertEqual(CONTENT_TYPE_AS2, headers['Content-Type'])
|
||||
self.assertEqual(as2.CONTENT_TYPE, headers['Content-Type'])
|
||||
|
||||
rsa_key = kwargs['auth'].header_signer._rsa._key
|
||||
self.assertEqual(self.user.private_pem(), rsa_key.exportKey())
|
||||
|
@ -513,7 +509,7 @@ class WebmentionTest(testutil.TestCase):
|
|||
'id': 'http://orig/author',
|
||||
}]
|
||||
orig_as2_resp = requests_response(
|
||||
self.orig_as2_data, content_type=CONTENT_TYPE_AS2 + '; charset=utf-8')
|
||||
self.orig_as2_data, content_type=as2.CONTENT_TYPE + '; charset=utf-8')
|
||||
|
||||
mock_get.side_effect = [self.reply, self.not_fediverse, orig_as2_resp,
|
||||
self.actor]
|
||||
|
@ -557,7 +553,7 @@ class WebmentionTest(testutil.TestCase):
|
|||
self.assertEqual(self.repost_as2, json_loads(kwargs['data']))
|
||||
|
||||
headers = kwargs['headers']
|
||||
self.assertEqual(CONTENT_TYPE_AS2, headers['Content-Type'])
|
||||
self.assertEqual(as2.CONTENT_TYPE, headers['Content-Type'])
|
||||
|
||||
rsa_key = kwargs['auth'].header_signer._rsa._key
|
||||
self.assertEqual(self.user.private_pem(), rsa_key.exportKey())
|
||||
|
@ -590,7 +586,7 @@ class WebmentionTest(testutil.TestCase):
|
|||
self.req('http://a/reply'),
|
||||
self.as2_req('http://not/fediverse'),
|
||||
self.as2_req('http://orig/post'),
|
||||
self.req('http://orig/as2', auth=mock.ANY, headers=CONNEG_HEADERS_AS2),
|
||||
self.req('http://orig/as2', auth=mock.ANY, headers=as2.CONNEG_HEADERS),
|
||||
self.as2_req('http://orig/author'),
|
||||
))
|
||||
|
||||
|
@ -809,7 +805,7 @@ class WebmentionTest(testutil.TestCase):
|
|||
self.assertEqual(self.follow_as2, json_loads(kwargs['data']))
|
||||
|
||||
headers = kwargs['headers']
|
||||
self.assertEqual(CONTENT_TYPE_AS2, headers['Content-Type'])
|
||||
self.assertEqual(as2.CONTENT_TYPE, headers['Content-Type'])
|
||||
|
||||
rsa_key = kwargs['auth'].header_signer._rsa._key
|
||||
self.assertEqual(self.user.private_pem(), rsa_key.exportKey())
|
||||
|
@ -870,7 +866,7 @@ class WebmentionTest(testutil.TestCase):
|
|||
self.assert_equals(self.follow_fragment_as2, json_loads(kwargs['data']))
|
||||
|
||||
headers = kwargs['headers']
|
||||
self.assert_equals(CONTENT_TYPE_AS2, headers['Content-Type'])
|
||||
self.assert_equals(as2.CONTENT_TYPE, headers['Content-Type'])
|
||||
|
||||
rsa_key = kwargs['auth'].header_signer._rsa._key
|
||||
self.assert_equals(self.user.private_pem(), rsa_key.exportKey())
|
||||
|
@ -922,7 +918,7 @@ class WebmentionTest(testutil.TestCase):
|
|||
self.assertEqual(self.follow_as2, json_loads(kwargs['data']))
|
||||
|
||||
headers = kwargs['headers']
|
||||
self.assertEqual(CONTENT_TYPE_AS2, headers['Content-Type'])
|
||||
self.assertEqual(as2.CONTENT_TYPE, headers['Content-Type'])
|
||||
|
||||
rsa_key = kwargs['auth'].header_signer._rsa._key
|
||||
self.assertEqual(self.user.private_pem(), rsa_key.exportKey())
|
||||
|
|
20
webfinger.py
20
webfinger.py
|
@ -9,7 +9,7 @@ import re
|
|||
import urllib.parse
|
||||
|
||||
from flask import render_template, request
|
||||
from granary import as2, microformats2
|
||||
from granary import as2, atom, microformats2
|
||||
import mf2util
|
||||
from oauth_dropins.webutil import flask_util, util
|
||||
from oauth_dropins.webutil.flask_util import error
|
||||
|
@ -74,11 +74,11 @@ class Actor(flask_util.XrdOrJrd):
|
|||
user.put()
|
||||
|
||||
# discover atom feed, if any
|
||||
atom = parsed.find('link', rel='alternate', type=common.CONTENT_TYPE_ATOM)
|
||||
if atom and atom['href']:
|
||||
atom = urllib.parse.urljoin(resp.url, atom['href'])
|
||||
feed = parsed.find('link', rel='alternate', type=atom.CONTENT_TYPE)
|
||||
if feed and feed['href']:
|
||||
feed = urllib.parse.urljoin(resp.url, feed['href'])
|
||||
else:
|
||||
atom = 'https://granary.io/url?' + urllib.parse.urlencode({
|
||||
feed = 'https://granary.io/url?' + urllib.parse.urlencode({
|
||||
'input': 'html',
|
||||
'output': 'atom',
|
||||
'url': resp.url,
|
||||
|
@ -114,29 +114,29 @@ class Actor(flask_util.XrdOrJrd):
|
|||
# ActivityPub
|
||||
{
|
||||
'rel': 'self',
|
||||
'type': common.CONTENT_TYPE_AS2,
|
||||
'type': as2.CONTENT_TYPE,
|
||||
# WARNING: in python 2 sometimes request.host_url lost port,
|
||||
# http://localhost:8080 would become just http://localhost. no
|
||||
# clue how or why. pay attention here if that happens again.
|
||||
'href': common.host_url(domain),
|
||||
}, {
|
||||
'rel': 'inbox',
|
||||
'type': common.CONTENT_TYPE_AS2,
|
||||
'type': as2.CONTENT_TYPE,
|
||||
'href': common.host_url(f'{domain}/inbox'),
|
||||
}, {
|
||||
# AP reads this from the AS2 actor, not webfinger, so strictly
|
||||
# speaking, it's probably not needed here.
|
||||
# https://www.w3.org/TR/activitypub/#sharedInbox
|
||||
'rel': 'sharedInbox',
|
||||
'type': common.CONTENT_TYPE_AS2,
|
||||
'type': as2.CONTENT_TYPE,
|
||||
'href': common.host_url('inbox'),
|
||||
},
|
||||
|
||||
# OStatus
|
||||
{
|
||||
'rel': 'http://schemas.google.com/g/2010#updates-from',
|
||||
'type': common.CONTENT_TYPE_ATOM,
|
||||
'href': atom,
|
||||
'type': atom.CONTENT_TYPE,
|
||||
'href': feed,
|
||||
}, {
|
||||
'rel': 'hub',
|
||||
'href': hub,
|
||||
|
|
Ładowanie…
Reference in New Issue