Example #1
0
 def test_object_to_json_multiple_object_urls(self):
     self.assert_equals(
         {
             'type': ['h-entry'],
             'properties': {
                 'content': [{
                     'html': '✁',
                     'value': '✁'
                 }],
                 'in-reply-to':
                 ['https://orig/post/1', 'https://orig/post/2'],
             },
         },
         microformats2.object_to_json({
             'objectType':
             'activity',
             'verb':
             'react',
             'content':
             '✁',
             'object': [
                 {
                     'url': 'https://orig/post/1'
                 },
                 {
                     'url': 'https://orig/post/2'
                 },
             ],
         }))
Example #2
0
    def test_tag_multiple_urls(self):
        expected_urls = ['http://1', 'https://2']
        expected_html = """
<a class="tag" href="http://1"></a>
<a class="tag" href="https://2"></a>
"""
        for tag in ({
                'url': 'http://1',
                'urls': [{
                    'value': 'http://1'
                }, {
                    'value': 'https://2'
                }]
        }, {
                'url': 'http://1',
                'urls': [{
                    'value': 'https://2'
                }]
        }, {
                'urls': [{
                    'value': 'http://1'
                }, {
                    'value': 'https://2'
                }]
        }):
            self.assert_equals(
                expected_urls,
                microformats2.object_to_json(tag)['properties']['url'], tag)
            self.assert_equals(expected_html,
                               microformats2.render_content({'tags': [tag]}),
                               tag)
Example #3
0
 def test_dont_stop_at_unknown_tag_type(self):
     obj = {
         'tags': [{
             'objectType': 'x',
             'url': 'http://x'
         }, {
             'objectType': 'person',
             'url': 'http://p',
             'displayName': 'p'
         }],
     }
     self.assert_equals(
         {
             'type': ['h-entry'],
             'properties': {
                 'category': [{
                     'type': ['h-card'],
                     'properties': {
                         'name': ['p'],
                         'url': ['http://p'],
                     },
                 }],
                 'content': [{
                     'html': '\n<a class="tag" href="http://x"></a>'
                 }],
             },
         }, microformats2.object_to_json(obj))
Example #4
0
  def write_response(self, response, actor=None, url=None, title=None):
    """Converts ActivityStreams activities and writes them out.

    Args:
      response: response dict with values based on OpenSocial ActivityStreams
        REST API, as returned by Source.get_activities_response()
      actor: optional ActivityStreams actor dict for current user. Only used
        for Atom output.
      url: the input URL
      title: string, Used in Atom output
    """
    expected_formats = ('activitystreams', 'json', 'atom', 'xml', 'html', 'json-mf2')
    format = self.request.get('format') or self.request.get('output') or 'json'
    if format not in expected_formats:
      raise exc.HTTPBadRequest('Invalid format: %s, expected one of %r' %
                               (format, expected_formats))

    activities = response['items']

    self.response.headers.update({
      'Access-Control-Allow-Origin': '*',
      'Strict-Transport-Security':
          'max-age=16070400; includeSubDomains; preload',  # 6 months
    })

    if format in ('json', 'activitystreams'):
      # list of official MIME types:
      # https://www.iana.org/assignments/media-types/media-types.xhtml
      self.response.headers['Content-Type'] = 'application/json'
      self.response.out.write(json.dumps(response, indent=2))
    elif format == 'atom':
      self.response.headers['Content-Type'] = 'application/atom+xml'
      hub = self.request.get('hub')
      self.response.out.write(atom.activities_to_atom(
        activities, actor,
        host_url=url or self.request.host_url + '/',
        request_url=self.request.url,
        xml_base=util.base_url(url),
        title=title,
        rels={'hub': hub} if hub else None))
      self.response.headers.add('Link', str('<%s>; rel="self"' % self.request.url))
      if hub:
        self.response.headers.add('Link', str('<%s>; rel="hub"' % hub))
    elif format == 'xml':
      self.response.headers['Content-Type'] = 'application/xml'
      self.response.out.write(XML_TEMPLATE % util.to_xml(response))
    elif format == 'html':
      self.response.headers['Content-Type'] = 'text/html'
      self.response.out.write(microformats2.activities_to_html(activities))
    elif format == 'json-mf2':
      self.response.headers['Content-Type'] = 'application/json'
      items = [microformats2.object_to_json(a) for a in activities]
      self.response.out.write(json.dumps({'items': items}, indent=2))

    if 'plaintext' in self.request.params:
      # override response content type
      self.response.headers['Content-Type'] = 'text/plain'
