bridgy-fed/convert.py

123 wiersze
4.3 KiB
Python
Czysty Zwykły widok Historia

"""Serves ``/convert/...`` URLs to convert data from one protocol to another.
URL pattern is ``/convert/SOURCE/DEST``, where ``SOURCE`` and ``DEST`` are the
``LABEL`` constants from the :class:`protocol.Protocol` subclasses.
"""
import logging
import re
from urllib.parse import quote, unquote
2024-05-08 00:01:01 +00:00
from flask import redirect, request
2023-05-24 23:00:41 +00:00
from granary import as1
from oauth_dropins.webutil import flask_util, util
from oauth_dropins.webutil.flask_util import error
from activitypub import ActivityPub
from common import CACHE_CONTROL, LOCAL_DOMAINS, subdomain_wrap, SUPERDOMAIN
from flask_app import app
from models import Object, PROTOCOLS
from protocol import Protocol
from web import Web
logger = logging.getLogger(__name__)
@app.get(f'/convert/<dest>/<path:_>')
@flask_util.headers(CACHE_CONTROL)
def convert(dest, _, src=None):
"""Converts data from one protocol to another and serves it.
Fetches the source data if it's not already stored.
Args:
dest (str): protocol
src (str): protocol, only used when called by
:func:`convert_source_path_redirect`
"""
if src:
src_cls = PROTOCOLS.get(src)
if not src_cls:
error(f'No protocol found for {src}', status=404)
logger.info(f'Overriding any domain protocol with {src}')
else:
src_cls = Protocol.for_request(fed=Protocol)
if not src_cls:
error(f'Unknown protocol {request.host.removesuffix(SUPERDOMAIN)}', status=404)
dest_cls = PROTOCOLS.get(dest)
if not dest_cls:
error('Unknown protocol {dest}', status=404)
2023-05-24 23:00:41 +00:00
# don't use urllib.parse.urlencode(request.args) because that doesn't
# guarantee us the same query param string as in the original URL, and we
# want exactly the same thing since we're looking up the URL's Object by id
path_prefix = f'convert/{dest}/'
id = unquote(request.url.removeprefix(request.root_url).removeprefix(path_prefix))
2023-05-24 23:00:41 +00:00
# our redirects evidently collapse :// down to :/ , maybe to prevent URL
# parsing bugs? if that happened to this URL, expand it back to ://
id = re.sub(r'^(https?:/)([^/])', r'\1/\2', id)
logger.info(f'Converting from {src_cls.LABEL} to {dest}: {id}')
2023-05-24 23:00:41 +00:00
# load, and maybe fetch. if it's a post/update, redirect to inner object.
obj = src_cls.load(id)
if not obj:
error(f"Couldn't load {id}", status=404)
elif not obj.as1:
2023-05-24 23:00:41 +00:00
error(f'Stored object for {id} has no data', status=404)
type = as1.object_type(obj.as1)
if type in ('post', 'update', 'delete'):
obj_id = as1.get_object(obj.as1).get('id')
if obj_id:
obj_obj = src_cls.load(obj_id, remote=False)
noop, lint fixes from flake8 remaining: $ flake8 --extend-ignore=E501 *.py tests/*.py "pyflakes" failed during execution due to "'FlakesChecker' object has no attribute 'NAMEDEXPR'" Run flake8 with greater verbosity to see more details activitypub.py:15:1: F401 'oauth_dropins.webutil.util.json_loads' imported but unused activitypub.py:36:1: F401 'web' imported but unused activitypub.py:48:1: E302 expected 2 blank lines, found 1 activitypub.py:51:9: F811 redefinition of unused 'web' from line 36 app.py:6:1: F401 'flask_app.app' imported but unused app.py:9:1: F401 'activitypub' imported but unused app.py:9:1: F401 'convert' imported but unused app.py:9:1: F401 'follow' imported but unused app.py:9:1: F401 'pages' imported but unused app.py:9:1: F401 'redirect' imported but unused app.py:9:1: F401 'superfeedr' imported but unused app.py:9:1: F401 'ui' imported but unused app.py:9:1: F401 'webfinger' imported but unused app.py:9:1: F401 'web' imported but unused app.py:9:1: F401 'xrpc_actor' imported but unused app.py:9:1: F401 'xrpc_feed' imported but unused app.py:9:1: F401 'xrpc_graph' imported but unused app.py:9:19: E401 multiple imports on one line models.py:19:1: F401 'oauth_dropins.webutil.util.json_loads' imported but unused models.py:364:31: E114 indentation is not a multiple of four (comment) models.py:364:31: E116 unexpected indentation (comment) protocol.py:17:1: F401 'oauth_dropins.webutil.util.json_loads' imported but unused redirect.py:26:1: F401 'oauth_dropins.webutil.util.json_loads' imported but unused web.py:18:1: F401 'oauth_dropins.webutil.util.json_loads' imported but unused webfinger.py:13:1: F401 'oauth_dropins.webutil.util.json_loads' imported but unused webfinger.py:110:13: E122 continuation line missing indentation or outdented webfinger.py:111:13: E122 continuation line missing indentation or outdented webfinger.py:131:13: E122 continuation line missing indentation or outdented webfinger.py:132:13: E122 continuation line missing indentation or outdented webfinger.py:133:13: E122 continuation line missing indentation or outdented webfinger.py:134:13: E122 continuation line missing indentation or outdented tests/__init__.py:2:1: F401 'oauth_dropins.webutil.tests' imported but unused tests/test_follow.py:11:1: F401 'oauth_dropins.webutil.util.json_dumps' imported but unused tests/test_follow.py:14:1: F401 '.testutil.Fake' imported but unused tests/test_models.py:156:15: E122 continuation line missing indentation or outdented tests/test_models.py:157:15: E122 continuation line missing indentation or outdented tests/test_models.py:158:11: E122 continuation line missing indentation or outdented tests/test_web.py:12:1: F401 'oauth_dropins.webutil.util.json_dumps' imported but unused tests/test_web.py:17:1: F401 '.testutil' imported but unused tests/test_web.py:1513:13: E128 continuation line under-indented for visual indent tests/test_web.py:1514:9: E124 closing bracket does not match visual indentation tests/testutil.py:106:1: E402 module level import not at top of file tests/testutil.py:107:1: E402 module level import not at top of file tests/testutil.py:108:1: E402 module level import not at top of file tests/testutil.py:109:1: E402 module level import not at top of file tests/testutil.py:110:1: E402 module level import not at top of file tests/testutil.py:301:24: E203 whitespace before ':' tests/testutil.py:301:25: E701 multiple statements on one line (colon) tests/testutil.py:301:25: E231 missing whitespace after ':'
2023-06-20 18:22:54 +00:00
if (obj_obj and obj_obj.as1
and not obj_obj.as1.keys() <= set(['id', 'url', 'objectType'])):
2023-05-24 23:00:41 +00:00
logger.info(f'{type} activity, redirecting to Object {obj_id}')
return redirect(f'/{path_prefix}{obj_id}', code=301)
headers = {
'Content-Type': dest_cls.CONTENT_TYPE,
'Vary': 'Accept',
}
2023-05-24 23:00:41 +00:00
# don't serve deletes or deleted objects
if obj.deleted or type == 'delete':
return '', 410, headers
2023-05-24 23:00:41 +00:00
# convert and serve
return dest_cls.convert(obj), headers
@app.get('/render')
def render_redirect():
"""Redirect from old /render?id=... endpoint to /convert/..."""
id = flask_util.get_required_param('id')
return redirect(subdomain_wrap(ActivityPub, f'/convert/web/{id}'), code=301)
@app.get(f'/convert/<src>/<dest>/<path:_>')
def convert_source_path_redirect(src, dest, _):
2023-09-26 20:32:27 +00:00
"""Old route that included source protocol in path instead of subdomain.
DEPRECATED! Only kept to support old webmention source URLs.
"""
if Protocol.for_request() not in (None, 'web'): # no per-protocol subdomains
error(f'Try again on fed.brid.gy', status=404)
# in prod, eg gunicorn, the path somehow gets URL-decoded before we see
# it, so we need to re-encode.
new_path = quote(request.full_path.rstrip('?').replace(f'/{src}/', '/'),
safe=':/%')
if request.host in LOCAL_DOMAINS:
request.url = request.url.replace(f'/{src}/', '/')
return convert(dest, None, src)
proto = PROTOCOLS.get(src)
if not proto:
error(f'No protocol found for {src}', status=404)
return redirect(subdomain_wrap(proto, new_path), code=301)