PluginProbe
ActivityPub / 3.2.5
ActivityPub v3.2.5
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
← All changes | includes/class-webfinger.php +154 -66 1.0.53.2.5 View file →
@@ -14,86 +14,107 @@
14 14 class Webfinger {
15 15 /**
16 16 * Returns a users WebFinger "resource"
17 17 *
18 - * @param int $user_id
18 + * @param int $user_id The WordPress user id
19 19 *
20 20 * @return string The user-resource
21 21 */
22 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 23 $user = Users::get_by_id( $user_id );
29 24 if ( ! $user || is_wp_error( $user ) ) {
30 25 return '';
31 26 }
32 27
33 - return $user->get_resource();
28 + return $user->get_webfinger();
34 29 }
35 30
36 31 /**
37 32 * Resolve a WebFinger resource
38 33 *
39 - * @param string $resource The WebFinger resource
34 + * @param string $uri The WebFinger Resource
40 35 *
41 36 * @return string|WP_Error The URL or WP_Error
42 37 */
43 - public static function resolve( $resource ) {
44 - if ( ! preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $resource, $m ) ) {
45 - return null;
38 + public static function resolve( $uri ) {
39 + $data = self::get_data( $uri );
40 +
41 + if ( \is_wp_error( $data ) ) {
42 + return $data;
46 43 }
47 - $transient_key = 'activitypub_resolve_' . ltrim( $resource, '@' );
48 44
49 - $link = \get_transient( $transient_key );
50 - if ( $link ) {
51 - return $link;
45 + if ( ! is_array( $data ) || empty( $data['links'] ) ) {
46 + return new WP_Error(
47 + 'webfinger_missing_links',
48 + __( 'No valid Link elements found.', 'activitypub' ),
49 + array(
50 + 'status' => 400,
51 + 'data' => $data,
52 + )
53 + );
52 54 }
53 55
54 - $url = \add_query_arg( 'resource', 'acct:' . ltrim( $resource, '@' ), 'https://' . $m[2] . '/.well-known/webfinger' );
55 - if ( ! \wp_http_validate_url( $url ) ) {
56 - $response = new WP_Error( 'invalid_webfinger_url', null, $url );
57 - \set_transient( $transient_key, $response, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
58 - return $response;
56 + foreach ( $data['links'] as $link ) {
57 + if (
58 + 'self' === $link['rel'] &&
59 + (
60 + 'application/activity+json' === $link['type'] ||
61 + 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' === $link['type']
62 + )
63 + ) {
64 + return $link['href'];
65 + }
59 66 }
60 67
61 - // try to access author URL
62 - $response = \wp_remote_get(
63 - $url,
68 + return new WP_Error(
69 + 'webfinger_url_no_activitypub',
70 + __( 'The Site supports WebFinger but not ActivityPub', 'activitypub' ),
64 71 array(
65 - 'headers' => array( 'Accept' => 'application/jrd+json' ),
66 - 'redirection' => 2,
67 - 'timeout' => 2,
72 + 'status' => 400,
73 + 'data' => $data,
68 74 )
69 75 );
76 + }
70 77
71 - if ( \is_wp_error( $response ) ) {
72 - $link = new WP_Error( 'webfinger_url_not_accessible', null, $url );
73 - \set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
74 - return $link;
78 + /**
79 + * Transform a URI to an acct <identifier>@<host>
80 + *
81 + * @param string $uri The URI (acct:, mailto:, http:, https:)
82 + *
83 + * @return string|WP_Error Error or acct URI
84 + */
85 + public static function uri_to_acct( $uri ) {
86 + $data = self::get_data( $uri );
87 +
88 + if ( is_wp_error( $data ) ) {
89 + return $data;
75 90 }
76 91
77 - $body = \wp_remote_retrieve_body( $response );
78 - $body = \json_decode( $body, true );
79 -
80 - if ( empty( $body['links'] ) ) {
81 - $link = new WP_Error( 'webfinger_url_invalid_response', null, $url );
82 - \set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
83 - return $link;
92 + // check if subject is an acct URI
93 + if (
94 + isset( $data['subject'] ) &&
95 + \str_starts_with( $data['subject'], 'acct:' )
96 + ) {
97 + return $data['subject'];
84 98 }
85 99
86 - foreach ( $body['links'] as $link ) {
87 - if ( 'self' === $link['rel'] && 'application/activity+json' === $link['type'] ) {
88 - \set_transient( $transient_key, $link['href'], WEEK_IN_SECONDS );
89 - return $link['href'];
100 + // search for an acct URI in the aliases
101 + if ( isset( $data['aliases'] ) ) {
102 + foreach ( $data['aliases'] as $alias ) {
103 + if ( \str_starts_with( $alias, 'acct:' ) ) {
104 + return $alias;
105 + }
90 106 }
91 107 }
92 108
93 - $link = new WP_Error( 'webfinger_url_no_activitypub', null, $body );
94 - \set_transient( $transient_key, $link, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
95 - return $link;
109 + return new WP_Error(
110 + 'webfinger_url_no_acct',
111 + __( 'No acct URI found.', 'activitypub' ),
112 + array(
113 + 'status' => 400,
114 + 'data' => $data,
115 + )
116 + );
96 117 }
97 118
98 119 /**
99 120 * Convert a URI string to an identifier and its host.
@@ -104,8 +125,19 @@
104 125 * @return WP_Error|array Error reaction or array with
105 126 * identifier and host as values
106 127 */
107 128 public static function get_identifier_and_host( $url ) {
129 + if ( ! $url ) {
130 + return new WP_Error(
131 + 'webfinger_invalid_identifier',
132 + __( 'Invalid Identifier', 'activitypub' ),
133 + array(
134 + 'status' => 400,
135 + 'data' => $url,
136 + )
137 + );
138 + }
139 +
108 140 // remove leading @
109 141 $url = ltrim( $url, '@' );
110 142
111 143 if ( ! preg_match( '/^([a-zA-Z+]+):/', $url, $match ) ) {
@@ -131,9 +163,16 @@
131 163 break;
132 164 }
133 165
134 166 if ( empty( $host ) ) {
135 - return new WP_Error( 'invalid_identifier', __( 'Invalid Identifier', 'activitypub' ) );
167 + return new WP_Error(
168 + 'webfinger_invalid_identifier',
169 + __( 'Invalid Identifier', 'activitypub' ),
170 + array(
171 + 'status' => 400,
172 + 'data' => $url,
173 + )
174 + );
136 175 }
137 176
138 177 return array( $identifier, $host );
139 178 }
@@ -140,57 +179,82 @@
140 179
141 180 /**
142 181 * Get the WebFinger data for a given URI
143 182 *
144 - * @param string $identifier The Identifier: <identifier>@<host>
145 - * @param string $host The Host: <identifier>@<host>
183 + * @param string $uri The Identifier: <identifier>@<host> or URI
146 184 *
147 185 * @return WP_Error|array Error reaction or array with
148 186 * identifier and host as values
149 187 */
150 - public static function get_data( $identifier, $host ) {
151 - $webfinger_url = 'https://' . $host . '/.well-known/webfinger?resource=' . rawurlencode( $identifier );
188 + public static function get_data( $uri ) {
189 + $identifier_and_host = self::get_identifier_and_host( $uri );
152 190
191 + if ( is_wp_error( $identifier_and_host ) ) {
192 + return $identifier_and_host;
193 + }
194 +
195 + $transient_key = self::generate_cache_key( $uri );
196 +
197 + list( $identifier, $host ) = $identifier_and_host;
198 +
199 + $data = \get_transient( $transient_key );
200 + if ( $data ) {
201 + return $data;
202 + }
203 +
204 + $webfinger_url = sprintf(
205 + 'https://%s/.well-known/webfinger?resource=%s',
206 + $host,
207 + rawurlencode( $identifier )
208 + );
209 +
153 210 $response = wp_safe_remote_get(
154 211 $webfinger_url,
155 212 array(
156 213 'headers' => array( 'Accept' => 'application/jrd+json' ),
157 - 'redirection' => 0,
158 - 'timeout' => 2,
159 214 )
160 215 );
161 216
162 217 if ( is_wp_error( $response ) ) {
163 - return new WP_Error( 'webfinger_url_not_accessible', null, $webfinger_url );
218 + return new WP_Error(
219 + 'webfinger_url_not_accessible',
220 + __( 'The WebFinger Resource is not accessible.', 'activitypub' ),
221 + array(
222 + 'status' => 400,
223 + 'data' => $webfinger_url,
224 + )
225 + );
164 226 }
165 227
166 228 $body = wp_remote_retrieve_body( $response );
229 + $data = json_decode( $body, true );
167 230
168 - return json_decode( $body, true );
231 + \set_transient( $transient_key, $data, WEEK_IN_SECONDS );
232 +
233 + return $data;
169 234 }
170 235
171 236 /**
172 - * Undocumented function
237 + * Get the Remote-Follow endpoint for a given URI
173 238 *
174 - * @return void
239 + * @return string|WP_Error Error or the Remote-Follow endpoint URI.
175 240 */
176 241 public static function get_remote_follow_endpoint( $uri ) {
177 - $identifier_and_host = self::get_identifier_and_host( $uri );
242 + $data = self::get_data( $uri );
178 243
179 - if ( is_wp_error( $identifier_and_host ) ) {
180 - return $identifier_and_host;
181 - }
182 -
183 - list( $identifier, $host ) = $identifier_and_host;
184 -
185 - $data = self::get_data( $identifier, $host );
186 -
187 244 if ( is_wp_error( $data ) ) {
188 245 return $data;
189 246 }
190 247
191 248 if ( empty( $data['links'] ) ) {
192 - return new WP_Error( 'webfinger_url_invalid_response', null, $data );
249 + return new WP_Error(
250 + 'webfinger_missing_links',
251 + __( 'No valid Link elements found.', 'activitypub' ),
252 + array(
253 + 'status' => 400,
254 + 'data' => $data,
255 + )
256 + );
193 257 }
194 258
195 259 foreach ( $data['links'] as $link ) {
196 260 if ( 'http://ostatus.org/schema/1.0/subscribe' === $link['rel'] ) {
@@ -197,7 +261,31 @@
197 261 return $link['template'];
198 262 }
199 263 }
200 264
201 - return new WP_Error( 'webfinger_remote_follow_endpoint_invalid', $data, array( 'status' => 417 ) );
265 + return new WP_Error(
266 + 'webfinger_missing_remote_follow_endpoint',
267 + __( 'No valid Remote-Follow endpoint found.', 'activitypub' ),
268 + array(
269 + 'status' => 400,
270 + 'data' => $data,
271 + )
272 + );
273 + }
274 +
275 + /**
276 + * Generate a cache key for a given URI
277 + *
278 + * @param string $uri A WebFinger Resource URI
279 + *
280 + * @return string The cache key
281 + */
282 + public static function generate_cache_key( $uri ) {
283 + $uri = ltrim( $uri, '@' );
284 +
285 + if ( filter_var( $uri, FILTER_VALIDATE_EMAIL ) ) {
286 + $uri = 'acct:' . $uri;
287 + }
288 +
289 + return 'webfinger_' . md5( $uri );
202 290 }
203 291 }