Example #5
0
    def test_attachments_to_children(self):
        obj = {
            'attachments': [
                {
                    'objectType': 'note',
                    'url': 'http://p',
                    'displayName': 'p'
                },
                {
                    'objectType': 'x',
                    'url': 'http://x'
                },
                {
                    'objectType': 'article',
                    'url': 'http://a'
                },
            ]
        }

        self.assert_equals([{
            'type': ['u-quotation-of', 'h-cite'],
            'properties': {
                'url': ['http://p'],
                'name': ['p']
            },
        }, {
            'type': ['u-quotation-of', 'h-cite'],
            'properties': {
                'url': ['http://a']
            },
        }],
                           microformats2.object_to_json(obj)['children'])

        html = microformats2.object_to_html(obj)
        self.assert_multiline_in(
            """\
<article class="u-quotation-of h-cite">
<span class="p-uid"></span>

<a class="p-name u-url" href="http://p">p</a>
<div class="">

</div>

</article>

<article class="u-quotation-of h-cite">
<span class="p-uid"></span>

<a class="p-name u-url" href="http://a"></a>
<div class="">

</div>

</article>
""", html)
Example #6
0
  def write_response(self, response, actor=None, url=None, title=None):
    """Converts ActivityStreams activities and writes them out.

    Args:
      response: response dict with values based on OpenSocial ActivityStreams
        REST API, as returned by Source.get_activities_response()
      actor: optional ActivityStreams actor dict for current user. Only used
        for Atom output.
      url: the input URL
      title: string, Used in Atom output
    """
    expected_formats = ('activitystreams', 'json', 'atom', 'xml', 'html', 'json-mf2')
    format = self.request.get('format') or self.request.get('output') or 'json'
    if format not in expected_formats:
      raise exc.HTTPBadRequest('Invalid format: %s, expected one of %r' %
                               (format, expected_formats))

    activities = response['items']

    self.response.headers.update({
      'Access-Control-Allow-Origin': '*',
      'Strict-Transport-Security':
          'max-age=16070400; includeSubDomains; preload',  # 6 months
    })

    if format in ('json', 'activitystreams'):
      self.response.headers['Content-Type'] = 'application/json'
      self.response.out.write(json.dumps(response, indent=2))
    elif format == 'atom':
      self.response.headers['Content-Type'] = 'text/xml'
      hub = self.request.get('hub')
      self.response.out.write(atom.activities_to_atom(
        activities, actor,
        host_url=url or self.request.host_url + '/',
        request_url=self.request.url,
        xml_base=util.base_url(url),
        title=title,
        rels={'hub': hub} if hub else None))
      self.response.headers.add('Link', str('<%s>; rel="self"' % self.request.url))
      if hub:
        self.response.headers.add('Link', str('<%s>; rel="hub"' % hub))
    elif format == 'xml':
      self.response.headers['Content-Type'] = 'text/xml'
      self.response.out.write(XML_TEMPLATE % util.to_xml(response))
    elif format == 'html':
      self.response.headers['Content-Type'] = 'text/html'
      self.response.out.write(microformats2.activities_to_html(activities))
    elif format == 'json-mf2':
      self.response.headers['Content-Type'] = 'application/json'
      items = [microformats2.object_to_json(a) for a in activities]
      self.response.out.write(json.dumps({'items': items}, indent=2))

    if 'plaintext' in self.request.params:
      # override response content type
      self.response.headers['Content-Type'] = 'text/plain'
Example #7
0
 def test_object_to_json_unescapes_html_entities(self):
   self.assertEquals({
     'type': ['h-entry'],
     'properties': {'content': [{
       'html': 'Entity &lt; <a href="http://my/link">link too</a>',
       'value': 'Entity < link too',
     }]},
    }, microformats2.object_to_json({
       'content': 'Entity &lt; link too',
       'tags': [{'url': 'http://my/link', 'startIndex': 12, 'length': 8}]
     }))
