. * * @category Discovery * @package GNUsocial * @author James Walker * @author Mikael Nordfeldth * @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); common_debug(sprintf('Performing discovery for "%s" (normalized "%s")', $id, $uri)); 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 (ClientException $e) { if ($e->getCode() === 403) { common_log(LOG_INFO, sprintf('%s: Aborting discovery on URL %s: %s', _ve($class), _ve($uri), _ve($e->getMessage()))); break; } } 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; } }