handle full conneg Accept header parsing in /r/ handler

for #352
redirect-conneg
Ryan Barrett 2022-12-25 21:09:34 -08:00
rodzic 647ae66763
commit 2150693893
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
3 zmienionych plików z 49 dodań i 29 usunięć

Wyświetl plik

@ -2,6 +2,7 @@
"""Misc common utilities.
"""
from base64 import b64encode
import copy
import datetime
from hashlib import sha256
import itertools
@ -36,7 +37,8 @@ LINK_HEADER_RE = re.compile(r""" *< *([^ >]+) *> *; *rel=['"]([^'"]+)['"] *""")
#
# ActivityPub Content-Type details:
# https://www.w3.org/TR/activitypub/#retrieving-objects
CONTENT_TYPE_AS2_LD = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
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_HTML = 'text/html; charset=utf-8'
@ -46,9 +48,8 @@ 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 = {
'Accept': CONNEG_HEADERS_AS2['Accept'] + ', %s; q=0.7' % CONTENT_TYPE_HTML,
}
CONNEG_HEADERS_AS2_HTML = copy.deepcopy(CONNEG_HEADERS_AS2)
CONNEG_HEADERS_AS2_HTML['Accept'] += ', {CONTENT_TYPE_HTML}; q=0.7'
SUPPORTED_VERBS = (
'checkin',
@ -189,7 +190,7 @@ 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_AS2_LD):
if content_type(resp) in (CONTENT_TYPE_AS2, CONTENT_TYPE_LD):
return resp
parsed = util.parse_html(resp)
@ -200,7 +201,7 @@ def get_as2(url, user=None):
resp = signed_get(urllib.parse.urljoin(resp.url, as2['href']),
headers=CONNEG_HEADERS_AS2)
if content_type(resp) in (CONTENT_TYPE_AS2, CONTENT_TYPE_AS2_LD):
if content_type(resp) in (CONTENT_TYPE_AS2, CONTENT_TYPE_LD):
return resp
_error(resp)
@ -310,7 +311,7 @@ def send_webmentions(activity_wrapped, proxy=None, **activity_props):
return True
def postprocess_as2(activity, user=None, target=None):
def postprocess_as2(activity, user=None, target=None, create=True):
"""Prepare an AS2 object to be served or sent via ActivityPub.
Args:
@ -319,6 +320,8 @@ def postprocess_as2(activity, user=None, target=None):
publicKey fields if needed.
target: dict, AS2 object, optional. The target of activity's inReplyTo or
Like/Announce/etc object, if any.
create: boolean, whether to wrap `Note` and `Article` objects in a
`Create` activity
"""
assert user
type = activity.get('type')
@ -420,7 +423,7 @@ def postprocess_as2(activity, user=None, target=None):
to.append(as2.PUBLIC_AUDIENCE)
# wrap articles and notes in a Create activity
if type in ('Article', 'Note'):
if create and type in ('Article', 'Note'):
activity = {
'@context': as2.CONTEXT,
'type': 'Create',

Wyświetl plik

@ -1,12 +1,14 @@
"""Simple endpoint that redirects to the embedded fully qualified URL.
"""Simple conneg endpoint that serves AS2 or redirects to to the original post.
May also instead fetch and convert to AS2, depending on conneg.
Used to wrap ActivityPub ids with the fed.brid.gy domain so that Mastodon
accepts them. Background:
Serves /r/https://foo.com/bar URL paths, where https://foo.com/bar is an
original post. Needed for Mastodon interop, they require that AS2 object ids and
urls are on the same domain that serves them. Background:
https://github.com/snarfed/bridgy-fed/issues/16#issuecomment-424799599
https://github.com/tootsuite/mastodon/pull/6219#issuecomment-429142747
The conneg makes these /r/ URLs searchable in Mastodon:
https://github.com/snarfed/bridgy-fed/issues/352
"""
import datetime
import logging
@ -16,19 +18,31 @@ import urllib.parse
from flask import redirect, request
from granary import as2, microformats2
import mf2util
from negotiator import ContentNegotiator, AcceptParameters, ContentType, Language
from oauth_dropins.webutil import flask_util, util
from oauth_dropins.webutil.flask_util import error
from oauth_dropins.webutil.util import json_dumps
from werkzeug.exceptions import abort
from app import app, cache
import common
from common import (
CONTENT_TYPE_AS2,
CONTENT_TYPE_AS2_LD,
CONTENT_TYPE_HTML,
postprocess_as2,
)
from models import User
logger = logging.getLogger(__name__)
CACHE_TIME = datetime.timedelta(seconds=15)
_negotiator = ContentNegotiator(acceptable=[
AcceptParameters(ContentType(CONTENT_TYPE_HTML)),
AcceptParameters(ContentType(CONTENT_TYPE_AS2)),
AcceptParameters(ContentType(CONTENT_TYPE_AS2_LD)),
])
@app.get(r'/r/<path:to>')
@flask_util.cached(cache, CACHE_TIME)
@ -60,11 +74,17 @@ def redir(to):
logger.info(f'No user found for any of {domains}; returning 404')
abort(404)
# poor man's conneg, only handle single Accept values, not multiple with
# priorities.
if request.headers.get('Accept') in (common.CONTENT_TYPE_AS2,
common.CONTENT_TYPE_AS2_LD):
return convert_to_as2(to, user)
# check conneg, serve AS2 if requested
accept = request.headers.get('Accept')
if accept:
negotiated = _negotiator.negotiate(accept)
if negotiated:
type = str(negotiated.content_type)
if type in (CONTENT_TYPE_AS2, CONTENT_TYPE_AS2_LD):
return convert_to_as2(to, user), {
'Content-Type': type,
'Access-Control-Allow-Origin': '*',
}
# redirect
logger.info(f'redirecting to {to}')
@ -74,22 +94,18 @@ def redir(to):
def convert_to_as2(url, domain):
"""Fetch a URL as HTML, convert it to AS2, and return it.
Currently mainly for Pixelfed.
https://github.com/snarfed/bridgy-fed/issues/39
Args:
url: str
domain: :class:`User`
Returns:
dict: AS2 object
"""
mf2 = util.fetch_mf2(url)
entry = mf2util.find_first_entry(mf2, ['h-entry'])
logger.info(f"Parsed mf2 for {mf2['url']}: {json_dumps(entry, indent=2)}")
obj = common.postprocess_as2(as2.from_as1(microformats2.json_to_object(entry)),
domain)
obj = postprocess_as2(as2.from_as1(microformats2.json_to_object(entry)),
domain, create=False)
logger.info(f'Returning: {json_dumps(obj, indent=2)}')
return obj, {
'Content-Type': common.CONTENT_TYPE_AS2,
'Access-Control-Allow-Origin': '*',
}
return obj

Wyświetl plik

@ -2,6 +2,7 @@ git+https://github.com/snarfed/django-salmon.git#egg=django_salmon
git+https://github.com/snarfed/httpsig.git@HTTPSignatureAuth-sign-header#egg=httpsig
git+https://github.com/snarfed/oauth-dropins.git#egg=oauth_dropins
git+https://github.com/snarfed/granary.git#egg=granary
git+https://github.com/snarfed/negotiator.git@py3#egg=negotiator
git+https://github.com/dvska/gdata-python3.git#egg=gdata
beautifulsoup4==4.11.1