PluginProbe
ActivityPub / 5.1.0
ActivityPub v5.1.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 5.1.0, at includes/class-http.php

323 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 $date = \gmdate( 'D, d M Y H:i:s T' );
39 $digest = Signature::generate_digest( $body );
40 $signature = Signature::generate_signature( $user_id, 'post', $url, $date, $digest );
41
42 $wp_version = get_masked_wp_version();
43
44 /**
45 * Filters the HTTP headers user agent string.
46 *
47 * @param string $user_agent The user agent string.
48 */
49 $user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) );
50 $args = array(
51 'timeout' => 100,
52 'limit_response_size' => 1048576,
53 'redirection' => 3,
54 'user-agent' => "$user_agent; ActivityPub",
55 'headers' => array(
56 'Accept' => 'application/activity+json',
57 'Content-Type' => 'application/activity+json',
58 'Digest' => $digest,
59 'Signature' => $signature,
60 'Date' => $date,
61 ),
62 'body' => $body,
63 );
64
65 $response = \wp_safe_remote_post( $url, $args );
66 $code = \wp_remote_retrieve_response_code( $response );
67
68 if ( $code >= 400 ) {
69 $response = new WP_Error(
70 $code,
71 __( 'Failed HTTP Request', 'activitypub' ),
72 array(
73 'status' => $code,
74 'response' => $response,
75 )
76 );
77 }
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 */
87 \do_action( 'activitypub_safe_remote_post_response', $response, $url, $body, $user_id );
88
89 return $response;
90 }
91
92 /**
93 * Send a GET Request with the needed HTTP Headers.
94 *
95 * @param string $url The URL endpoint.
96 * @param bool|int $cached Optional. Whether the result should be cached, or its duration. Default false.
97 *
98 * @return array|WP_Error The GET Response or a WP_Error.
99 */
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 */
106 \do_action( 'activitypub_pre_http_get', $url );
107
108 if ( $cached ) {
109 $transient_key = self::generate_cache_key( $url );
110
111 $response = \get_transient( $transient_key );
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
131 /**
132 * Filters the HTTP headers user agent string.
133 *
134 * This filter allows developers to modify the user agent string that is
135 * sent with HTTP requests.
136 *
137 * @param string $user_agent The user agent string.
138 */
139 $user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) );
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
148 $args = array(
149 'timeout' => $timeout,
150 'limit_response_size' => 1048576,
151 'redirection' => 3,
152 'user-agent' => "$user_agent; ActivityPub",
153 'headers' => array(
154 'Accept' => 'application/activity+json',
155 'Content-Type' => 'application/activity+json',
156 'Signature' => $signature,
157 'Date' => $date,
158 ),
159 );
160
161 $response = \wp_safe_remote_get( $url, $args );
162 $code = \wp_remote_retrieve_response_code( $response );
163
164 if ( $code >= 400 ) {
165 $response = new WP_Error( $code, __( 'Failed HTTP Request', 'activitypub' ), array( 'status' => $code ) );
166 }
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 */
174 \do_action( 'activitypub_safe_remote_get_response', $response, $url );
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
184 return $response;
185 }
186
187 /**
188 * Check for URL for Tombstone.
189 *
190 * @param string $url The URL to check.
191 *
192 * @return bool True if the URL is a tombstone.
193 */
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 */
200 \do_action( 'activitypub_pre_http_is_tombstone', $url );
201
202 $response = \wp_safe_remote_get( $url, array( 'headers' => array( 'Accept' => 'application/activity+json' ) ) );
203 $code = \wp_remote_retrieve_response_code( $response );
204
205 if ( in_array( (int) $code, array( 404, 410 ), true ) ) {
206 return true;
207 }
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
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 if ( is_array( $url_or_object ) ) {
239 if ( array_key_exists( 'id', $url_or_object ) ) {
240 $url = $url_or_object['id'];
241 } elseif ( array_key_exists( 'url', $url_or_object ) ) {
242 $url = $url_or_object['url'];
243 } else {
244 return new WP_Error(
245 'activitypub_no_valid_actor_identifier',
246 \__( 'The "actor" identifier is not valid', 'activitypub' ),
247 array(
248 'status' => 404,
249 'object' => $url_or_object,
250 )
251 );
252 }
253 } else {
254 $url = $url_or_object;
255 }
256
257 if ( preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $url ) ) {
258 $url = Webfinger::resolve( $url );
259 }
260
261 if ( ! $url ) {
262 return new WP_Error(
263 'activitypub_no_valid_actor_identifier',
264 \__( 'The "actor" identifier is not valid', 'activitypub' ),
265 array(
266 'status' => 404,
267 'object' => $url,
268 )
269 );
270 }
271
272 if ( is_wp_error( $url ) ) {
273 return $url;
274 }
275
276 $transient_key = self::generate_cache_key( $url );
277
278 // Only check the cache if needed.
279 if ( $cached ) {
280 $data = \get_transient( $transient_key );
281
282 if ( $data ) {
283 return $data;
284 }
285 }
286
287 if ( ! \wp_http_validate_url( $url ) ) {
288 return new WP_Error(
289 'activitypub_no_valid_object_url',
290 \__( 'The "object" is/has no valid URL', 'activitypub' ),
291 array(
292 'status' => 400,
293 'object' => $url,
294 )
295 );
296 }
297
298 $response = self::get( $url );
299
300 if ( \is_wp_error( $response ) ) {
301 return $response;
302 }
303
304 $data = \wp_remote_retrieve_body( $response );
305 $data = \json_decode( $data, true );
306
307 if ( ! $data ) {
308 return new WP_Error(
309 'activitypub_invalid_json',
310 \__( 'No valid JSON data', 'activitypub' ),
311 array(
312 'status' => 400,
313 'object' => $url,
314 )
315 );
316 }
317
318 \set_transient( $transient_key, $data, WEEK_IN_SECONDS );
319
320 return $data;
321 }
322 }
323