PluginProbe
ActivityPub / 4.7.1
ActivityPub v4.7.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
activitypub / includes / class-http.php

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

310 lines 8.2 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( $code, __( 'Failed HTTP Request', 'activitypub' ), array( 'status' => $code ) );
70 }
71
72 /**
73 * Action to save the response of the remote POST request.
74 *
75 * @param array|WP_Error $response The response of the remote POST request.
76 * @param string $url The URL endpoint.
77 * @param string $body The Post Body.
78 * @param int $user_id The WordPress User-ID.
79 */
80 \do_action( 'activitypub_safe_remote_post_response', $response, $url, $body, $user_id );
81
82 return $response;
83 }
84
85 /**
86 * Send a GET Request with the needed HTTP Headers.
87 *
88 * @param string $url The URL endpoint.
89 * @param bool|int $cached Optional. Whether the result should be cached, or its duration. Default false.
90 *
91 * @return array|WP_Error The GET Response or a WP_Error.
92 */
93 public static function get( $url, $cached = false ) {
94 /**
95 * Fires before an HTTP GET request is made.
96 *
97 * @param string $url The URL endpoint.
98 */
99 \do_action( 'activitypub_pre_http_get', $url );
100
101 if ( $cached ) {
102 $transient_key = self::generate_cache_key( $url );
103
104 $response = \get_transient( $transient_key );
105
106 if ( $response ) {
107 /**
108 * Action to save the response of the remote GET request.
109 *
110 * @param array|WP_Error $response The response of the remote GET request.
111 * @param string $url The URL endpoint.
112 */
113 \do_action( 'activitypub_safe_remote_get_response', $response, $url );
114
115 return $response;
116 }
117 }
118
119 $date = \gmdate( 'D, d M Y H:i:s T' );
120 $signature = Signature::generate_signature( Actors::APPLICATION_USER_ID, 'get', $url, $date );
121
122 $wp_version = get_masked_wp_version();
123
124 /**
125 * Filters the HTTP headers user agent string.
126 *
127 * This filter allows developers to modify the user agent string that is
128 * sent with HTTP requests.
129 *
130 * @param string $user_agent The user agent string.
131 */
132 $user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) );
133
134 /**
135 * Filters the timeout duration for remote GET requests in ActivityPub.
136 *
137 * @param int $timeout The timeout value in seconds. Default 100 seconds.
138 */
139 $timeout = \apply_filters( 'activitypub_remote_get_timeout', 100 );
140
141 $args = array(
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 'Signature' => $signature,
150 'Date' => $date,
151 ),
152 );
153
154 $response = \wp_safe_remote_get( $url, $args );
155 $code = \wp_remote_retrieve_response_code( $response );
156
157 if ( $code >= 400 ) {
158 $response = new WP_Error( $code, __( 'Failed HTTP Request', 'activitypub' ), array( 'status' => $code ) );
159 }
160
161 /**
162 * Action to save the response of the remote GET request.
163 *
164 * @param array|WP_Error $response The response of the remote GET request.
165 * @param string $url The URL endpoint.
166 */
167 \do_action( 'activitypub_safe_remote_get_response', $response, $url );
168
169 if ( $cached ) {
170 $cache_duration = $cached;
171 if ( ! is_int( $cache_duration ) ) {
172 $cache_duration = HOUR_IN_SECONDS;
173 }
174 \set_transient( $transient_key, $response, $cache_duration );
175 }
176
177 return $response;
178 }
179
180 /**
181 * Check for URL for Tombstone.
182 *
183 * @param string $url The URL to check.
184 *
185 * @return bool True if the URL is a tombstone.
186 */
187 public static function is_tombstone( $url ) {
188 /**
189 * Fires before checking if the URL is a tombstone.
190 *
191 * @param string $url The URL to check.
192 */
193 \do_action( 'activitypub_pre_http_is_tombstone', $url );
194
195 $response = \wp_safe_remote_get( $url );
196 $code = \wp_remote_retrieve_response_code( $response );
197
198 if ( in_array( (int) $code, array( 404, 410 ), true ) ) {
199 return true;
200 }
201
202 return false;
203 }
204
205 /**
206 * Generate a cache key for the URL.
207 *
208 * @param string $url The URL to generate the cache key for.
209 *
210 * @return string The cache key.
211 */
212 public static function generate_cache_key( $url ) {
213 return 'activitypub_http_' . \md5( $url );
214 }
215
216 /**
217 * Requests the Data from the Object-URL or Object-Array.
218 *
219 * @param array|string $url_or_object The Object or the Object URL.
220 * @param bool $cached Optional. Whether the result should be cached. Default true.
221 *
222 * @return array|WP_Error The Object data as array or WP_Error on failure.
223 */
224 public static function get_remote_object( $url_or_object, $cached = true ) {
225 if ( is_array( $url_or_object ) ) {
226 if ( array_key_exists( 'id', $url_or_object ) ) {
227 $url = $url_or_object['id'];
228 } elseif ( array_key_exists( 'url', $url_or_object ) ) {
229 $url = $url_or_object['url'];
230 } else {
231 return new WP_Error(
232 'activitypub_no_valid_actor_identifier',
233 \__( 'The "actor" identifier is not valid', 'activitypub' ),
234 array(
235 'status' => 404,
236 'object' => $url_or_object,
237 )
238 );
239 }
240 } else {
241 $url = $url_or_object;
242 }
243
244 if ( preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $url ) ) {
245 $url = Webfinger::resolve( $url );
246 }
247
248 if ( ! $url ) {
249 return new WP_Error(
250 'activitypub_no_valid_actor_identifier',
251 \__( 'The "actor" identifier is not valid', 'activitypub' ),
252 array(
253 'status' => 404,
254 'object' => $url,
255 )
256 );
257 }
258
259 if ( is_wp_error( $url ) ) {
260 return $url;
261 }
262
263 $transient_key = self::generate_cache_key( $url );
264
265 // Only check the cache if needed.
266 if ( $cached ) {
267 $data = \get_transient( $transient_key );
268
269 if ( $data ) {
270 return $data;
271 }
272 }
273
274 if ( ! \wp_http_validate_url( $url ) ) {
275 return new WP_Error(
276 'activitypub_no_valid_object_url',
277 \__( 'The "object" is/has no valid URL', 'activitypub' ),
278 array(
279 'status' => 400,
280 'object' => $url,
281 )
282 );
283 }
284
285 $response = self::get( $url );
286
287 if ( \is_wp_error( $response ) ) {
288 return $response;
289 }
290
291 $data = \wp_remote_retrieve_body( $response );
292 $data = \json_decode( $data, true );
293
294 if ( ! $data ) {
295 return new WP_Error(
296 'activitypub_invalid_json',
297 \__( 'No valid JSON data', 'activitypub' ),
298 array(
299 'status' => 400,
300 'object' => $url,
301 )
302 );
303 }
304
305 \set_transient( $transient_key, $data, WEEK_IN_SECONDS );
306
307 return $data;
308 }
309 }
310