PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 4.3.5
Jetpack – WP Security, Backup, Speed, & Growth v4.3.5
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / class.jetpack-client.php
class.jetpack-client.php
289 lines 8.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 class Jetpack_Client {
4 const WPCOM_JSON_API_VERSION = '1.1';
5
6 /**
7 * Makes an authorized remote request using Jetpack_Signature
8 *
9 * @return array|WP_Error WP HTTP response on success
10 */
11 public static function remote_request( $args, $body = null ) {
12 $defaults = array(
13 'url' => '',
14 'user_id' => 0,
15 'blog_id' => 0,
16 'auth_location' => JETPACK_CLIENT__AUTH_LOCATION,
17 'method' => 'POST',
18 'timeout' => 10,
19 'redirection' => 0,
20 'headers' => array(),
21 );
22
23 $args = wp_parse_args( $args, $defaults );
24
25 $args['blog_id'] = (int) $args['blog_id'];
26
27 if ( 'header' != $args['auth_location'] ) {
28 $args['auth_location'] = 'query_string';
29 }
30
31 $token = Jetpack_Data::get_access_token( $args['user_id'] );
32 if ( !$token ) {
33 return new Jetpack_Error( 'missing_token' );
34 }
35
36 $method = strtoupper( $args['method'] );
37
38 $timeout = intval( $args['timeout'] );
39
40 $redirection = $args['redirection'];
41
42 $request = compact( 'method', 'body', 'timeout', 'redirection' );
43
44 @list( $token_key, $secret ) = explode( '.', $token->secret );
45 if ( empty( $token ) || empty( $secret ) ) {
46 return new Jetpack_Error( 'malformed_token' );
47 }
48
49 $token_key = sprintf( '%s:%d:%d', $token_key, JETPACK__API_VERSION, $token->external_user_id );
50
51 require_once JETPACK__PLUGIN_DIR . 'class.jetpack-signature.php';
52
53 $time_diff = (int) Jetpack_Options::get_option( 'time_diff' );
54 $jetpack_signature = new Jetpack_Signature( $token->secret, $time_diff );
55
56 $timestamp = time() + $time_diff;
57
58 if( function_exists( 'wp_generate_password' ) ) {
59 $nonce = wp_generate_password( 10, false );
60 } else {
61 $nonce = substr( sha1( rand( 0, 1000000 ) ), 0, 10);
62 }
63
64 // Kind of annoying. Maybe refactor Jetpack_Signature to handle body-hashing
65 if ( is_null( $body ) ) {
66 $body_hash = '';
67 } else {
68 if ( !is_string( $body ) ) {
69 return new Jetpack_Error( 'invalid_body', 'Body is malformed.' );
70 }
71 $body_hash = jetpack_sha1_base64( $body );
72 }
73
74 $auth = array(
75 'token' => $token_key,
76 'timestamp' => $timestamp,
77 'nonce' => $nonce,
78 'body-hash' => $body_hash,
79 );
80
81 if ( false !== strpos( $args['url'], 'xmlrpc.php' ) ) {
82 $url_args = array(
83 'for' => 'jetpack',
84 'wpcom_blog_id' => Jetpack_Options::get_option( 'id' ),
85 );
86 } else {
87 $url_args = array();
88 }
89
90 if ( 'header' != $args['auth_location'] ) {
91 $url_args += $auth;
92 }
93
94 $url = add_query_arg( urlencode_deep( $url_args ), $args['url'] );
95 $url = Jetpack::fix_url_for_bad_hosts( $url );
96
97 $signature = $jetpack_signature->sign_request( $token_key, $timestamp, $nonce, $body_hash, $method, $url, $body, false );
98
99 if ( !$signature || is_wp_error( $signature ) ) {
100 return $signature;
101 }
102
103 // Send an Authorization header so various caches/proxies do the right thing
104 $auth['signature'] = $signature;
105 $auth['version'] = JETPACK__VERSION;
106 $header_pieces = array();
107 foreach ( $auth as $key => $value ) {
108 $header_pieces[] = sprintf( '%s="%s"', $key, $value );
109 }
110 $request['headers'] = array_merge( $args['headers'], array(
111 'Authorization' => "X_JETPACK " . join( ' ', $header_pieces ),
112 ) );
113
114 // Make sure we keep the host when we do JETPACK__WPCOM_JSON_API_HOST requests.
115 $host = parse_url( $url, PHP_URL_HOST );
116 if ( $host === JETPACK__WPCOM_JSON_API_HOST ) {
117 $request['headers']['Host'] = 'public-api.wordpress.com';
118 }
119
120 if ( 'header' != $args['auth_location'] ) {
121 $url = add_query_arg( 'signature', urlencode( $signature ), $url );
122 }
123
124 return Jetpack_Client::_wp_remote_request( $url, $request );
125 }
126
127 /**
128 * Wrapper for wp_remote_request(). Turns off SSL verification for certain SSL errors.
129 * This is lame, but many, many, many hosts have misconfigured SSL.
130 *
131 * When Jetpack is registered, the jetpack_fallback_no_verify_ssl_certs option is set to the current time if:
132 * 1. a certificate error is found AND
133 * 2. not verifying the certificate works around the problem.
134 *
135 * The option is checked on each request.
136 *
137 * @internal
138 * @see Jetpack::fix_url_for_bad_hosts()
139 *
140 * @return array|WP_Error WP HTTP response on success
141 */
142 public static function _wp_remote_request( $url, $args, $set_fallback = false ) {
143 /**
144 * SSL verification (`sslverify`) for the JetpackClient remote request
145 * defaults to off, use this filter to force it on.
146 *
147 * Return `true` to ENABLE SSL verification, return `false`
148 * to DISABLE SSL verification.
149 *
150 * @since 3.6.0
151 *
152 * @param bool Whether to force `sslverify` or not.
153 */
154 if ( apply_filters( 'jetpack_client_verify_ssl_certs', false ) ) {
155 return wp_remote_request( $url, $args );
156 }
157
158 $fallback = Jetpack_Options::get_option( 'fallback_no_verify_ssl_certs' );
159 if ( false === $fallback ) {
160 Jetpack_Options::update_option( 'fallback_no_verify_ssl_certs', 0 );
161 }
162
163 if ( (int) $fallback ) {
164 // We're flagged to fallback
165 $args['sslverify'] = false;
166 }
167
168 $response = wp_remote_request( $url, $args );
169
170 if (
171 !$set_fallback // We're not allowed to set the flag on this request, so whatever happens happens
172 ||
173 isset( $args['sslverify'] ) && !$args['sslverify'] // No verification - no point in doing it again
174 ||
175 !is_wp_error( $response ) // Let it ride
176 ) {
177 Jetpack_Client::set_time_diff( $response, $set_fallback );
178 return $response;
179 }
180
181 // At this point, we're not flagged to fallback and we are allowed to set the flag on this request.
182
183 $message = $response->get_error_message();
184
185 // Is it an SSL Certificate verification error?
186 if (
187 false === strpos( $message, '14090086' ) // OpenSSL SSL3 certificate error
188 &&
189 false === strpos( $message, '1407E086' ) // OpenSSL SSL2 certificate error
190 &&
191 false === strpos( $message, 'error setting certificate verify locations' ) // cURL CA bundle not found
192 &&
193 false === strpos( $message, 'Peer certificate cannot be authenticated with' ) // cURL CURLE_SSL_CACERT: CA bundle found, but not helpful
194 // different versions of curl have different error messages
195 // this string should catch them all
196 &&
197 false === strpos( $message, 'Problem with the SSL CA cert' ) // cURL CURLE_SSL_CACERT_BADFILE: probably access rights
198 ) {
199 // No, it is not.
200 return $response;
201 }
202
203 // Redo the request without SSL certificate verification.
204 $args['sslverify'] = false;
205 $response = wp_remote_request( $url, $args );
206
207 if ( !is_wp_error( $response ) ) {
208 // The request went through this time, flag for future fallbacks
209 Jetpack_Options::update_option( 'fallback_no_verify_ssl_certs', time() );
210 Jetpack_Client::set_time_diff( $response, $set_fallback );
211 }
212
213 return $response;
214 }
215
216 public static function set_time_diff( &$response, $force_set = false ) {
217 $code = wp_remote_retrieve_response_code( $response );
218
219 // Only trust the Date header on some responses
220 if ( 200 != $code && 304 != $code && 400 != $code && 401 != $code ) {
221 return;
222 }
223
224 if ( !$date = wp_remote_retrieve_header( $response, 'date' ) ) {
225 return;
226 }
227
228 if ( 0 >= $time = (int) strtotime( $date ) ) {
229 return;
230 }
231
232 $time_diff = $time - time();
233
234 if ( $force_set ) { // during register
235 Jetpack_Options::update_option( 'time_diff', $time_diff );
236 } else { // otherwise
237 $old_diff = Jetpack_Options::get_option( 'time_diff' );
238 if ( false === $old_diff || abs( $time_diff - (int) $old_diff ) > 10 ) {
239 Jetpack_Options::update_option( 'time_diff', $time_diff );
240 }
241 }
242 }
243
244 /**
245 * Query the WordPress.com REST API using the blog token
246 *
247 * @param string $path
248 * @param string $version
249 * @param array $args
250 * @param string $body
251 * @return array|WP_Error $response Data.
252 */
253 static function wpcom_json_api_request_as_blog( $path, $version = self::WPCOM_JSON_API_VERSION, $args = array(), $body = null ) {
254 $filtered_args = array_intersect_key( $args, array(
255 'method' => 'string',
256 'timeout' => 'int',
257 'redirection' => 'int',
258 ) );
259
260 /**
261 * Determines whether Jetpack can send outbound https requests to the WPCOM api.
262 *
263 * @since 3.6.0
264 *
265 * @param bool $proto Defaults to true.
266 */
267 $proto = apply_filters( 'jetpack_can_make_outbound_https', true ) ? 'https' : 'http';
268
269 // unprecedingslashit
270 $_path = preg_replace( '/^\//', '', $path );
271
272 // Use GET by default whereas `remote_request` uses POST
273 if ( isset( $filtered_args['method'] ) && strtoupper( $filtered_args['method'] === 'POST' ) ) {
274 $request_method = 'POST';
275 } else {
276 $request_method = 'GET';
277 }
278
279 $validated_args = array_merge( $filtered_args, array(
280 'url' => sprintf( '%s://%s/rest/v%s/%s', $proto, JETPACK__WPCOM_JSON_API_HOST, $version, $_path ),
281 'blog_id' => (int) Jetpack_Options::get_option( 'id' ),
282 'method' => $request_method,
283 ) );
284
285 return Jetpack_Client::remote_request( $validated_args, $body );
286 }
287
288 }
289