PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 2.9.6
Jetpack – WP Security, Backup, Speed, & Growth v2.9.6
15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / class.json-api.php
jetpack Last commit date
3rd-party 10 years ago _inc 10 years ago languages 10 years ago modules 5 years ago views 10 years ago .svnignore 10 years ago class.jetpack-bbpress-json-api-compat.php 10 years ago class.jetpack-cli.php 10 years ago class.jetpack-client-server.php 10 years ago class.jetpack-client.php 10 years ago class.jetpack-data.php 10 years ago class.jetpack-debugger.php 10 years ago class.jetpack-error.php 10 years ago class.jetpack-heartbeat.php 10 years ago class.jetpack-ixr-client.php 10 years ago class.jetpack-network-sites-list-table.php 10 years ago class.jetpack-network.php 10 years ago class.jetpack-options.php 10 years ago class.jetpack-post-images.php 10 years ago class.jetpack-signature.php 10 years ago class.jetpack-sync.php 10 years ago class.jetpack-user-agent.php 10 years ago class.jetpack-xmlrpc-server.php 10 years ago class.jetpack.php 10 years ago class.json-api-endpoints.php 3 years ago class.json-api.php 10 years ago class.media-extractor.php 10 years ago class.media-summary.php 10 years ago class.photon.php 10 years ago composer.json 10 years ago functions.compat.php 10 years ago functions.gallery.php 10 years ago functions.opengraph.php 10 years ago functions.photon.php 10 years ago functions.twitter-cards.php 10 years ago jetpack.php 3 years ago locales.php 10 years ago readme.txt 3 years ago require-lib.php 10 years ago uninstall.php 10 years ago
class.json-api.php
501 lines
1 <?php
2
3 defined( 'WPCOM_JSON_API__DEBUG' ) or define( 'WPCOM_JSON_API__DEBUG', false );
4
5 class WPCOM_JSON_API {
6 static $self = null;
7
8 var $endpoints = array();
9
10 var $token_details = array();
11
12 var $method = '';
13 var $url = '';
14 var $path = '';
15 var $version = null;
16 var $query = array();
17 var $post_body = null;
18 var $files = null;
19 var $content_type = null;
20 var $accept = '';
21
22 var $_server_https;
23 var $exit = true;
24 var $public_api_scheme = 'https';
25
26 var $trapped_error = null;
27 var $did_output = false;
28
29 static function init( $method = null, $url = null, $post_body = null ) {
30 if ( !self::$self ) {
31 $class = function_exists( 'get_called_class' ) ? get_called_class() : __CLASS__;
32 self::$self = new $class( $method, $url, $post_body );
33 }
34 return self::$self;
35 }
36
37 function add( WPCOM_JSON_API_Endpoint $endpoint ) {
38 if ( !isset( $this->endpoints[$endpoint->path] ) ) {
39 $this->endpoints[$endpoint->path] = array();
40 }
41 $this->endpoints[$endpoint->path][$endpoint->method] = $endpoint;
42 }
43
44 static function is_truthy( $value ) {
45 switch ( strtolower( (string) $value ) ) {
46 case '1' :
47 case 't' :
48 case 'true' :
49 return true;
50 }
51
52 return false;
53 }
54
55 function __construct() {
56 $args = func_get_args();
57 call_user_func_array( array( $this, 'setup_inputs' ), $args );
58 }
59
60 function setup_inputs( $method = null, $url = null, $post_body = null ) {
61 if ( is_null( $method ) ) {
62 $this->method = strtoupper( $_SERVER['REQUEST_METHOD'] );
63 } else {
64 $this->method = strtoupper( $method );
65 }
66 if ( is_null( $url ) ) {
67 $this->url = ( is_ssl() ? 'https' : 'http' ) . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
68 } else {
69 $this->url = $url;
70 }
71
72 $parsed = parse_url( $this->url );
73 $this->path = $parsed['path'];
74
75 if ( !empty( $parsed['query'] ) ) {
76 wp_parse_str( $parsed['query'], $this->query );
77 }
78
79 if ( isset( $_SERVER['HTTP_ACCEPT'] ) && $_SERVER['HTTP_ACCEPT'] ) {
80 $this->accept = $_SERVER['HTTP_ACCEPT'];
81 }
82
83 if ( 'POST' === $this->method ) {
84 if ( is_null( $post_body ) ) {
85 $this->post_body = file_get_contents( 'php://input' );
86
87 if ( isset( $_SERVER['HTTP_CONTENT_TYPE'] ) && $_SERVER['HTTP_CONTENT_TYPE'] ) {
88 $this->content_type = $_SERVER['HTTP_CONTENT_TYPE'];
89 } elseif ( isset( $_SERVER['CONTENT_TYPE'] ) && $_SERVER['CONTENT_TYPE'] ) {
90 $this->content_type = $_SERVER['CONTENT_TYPE'] ;
91 } elseif ( '{' === $this->post_body[0] ) {
92 $this->content_type = 'application/json';
93 } else {
94 $this->content_type = 'application/x-www-form-urlencoded';
95 }
96
97 if ( 0 === strpos( strtolower( $this->content_type ), 'multipart/' ) ) {
98 $this->post_body = http_build_query( stripslashes_deep( $_POST ) );
99 $this->files = $_FILES;
100 $this->content_type = 'multipart/form-data';
101 }
102 } else {
103 $this->post_body = $post_body;
104 $this->content_type = '{' === $this->post_body[0] ? 'application/json' : 'application/x-www-form-urlencoded';
105 }
106 } else {
107 $this->post_body = null;
108 $this->content_type = null;
109 }
110
111 $this->_server_https = array_key_exists( 'HTTPS', $_SERVER ) ? $_SERVER['HTTPS'] : '--UNset--';
112 }
113
114 function initialize() {
115 $this->token_details['blog_id'] = Jetpack_Options::get_option( 'id' );
116 }
117
118 function serve( $exit = true ) {
119 $this->exit = (bool) $exit;
120
121 add_filter( 'home_url', array( $this, 'ensure_http_scheme_of_home_url' ), 10, 3 );
122
123 add_filter( 'user_can_richedit', '__return_true' );
124
125 add_filter( 'comment_edit_pre', array( $this, 'comment_edit_pre' ) );
126
127 $initialization = $this->initialize();
128 if ( is_wp_error( $initialization ) ) {
129 $this->output_error( $initialization );
130 return;
131 }
132
133 // Normalize path and extract API version
134 $this->path = untrailingslashit( $this->path );
135 preg_match( '#^/rest/v1(\.\d+)*#', $this->path, $matches );
136 $this->path = substr( $this->path, strlen( $matches[0] ) );
137 $this->version = $matches[1];
138
139 $allowed_methods = array( 'GET', 'POST' );
140 $four_oh_five = false;
141
142 $is_help = preg_match( '#/help/?$#i', $this->path );
143 $matching_endpoints = array();
144
145 if ( $is_help ) {
146 $this->path = substr( rtrim( $this->path, '/' ), 0, -5 );
147 // Show help for all matching endpoints regardless of method
148 $methods = $allowed_methods;
149 $find_all_matching_endpoints = true;
150 // How deep to truncate each endpoint's path to see if it matches this help request
151 $depth = substr_count( $this->path, '/' ) + 1;
152 if ( false !== stripos( $this->accept, 'javascript' ) || false !== stripos( $this->accept, 'json' ) ) {
153 $help_content_type = 'json';
154 } else {
155 $help_content_type = 'html';
156 }
157 } else {
158 if ( in_array( $this->method, $allowed_methods ) ) {
159 // Only serve requested method
160 $methods = array( $this->method );
161 $find_all_matching_endpoints = false;
162 } else {
163 // We don't allow this requested method - find matching endpoints and send 405
164 $methods = $allowed_methods;
165 $find_all_matching_endpoints = true;
166 $four_oh_five = true;
167 }
168 }
169
170 // Find which endpoint to serve
171 $found = false;
172 foreach ( $this->endpoints as $endpoint_path => $endpoints_by_method ) {
173 foreach ( $methods as $method ) {
174 if ( !isset( $endpoints_by_method[$method] ) ) {
175 continue;
176 }
177
178 // Normalize
179 $endpoint_path = untrailingslashit( $endpoint_path );
180 if ( $is_help ) {
181 // Truncate path at help depth
182 $endpoint_path = join( '/', array_slice( explode( '/', $endpoint_path ), 0, $depth ) );
183 }
184
185 // Generate regular expression from sprintf()
186 $endpoint_path_regex = str_replace( array( '%s', '%d' ), array( '([^/?&]+)', '(\d+)' ), $endpoint_path );
187
188 if ( !preg_match( "#^$endpoint_path_regex\$#", $this->path, $path_pieces ) ) {
189 // This endpoint does not match the requested path.
190 continue;
191 }
192
193 $found = true;
194
195 if ( $find_all_matching_endpoints ) {
196 $matching_endpoints[] = array( $endpoints_by_method[$method], $path_pieces );
197 } else {
198 // The method parameters are now in $path_pieces
199 $endpoint = $endpoints_by_method[$method];
200 break 2;
201 }
202 }
203 }
204
205 if ( !$found ) {
206 return $this->output( 404, '', 'text/plain' );
207 }
208
209 if ( $four_oh_five ) {
210 $allowed_methods = array();
211 foreach ( $matching_endpoints as $matching_endpoint ) {
212 $allowed_methods[] = $matching_endpoint[0]->method;
213 }
214
215 header( 'Allow: ' . strtoupper( join( ',', array_unique( $allowed_methods ) ) ) );
216 return $this->output( 405, array( 'error' => 'not_allowed', 'error_message' => 'Method not allowed' ) );
217 }
218
219 if ( $is_help ) {
220 do_action( 'wpcom_json_api_output', 'help' );
221 if ( 'json' === $help_content_type ) {
222 $docs = array();
223 foreach ( $matching_endpoints as $matching_endpoint ) {
224 if ( !$matching_endpoint[0]->in_testing || WPCOM_JSON_API__DEBUG )
225 $docs[] = call_user_func( array( $matching_endpoint[0], 'generate_documentation' ) );
226 }
227 return $this->output( 200, $docs );
228 } else {
229 status_header( 200 );
230 foreach ( $matching_endpoints as $matching_endpoint ) {
231 if ( !$matching_endpoint[0]->in_testing || WPCOM_JSON_API__DEBUG )
232 call_user_func( array( $matching_endpoint[0], 'document' ) );
233 }
234 }
235 exit;
236 }
237
238 if ( $endpoint->in_testing && !WPCOM_JSON_API__DEBUG ) {
239 return $this->output( 404, '', 'text/plain' );
240 }
241
242 do_action( 'wpcom_json_api_output', $endpoint->stat );
243
244 $response = $this->process_request( $endpoint, $path_pieces );
245
246 if ( !$response ) {
247 return $this->output( 500, '', 'text/plain' );
248 } elseif ( is_wp_error( $response ) ) {
249 return $this->output_error( $response );
250 }
251
252 return $this->output( 200, $response );
253 }
254
255 function process_request( WPCOM_JSON_API_Endpoint $endpoint, $path_pieces ) {
256 $this->endpoint = $endpoint;
257 return call_user_func_array( array( $endpoint, 'callback' ), $path_pieces );
258 }
259
260 function output_early( $status_code, $response = null, $content_type = 'application/json' ) {
261 $exit = $this->exit;
262 $this->exit = false;
263 if ( is_wp_error( $response ) )
264 $this->output_error( $response );
265 else
266 $this->output( $status_code, $response, $content_type );
267 $this->exit = $exit;
268 $this->finish_request();
269 }
270
271 function output( $status_code, $response = null, $content_type = 'application/json' ) {
272 // In case output() was called before the callback returned
273 if ( $this->did_output ) {
274 if ( $this->exit )
275 exit;
276 return $content_type;
277 }
278 $this->did_output = true;
279
280 if ( is_null( $response ) ) {
281 $response = new stdClass;
282 }
283
284 if ( 'text/plain' === $content_type ) {
285 status_header( (int) $status_code );
286 header( 'Content-Type: text/plain' );
287 echo $response;
288 if ( $this->exit ) {
289 exit;
290 }
291
292 return $content_type;
293 }
294
295 if ( isset( $this->query['http_envelope'] ) && self::is_truthy( $this->query['http_envelope'] ) ) {
296 $response = array(
297 'code' => (int) $status_code,
298 'headers' => array(
299 array(
300 'name' => 'Content-Type',
301 'value' => $content_type,
302 ),
303 ),
304 'body' => $response,
305 );
306 $status_code = 200;
307 $content_type = 'application/json';
308 }
309
310 status_header( (int) $status_code );
311 header( "Content-Type: $content_type" );
312 if ( isset( $this->query['callback'] ) && is_string( $this->query['callback'] ) ) {
313 $callback = preg_replace( '/[^a-z0-9_.]/i', '', $this->query['callback'] );
314 } else {
315 $callback = false;
316 }
317
318 if ( $callback ) {
319 echo "$callback(";
320 }
321 echo $this->json_encode( $response );
322 if ( $callback ) {
323 echo ");";
324 }
325
326 if ( $this->exit ) {
327 exit;
328 }
329
330 return $content_type;
331 }
332
333 function output_error( $error ) {
334 $status_code = $error->get_error_data();
335
336 if ( is_array( $status_code ) )
337 $status_code = $status_code['status_code'];
338
339 if ( !$status_code ) {
340 $status_code = 400;
341 }
342 $response = array(
343 'error' => $error->get_error_code(),
344 'message' => $error->get_error_message(),
345 );
346 return $this->output( $status_code, $response );
347 }
348
349 function ensure_http_scheme_of_home_url( $url, $path, $original_scheme ) {
350 if ( $original_scheme ) {
351 return $url;
352 }
353
354 return preg_replace( '#^https:#', 'http:', $url );
355 }
356
357 function comment_edit_pre( $comment_content ) {
358 return htmlspecialchars_decode( $comment_content, ENT_QUOTES );
359 }
360
361 function json_encode( $data ) {
362 return json_encode( $data );
363 }
364
365 function ends_with( $haystack, $needle ) {
366 return $needle === substr( $haystack, -strlen( $needle ) );
367 }
368
369 // Returns the site's blog_id in the WP.com ecosystem
370 function get_blog_id_for_output() {
371 return $this->token_details['blog_id'];
372 }
373
374 // Returns the site's local blog_id
375 function get_blog_id( $blog_id ) {
376 return $GLOBALS['blog_id'];
377 }
378
379 function switch_to_blog_and_validate_user( $blog_id = 0, $verify_token_for_blog = true ) {
380 if ( -1 == get_option( 'blog_public' ) && !current_user_can( 'read' ) ) {
381 return new WP_Error( 'unauthorized', 'User cannot access this private blog.', 403 );
382 }
383
384 return $blog_id;
385 }
386
387 function post_like_count( $blog_id, $post_id ) {
388 return 0;
389 }
390
391 function is_liked( $blog_id, $post_id ) {
392 return false;
393 }
394
395 function is_reblogged( $blog_id, $post_id ) {
396 return false;
397 }
398
399 function is_following( $blog_id ) {
400 return false;
401 }
402
403 function add_global_ID( $blog_id, $post_id ) {
404 return '';
405 }
406
407 function get_avatar_url( $email ) {
408 add_filter( 'pre_option_show_avatars', '__return_true', 999 );
409 $_SERVER['HTTPS'] = 'off';
410
411 $avatar_img_element = get_avatar( $email, 96, '' );
412
413 if ( !$avatar_img_element || is_wp_error( $avatar_img_element ) ) {
414 $return = '';
415 } elseif ( !preg_match( '#src=([\'"])?(.*?)(?(1)\\1|\s)#', $avatar_img_element, $matches ) ) {
416 $return = '';
417 } else {
418 $return = esc_url_raw( htmlspecialchars_decode( $matches[2] ) );
419 }
420
421 remove_filter( 'pre_option_show_avatars', '__return_true', 999 );
422 if ( '--UNset--' === $this->_server_https ) {
423 unset( $_SERVER['HTTPS'] );
424 } else {
425 $_SERVER['HTTPS'] = $this->_server_https;
426 }
427
428 return $return;
429 }
430
431 /**
432 * Traps `wp_die()` calls and outputs a JSON response instead.
433 * The result is always output, never returned.
434 *
435 * @param string|null $error_code. Call with string to start the trapping. Call with null to stop.
436 */
437 function trap_wp_die( $error_code = null ) {
438 // Stop trapping
439 if ( is_null( $error_code ) ) {
440 $this->trapped_error = null;
441 remove_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
442 return;
443 }
444
445 // If API called via PHP, bail: don't do our custom wp_die(). Do the normal wp_die().
446 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
447 if ( ! defined( 'REST_API_REQUEST' ) || ! REST_API_REQUEST ) {
448 return;
449 }
450 } else {
451 if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
452 return;
453 }
454 }
455
456 // Start trapping
457 $this->trapped_error = array(
458 'status' => 500,
459 'code' => $error_code,
460 'message' => '',
461 );
462
463 add_filter( 'wp_die_handler', array( $this, 'wp_die_handler_callback' ) );
464 }
465
466 function wp_die_handler_callback() {
467 return array( $this, 'wp_die_handler' );
468 }
469
470 function wp_die_handler( $message, $title = '', $args = array() ) {
471 $args = wp_parse_args( $args, array(
472 'response' => 500,
473 ) );
474
475 if ( $title ) {
476 $message = "$title: $message";
477 }
478
479 $this->trapped_error['status'] = $args['response'];
480 $this->trapped_error['message'] = wp_kses( $message, array() );
481
482 // We still want to exit so that code execution stops where it should.
483 // Attach the JSON output to WordPress' shutdown handler
484 add_action( 'shutdown', array( $this, 'output_trapped_error' ), 0 );
485 exit;
486 }
487
488 function output_trapped_error() {
489 $this->exit = false; // We're already exiting once. Don't do it twice.
490 $this->output( $this->trapped_error['status'], (object) array(
491 'error' => $this->trapped_error['code'],
492 'message' => $this->trapped_error['message'],
493 ) );
494 }
495
496 function finish_request() {
497 if ( function_exists( 'fastcgi_finish_request' ) )
498 return fastcgi_finish_request();
499 }
500 }
501