gnu-social/plugins/LRDD/lib/discovery.php
Mikael Nordfeldth 3987cad9b7 Use delivered content-type to parse XML_XRD
In issue #205 we saw data coming in with an additional line-break before
the JSON data which fuzzed the auto-detection in XML_XRD (which assumed
a { as the first character). If we use the Content-type header from HTTP
we can avoid that issue.
2016-07-02 13:44:25 +02:00

217 lines
6.9 KiB
PHP

<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc.
*
* This class performs lookups based on methods implemented in separate
* classes, where a resource uri is given. Examples are WebFinger (RFC7033)
* and the LRDD (Link-based Resource Descriptor Discovery) in RFC6415.
*
* PHP version 5
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Discovery
* @package GNUsocial
* @author James Walker <james@status.net>
* @author Mikael Nordfeldth <mmn@hethane.se>
* @copyright 2010 StatusNet, Inc.
* @copyright 2013 Free Software Foundation, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://www.gnu.org/software/social/
*/
if (!defined('GNUSOCIAL')) { exit(1); }
class Discovery
{
const LRDD_REL = 'lrdd';
const UPDATESFROM = 'http://schemas.google.com/g/2010#updates-from';
const HCARD = 'http://microformats.org/profile/hcard';
const MF2_HCARD = 'http://microformats.org/profile/h-card'; // microformats2 h-card
const JRD_MIMETYPE_OLD = 'application/json'; // RFC6415 uses this
const JRD_MIMETYPE = 'application/jrd+json';
const XRD_MIMETYPE = 'application/xrd+xml';
public $methods = array();
/**
* Constructor for a discovery object
*
* Registers different discovery methods.
*
* @return Discovery this
*/
public function __construct()
{
if (Event::handle('StartDiscoveryMethodRegistration', array($this))) {
Event::handle('EndDiscoveryMethodRegistration', array($this));
}
}
public static function supportedMimeTypes()
{
return array('json'=>self::JRD_MIMETYPE,
'jsonold'=>self::JRD_MIMETYPE_OLD,
'xml'=>self::XRD_MIMETYPE);
}
/**
* Register a discovery class
*
* @param string $class Class name
*
* @return void
*/
public function registerMethod($class)
{
$this->methods[] = $class;
}
/**
* Given a user ID, return the first available resource descriptor
*
* @param string $id User ID URI
*
* @return XML_XRD object for the resource descriptor of the id
*/
public function lookup($id)
{
// Normalize the incoming $id to make sure we have a uri
$uri = self::normalize($id);
foreach ($this->methods as $class) {
try {
$xrd = new XML_XRD();
common_debug("LRDD discovery method for '$uri': {$class}");
$lrdd = new $class;
$links = $lrdd->discover($uri);
$link = Discovery::getService($links, Discovery::LRDD_REL);
// Load the LRDD XRD
if (!empty($link->template)) {
$xrd_uri = Discovery::applyTemplate($link->template, $uri);
} elseif (!empty($link->href)) {
$xrd_uri = $link->href;
} else {
throw new Exception('No resource descriptor URI in link.');
}
$client = new HTTPClient();
$headers = array();
if (!is_null($link->type)) {
$headers[] = "Accept: {$link->type}";
}
$response = $client->get($xrd_uri, $headers);
if ($response->getStatus() != 200) {
throw new Exception('Unexpected HTTP status code.');
}
switch ($response->getHeader('content-type')) {
case self::JRD_MIMETYPE_OLD:
case self::JRD_MIMETYPE:
$type = 'json';
break;
case self::XRD_MIMETYPE:
$type = 'xml';
break;
default:
// fall back to letting XML_XRD auto-detect
common_debug('No recognized content-type header for resource descriptor body.');
$type = null;
}
$xrd->loadString($response->getBody(), $type);
return $xrd;
} catch (Exception $e) {
common_log(LOG_INFO, sprintf('%s: Failed for %s: %s', _ve($class), _ve($uri), _ve($e->getMessage())));
continue;
}
}
// TRANS: Exception. %s is an ID.
throw new Exception(sprintf(_('Unable to find services for %s.'), $id));
}
/**
* Given an array of links, returns the matching service
*
* @param array $links Links to check (as instances of XML_XRD_Element_Link)
* @param string $service Service to find
*
* @return array $link assoc array representing the link
*/
public static function getService(array $links, $service)
{
foreach ($links as $link) {
if ($link->rel === $service) {
return $link;
}
common_debug('LINK: rel '.$link->rel.' !== '.$service);
}
throw new Exception('No service link found');
}
/**
* Given a "user id" make sure it's normalized to an acct: uri
*
* @param string $user_id User ID to normalize
*
* @return string normalized acct: URI
*/
public static function normalize($uri)
{
if (is_null($uri) || $uri==='') {
throw new Exception(_('No resource given.'));
}
$parts = parse_url($uri);
// If we don't have a scheme, but the path implies user@host,
// though this is far from a perfect matching procedure...
if (!isset($parts['scheme']) && isset($parts['path'])
&& preg_match('/[\w@\w]/u', $parts['path'])) {
return 'acct:' . $uri;
}
return $uri;
}
public static function isAcct($uri)
{
return (mb_strtolower(mb_substr($uri, 0, 5)) == 'acct:');
}
/**
* Apply a template using an ID
*
* Replaces {uri} in template string with the ID given.
*
* @param string $template Template to match
* @param string $uri URI to replace with
*
* @return string replaced values
*/
public static function applyTemplate($template, $uri)
{
$template = str_replace('{uri}', urlencode($uri), $template);
return $template;
}
}