Example #8
0
 def test_object_to_json_preserves_url_order(self):
   self.assertEquals({
     'type': ['h-card'],
     'properties': {
       'url': ['http://2', 'http://4', 'http://6'],
     },
   }, microformats2.object_to_json({
     'objectType': 'person',
     'url': 'http://2',
     'urls': [{'value': 'http://4'},
              {'value': 'http://6'}],
   }))
Example #9
0
 def test_object_to_json_reaction(self):
   self.assert_equals({
     'type': ['h-entry'],
     'properties': {
       'content': [{'html': u'✁', 'value': u'✁'}],
       'in-reply-to': ['https://orig/post'],
     },
   }, microformats2.object_to_json({
     'objectType': 'activity',
     'verb': 'react',
     'content': u'✁',
     'object': {'url': 'https://orig/post'},
   }))
Example #10
0
 def test_object_to_json_unescapes_html_entities(self):
   self.assertEquals({
     'type': ['h-entry'],
     'properties': {'content': [{
       'html': 'Entity &lt; <a href="http://my/link">link too</a>',
       'value': 'Entity < link too',
     }]},
    }, microformats2.object_to_json({
      'verb': 'post',
      'object': {
       'content': 'Entity &lt; link too',
       'tags': [{'url': 'http://my/link', 'startIndex': 12, 'length': 8}]
      }
    }))
Example #11
0
 def test_object_to_json_note_with_in_reply_to(self):
   self.assertEquals({
     'type': ['h-entry'],
     'properties': {
       'content': [{
         'html': '@hey great post',
         'value': '@hey great post',
       }],
       'in-reply-to': ['http://reply/target'],
     },
   }, microformats2.object_to_json({
       'content': '@hey great post',
     }, ctx={
       'inReplyTo': [{
         'url': 'http://reply/target',
       }]
     }))
Example #12
0
 def test_object_to_json_reaction(self):
     self.assert_equals(
         {
             'type': ['h-entry'],
             'properties': {
                 'content': ['✁'],
                 'in-reply-to': ['https://orig/post'],
             },
         },
         microformats2.object_to_json({
             'objectType': 'activity',
             'verb': 'react',
             'content': '✁',
             'object': {
                 'url': 'https://orig/post'
             },
         }))
Example #13
0
  def test_tag_multiple_urls(self):
    expected_urls = ['http://1', 'https://2']
    expected_html = """
<a class="tag" href="http://1"></a>
<a class="tag" href="https://2"></a>
"""
    for tag in ({'url': 'http://1',
                  'urls': [{'value': 'http://1'}, {'value': 'https://2'}]},
                {'url': 'http://1',
                 'urls': [{'value': 'https://2'}]},
                {'urls': [{'value': 'http://1'}, {'value': 'https://2'}]}):
      self.assert_equals(expected_urls,
                         microformats2.object_to_json(tag)['properties']['url'],
                         tag)
      self.assert_equals(expected_html,
                         microformats2.render_content({'tags': [tag]}),
                         tag)
Example #14
0
 def test_dont_stop_at_unknown_tag_type(self):
   obj = {'tags': [
     {'objectType': 'x', 'url': 'http://x'},
     {'objectType': 'person', 'url': 'http://p', 'displayName': 'p'}],
   }
   self.assert_equals({
     'type': ['h-entry'],
     'properties': {
       'category': [{
         'type': ['h-card'],
         'properties': {
           'name': ['p'],
           'url': ['http://p'],
         },
       }],
       'content': [{'html': '\n<a class="tag" href="http://x"></a>'}],
     },
   }, microformats2.object_to_json(obj))
Example #15
0
 def test_object_to_json_note_with_in_reply_to(self):
     self.assertEquals(
         {
             'type': ['h-entry'],
             'properties': {
                 'content': [{
                     'html': '@hey great post',
                     'value': '@hey great post',
                 }],
                 'in-reply-to': ['http://reply/target'],
             },
         },
         microformats2.object_to_json(
             {
                 'content': '@hey great post',
             },
             ctx={'inReplyTo': [{
                 'url': 'http://reply/target',
             }]}))
