PluginProbe
ActivityPub / 1.0.6
ActivityPub v1.0.6
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.0.6, at includes/class-webfinger.php

204 lines 5.2 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 ( ! preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $resource, $m ) ) {
45 return null;
46 }
47 $transient_key = 'activitypub_resolve_' . ltrim( $resource, '@' );
48
49 $link = \get_transient( $transient_key );
50 if ( $link ) {
51 return $link;
52 }
53
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;
59 }
60
61 // try to access author URL
62 $response = \wp_remote_get(
63 $url,
64 array(
65 'headers' => array( 'Accept' => 'application/jrd+json' ),
66 'redirection' => 2,
67 'timeout' => 2,
68 )
69 );
70
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;
75 }
76
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;
84 }
85
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'];
90 }
91 }
92
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;
96 }
97
98 /**
99 * Convert a URI string to an identifier and its host.
100 * Automatically adds acct: if it's missing.
101 *
102 * @param string $url The URI (acct:, mailto:, http:, https:)
103 *
104 * @return WP_Error|array Error reaction or array with
105 * identifier and host as values
106 */
107 public static function get_identifier_and_host( $url ) {
108 // remove leading @
109 $url = ltrim( $url, '@' );
110
111 if ( ! preg_match( '/^([a-zA-Z+]+):/', $url, $match ) ) {
112 $identifier = 'acct:' . $url;
113 $scheme = 'acct';
114 } else {
115 $identifier = $url;
116 $scheme = $match[1];
117 }
118
119 $host = null;
120
121 switch ( $scheme ) {
122 case 'acct':
123 case 'mailto':
124 case 'xmpp':
125 if ( strpos( $identifier, '@' ) !== false ) {
126 $host = substr( $identifier, strpos( $identifier, '@' ) + 1 );
127 }
128 break;
129 default:
130 $host = wp_parse_url( $identifier, PHP_URL_HOST );
131 break;
132 }
133
134 if ( empty( $host ) ) {
135 return new WP_Error( 'invalid_identifier', __( 'Invalid Identifier', 'activitypub' ) );
136 }
137
138 return array( $identifier, $host );
139 }
140
141 /**
142 * Get the WebFinger data for a given URI
143 *
144 * @param string $identifier The Identifier: <identifier>@<host>
145 * @param string $host The Host: <identifier>@<host>
146 *
147 * @return WP_Error|array Error reaction or array with
148 * identifier and host as values
149 */
150 public static function get_data( $identifier, $host ) {
151 $webfinger_url = 'https://' . $host . '/.well-known/webfinger?resource=' . rawurlencode( $identifier );
152
153 $response = wp_safe_remote_get(
154 $webfinger_url,
155 array(
156 'headers' => array( 'Accept' => 'application/jrd+json' ),
157 'redirection' => 0,
158 'timeout' => 2,
159 )
160 );
161
162 if ( is_wp_error( $response ) ) {
163 return new WP_Error( 'webfinger_url_not_accessible', null, $webfinger_url );
164 }
165
166 $body = wp_remote_retrieve_body( $response );
167
168 return json_decode( $body, true );
169 }
170
171 /**
172 * Undocumented function
173 *
174 * @return void
175 */
176 public static function get_remote_follow_endpoint( $uri ) {
177 $identifier_and_host = self::get_identifier_and_host( $uri );
178
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 if ( is_wp_error( $data ) ) {
188 return $data;
189 }
190
191 if ( empty( $data['links'] ) ) {
192 return new WP_Error( 'webfinger_url_invalid_response', null, $data );
193 }
194
195 foreach ( $data['links'] as $link ) {
196 if ( 'http://ostatus.org/schema/1.0/subscribe' === $link['rel'] ) {
197 return $link['template'];
198 }
199 }
200
201 return new WP_Error( 'webfinger_remote_follow_endpoint_invalid', $data, array( 'status' => 417 ) );
202 }
203 }
204