activitypub: follow HTML link rels that point to AS2 objects

...by reusing common.get_as2(). also lots of misc refactoring.
pull/27/head
Ryan Barrett 2017-10-20 07:49:25 -07:00
rodzic e985516dc3
commit c81a6b8ed2
7 zmienionych plików z 150 dodań i 114 usunięć

Wyświetl plik

@ -80,7 +80,7 @@ class InboxHandler(webapp2.RequestHandler):
if activity.get('type') in ('Like', 'Announce'):
actor = activity.get('actor')
if actor:
activity['actor'] = common.get_as2(actor)
activity['actor'] = common.get_as2(actor).json()
# send webmentions to each target
as1 = as2.to_as1(activity)

Wyświetl plik

@ -22,8 +22,6 @@ ACCT_RE = r'(?:acct:)?([^@]+)@' + DOMAIN_RE
HEADERS = {
'User-Agent': 'Bridgy Fed (https://fed.brid.gy/)',
}
ATOM_CONTENT_TYPE = 'application/atom+xml'
MAGIC_ENVELOPE_CONTENT_TYPE = 'application/magic-envelope+xml'
XML_UTF8 = "<?xml version='1.0' encoding='UTF-8'?>\n"
USERNAME = 'me'
# USERNAME_EMOJI = '🌎' # globe
@ -35,11 +33,15 @@ CONTENT_TYPE_AS2_LD = 'application/ld+json; profile="https://www.w3.org/ns/activ
CONTENT_TYPE_AS2 = 'application/activity+json'
CONTENT_TYPE_AS1 = 'application/stream+json'
CONTENT_TYPE_HTML = 'text/html'
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.copy(CONNEG_HEADERS_AS2)
CONNEG_HEADERS_AS2_HTML['Accept'] += ', %s; q=0.7' % CONTENT_TYPE_HTML
CONNEG_HEADERS_AS2_HTML = {
'Accept': CONNEG_HEADERS_AS2['Accept'] + ', %s; q=0.7' % CONTENT_TYPE_HTML,
}
SUPPORTED_VERBS = (
'checkin',
@ -59,19 +61,20 @@ def requests_post(url, **kwargs):
return _requests_fn(util.requests_post, url, **kwargs)
def _requests_fn(fn, url, parse_json=False, log=False, **kwargs):
def _requests_fn(fn, url, parse_json=False, **kwargs):
"""Wraps requests.* and adds raise_for_status() and User-Agent."""
kwargs.setdefault('headers', {}).update(HEADERS)
resp = fn(url, **kwargs)
if log:
logging.info('Got %s\n headers:%s\n%s', resp.status_code, resp.headers,
resp.text)
logging.info('Got %s headers:%s', resp.status_code, resp.headers)
type = resp.headers.get('Content-Type')
if type and type.startswith('text/') and type != 'text/json':
logging.info(resp.text)
if resp.status_code // 100 in (4, 5):
msg = 'Received %s from %s:\n%s' % (resp.status_code, url, resp.text)
logging.error(msg)
raise exc.HTTPBadGateway(msg)
raise exc.HTTPBadGateway('Received %s from %s:\n%s' %
(resp.status_code, url, resp.text))
if parse_json:
try:
@ -95,32 +98,37 @@ def get_as2(url):
url: string
Returns:
dict, AS2 object parsed from JSON
requests.Response
Raises:
requests.HTTPError, webob.exc.HTTPException
If we raise webob HTTPException, it will have an additional response
attribute with the last requests.Response we received.
"""
def _error():
def _error(resp):
msg = "Couldn't fetch %s as ActivityStreams 2" % url
logging.error(msg)
raise exc.HTTPBadGateway(msg)
err = exc.HTTPBadGateway(msg)
err.response = resp
raise err
resp = requests_get(url, headers=CONNEG_HEADERS_AS2_HTML)
if resp.headers.get('Content-Type') in (CONTENT_TYPE_AS2, CONTENT_TYPE_AS2_LD):
return resp.json()
return resp
parsed = BeautifulSoup(resp.content, from_encoding=resp.encoding)
as2 = parsed.find('link', rel=('alternate', 'self'), type=(
CONTENT_TYPE_AS2, CONTENT_TYPE_AS2_LD))
if not (as2 and as2['href']):
_error()
_error(resp)
resp = requests_get(urlparse.urljoin(resp.url, as2['href']),
headers=CONNEG_HEADERS_AS2)
if resp.headers.get('Content-Type') in (CONTENT_TYPE_AS2, CONTENT_TYPE_AS2_LD):
return resp.json()
return resp
_error()
_error(resp)
def error(handler, msg, status=None, exc_info=False):

Wyświetl plik

@ -35,7 +35,7 @@ class CommonTest(testutil.TestCase):
@mock.patch('requests.get', return_value=AS2)
def test_get_as2_direct(self, mock_get):
resp = common.get_as2('http://orig')
self.assertEqual(AS2_OBJ, resp)
self.assertEqual(AS2, resp)
mock_get.assert_has_calls((
self.req('http://orig', headers=common.CONNEG_HEADERS_AS2_HTML),
))
@ -43,7 +43,7 @@ class CommonTest(testutil.TestCase):
@mock.patch('requests.get', side_effect=[HTML_WITH_AS2, AS2])
def test_get_as2_via_html(self, mock_get):
resp = common.get_as2('http://orig')
self.assertEqual(AS2_OBJ, resp)
self.assertEqual(AS2, resp)
mock_get.assert_has_calls((
self.req('http://orig', headers=common.CONNEG_HEADERS_AS2_HTML),
self.req('http://as2', headers=common.CONNEG_HEADERS_AS2),

Wyświetl plik

@ -50,7 +50,7 @@ class SalmonTest(testutil.TestCase):
# webmention post
mock_post.return_value = requests_response()
slap = magicsigs.magic_envelope(atom_slap, common.ATOM_CONTENT_TYPE, self.key)
slap = magicsigs.magic_envelope(atom_slap, common.CONTENT_TYPE_ATOM, self.key)
got = app.get_response('/me@foo.com/salmon', method='POST', body=slap)
self.assertEquals(200, got.status_int)
@ -140,7 +140,7 @@ class SalmonTest(testutil.TestCase):
self.assertEquals(400, got.status_int)
def test_bad_inner_xml(self, mock_urlopen, mock_get, mock_post):
slap = magicsigs.magic_envelope('not xml', common.ATOM_CONTENT_TYPE, self.key)
slap = magicsigs.magic_envelope('not xml', common.CONTENT_TYPE_ATOM, self.key)
got = app.get_response('/foo.com/salmon', method='POST', body=slap)
self.assertEquals(400, got.status_int)
@ -152,6 +152,6 @@ class SalmonTest(testutil.TestCase):
<uri>https://my/rsvp</uri>
<activity:verb>http://activitystrea.ms/schema/1.0/rsvp</activity:verb>
<activity:object>http://orig/event</activity:object>
</entry>""", common.ATOM_CONTENT_TYPE, self.key)
</entry>""", common.CONTENT_TYPE_ATOM, self.key)
got = app.get_response('/foo.com/salmon', method='POST', body=slap)
self.assertEquals(501, got.status_int)

Wyświetl plik

@ -6,7 +6,6 @@ TODO: test error handling
from __future__ import unicode_literals
import copy
import json
import logging
import urllib
import urllib2
@ -22,7 +21,15 @@ from oauth_dropins.webutil.testutil import requests_response
import requests
import activitypub
import common
from common import (
AS2_PUBLIC_AUDIENCE,
CONNEG_HEADERS_AS2,
CONNEG_HEADERS_AS2_HTML,
CONTENT_TYPE_AS2,
CONTENT_TYPE_HTML,
CONTENT_TYPE_MAGIC_ENVELOPE,
HEADERS,
)
from models import MagicKey, Response
import testutil
import webmention
@ -37,14 +44,21 @@ class WebmentionTest(testutil.TestCase):
super(WebmentionTest, self).setUp()
self.key = MagicKey.get_or_create('a')
self.orig = requests_response("""\
self.orig_html_as2 = requests_response("""\
<html>
<meta>
<link href='http://orig/atom' rel='alternate' type='application/atom+xml'>
<link href='http://orig/as2' rel='alternate' type='application/activity+json'>
</meta>
</html>
""", url='http://orig/post', content_type=CONTENT_TYPE_HTML)
self.orig_html_atom = requests_response("""\
<html>
<meta>
<link href='http://orig/atom' rel='alternate' type='application/atom+xml'>
</meta>
</html>
""", url='http://orig/post', content_type='text/html; charset=utf-8')
""", url='http://orig/post', content_type=CONTENT_TYPE_HTML)
self.orig_atom = requests_response("""\
<?xml version="1.0"?>
<entry xmlns="http://www.w3.org/2005/Atom">
@ -53,7 +67,14 @@ class WebmentionTest(testutil.TestCase):
<content type="html">baz baj</content>
</entry>
""")
self.orig_as2 = requests_response({
'@context': ['https://www.w3.org/ns/activitystreams'],
'type': 'Article',
'content': 'Lots of ☕ words...',
'actor': {
'url': 'http://orig/author',
},
}, url='http://orig/as2', content_type=CONTENT_TYPE_AS2)
self.reply_html = """\
<html>
@ -70,7 +91,7 @@ class WebmentionTest(testutil.TestCase):
</html>
"""
self.reply = requests_response(
self.reply_html, content_type='text/html; charset=utf-8')
self.reply_html, content_type=CONTENT_TYPE_HTML)
self.reply_mf2 = mf2py.parse(self.reply_html, url='http://a/reply')
self.reply_obj = microformats2.json_to_object(self.reply_mf2['items'][0])
@ -84,7 +105,7 @@ class WebmentionTest(testutil.TestCase):
</html>
"""
self.repost = requests_response(
self.repost_html, content_type='text/html; charset=utf-8')
self.repost_html, content_type=CONTENT_TYPE_HTML)
self.repost_mf2 = mf2py.parse(self.repost_html, url='http://a/repost')
self.like_html = """\
@ -98,24 +119,16 @@ class WebmentionTest(testutil.TestCase):
</html>
"""
self.like = requests_response(
self.like_html, content_type='text/html; charset=utf-8')
self.like_html, content_type=CONTENT_TYPE_HTML)
self.like_mf2 = mf2py.parse(self.like_html, url='http://a/like')
self.article = requests_response({
'@context': ['https://www.w3.org/ns/activitystreams'],
'type': 'Article',
'content': 'Lots of ☕ words...',
'actor': {
'url': 'http://orig/author',
},
})
self.actor = requests_response({
'objectType' : 'person',
'displayName': 'Mrs. ☕ Foo',
'url': 'https://foo.com/about-me',
'inbox': 'https://foo.com/inbox',
})
self.activitypub_gets = [self.reply, self.article, self.actor]
}, content_type=CONTENT_TYPE_AS2)
self.activitypub_gets = [self.reply, self.orig_as2, self.actor]
self.as2_create = {
'@context': 'https://www.w3.org/ns/activitystreams',
@ -129,7 +142,7 @@ class WebmentionTest(testutil.TestCase):
'content': ' <a class="u-in-reply-to" href="http://orig/post">foo ☕ bar</a> <a href="https://fed.brid.gy/"></a> ',
'inReplyTo': 'http://orig/post',
'cc': [
common.AS2_PUBLIC_AUDIENCE,
AS2_PUBLIC_AUDIENCE,
'http://orig/post',
],
'attributedTo': [{
@ -146,7 +159,7 @@ class WebmentionTest(testutil.TestCase):
def verify_salmon(self, mock_post):
args, kwargs = mock_post.call_args
self.assertEqual(('http://orig/salmon',), args)
self.assertEqual(common.MAGIC_ENVELOPE_CONTENT_TYPE,
self.assertEqual(CONTENT_TYPE_MAGIC_ENVELOPE,
kwargs['headers']['Content-Type'])
env = utils.parse_magic_envelope(kwargs['data'])
@ -167,23 +180,22 @@ class WebmentionTest(testutil.TestCase):
self.assertEquals(200, got.status_int)
mock_get.assert_has_calls((
call('http://a/reply', headers=common.HEADERS, timeout=util.HTTP_TIMEOUT),
call('http://orig/post', headers=activitypub.CONNEG_HEADER,
timeout=util.HTTP_TIMEOUT),
call('http://orig/author', headers=activitypub.CONNEG_HEADER,
timeout=util.HTTP_TIMEOUT),))
self.req('http://a/reply'),
self.req('http://orig/post', headers=CONNEG_HEADERS_AS2_HTML),
self.req('http://orig/author', headers=CONNEG_HEADERS_AS2_HTML),
))
args, kwargs = mock_post.call_args
self.assertEqual(('https://foo.com/inbox',), args)
self.assertEqual(self.as2_create, kwargs['json'])
headers = kwargs['headers']
self.assertEqual(activitypub.CONTENT_TYPE_AS, headers['Content-Type'])
self.assertEqual(CONTENT_TYPE_AS2, headers['Content-Type'])
rsa_key = kwargs['auth'].header_signer._rsa._key
self.assertEqual(self.key.private_pem(), rsa_key.exportKey())
resp = Response.get_by_id('http://a/reply http://orig/post')
resp = Response.get_by_id('http://a/reply http://orig/as2')
self.assertEqual('out', resp.direction)
self.assertEqual('activitypub', resp.protocol)
self.assertEqual('complete', resp.status)
@ -196,7 +208,7 @@ class WebmentionTest(testutil.TestCase):
# self.assertEqual(['abc xyz'], resp.responses)
def test_activitypub_update_reply(self, mock_get, mock_post):
Response(id='http://a/reply http://orig/post', status='complete').put()
Response(id='http://a/reply http://orig/as2', status='complete').put()
mock_get.side_effect = self.activitypub_gets
mock_post.return_value = requests_response('abc xyz')
@ -213,7 +225,7 @@ class WebmentionTest(testutil.TestCase):
self.assertEqual(self.as2_update, kwargs['json'])
def test_activitypub_create_repost(self, mock_get, mock_post):
mock_get.side_effect = [self.repost, self.article, self.actor]
mock_get.side_effect = [self.repost, self.orig_as2, self.actor]
mock_post.return_value = requests_response('abc xyz')
got = app.get_response(
@ -224,11 +236,10 @@ class WebmentionTest(testutil.TestCase):
self.assertEquals(200, got.status_int)
mock_get.assert_has_calls((
call('http://a/repost', headers=common.HEADERS, timeout=util.HTTP_TIMEOUT),
call('http://orig/post', headers=activitypub.CONNEG_HEADER,
timeout=util.HTTP_TIMEOUT),
call('http://orig/author', headers=activitypub.CONNEG_HEADER,
timeout=util.HTTP_TIMEOUT),))
self.req('http://a/repost'),
self.req('http://orig/post', headers=CONNEG_HEADERS_AS2_HTML),
self.req('http://orig/author', headers=CONNEG_HEADERS_AS2_HTML),
))
args, kwargs = mock_post.call_args
self.assertEqual(('https://foo.com/inbox',), args)
@ -238,7 +249,7 @@ class WebmentionTest(testutil.TestCase):
'url': 'http://a/repost',
'name': 'reposted!',
'object': 'http://orig/post',
'cc': [common.AS2_PUBLIC_AUDIENCE],
'cc': [AS2_PUBLIC_AUDIENCE],
'actor': {
'type': 'Person',
'url': 'http://orig',
@ -247,19 +258,42 @@ class WebmentionTest(testutil.TestCase):
}, kwargs['json'])
headers = kwargs['headers']
self.assertEqual(activitypub.CONTENT_TYPE_AS, headers['Content-Type'])
self.assertEqual(CONTENT_TYPE_AS2, headers['Content-Type'])
rsa_key = kwargs['auth'].header_signer._rsa._key
self.assertEqual(self.key.private_pem(), rsa_key.exportKey())
resp = Response.get_by_id('http://a/repost http://orig/post')
resp = Response.get_by_id('http://a/repost http://orig/as2')
self.assertEqual('out', resp.direction)
self.assertEqual('activitypub', resp.protocol)
self.assertEqual('complete', resp.status)
self.assertEqual(self.repost_mf2, json.loads(resp.source_mf2))
def test_activitypub_link_rel_alternate_as2(self, mock_get, mock_post):
mock_get.side_effect = [self.reply, self.orig_html_as2, self.orig_as2,
self.actor]
mock_post.return_value = requests_response('abc xyz')
got = app.get_response(
'/webmention', method='POST', body=urllib.urlencode({
'source': 'http://a/reply',
'target': 'https://fed.brid.gy/',
}))
self.assertEquals(200, got.status_int)
mock_get.assert_has_calls((
self.req('http://a/reply'),
self.req('http://orig/post', headers=CONNEG_HEADERS_AS2_HTML),
self.req('http://orig/as2', headers=CONNEG_HEADERS_AS2),
self.req('http://orig/author', headers=CONNEG_HEADERS_AS2_HTML),
))
args, kwargs = mock_post.call_args
self.assertEqual(('https://foo.com/inbox',), args)
self.assertEqual(self.as2_create, kwargs['json'])
def test_salmon_reply(self, mock_get, mock_post):
mock_get.side_effect = [self.reply, self.orig, self.orig_atom]
mock_get.side_effect = [self.reply, self.orig_html_atom, self.orig_atom]
got = app.get_response(
'/webmention', method='POST', body=urllib.urlencode({
@ -269,10 +303,9 @@ class WebmentionTest(testutil.TestCase):
self.assertEquals(200, got.status_int)
mock_get.assert_has_calls((
call('http://a/reply', headers=common.HEADERS, timeout=util.HTTP_TIMEOUT),
call('http://orig/post', headers=activitypub.CONNEG_HEADER,
timeout=util.HTTP_TIMEOUT),
call('http://orig/atom', headers=common.HEADERS, timeout=util.HTTP_TIMEOUT),
self.req('http://a/reply'),
self.req('http://orig/post', headers=CONNEG_HEADERS_AS2_HTML),
self.req('http://orig/atom'),
))
data = self.verify_salmon(mock_post)
@ -301,7 +334,7 @@ class WebmentionTest(testutil.TestCase):
self.assertEqual(self.reply_mf2, json.loads(resp.source_mf2))
def test_salmon_like(self, mock_get, mock_post):
mock_get.side_effect = [self.like, self.orig, self.orig_atom]
mock_get.side_effect = [self.like, self.orig_html_atom, self.orig_atom]
got = app.get_response(
'/webmention', method='POST', body=urllib.urlencode({
@ -311,10 +344,9 @@ class WebmentionTest(testutil.TestCase):
self.assertEquals(200, got.status_int)
mock_get.assert_has_calls((
call('http://a/like', headers=common.HEADERS, timeout=util.HTTP_TIMEOUT),
call('http://orig/post', headers=activitypub.CONNEG_HEADER,
timeout=util.HTTP_TIMEOUT),
call('http://orig/atom', headers=common.HEADERS, timeout=util.HTTP_TIMEOUT),
self.req('http://a/like'),
self.req('http://orig/post', headers=CONNEG_HEADERS_AS2_HTML),
self.req('http://orig/atom'),
))
data = self.verify_salmon(mock_post)
@ -353,7 +385,7 @@ class WebmentionTest(testutil.TestCase):
'href': 'http://orig/@ryan/salmon',
}],
})
mock_get.side_effect = [self.reply, self.orig, orig_atom, webfinger]
mock_get.side_effect = [self.reply, self.orig_html_atom, orig_atom, webfinger]
got = app.get_response('/webmention', method='POST', body=urllib.urlencode({
'source': 'http://a/reply',
@ -363,7 +395,7 @@ class WebmentionTest(testutil.TestCase):
mock_get.assert_any_call(
'http://orig/.well-known/webfinger?resource=acct:ryan@orig',
headers=common.HEADERS, timeout=util.HTTP_TIMEOUT, verify=False)
headers=HEADERS, timeout=util.HTTP_TIMEOUT, verify=False)
self.assertEqual(('http://orig/@ryan/salmon',), mock_post.call_args[0])
def test_salmon_no_target_atom(self, mock_get, mock_post):

Wyświetl plik

@ -65,7 +65,7 @@ representative h-card</a> on %s""" % resp.url)
canonical_url = urls[0]
# discover atom feed, if any
atom = parsed.find('link', rel='alternate', type=common.ATOM_CONTENT_TYPE)
atom = parsed.find('link', rel='alternate', type=common.CONTENT_TYPE_ATOM)
if atom and atom['href']:
atom = urlparse.urljoin(resp.url, atom['href'])
else:
@ -120,7 +120,7 @@ representative h-card</a> on %s""" % resp.url)
# OStatus
{
'rel': 'http://schemas.google.com/g/2010#updates-from',
'type': common.ATOM_CONTENT_TYPE,
'type': common.CONTENT_TYPE_ATOM,
'href': atom,
}, {
'rel': 'hub',

Wyświetl plik

@ -23,6 +23,7 @@ import mf2util
from oauth_dropins.webutil import util
import requests
import webapp2
from webob import exc
import activitypub
import common
@ -51,12 +52,12 @@ class WebmentionHandler(webapp2.RequestHandler):
logging.warning('Error sending email', exc_info=True)
# fetch source page, convert to ActivityStreams
resp = common.requests_get(source)
mf2 = mf2py.parse(resp.text, url=resp.url)
# logging.debug('Parsed mf2 for %s: %s', resp.url, json.dumps(mf2, indent=2))
source_url = resp.url or source
source_resp = common.requests_get(source)
source_url = source_resp.url or source
source_mf2 = mf2py.parse(source_resp.text, url=source_url)
# logging.debug('Parsed mf2 for %s: %s', source_resp.url, json.dumps(mf2, indent=2))
entry = mf2util.find_first_entry(mf2, ['h-entry'])
entry = mf2util.find_first_entry(source_mf2, ['h-entry'])
logging.info('First entry: %s', json.dumps(entry, indent=2))
source_obj = microformats2.json_to_object(entry)
logging.info('Converted to AS: %s', json.dumps(source_obj, indent=2))
@ -70,22 +71,20 @@ class WebmentionHandler(webapp2.RequestHandler):
'found in %s' % source_url)
try:
resp = common.requests_get(target, headers=activitypub.CONNEG_HEADER,
log=True)
target_url = resp.url or target
except requests.HTTPError as e:
if e.response.status_code // 100 == 4:
return self.send_salmon(source_obj, target_url=target_url)
target_resp = common.get_as2(target)
except (requests.HTTPError, exc.HTTPBadGateway) as e:
if (e.response.status_code // 100 == 2 and
e.response.headers.get('Content-Type').startswith('text/html')):
return self.send_salmon(source_obj, source_mf2, target_resp=e.response)
raise
self.response = Response.get_or_create(
target_url = target_resp.url or target
stored_response = Response.get_or_create(
source=source_url, target=target_url, direction='out',
source_mf2=json.dumps(mf2))
if resp.headers.get('Content-Type').startswith('text/html'):
return self.send_salmon(source_obj, target_resp=resp)
source_mf2=json.dumps(source_mf2))
# find actor's inbox
target_obj = resp.json()
target_obj = target_resp.json()
inbox_url = target_obj.get('inbox')
if not inbox_url:
@ -99,22 +98,21 @@ class WebmentionHandler(webapp2.RequestHandler):
if not inbox_url:
# fetch actor as AS object
actor = common.requests_get(actor, parse_json=True,
headers=activitypub.CONNEG_HEADER)
actor = common.get_as2(actor).json()
inbox_url = actor.get('inbox')
if not inbox_url:
# TODO: probably need a way to save errors like this so that we can
# return them if ostatus fails too.
# common.error(self, 'Target actor has no inbox')
return self.send_salmon(source_obj, target_url=target_url)
return self.send_salmon(source_obj, source_mf2, target_resp=target_resp)
# convert to AS2
source_domain = urlparse.urlparse(source_url).netloc
key = MagicKey.get_or_create(source_domain)
source_activity = common.postprocess_as2(as2.from_as1(source_obj), key=key)
if self.response.status == 'complete':
if stored_response.status == 'complete':
source_activity['type'] = 'Update'
# prepare HTTP Signature (required by Mastodon)
@ -127,33 +125,32 @@ class WebmentionHandler(webapp2.RequestHandler):
# deliver source object to target actor's inbox.
headers = {
'Content-Type': activitypub.CONTENT_TYPE_AS,
'Content-Type': common.CONTENT_TYPE_AS2,
# required for HTTP Signature
# https://tools.ietf.org/html/draft-cavage-http-signatures-07#section-2.1.3
'Date': datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT'),
}
common.requests_post(
urlparse.urljoin(target_url, inbox_url), json=source_activity, auth=auth,
headers=headers, log=True)
headers=headers)
self.response.status = 'complete'
self.response.protocol = 'activitypub'
self.response.put()
stored_response.status = 'complete'
stored_response.protocol = 'activitypub'
stored_response.put()
def send_salmon(self, source_obj, target_url=None, target_resp=None):
def send_salmon(self, source_obj, source_mf2, target_url=None, target_resp=None):
# fetch target HTML page, extract Atom rel-alternate link
if target_url:
assert not target_resp
target_resp = common.requests_get(target_url)
else:
assert target_resp
# TODO: this could be different due to redirects
target_url = target_resp.url
parsed = BeautifulSoup(target_resp.content, from_encoding=target_resp.encoding)
atom_url = parsed.find('link', rel='alternate', type=common.ATOM_CONTENT_TYPE)
if not atom_url or not atom_url['href']:
common.error(self, 'Target post %s has no Atom link' % target_resp.url,
atom_url = parsed.find('link', rel='alternate', type=common.CONTENT_TYPE_ATOM)
if not atom_url or not atom_url.get('href'):
common.error(self, 'Target post %s has no Atom link' % target_url,
status=400)
# fetch Atom target post, extract and inject id into source object
@ -194,8 +191,7 @@ class WebmentionHandler(webapp2.RequestHandler):
# TODO: always https?
resp = common.requests_get(
'%s://%s/.well-known/webfinger?resource=acct:%s' %
(parsed.scheme, parsed.netloc, email),
log=True, verify=False)
(parsed.scheme, parsed.netloc, email), verify=False)
endpoint = django_salmon.get_salmon_replies_link(resp.json())
except requests.HTTPError as e:
pass
@ -216,16 +212,16 @@ class WebmentionHandler(webapp2.RequestHandler):
key = MagicKey.get_or_create(domain)
logging.info('Using key for %s: %s', domain, key)
magic_envelope = magicsigs.magic_envelope(
entry, common.ATOM_CONTENT_TYPE, key)
entry, common.CONTENT_TYPE_ATOM, key)
logging.info('Sending Salmon slap to %s', endpoint)
common.requests_post(
endpoint, data=common.XML_UTF8 + magic_envelope, log=True,
headers={'Content-Type': common.MAGIC_ENVELOPE_CONTENT_TYPE})
endpoint, data=common.XML_UTF8 + magic_envelope,
headers={'Content-Type': common.CONTENT_TYPE_MAGIC_ENVELOPE})
self.response.status = 'complete'
self.response.protocol = 'ostatus'
self.response.put()
Response(source=source_url, target=target_url, direction='out',
protocol = 'ostatus', status = 'complete',
source_mf2=json.dumps(source_mf2)).put()
app = webapp2.WSGIApplication([