Example #16
0
  def test_object_to_json_not_dict(self):
    """This can happen if we get a dict instead of a list, e.g. with AS 2.0.

    Found via AS2 on http://evanminto.com/indieweb/activity-stream.php, e.g.:

    {
      "@context": "https://www.w3.org/ns/activitystreams",
      "0": {
        "name": "Evan reposted another post.",
        "type": "Announce",
        "actor": {
          "type": "Person",
          "name": "Evan Minto",
          "url": "http://evanminto.com"
        },
        "object": "http://www.harkavagrant.com/index.php?id=402"
      },
    ...
    """
    self.assert_equals({}, microformats2.object_to_json('foo bar'))
Example #17
0
    def test_object_to_json_not_dict(self):
        """This can happen if we get a dict instead of a list, e.g. with AS 2.0.

    Found via AS2 on http://evanminto.com/indieweb/activity-stream.php, e.g.:

    {
      "@context": "https://www.w3.org/ns/activitystreams",
      "0": {
        "name": "Evan reposted another post.",
        "type": "Announce",
        "actor": {
          "type": "Person",
          "name": "Evan Minto",
          "url": "http://evanminto.com"
        },
        "object": "http://www.harkavagrant.com/index.php?id=402"
      },
    ...
    """
        self.assert_equals({}, microformats2.object_to_json('foo bar'))
Example #18
0
  def write_response(self, response, actor=None):
    """Converts ActivityStreams activities and writes them out.

    Args:
      response: response dict with values based on OpenSocial ActivityStreams
        REST API, as returned by Source.get_activities_response()
      actor: optional ActivityStreams actor dict for current user. Only used
        for Atom output.
    """
    expected_formats = ('activitystreams', 'json', 'atom', 'xml', 'html', 'json-mf2')
    format = self.request.get('format') or self.request.get('output') or 'json'
    if format not in expected_formats:
      raise exc.HTTPBadRequest('Invalid format: %s, expected one of %r' %
                               (format, expected_formats))

    activities = response['items']

    self.response.headers['Access-Control-Allow-Origin'] = '*'
    if format in ('json', 'activitystreams'):
      self.response.headers['Content-Type'] = 'application/json'
      self.response.out.write(json.dumps(response, indent=2))
    elif format == 'atom':
      self.response.headers['Content-Type'] = 'text/xml'
      self.response.out.write(atom.activities_to_atom(
          activities, actor, host_url=self.request.host_url + '/',
          request_url=self.request.path_url))
    elif format == 'xml':
      self.response.headers['Content-Type'] = 'text/xml'
      self.response.out.write(XML_TEMPLATE % util.to_xml(response))
    elif format == 'html':
      self.response.headers['Content-Type'] = 'text/html'
      self.response.out.write(microformats2.activities_to_html(activities))
    elif format == 'json-mf2':
      self.response.headers['Content-Type'] = 'application/json'
      items = [microformats2.object_to_json(a['object'], a.get('context', {}))
               for a in activities]
      self.response.out.write(json.dumps({'items': items}, indent=2))

    if 'plaintext' in self.request.params:
      # override response content type
      self.response.headers['Content-Type'] = 'text/plain'
