add common.get_as2()

pull/27/head
Ryan Barrett 2017-10-20 07:00:42 -07:00
rodzic 7ca72a1b36
commit 527ca5585b
2 zmienionych plików z 113 dodań i 0 usunięć

Wyświetl plik

@ -2,10 +2,13 @@
"""Misc common utilities.
"""
from __future__ import unicode_literals
import copy
import json
import logging
import re
import urlparse
from bs4 import BeautifulSoup
from granary import as2
from oauth_dropins.webutil import util
import requests
@ -27,6 +30,17 @@ USERNAME = 'me'
LINK_HEADER_RE = re.compile(r""" *< *([^ >]+) *> *; *rel=['"]([^'"]+)['"] *""")
AS2_PUBLIC_AUDIENCE = 'https://www.w3.org/ns/activitystreams#Public'
# https://www.w3.org/TR/activitypub/#retrieving-objects
CONTENT_TYPE_AS2_LD = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
CONTENT_TYPE_AS2 = 'application/activity+json'
CONTENT_TYPE_AS1 = 'application/stream+json'
CONTENT_TYPE_HTML = 'text/html'
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
SUPPORTED_VERBS = (
'checkin',
'create',
@ -70,6 +84,45 @@ def _requests_fn(fn, url, parse_json=False, log=False, **kwargs):
return resp
def get_as2(url):
"""Tries to fetch the given URL as ActivityStreams 2.
Uses HTTP content negotiation via the Content-Type header. If the url is
HTML and it has a rel-alternate link with an AS2 content type, fetches and
returns that URL.
Args:
url: string
Returns:
requests.Response
Raises:
requests.HTTPError, webob.exc.HTTPException
"""
def _error():
msg = "Couldn't fetch %s as ActivityStreams 2" % url
logging.error(msg)
raise exc.HTTPBadGateway(msg)
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
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()
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
_error()
def error(handler, msg, status=None, exc_info=False):
if not status:
status = 400
@ -78,6 +131,7 @@ def error(handler, msg, status=None, exc_info=False):
def send_webmentions(handler, activity, **response_props):
"""Sends webmentions for an incoming Salmon slap or ActivityPub inbox delivery.
Args:
handler: RequestHandler

Wyświetl plik

@ -0,0 +1,59 @@
# coding=utf-8
"""Unit tests for common.py."""
from __future__ import unicode_literals
import json
import logging
import mock
from oauth_dropins.webutil import util
from oauth_dropins.webutil.testutil import requests_response
import requests
from webob import exc
import common
import testutil
HTML = requests_response('<html></html>', headers={
'Content-Type': common.CONTENT_TYPE_HTML,
})
HTML_WITH_AS2 = requests_response("""\
<html><meta>
<link href='http://as2' rel='alternate' type='application/activity+json'>
</meta></html>
""", headers={
'Content-Type': common.CONTENT_TYPE_HTML,
})
AS2 = requests_response({}, headers={
'Content-Type': common.CONTENT_TYPE_AS2,
})
NOT_ACCEPTABLE = requests_response(status=406)
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, resp)
mock_get.assert_has_calls((
self.req('http://orig', headers=common.CONNEG_HEADERS_AS2_HTML),
))
@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, 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),
))
@mock.patch('requests.get', return_value=HTML)
def test_get_as2_only_html(self, mock_get):
with self.assertRaises(exc.HTTPBadGateway):
resp = common.get_as2('http://orig')
@mock.patch('requests.get', return_value=NOT_ACCEPTABLE)
def test_get_as2_not_acceptable(self, mock_get):
with self.assertRaises(exc.HTTPBadGateway):
resp = common.get_as2('http://orig')