PluginProbe
ActivityPub / 1.3.0
ActivityPub v1.3.0
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
activitypub / includes / class-webfinger.php

class-webfinger.php in ActivityPub 1.3.0, at includes/class-webfinger.php

209 lines 5.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Activitypub;
3
4 use WP_Error;
5 use Activitypub\Collection\Users;
6
7 /**
8 * ActivityPub WebFinger Class
9 *
10 * @author Matthias Pfefferle
11 *
12 * @see https://webfinger.net/
13 */
14 class Webfinger {
15 /**
16 * Returns a users WebFinger "resource"
17 *
18 * @param int $user_id
19 *
20 * @return string The user-resource
21 */
22 public static function get_user_resource( $user_id ) {
23 // use WebFinger plugin if installed
24 if ( \function_exists( '\get_webfinger_resource' ) ) {
25 return \get_webfinger_resource( $user_id, false );
26 }
27
28 $user = Users::get_by_id( $user_id );
29 if ( ! $user || is_wp_error( $user ) ) {
30 return '';
31 }
32
33 return $user->get_resource();
34 }
35
36 /**
37 * Resolve a WebFinger resource
38 *
39 * @param string $resource The WebFinger resource
40 *
41 * @return string|WP_Error The URL or WP_Error
42 */
43 public static function resolve( $resource ) {
44 if ( ! $resource ) {
45 return null;
46 }
47
48 if ( ! preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $resource, $m ) ) {
49 return null;
50 }
51
52 $transient_key = 'activitypub_resolve_' . ltrim( $resource, '@' );
53
54 $link = \get_transient( $transient_key );
55 if ( $link ) {
56 return $link;
57 }
58
59 $url = \add_query_arg( 'resource', 'acct:' . ltrim( $resource, '@' ), 'https://' . $m[2] . '/.well-known/webfinger' );
60 if ( ! \wp_http_validate_url( $url ) ) {
61 $response = new WP_Error( 'invalid_webfinger_url', null, $url );
62 \set_transient( $transient_key, $response, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
63 return $response;
64 }
65
66 // try to access author URL
67 $response = \wp_remote_get(
68 $url,
69 array(
70 'headers' => array( 'Accept' => 'application/jrd+json' ),
71 'redirection' => 2,
72 'timeout' => 2,
73 )
74 );
75
76 if ( \is_wp_error( $response ) ) {
77 $link = new WP_Error( 'webfinger_url_not_accessible', null, $url );
78 \set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
79 return $link;
80 }
81
82 $body = \wp_remote_retrieve_body( $response );
83 $body = \json_decode( $body, true );
84
85 if ( empty( $body['links'] ) ) {
86 $link = new WP_Error( 'webfinger_url_invalid_response', null, $url );
87 \set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
88 return $link;
89 }
90
91 foreach ( $body['links'] as $link ) {
92 if ( 'self' === $link['rel'] && 'application/activity+json' === $link['type'] ) {
93 \set_transient( $transient_key, $link['href'], WEEK_IN_SECONDS );
94 return $link['href'];
95 }
96 }
97
98 $link = new WP_Error( 'webfinger_url_no_activitypub', null, $body );
99 \set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
100 return $link;
101 }
102
103 /**
104 * Convert a URI string to an identifier and its host.
105 * Automatically adds acct: if it's missing.
106 *
107 * @param string $url The URI (acct:, mailto:, http:, https:)
108 *
109 * @return WP_Error|array Error reaction or array with
110 * identifier and host as values
111 */
112 public static function get_identifier_and_host( $url ) {
113 // remove leading @
114 $url = ltrim( $url, '@' );
115
116 if ( ! preg_match( '/^([a-zA-Z+]+):/', $url, $match ) ) {
117 $identifier = 'acct:' . $url;
118 $scheme = 'acct';
119 } else {
120 $identifier = $url;
121 $scheme = $match[1];
122 }
123
124 $host = null;
125
126 switch ( $scheme ) {
127 case 'acct':
128 case 'mailto':
129 case 'xmpp':
130 if ( strpos( $identifier, '@' ) !== false ) {
131 $host = substr( $identifier, strpos( $identifier, '@' ) + 1 );
132 }
133 break;
134 default:
135 $host = wp_parse_url( $identifier, PHP_URL_HOST );
136 break;
137 }
138
139 if ( empty( $host ) ) {
140 return new WP_Error( 'invalid_identifier', __( 'Invalid Identifier', 'activitypub' ) );
141 }
142
143 return array( $identifier, $host );
144 }
145
146 /**
147 * Get the WebFinger data for a given URI
148 *
149 * @param string $identifier The Identifier: <identifier>@<host>
150 * @param string $host The Host: <identifier>@<host>
151 *
152 * @return WP_Error|array Error reaction or array with
153 * identifier and host as values
154 */
155 public static function get_data( $identifier, $host ) {
156 $webfinger_url = 'https://' . $host . '/.well-known/webfinger?resource=' . rawurlencode( $identifier );
157
158 $response = wp_safe_remote_get(
159 $webfinger_url,
160 array(
161 'headers' => array( 'Accept' => 'application/jrd+json' ),
162 'redirection' => 0,
163 'timeout' => 2,
164 )
165 );
166
167 if ( is_wp_error( $response ) ) {
168 return new WP_Error( 'webfinger_url_not_accessible', null, $webfinger_url );
169 }
170
171 $body = wp_remote_retrieve_body( $response );
172
173 return json_decode( $body, true );
174 }
175
176 /**
177 * Undocumented function
178 *
179 * @return void
180 */
181 public static function get_remote_follow_endpoint( $uri ) {
182 $identifier_and_host = self::get_identifier_and_host( $uri );
183
184 if ( is_wp_error( $identifier_and_host ) ) {
185 return $identifier_and_host;
186 }
187
188 list( $identifier, $host ) = $identifier_and_host;
189
190 $data = self::get_data( $identifier, $host );
191
192 if ( is_wp_error( $data ) ) {
193 return $data;
194 }
195
196 if ( empty( $data['links'] ) ) {
197 return new WP_Error( 'webfinger_url_invalid_response', null, $data );
198 }
199
200 foreach ( $data['links'] as $link ) {
201 if ( 'http://ostatus.org/schema/1.0/subscribe' === $link['rel'] ) {
202 return $link['template'];
203 }
204 }
205
206 return new WP_Error( 'webfinger_remote_follow_endpoint_invalid', $data, array( 'status' => 417 ) );
207 }
208 }
209