Example #19
0
  def test_attachments_to_children(self):
    obj = {'attachments': [
      {'objectType': 'note', 'url': 'http://p', 'displayName': 'p'},
      {'objectType': 'x', 'url': 'http://x'},
      {'objectType': 'article', 'url': 'http://a'},
    ]}

    self.assert_equals([{
      'type': ['h-cite'],
      'properties': {'url': ['http://p'], 'name': ['p']},
    }, {
      'type': ['h-cite'],
      'properties': {'url': ['http://a']},
    }], microformats2.object_to_json(obj)['children'])

    html = microformats2.object_to_html(obj)
    self.assert_multiline_in("""\
<article class="h-cite">
<span class="p-uid"></span>

<a class="p-name u-url" href="http://p">p</a>
<div class="">

</div>

</article>

<article class="h-cite">
<span class="p-uid"></span>

<a class="u-url" href="http://a">http://a</a>
<div class="">

</div>

</article>
""", html)
Example #20
0
    def dispatch_request(self, site, key_id, **kwargs):
        """Handle HTTP request."""
        source_cls = models.sources.get(site)
        if not source_cls:
            error(
                f"Source type '{site}' not found. Known sources: {[s for s in models.sources.keys() if s]}"
            )

        self.source = source_cls.get_by_id(key_id)
        if not self.source:
            error(f'Source {site} {key_id} not found')
        elif (self.source.status == 'disabled'
              or 'listen' not in self.source.features):
            error(
                f'Source {self.source.bridgy_path()} is disabled for backfeed')

        format = request.values.get('format', 'html')
        if format not in ('html', 'json'):
            error(f'Invalid format {format}, expected html or json')

        for id in kwargs.values():
            if not self.VALID_ID.match(id):
                error(f'Invalid id {id}', 404)

        try:
            obj = self.get_item(**kwargs)
        except models.DisableSource:
            error(
                "Bridgy's access to your account has expired. Please visit https://brid.gy/ to refresh it!",
                401)
        except ValueError as e:
            error(f'{self.source.GR_CLASS.NAME} error: {e}')

        if not obj:
            error(f'Not found: {site}:{key_id} {kwargs}', 404)

        if self.source.is_blocked(obj):
            error('That user is currently blocked', 410)

        # use https for profile pictures so we don't cause SSL mixed mode errors
        # when serving over https.
        author = obj.get('author', {})
        image = author.get('image', {})
        url = image.get('url')
        if url:
            image['url'] = util.update_scheme(url, request)

        mf2_json = microformats2.object_to_json(obj, synthesize_content=False)

        # try to include the author's silo profile url
        author = first_props(mf2_json.get('properties', {})).get('author', {})
        author_uid = first_props(author.get('properties', {})).get('uid', '')
        if author_uid:
            parsed = util.parse_tag_uri(author_uid)
            if parsed:
                urls = author.get('properties', {}).setdefault('url', [])
                try:
                    silo_url = self.source.gr_source.user_url(parsed[1])
                    if silo_url not in microformats2.get_string_urls(urls):
                        urls.append(silo_url)
                except NotImplementedError:  # from gr_source.user_url()
                    pass

        # write the response!
        if format == 'html':
            url = obj.get('url', '')
            return TEMPLATE.substitute({
                'refresh':
                (f'<meta http-equiv="refresh" content="0;url={url}">'
                 if url else ''),
                'url':
                url,
                'body':
                microformats2.json_to_html(mf2_json),
                'title':
                obj.get('title') or obj.get('content') or 'Bridgy Response',
            })
        elif format == 'json':
            return mf2_json
Example #21
0
        return
      else:
        raise

    if not obj:
      self.abort(404, label)

    # use https for profile pictures so we don't cause SSL mixed mode errors
    # when serving over https.
    author = obj.get('author', {})
    image = author.get('image', {})
    url = image.get('url')
    if url:
      image['url'] = util.update_scheme(url, self)

    mf2_json = microformats2.object_to_json(obj)

    # try to include the author's silo profile url
    author = first_props(mf2_json.get('properties', {})).get('author', {})
    author_uid = first_props(author.get('properties', {})).get('uid', '')
    if author_uid:
      parsed = util.parse_tag_uri(author_uid)
      if parsed:
        silo_url = self.source.gr_source.user_url(parsed[1])
        urls = author.get('properties', {}).setdefault('url', [])
        if silo_url not in microformats2.get_string_urls(urls):
          urls.append(silo_url)

    # write the response!
    self.response.headers['Access-Control-Allow-Origin'] = '*'
    if format == 'html':
