PluginProbe
ActivityPub / 9.0.0
ActivityPub v9.0.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-http.php

class-http.php in ActivityPub 9.0.0, at includes/class-http.php

317 lines 9.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ActivityPub HTTP Class.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub;
9
10 use Activitypub\Collection\Actors;
11
12 /**
13 * ActivityPub HTTP Class
14 *
15 * @author Matthias Pfefferle
16 */
17 class Http {
18 /**
19 * Send a POST Request with the needed HTTP Headers
20 *
21 * @param string $url The URL endpoint.
22 * @param string $body The Post Body.
23 * @param int $user_id The WordPress User-ID.
24 *
25 * @return array|\WP_Error The POST Response or an WP_Error.
26 */
27 public static function post( $url, $body, $user_id ) {
28 /**
29 * Fires before an HTTP POST request is made.
30 *
31 * @param string $url The URL endpoint.
32 * @param string $body The POST body.
33 * @param int $user_id The WordPress User ID.
34 */
35 \do_action( 'activitypub_pre_http_post', $url, $body, $user_id );
36
37 /**
38 * Filters the HTTP headers user agent string.
39 *
40 * @param string $user_agent The user agent string.
41 * @param string $url The request URL.
42 */
43 $user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . get_masked_wp_version() . '; ' . \get_bloginfo( 'url' ), $url );
44
45 /**
46 * Filters the timeout duration for remote POST requests in ActivityPub.
47 *
48 * @param int $timeout The timeout value in seconds. Default 10 seconds.
49 */
50 $timeout = \apply_filters( 'activitypub_remote_post_timeout', 10 );
51
52 $args = array(
53 'timeout' => $timeout,
54 'limit_response_size' => 1048576,
55 'redirection' => 3,
56 'user-agent' => "$user_agent; ActivityPub",
57 'headers' => array(
58 'Accept' => 'application/activity+json',
59 'Content-Type' => 'application/activity+json',
60 'Date' => \gmdate( 'D, d M Y H:i:s T' ),
61 ),
62 'body' => $body,
63 'key_id' => \json_decode( $body )->actor . '#main-key',
64 'private_key' => Actors::get_private_key( $user_id ),
65 'user_id' => $user_id,
66 );
67
68 $response = \wp_safe_remote_post( $url, $args );
69 $code = \wp_remote_retrieve_response_code( $response );
70
71 if ( $code >= 400 ) {
72 $response = new \WP_Error(
73 $code,
74 __( 'Failed HTTP Request', 'activitypub' ),
75 array(
76 'status' => $code,
77 'response' => $response,
78 )
79 );
80 }
81
82 /**
83 * Action to save the response of the remote POST request.
84 *
85 * @param array|\WP_Error $response The response of the remote POST request.
86 * @param string $url The URL endpoint.
87 * @param string $body The Post Body.
88 * @param int $user_id The WordPress User-ID.
89 */
90 \do_action( 'activitypub_safe_remote_post_response', $response, $url, $body, $user_id );
91
92 return $response;
93 }
94
95 /**
96 * Send a GET Request with the needed HTTP Headers.
97 *
98 * @param string $url The URL endpoint.
99 * @param array $args Optional. Additional arguments to customize the request.
100 * - 'headers': Array of headers to override defaults.
101 * @param bool|int $cached Optional. Whether to return cached results, or cache duration. Default false.
102 *
103 * @return array|\WP_Error The GET Response or a WP_Error.
104 */
105 public static function get( $url, $args = array(), $cached = false ) {
106 // Backward compatibility: if $args is boolean/int, it's the old $cached parameter.
107 if ( ! \is_array( $args ) ) {
108 \_deprecated_argument(
109 __METHOD__,
110 '7.9.0',
111 \esc_html__( 'The $cached parameter should now be passed as the third argument.', 'activitypub' )
112 );
113 $cached = $args;
114 $args = array();
115 }
116
117 /**
118 * Fires before an HTTP GET request is made.
119 *
120 * @param string $url The URL endpoint.
121 */
122 \do_action( 'activitypub_pre_http_get', $url );
123
124 $transient_key = self::generate_cache_key( $url );
125
126 // Check cache only if caching is requested.
127 if ( $cached ) {
128 $response = \get_transient( $transient_key );
129
130 if ( $response ) {
131 /**
132 * Action to save the response of the remote GET request.
133 *
134 * @param array|\WP_Error $response The response of the remote GET request.
135 * @param string $url The URL endpoint.
136 */
137 \do_action( 'activitypub_safe_remote_get_response', $response, $url );
138
139 return $response;
140 }
141 }
142
143 /**
144 * Filters the HTTP headers user agent string.
145 *
146 * @param string $user_agent The user agent string.
147 * @param string $url The request URL.
148 */
149 $user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . get_masked_wp_version() . '; ' . \get_bloginfo( 'url' ), $url );
150
151 /**
152 * Filters the timeout duration for remote GET requests in ActivityPub.
153 *
154 * @param int $timeout The timeout value in seconds. Default 10 seconds.
155 */
156 $timeout = \apply_filters( 'activitypub_remote_get_timeout', 10 );
157
158 $defaults = array(
159 'timeout' => $timeout,
160 'limit_response_size' => 1048576,
161 'redirection' => 3,
162 'user-agent' => "$user_agent; ActivityPub",
163 'headers' => array(
164 'Accept' => 'application/activity+json',
165 'Content-Type' => 'application/activity+json',
166 'Date' => \gmdate( 'D, d M Y H:i:s T' ),
167 ),
168 'key_id' => Actors::get_by_id( Actors::APPLICATION_USER_ID )->get_id() . '#main-key',
169 'private_key' => Actors::get_private_key( Actors::APPLICATION_USER_ID ),
170 );
171
172 $args = \wp_parse_args( $args, $defaults );
173 $args['headers'] = \wp_parse_args( $args['headers'], $defaults['headers'] );
174
175 $response = \wp_safe_remote_get( $url, $args );
176 $code = \wp_remote_retrieve_response_code( $response );
177
178 if ( \is_wp_error( $response ) || $code >= 400 ) {
179 if ( ! $code ) {
180 $code = 0;
181 }
182 $response = new \WP_Error( $code, __( 'Failed HTTP Request', 'activitypub' ), array( 'status' => $code ) );
183
184 /*
185 * Always cache errors to prevent repeated timeout waits.
186 * - Retriable errors (timeouts, 5xx): 1 minute (server may recover quickly).
187 * - Other errors (4xx): 15 minutes (client errors are more permanent).
188 */
189 if ( \in_array( $code, ACTIVITYPUB_RETRY_ERROR_CODES, true ) || 0 === $code ) {
190 $cache_duration = MINUTE_IN_SECONDS;
191 } else {
192 $cache_duration = 15 * MINUTE_IN_SECONDS;
193 }
194
195 \set_transient( $transient_key, $response, $cache_duration );
196
197 return $response;
198 }
199
200 /**
201 * Action to save the response of the remote GET request.
202 *
203 * @param array|\WP_Error $response The response of the remote GET request.
204 * @param string $url The URL endpoint.
205 */
206 \do_action( 'activitypub_safe_remote_get_response', $response, $url );
207
208 // Always cache successful responses.
209 $cache_duration = $cached;
210 if ( ! is_int( $cache_duration ) ) {
211 $cache_duration = HOUR_IN_SECONDS;
212 }
213 \set_transient( $transient_key, $response, $cache_duration );
214
215 return $response;
216 }
217
218 /**
219 * Check for URL for Tombstone.
220 *
221 * @param string $url The URL to check.
222 *
223 * @return bool True if the URL is a tombstone.
224 */
225 public static function is_tombstone( $url ) {
226 _deprecated_function( __METHOD__, '7.3.0', 'Activitypub\Tombstone::exists_remote' );
227
228 return Tombstone::exists_remote( $url );
229 }
230
231 /**
232 * Generate a cache key for the URL.
233 *
234 * @param string $url The URL to generate the cache key for.
235 *
236 * @return string The cache key.
237 */
238 public static function generate_cache_key( $url ) {
239 return 'activitypub_http_' . \md5( $url );
240 }
241
242 /**
243 * Requests the Data from the Object-URL or Object-Array.
244 *
245 * @param array|string $url_or_object The Object or the Object URL.
246 * @param bool $cached Optional. Whether the result should be cached. Default true.
247 *
248 * @return array|\WP_Error The Object data as array or WP_Error on failure.
249 */
250 public static function get_remote_object( $url_or_object, $cached = true ) {
251 /**
252 * Filters the preemptive return value of a remote object request.
253 *
254 * @param array|string|null $response The response.
255 * @param array|string|null $url_or_object The Object or the Object URL.
256 */
257 $response = apply_filters( 'activitypub_pre_http_get_remote_object', null, $url_or_object );
258 if ( null !== $response ) {
259 return $response;
260 }
261
262 $url = object_to_uri( $url_or_object );
263
264 if ( Webfinger::is_acct( $url ) ) {
265 $url = Webfinger::resolve( $url );
266 }
267
268 if ( ! $url ) {
269 return new \WP_Error(
270 'activitypub_no_valid_actor_identifier',
271 \__( 'The "actor" identifier is not valid', 'activitypub' ),
272 array(
273 'status' => 404,
274 'object' => $url,
275 )
276 );
277 }
278
279 if ( \is_wp_error( $url ) ) {
280 return $url;
281 }
282
283 if ( ! \wp_http_validate_url( $url ) ) {
284 return new \WP_Error(
285 'activitypub_no_valid_object_url',
286 \__( 'The "object" is/has no valid URL', 'activitypub' ),
287 array(
288 'status' => 400,
289 'object' => $url,
290 )
291 );
292 }
293
294 $response = self::get( $url, array(), $cached );
295
296 if ( \is_wp_error( $response ) ) {
297 return $response;
298 }
299
300 $data = \wp_remote_retrieve_body( $response );
301 $data = \json_decode( $data, true );
302
303 if ( ! $data ) {
304 return new \WP_Error(
305 'activitypub_invalid_json',
306 \__( 'No valid JSON data', 'activitypub' ),
307 array(
308 'status' => 400,
309 'object' => $url,
310 )
311 );
312 }
313
314 return $data;
315 }
316 }
317