3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2010, StatusNet, Inc.
6 * This class performs lookups based on methods implemented in separate
7 * classes, where a resource uri is given. Examples are WebFinger (RFC7033)
8 * and the LRDD (Link-based Resource Descriptor Discovery) in RFC6415.
12 * This program is free software: you can redistribute it and/or modify
13 * it under the terms of the GNU Affero General Public License as published by
14 * the Free Software Foundation, either version 3 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU Affero General Public License for more details.
22 * You should have received a copy of the GNU Affero General Public License
23 * along with this program. If not, see <http://www.gnu.org/licenses/>.
27 * @author James Walker <james@status.net>
28 * @author Mikael Nordfeldth <mmn@hethane.se>
29 * @copyright 2010 StatusNet, Inc.
30 * @copyright 2013 Free Software Foundation, Inc.
31 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
32 * @link http://www.gnu.org/software/social/
35 if (!defined('GNUSOCIAL')) { exit(1); }
39 const LRDD_REL = 'lrdd';
40 const UPDATESFROM = 'http://schemas.google.com/g/2010#updates-from';
41 const HCARD = 'http://microformats.org/profile/hcard';
42 const MF2_HCARD = 'http://microformats.org/profile/h-card'; // microformats2 h-card
44 const JRD_MIMETYPE_OLD = 'application/json'; // RFC6415 uses this
45 const JRD_MIMETYPE = 'application/jrd+json';
46 const XRD_MIMETYPE = 'application/xrd+xml';
48 public $methods = array();
51 * Constructor for a discovery object
53 * Registers different discovery methods.
55 * @return Discovery this
58 public function __construct()
60 if (Event::handle('StartDiscoveryMethodRegistration', array($this))) {
61 Event::handle('EndDiscoveryMethodRegistration', array($this));
65 public static function supportedMimeTypes()
67 return array('json'=>self::JRD_MIMETYPE,
68 'jsonold'=>self::JRD_MIMETYPE_OLD,
69 'xml'=>self::XRD_MIMETYPE);
73 * Register a discovery class
75 * @param string $class Class name
79 public function registerMethod($class)
81 $this->methods[] = $class;
85 * Given a user ID, return the first available resource descriptor
87 * @param string $id User ID URI
89 * @return XML_XRD object for the resource descriptor of the id
91 public function lookup($id)
93 // Normalize the incoming $id to make sure we have a uri
94 $uri = self::normalize($id);
96 common_debug(sprintf('Performing discovery for "%s" (normalized "%s")', $id, $uri));
98 foreach ($this->methods as $class) {
100 $xrd = new XML_XRD();
102 common_debug("LRDD discovery method for '$uri': {$class}");
104 $links = call_user_func(array($lrdd, 'discover'), $uri);
105 $link = Discovery::getService($links, Discovery::LRDD_REL);
108 if (!empty($link->template)) {
109 $xrd_uri = Discovery::applyTemplate($link->template, $uri);
110 } elseif (!empty($link->href)) {
111 $xrd_uri = $link->href;
113 throw new Exception('No resource descriptor URI in link.');
116 $client = new HTTPClient();
118 if (!is_null($link->type)) {
119 $headers[] = "Accept: {$link->type}";
122 $response = $client->get($xrd_uri, $headers);
123 if ($response->getStatus() != 200) {
124 throw new Exception('Unexpected HTTP status code.');
127 $xrd->loadString($response->getBody());
130 } catch (ClientException $e) {
131 if ($e->getCode() === 403) {
132 common_log(LOG_INFO, sprintf('%s: Aborting discovery on URL %s: %s', _ve($class), _ve($uri), _ve($e->getMessage())));
135 } catch (Exception $e) {
136 common_log(LOG_INFO, sprintf('%s: Failed for %s: %s', _ve($class), _ve($uri), _ve($e->getMessage())));
141 // TRANS: Exception. %s is an ID.
142 throw new Exception(sprintf(_('Unable to find services for %s.'), $id));
146 * Given an array of links, returns the matching service
148 * @param array $links Links to check (as instances of XML_XRD_Element_Link)
149 * @param string $service Service to find
151 * @return array $link assoc array representing the link
153 public static function getService(array $links, $service)
155 foreach ($links as $link) {
156 if ($link->rel === $service) {
159 common_debug('LINK: rel '.$link->rel.' !== '.$service);
162 throw new Exception('No service link found');
166 * Given a "user id" make sure it's normalized to an acct: uri
168 * @param string $user_id User ID to normalize
170 * @return string normalized acct: URI
172 public static function normalize($uri)
174 if (is_null($uri) || $uri==='') {
175 throw new Exception(_('No resource given.'));
178 $parts = parse_url($uri);
179 // If we don't have a scheme, but the path implies user@host,
180 // though this is far from a perfect matching procedure...
181 if (!isset($parts['scheme']) && isset($parts['path'])
182 && preg_match('/[\w@\w]/u', $parts['path'])) {
183 return 'acct:' . $uri;
189 public static function isAcct($uri)
191 return (mb_strtolower(mb_substr($uri, 0, 5)) == 'acct:');
195 * Apply a template using an ID
197 * Replaces {uri} in template string with the ID given.
199 * @param string $template Template to match
200 * @param string $uri URI to replace with
202 * @return string replaced values
204 public static function applyTemplate($template, $uri)
206 $template = str_replace('{uri}', urlencode($uri), $template);