Example #22
0
class ItemHandler(webapp2.RequestHandler):
    """Fetches a post, repost, like, or comment and serves it as mf2 HTML or JSON.
  """
    handle_exception = handlers.handle_exception
    source = None

    VALID_ID = re.compile(r'^[\w.+:@-]+$')

    def head(self, *args):
        """Return an empty 200 with no caching directives."""

    def get_item(self, id):
        """Fetches and returns an object from the given source.

    To be implemented by subclasses.

    Args:
      source: bridgy.Source subclass
      id: string

    Returns: ActivityStreams object dict
    """
        raise NotImplementedError()

    def get_title(self, obj):
        """Returns the string to be used in the <title> tag.

    Args:
      obj: ActivityStreams object
    """
        return obj.get('title') or obj.get('content') or 'Bridgy Response'

    def get_post(self, id, **kwargs):
        """Fetch a post.

    Args:
      id: string, site-specific post id
      is_event: bool
      kwargs: passed through to get_activities

    Returns: ActivityStreams object dict
    """
        try:
            posts = self.source.get_activities(activity_id=id,
                                               user_id=self.source.key.id(),
                                               **kwargs)
            if posts:
                return posts[0]
            logging.warning('Source post %s not found', id)
        except Exception as e:
            util.interpret_http_exception(e)

    def get(self, type, source_short_name, string_id, *ids):
        source_cls = models.sources.get(source_short_name)
        if not source_cls:
            self.abort(
                400, "Source type '%s' not found. Known sources: %s" %
                (source_short_name, filter(None, models.sources.keys())))

        self.source = source_cls.get_by_id(string_id)
        if not self.source:
            self.abort(
                400, 'Source %s %s not found' % (source_short_name, string_id))

        format = self.request.get('format', 'html')
        if format not in ('html', 'json'):
            self.abort(400,
                       'Invalid format %s, expected html or json' % format)

        for id in ids:
            if not self.VALID_ID.match(id):
                self.abort(404, 'Invalid id %s' % id)

        label = '%s:%s %s %s' % (source_short_name, string_id, type, ids)
        cache_key = 'H ' + label
        obj = memcache.get(cache_key)
        if obj:
            logging.info('Using cached object for %s', label)
        else:
            logging.info('Fetching %s', label)
            try:
                obj = self.get_item(*ids)
            except Exception, e:
                # pass through all API HTTP errors if we can identify them
                code, body = util.interpret_http_exception(e)
                if not code and util.is_connection_failure(e):
                    code = 503
                    body = str(e)
                if code:
                    self.response.status_int = int(code)
                    self.response.headers['Content-Type'] = 'text/plain'
                    self.response.write('%s error:\n%s' %
                                        (self.source.GR_CLASS.NAME, body))
                    return
                else:
                    raise
            memcache.set(cache_key, obj, time=CACHE_TIME)

        if not obj:
            self.abort(404, label)

        # use https for profile pictures so we don't cause SSL mixed mode errors
        # when serving over https.
        author = obj.get('author', {})
        image = author.get('image', {})
        url = image.get('url')
        if url:
            image['url'] = util.update_scheme(url, self)

        mf2_json = microformats2.object_to_json(obj, synthesize_content=False)

        # try to include the author's silo profile url
        author = first_props(mf2_json.get('properties', {})).get('author', {})
        author_uid = first_props(author.get('properties', {})).get('uid', '')
        if author_uid:
            parsed = util.parse_tag_uri(author_uid)
            if parsed:
                silo_url = self.source.gr_source.user_url(parsed[1])
                urls = author.get('properties', {}).setdefault('url', [])
                if silo_url not in microformats2.get_string_urls(urls):
                    urls.append(silo_url)

        # write the response!
        self.response.headers['Access-Control-Allow-Origin'] = '*'
        if format == 'html':
            self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
            self.response.out.write(
                TEMPLATE.substitute({
                    'url':
                    obj.get('url', ''),
                    'body':
                    microformats2.json_to_html(mf2_json),
                    'title':
                    self.get_title(obj),
                }))
        elif format == 'json':
            self.response.headers[
                'Content-Type'] = 'application/json; charset=utf-8'
            self.response.out.write(json.dumps(mf2_json, indent=2))
Example #23
0
    	#print trm.name
        if trm.name in MF2_TYPES:
            debug_print(trm.name)
            #if trm.name == 'Image':
                #debug_print('initial content: ' + post.content)
            customfields = process_mf2_data(customfields, trm.name)
            if trm.name == 'Image':
                debug_print('fields: ')
                debug_print(customfields)
            if SYNDLINKS in customfields.keys():
                syndlink = parse_response_urlstr(customfields[SYNDLINKS])
                if syndlink == '':
                    del customfields[SYNDLINKS]
                else:
                    customfields[SYNDLINKS] = syndlink
            post.content = insert_url_content(post,customfields,trm.name)
            debug_print('returned: ' + post.content)
    postdict['content'] = post.content
    #TODO: add tags, from post.terms
    itemdict = {}
    #itemdict['@context'] = 'https://www.w3.org/ns/activitystreams'  #not sure this is necessary
    itemdict['object'] = postdict
    mf2dict['items'].append(itemdict)
	
