]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/LRDD/lib/discovery.php
LRDD blacklisted URL test
[quix0rs-gnu-social.git] / plugins / LRDD / lib / discovery.php
1 <?php
2 /**
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2010, StatusNet, Inc.
5  *
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.
9  *
10  * PHP version 5
11  *
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.
16  *
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.
21  *
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/>.
24  *
25  * @category  Discovery
26  * @package   GNUsocial
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/
33  */
34
35 if (!defined('GNUSOCIAL')) { exit(1); }
36
37 class Discovery
38 {
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
43
44     const JRD_MIMETYPE_OLD = 'application/json';    // RFC6415 uses this
45     const JRD_MIMETYPE = 'application/jrd+json';
46     const XRD_MIMETYPE = 'application/xrd+xml';
47
48     public $methods = array();
49
50     /**
51      * Constructor for a discovery object
52      *
53      * Registers different discovery methods.
54      *
55      * @return Discovery this
56      */
57
58     public function __construct()
59     {
60         if (Event::handle('StartDiscoveryMethodRegistration', array($this))) {
61             Event::handle('EndDiscoveryMethodRegistration', array($this));
62         }
63     }
64
65     public static function supportedMimeTypes()
66     {
67         return array('json'=>self::JRD_MIMETYPE,
68                      'jsonold'=>self::JRD_MIMETYPE_OLD,
69                      'xml'=>self::XRD_MIMETYPE);
70     }
71
72     /**
73      * Register a discovery class
74      *
75      * @param string $class Class name
76      *
77      * @return void
78      */
79     public function registerMethod($class)
80     {
81         $this->methods[] = $class;
82     }
83
84     /**
85      * Given a user ID, return the first available resource descriptor
86      *
87      * @param string $id User ID URI
88      *
89      * @return XML_XRD object for the resource descriptor of the id
90      */
91     public function lookup($id)
92     {
93         // Normalize the incoming $id to make sure we have a uri
94         $uri = self::normalize($id);
95
96         common_debug(sprintf('Performing discovery for "%s" (normalized "%s")', $id, $uri));
97
98         foreach ($this->methods as $class) {
99             try {
100                 $xrd = new XML_XRD();
101
102                 common_debug("LRDD discovery method for '$uri': {$class}");
103                 $lrdd = new $class;
104                 $links = call_user_func(array($lrdd, 'discover'), $uri);
105                 $link = Discovery::getService($links, Discovery::LRDD_REL);
106
107                 // Load the LRDD XRD
108                 if (!empty($link->template)) {
109                     $xrd_uri = Discovery::applyTemplate($link->template, $uri);
110                 } elseif (!empty($link->href)) {
111                     $xrd_uri = $link->href;
112                 } else {
113                     throw new Exception('No resource descriptor URI in link.');
114                 }
115
116                 $client  = new HTTPClient();
117                 $headers = array();
118                 if (!is_null($link->type)) {
119                     $headers[] = "Accept: {$link->type}";
120                 }
121
122                 $response = $client->get($xrd_uri, $headers);
123                 if ($response->getStatus() != 200) {
124                     throw new Exception('Unexpected HTTP status code.');
125                 }
126
127                 $xrd->loadString($response->getBody());
128                 return $xrd;
129
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())));
133                     break;
134                 }
135             } catch (Exception $e) {
136                 common_log(LOG_INFO, sprintf('%s: Failed for %s: %s', _ve($class), _ve($uri), _ve($e->getMessage())));
137                 continue;
138             }
139         }
140
141         // TRANS: Exception. %s is an ID.
142         throw new Exception(sprintf(_('Unable to find services for %s.'), $id));
143     }
144
145     /**
146      * Given an array of links, returns the matching service
147      *
148      * @param array  $links   Links to check (as instances of XML_XRD_Element_Link)
149      * @param string $service Service to find
150      *
151      * @return array $link assoc array representing the link
152      */
153     public static function getService(array $links, $service)
154     {
155         foreach ($links as $link) {
156             if ($link->rel === $service) {
157                 return $link;
158             }
159             common_debug('LINK: rel '.$link->rel.' !== '.$service);
160         }
161
162         throw new Exception('No service link found');
163     }
164
165     /**
166      * Given a "user id" make sure it's normalized to an acct: uri
167      *
168      * @param string $user_id User ID to normalize
169      *
170      * @return string normalized acct: URI
171      */
172     public static function normalize($uri)
173     {
174         if (is_null($uri) || $uri==='') {
175             throw new Exception(_('No resource given.'));
176         }
177
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;
184         }
185
186         return $uri;
187     }
188
189     public static function isAcct($uri)
190     {
191         return (mb_strtolower(mb_substr($uri, 0, 5)) == 'acct:');
192     }
193
194     /**
195      * Apply a template using an ID
196      *
197      * Replaces {uri} in template string with the ID given.
198      *
199      * @param string $template Template to match
200      * @param string $uri      URI to replace with
201      *
202      * @return string replaced values
203      */
204     public static function applyTemplate($template, $uri)
205     {
206         $template = str_replace('{uri}', urlencode($uri), $template);
207
208         return $template;
209     }
210 }