PluginProbe
ActivityPub / 7.0.0
ActivityPub v7.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 7.0.0, at includes/class-http.php

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