mf2dict['totalItems'] = totalitems
debug_print(mf2dict)
debug_print('\n')
mf2 = microformats2.object_to_json(mf2dict)
print(mf2)

Example #24
0
    def get(self, type, source_short_name, string_id, *ids):
        source_cls = models.sources.get(source_short_name)
        if not source_cls:
            self.abort(
                400, "Source type '%s' not found. Known sources: %s" %
                (source_short_name, filter(None, models.sources.keys())))

        self.source = source_cls.get_by_id(string_id)
        if not self.source:
            self.abort(
                400, 'Source %s %s not found' % (source_short_name, string_id))
        elif (self.source.status == 'disabled'
              or ('listen' not in self.source.features
                  and 'email' not in self.source.features)):
            self.abort(
                400, 'Source %s is disabled for backfeed' %
                self.source.bridgy_path())

        format = self.request.get('format', 'html')
        if format not in ('html', 'json'):
            self.abort(400,
                       'Invalid format %s, expected html or json' % format)

        for id in ids:
            if not self.VALID_ID.match(id):
                self.abort(404, 'Invalid id %s' % id)

        label = '%s:%s %s %s' % (source_short_name, string_id, type, ids)
        cache_key = 'H ' + label
        obj = memcache.get(cache_key)
        if obj and not appengine_config.DEBUG:
            logging.info('Using cached object for %s', label)
        else:
            logging.info('Fetching %s', label)
            try:
                obj = self.get_item(*ids)
            except models.DisableSource as e:
                self.abort(
                    401,
                    "Bridgy's access to your account has expired. Please visit https://brid.gy/ to refresh it!"
                )
            except ValueError as e:
                self.abort(400,
                           '%s error:\n%s' % (self.source.GR_CLASS.NAME, e))
            except Exception as e:
                # pass through all API HTTP errors if we can identify them
                code, body = util.interpret_http_exception(e)
                # temporary, trying to debug a flaky test failure
                # eg https://circleci.com/gh/snarfed/bridgy/769
                if code:
                    self.response.status_int = int(code)
                    self.response.headers['Content-Type'] = 'text/plain'
                    self.response.write('%s error:\n%s' %
                                        (self.source.GR_CLASS.NAME, body))
                    return
                else:
                    raise
            memcache.set(cache_key, obj, time=CACHE_TIME)

        if not obj:
            self.abort(404, label)

        if self.source.is_blocked(obj):
            self.abort(410, 'That user is currently blocked')

        # use https for profile pictures so we don't cause SSL mixed mode errors
        # when serving over https.
        author = obj.get('author', {})
        image = author.get('image', {})
        url = image.get('url')
        if url:
            image['url'] = util.update_scheme(url, self)

        mf2_json = microformats2.object_to_json(obj, synthesize_content=False)

        # try to include the author's silo profile url
        author = first_props(mf2_json.get('properties', {})).get('author', {})
        author_uid = first_props(author.get('properties', {})).get('uid', '')
        if author_uid:
            parsed = util.parse_tag_uri(author_uid)
            if parsed:
                silo_url = self.source.gr_source.user_url(parsed[1])
                urls = author.get('properties', {}).setdefault('url', [])
                if silo_url not in microformats2.get_string_urls(urls):
                    urls.append(silo_url)

        # write the response!
        self.response.headers['Access-Control-Allow-Origin'] = '*'
        if format == 'html':
            self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
            url = obj.get('url', '')
            self.response.out.write(
                TEMPLATE.substitute({
                    'refresh':
                    (('<meta http-equiv="refresh" content="0;url=%s">' %
                      url) if url else ''),
                    'url':
                    url,
                    'body':
                    microformats2.json_to_html(mf2_json),
                    'title':
                    self.get_title(obj),
                }))
        elif format == 'json':
            self.response.headers[
                'Content-Type'] = 'application/json; charset=utf-8'
            self.response.out.write(json.dumps(mf2_json, indent=2))
