PluginProbe
ActivityPub / 5.3.1
ActivityPub v5.3.1
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-http.php +211 -37 2.0.05.3.1 View file →
@@ -1,9 +1,15 @@
1 1 <?php
2 +/**
3 + * ActivityPub HTTP Class.
4 + *
5 + * @package Activitypub
6 + */
7 +
2 8 namespace Activitypub;
3 9
4 10 use WP_Error;
5 -use Activitypub\Collection\Users;
11 +use Activitypub\Collection\Actors;
6 12
7 13 /**
8 14 * ActivityPub HTTP Class
9 15 *
@@ -12,42 +18,49 @@
12 18 class Http {
13 19 /**
14 20 * Send a POST Request with the needed HTTP Headers
15 21 *
16 - * @param string $url The URL endpoint
17 - * @param string $body The Post Body
18 - * @param int $user_id The WordPress User-ID
22 + * @param string $url The URL endpoint.
23 + * @param string $body The Post Body.
24 + * @param int $user_id The WordPress User-ID.
19 25 *
20 - * @return array|WP_Error The POST Response or an WP_ERROR
26 + * @return array|WP_Error The POST Response or an WP_Error.
21 27 */
22 28 public static function post( $url, $body, $user_id ) {
29 + /**
30 + * Fires before an HTTP POST request is made.
31 + *
32 + * @param string $url The URL endpoint.
33 + * @param string $body The POST body.
34 + * @param int $user_id The WordPress User ID.
35 + */
23 36 \do_action( 'activitypub_pre_http_post', $url, $body, $user_id );
24 37
25 - $date = \gmdate( 'D, d M Y H:i:s T' );
26 - $digest = Signature::generate_digest( $body );
38 + $date = \gmdate( 'D, d M Y H:i:s T' );
39 + $digest = Signature::generate_digest( $body );
27 40 $signature = Signature::generate_signature( $user_id, 'post', $url, $date, $digest );
28 41
29 - $wp_version = \get_bloginfo( 'version' );
42 + $wp_version = get_masked_wp_version();
30 43
31 44 /**
32 - * Filter the HTTP headers user agent.
45 + * Filters the HTTP headers user agent string.
33 46 *
34 47 * @param string $user_agent The user agent string.
35 48 */
36 49 $user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) );
37 - $args = array(
38 - 'timeout' => 100,
50 + $args = array(
51 + 'timeout' => 100,
39 52 'limit_response_size' => 1048576,
40 - 'redirection' => 3,
41 - 'user-agent' => "$user_agent; ActivityPub",
42 - 'headers' => array(
43 - 'Accept' => 'application/activity+json',
53 + 'redirection' => 3,
54 + 'user-agent' => "$user_agent; ActivityPub",
55 + 'headers' => array(
56 + 'Accept' => 'application/activity+json',
44 57 'Content-Type' => 'application/activity+json',
45 - 'Digest' => $digest,
46 - 'Signature' => $signature,
47 - 'Date' => $date,
58 + 'Digest' => $digest,
59 + 'Signature' => $signature,
60 + 'Date' => $date,
48 61 ),
49 - 'body' => $body,
62 + 'body' => $body,
50 63 );
51 64
52 65 $response = \wp_safe_remote_post( $url, $args );
53 66 $code = \wp_remote_retrieve_response_code( $response );
@@ -52,11 +65,26 @@
52 65 $response = \wp_safe_remote_post( $url, $args );
53 66 $code = \wp_remote_retrieve_response_code( $response );
54 67
55 68 if ( $code >= 400 ) {
56 - $response = new WP_Error( $code, __( 'Failed HTTP Request', 'activitypub' ), array( 'status' => $code ) );
69 + $response = new WP_Error(
70 + $code,
71 + __( 'Failed HTTP Request', 'activitypub' ),
72 + array(
73 + 'status' => $code,
74 + 'response' => $response,
75 + )
76 + );
57 77 }
58 78
79 + /**
80 + * Action to save the response of the remote POST request.
81 + *
82 + * @param array|WP_Error $response The response of the remote POST request.
83 + * @param string $url The URL endpoint.
84 + * @param string $body The Post Body.
85 + * @param int $user_id The WordPress User-ID.
86 + */
59 87 \do_action( 'activitypub_safe_remote_post_response', $response, $url, $body, $user_id );
60 88
61 89 return $response;
62 90 }
@@ -61,40 +89,73 @@
61 89 return $response;
62 90 }
63 91
64 92 /**
65 - * Send a GET Request with the needed HTTP Headers
93 + * Send a GET Request with the needed HTTP Headers.
66 94 *
67 - * @param string $url The URL endpoint
68 - * @param int $user_id The WordPress User-ID
95 + * @param string $url The URL endpoint.
96 + * @param bool|int $cached Optional. Whether the result should be cached, or its duration. Default false.
69 97 *
70 - * @return array|WP_Error The GET Response or an WP_ERROR
98 + * @return array|WP_Error The GET Response or a WP_Error.
71 99 */
72 - public static function get( $url ) {
100 + public static function get( $url, $cached = false ) {
101 + /**
102 + * Fires before an HTTP GET request is made.
103 + *
104 + * @param string $url The URL endpoint.
105 + */
73 106 \do_action( 'activitypub_pre_http_get', $url );
74 107
75 - $date = \gmdate( 'D, d M Y H:i:s T' );
76 - $signature = Signature::generate_signature( Users::APPLICATION_USER_ID, 'get', $url, $date );
108 + if ( $cached ) {
109 + $transient_key = self::generate_cache_key( $url );
77 110
78 - $wp_version = \get_bloginfo( 'version' );
111 + $response = \get_transient( $transient_key );
79 112
113 + if ( $response ) {
114 + /**
115 + * Action to save the response of the remote GET request.
116 + *
117 + * @param array|WP_Error $response The response of the remote GET request.
118 + * @param string $url The URL endpoint.
119 + */
120 + \do_action( 'activitypub_safe_remote_get_response', $response, $url );
121 +
122 + return $response;
123 + }
124 + }
125 +
126 + $date = \gmdate( 'D, d M Y H:i:s T' );
127 + $signature = Signature::generate_signature( Actors::APPLICATION_USER_ID, 'get', $url, $date );
128 +
129 + $wp_version = get_masked_wp_version();
130 +
80 131 /**
81 - * Filter the HTTP headers user agent.
132 + * Filters the HTTP headers user agent string.
82 133 *
134 + * This filter allows developers to modify the user agent string that is
135 + * sent with HTTP requests.
136 + *
83 137 * @param string $user_agent The user agent string.
84 138 */
85 139 $user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) );
86 140
141 + /**
142 + * Filters the timeout duration for remote GET requests in ActivityPub.
143 + *
144 + * @param int $timeout The timeout value in seconds. Default 100 seconds.
145 + */
146 + $timeout = \apply_filters( 'activitypub_remote_get_timeout', 100 );
147 +
87 148 $args = array(
88 - 'timeout' => apply_filters( 'activitypub_remote_get_timeout', 100 ),
149 + 'timeout' => $timeout,
89 150 'limit_response_size' => 1048576,
90 - 'redirection' => 3,
91 - 'user-agent' => "$user_agent; ActivityPub",
92 - 'headers' => array(
93 - 'Accept' => 'application/activity+json',
151 + 'redirection' => 3,
152 + 'user-agent' => "$user_agent; ActivityPub",
153 + 'headers' => array(
154 + 'Accept' => 'application/activity+json',
94 155 'Content-Type' => 'application/activity+json',
95 - 'Signature' => $signature,
96 - 'Date' => $date,
156 + 'Signature' => $signature,
157 + 'Date' => $date,
97 158 ),
98 159 );
99 160
100 161 $response = \wp_safe_remote_get( $url, $args );
@@ -103,10 +164,24 @@
103 164 if ( $code >= 400 ) {
104 165 $response = new WP_Error( $code, __( 'Failed HTTP Request', 'activitypub' ), array( 'status' => $code ) );
105 166 }
106 167
168 + /**
169 + * Action to save the response of the remote GET request.
170 + *
171 + * @param array|WP_Error $response The response of the remote GET request.
172 + * @param string $url The URL endpoint.
173 + */
107 174 \do_action( 'activitypub_safe_remote_get_response', $response, $url );
108 175
176 + if ( $cached ) {
177 + $cache_duration = $cached;
178 + if ( ! is_int( $cache_duration ) ) {
179 + $cache_duration = HOUR_IN_SECONDS;
180 + }
181 + \set_transient( $transient_key, $response, $cache_duration );
182 + }
183 +
109 184 return $response;
110 185 }
111 186
112 187 /**
@@ -116,11 +191,16 @@
116 191 *
117 192 * @return bool True if the URL is a tombstone.
118 193 */
119 194 public static function is_tombstone( $url ) {
195 + /**
196 + * Fires before checking if the URL is a tombstone.
197 + *
198 + * @param string $url The URL to check.
199 + */
120 200 \do_action( 'activitypub_pre_http_is_tombstone', $url );
121 201
122 - $response = \wp_safe_remote_get( $url );
202 + $response = \wp_safe_remote_get( $url, array( 'headers' => array( 'Accept' => 'application/activity+json' ) ) );
123 203 $code = \wp_remote_retrieve_response_code( $response );
124 204
125 205 if ( in_array( (int) $code, array( 404, 410 ), true ) ) {
126 206 return true;
@@ -125,7 +205,101 @@
125 205 if ( in_array( (int) $code, array( 404, 410 ), true ) ) {
126 206 return true;
127 207 }
128 208
209 + $data = \wp_remote_retrieve_body( $response );
210 + $data = \json_decode( $data, true );
211 + if ( $data && isset( $data['type'] ) && 'Tombstone' === $data['type'] ) {
212 + return true;
213 + }
214 +
129 215 return false;
216 + }
217 +
218 + /**
219 + * Generate a cache key for the URL.
220 + *
221 + * @param string $url The URL to generate the cache key for.
222 + *
223 + * @return string The cache key.
224 + */
225 + public static function generate_cache_key( $url ) {
226 + return 'activitypub_http_' . \md5( $url );
227 + }
228 +
229 + /**
230 + * Requests the Data from the Object-URL or Object-Array.
231 + *
232 + * @param array|string $url_or_object The Object or the Object URL.
233 + * @param bool $cached Optional. Whether the result should be cached. Default true.
234 + *
235 + * @return array|WP_Error The Object data as array or WP_Error on failure.
236 + */
237 + public static function get_remote_object( $url_or_object, $cached = true ) {
238 + $url = object_to_uri( $url_or_object );
239 +
240 + if ( preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $url ) ) {
241 + $url = Webfinger::resolve( $url );
242 + }
243 +
244 + if ( ! $url ) {
245 + return new WP_Error(
246 + 'activitypub_no_valid_actor_identifier',
247 + \__( 'The "actor" identifier is not valid', 'activitypub' ),
248 + array(
249 + 'status' => 404,
250 + 'object' => $url,
251 + )
252 + );
253 + }
254 +
255 + if ( is_wp_error( $url ) ) {
256 + return $url;
257 + }
258 +
259 + $transient_key = self::generate_cache_key( $url );
260 +
261 + // Only check the cache if needed.
262 + if ( $cached ) {
263 + $data = \get_transient( $transient_key );
264 +
265 + if ( $data ) {
266 + return $data;
267 + }
268 + }
269 +
270 + if ( ! \wp_http_validate_url( $url ) ) {
271 + return new WP_Error(
272 + 'activitypub_no_valid_object_url',
273 + \__( 'The "object" is/has no valid URL', 'activitypub' ),
274 + array(
275 + 'status' => 400,
276 + 'object' => $url,
277 + )
278 + );
279 + }
280 +
281 + $response = self::get( $url );
282 +
283 + if ( \is_wp_error( $response ) ) {
284 + return $response;
285 + }
286 +
287 + $data = \wp_remote_retrieve_body( $response );
288 + $data = \json_decode( $data, true );
289 +
290 + if ( ! $data ) {
291 + return new WP_Error(
292 + 'activitypub_invalid_json',
293 + \__( 'No valid JSON data', 'activitypub' ),
294 + array(
295 + 'status' => 400,
296 + 'object' => $url,
297 + )
298 + );
299 + }
300 +
301 + \set_transient( $transient_key, $data, WEEK_IN_SECONDS );
302 +
303 + return $data;
130 304 }
131 305 }