Example #25
0
  def get(self, type, source_short_name, string_id, *ids):
    source_cls = models.sources.get(source_short_name)
    if not source_cls:
      self.abort(400, "Source type '%s' not found. Known sources: %s" %
                 (source_short_name, filter(None, models.sources.keys())))

    self.source = source_cls.get_by_id(string_id)
    if not self.source:
      self.abort(400, 'Source %s %s not found' % (source_short_name, string_id))
    elif self.source.status == 'disabled' or 'listen' not in self.source.features:
      self.abort(400, 'Source %s is disabled for backfeed' % self.source.bridgy_path())

    format = self.request.get('format', 'html')
    if format not in ('html', 'json'):
      self.abort(400, 'Invalid format %s, expected html or json' % format)

    for id in ids:
      if not self.VALID_ID.match(id):
        self.abort(404, 'Invalid id %s' % id)

    label = '%s:%s %s %s' % (source_short_name, string_id, type, ids)
    cache_key = 'H ' + label
    obj = memcache.get(cache_key)
    if obj:
      logging.info('Using cached object for %s', label)
    else:
      logging.info('Fetching %s', label)
      try:
        obj = self.get_item(*ids)
      except models.DisableSource as e:
        self.abort(401, "Bridgy's access to your account has expired. Please visit https://brid.gy/ to refresh it!")
      except Exception as e:
        # pass through all API HTTP errors if we can identify them
        code, body = util.interpret_http_exception(e)
        if not code and util.is_connection_failure(e):
          code = 503
          body = str(e)
        if code:
          self.response.status_int = int(code)
          self.response.headers['Content-Type'] = 'text/plain'
          self.response.write('%s error:\n%s' % (self.source.GR_CLASS.NAME, body))
          return
        else:
          raise
      memcache.set(cache_key, obj, time=CACHE_TIME)

    if not obj:
      self.abort(404, label)

    # use https for profile pictures so we don't cause SSL mixed mode errors
    # when serving over https.
    author = obj.get('author', {})
    image = author.get('image', {})
    url = image.get('url')
    if url:
      image['url'] = util.update_scheme(url, self)

    mf2_json = microformats2.object_to_json(obj, synthesize_content=False)

    # try to include the author's silo profile url
    author = first_props(mf2_json.get('properties', {})).get('author', {})
    author_uid = first_props(author.get('properties', {})).get('uid', '')
    if author_uid:
      parsed = util.parse_tag_uri(author_uid)
      if parsed:
        silo_url = self.source.gr_source.user_url(parsed[1])
        urls = author.get('properties', {}).setdefault('url', [])
        if silo_url not in microformats2.get_string_urls(urls):
          urls.append(silo_url)

    # write the response!
    self.response.headers['Access-Control-Allow-Origin'] = '*'
    if format == 'html':
      self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
      self.response.out.write(TEMPLATE.substitute({
            'url': obj.get('url', ''),
            'body': microformats2.json_to_html(mf2_json),
            'title': self.get_title(obj),
            }))
    elif format == 'json':
      self.response.headers['Content-Type'] = 'application/json; charset=utf-8'
      self.response.out.write(json.dumps(mf2_json, indent=2))
Example #26
0
                return
            else:
                raise

        if not obj:
            self.abort(404, label)

        # use https for profile pictures so we don't cause SSL mixed mode errors
        # when serving over https.
        author = obj.get("author", {})
        image = author.get("image", {})
        url = image.get("url")
        if url:
            image["url"] = util.update_scheme(url, self)

        mf2_json = microformats2.object_to_json(obj, synthesize_content=False)

        # try to include the author's silo profile url
        author = first_props(mf2_json.get("properties", {})).get("author", {})
        author_uid = first_props(author.get("properties", {})).get("uid", "")
        if author_uid:
            parsed = util.parse_tag_uri(author_uid)
            if parsed:
                silo_url = self.source.gr_source.user_url(parsed[1])
                urls = author.get("properties", {}).setdefault("url", [])
                if silo_url not in microformats2.get_string_urls(urls):
                    urls.append(silo_url)

        # write the response!
        self.response.headers["Access-Control-Allow-Origin"] = "*"
        if format == "html":