PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 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 All 508 releases
← All changes | jetpack_vendor/automattic/jetpack-waf/src/class-waf-request.php +97 -19 12.0.3 → 16.3 View file →
@@ -8,12 +8,14 @@
8 8 namespace Automattic\Jetpack\Waf;
9 9
10 10 require_once __DIR__ . '/functions.php';
11 11
12 +<<<'PHAN'
13 +@phan-type RequestFile = array{ name: string, filename: string }
14 +PHAN;
15 +
12 16 /**
13 17 * Request representation.
14 - *
15 - * @template RequestFile as array{ name: string, filename: string }
16 18 */
17 19 class Waf_Request {
18 20 /**
19 21 * The request URL, broken into three pieces: the host, the filename, and the query string
@@ -19,9 +21,9 @@
19 21 * The request URL, broken into three pieces: the host, the filename, and the query string
20 22 *
21 23 * @example for `https://wordpress.com/index.php?myvar=red`
22 24 * $this->url = [ 'https://wordpress.com', '/index.php', '?myvar=red' ]
23 - * @var array{ 0: string, 1: string, 2: string }|null
25 + * @var array{0: string, 1: string, 2: string}|null
24 26 */
25 27 protected $url = null;
26 28
27 29 /**
@@ -116,13 +118,12 @@
116 118
117 119 /**
118 120 * Returns the headers that were sent with this request
119 121 *
120 - * @return array{ 0: string, 1: scalar }[]
122 + * @return array{0: string, 1: scalar}[]
121 123 */
122 124 public function get_headers() {
123 125 $value = array();
124 - $has_content_type = false;
125 126 $has_content_length = false;
126 127 foreach ( $_SERVER as $k => $v ) {
127 128 $k = strtolower( $k );
128 129 if ( 'http_' === substr( $k, 0, 5 ) ) {
@@ -127,19 +128,14 @@
127 128 $k = strtolower( $k );
128 129 if ( 'http_' === substr( $k, 0, 5 ) ) {
129 130 $value[] = array( $this->normalize_header_name( substr( $k, 5 ) ), $v );
130 131 } elseif ( 'content_type' === $k && '' !== $v ) {
131 - $has_content_type = true;
132 - $value[] = array( 'content-type', $v );
132 + $value[] = array( 'content-type', $v );
133 133 } elseif ( 'content_length' === $k && '' !== $v ) {
134 134 $has_content_length = true;
135 135 $value[] = array( 'content-length', $v );
136 136 }
137 137 }
138 - if ( ! $has_content_type ) {
139 - // default Content-Type per RFC 7231 section 3.1.5.5.
140 - $value[] = array( 'content-type', 'application/octet-stream' );
141 - }
142 138 if ( ! $has_content_length ) {
143 139 $value[] = array( 'content-length', '0' );
144 140 }
145 141
@@ -146,8 +142,24 @@
146 142 return $value;
147 143 }
148 144
149 145 /**
146 + * Returns the value of a specific header that was sent with this request
147 + *
148 + * @param string $name The name of the header to retrieve.
149 + * @return string
150 + */
151 + public function get_header( $name ) {
152 + $name = $this->normalize_header_name( $name );
153 + foreach ( $this->get_headers() as list( $header_name, $header_value ) ) {
154 + if ( $header_name === $name ) {
155 + return $header_value;
156 + }
157 + }
158 + return '';
159 + }
160 +
161 + /**
150 162 * Change a header name to all-lowercase and replace spaces and underscores with dashes.
151 163 *
152 164 * @param string $name The header name to normalize.
153 165 * @return string
@@ -181,9 +193,9 @@
181 193 /**
182 194 * Returns the URL parts for this request.
183 195 *
184 196 * @see $this->url
185 - * @return array{ 0: string, 1: string, 2: string }
197 + * @return array{0: string, 1: string, 2: string}
186 198 */
187 199 protected function get_url() {
188 200 if ( null !== $this->url ) {
189 201 return $this->url;
@@ -191,9 +203,11 @@
191 203
192 204 $uri = isset( $_SERVER['REQUEST_URI'] ) ? filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ), FILTER_DEFAULT ) : '/';
193 205 if ( false !== strpos( $uri, '?' ) ) {
194 206 // remove the query string (we'll pull it from elsewhere later)
195 - $uri = substr( $uri, 0, strpos( $uri, '?' ) );
207 + $uri = urldecode( substr( $uri, 0, strpos( $uri, '?' ) ) );
208 + } else {
209 + $uri = urldecode( $uri );
196 210 }
197 211 $query_string = isset( $_SERVER['QUERY_STRING'] ) ? '?' . filter_var( wp_unslash( $_SERVER['QUERY_STRING'] ), FILTER_DEFAULT ) : '';
198 212 if ( 1 === preg_match( '/^https?:\/\//', $uri ) ) {
199 213 // sometimes $_SERVER[REQUEST_URI] already includes the full domain name
@@ -201,9 +215,9 @@
201 215 $uri_path = substr( $uri, strlen( $uri_host ) );
202 216 $this->url = array( $uri_host, $uri_path, $query_string );
203 217 } else {
204 218 // otherwise build the URI manually
205 - $uri_scheme = ( ! empty( $_SERVER['HTTPS'] ) && 'on' === $_SERVER['HTTPS'] )
219 + $uri_scheme = ( ! empty( $_SERVER['HTTPS'] ) && 'off' !== $_SERVER['HTTPS'] )
206 220 ? 'https'
207 221 : 'http';
208 222 $uri_host = isset( $_SERVER['HTTP_HOST'] )
209 223 ? filter_var( wp_unslash( $_SERVER['HTTP_HOST'] ), FILTER_DEFAULT )
@@ -253,8 +267,26 @@
253 267 return $this->get_url()[1];
254 268 }
255 269
256 270 /**
271 + * Return the basename part of the request
272 + *
273 + * @example for 'https://wordpress.com/some/page.php?id=5', return 'page.php'
274 + * @return string
275 + */
276 + public function get_basename() {
277 + // Get the filename part of the request
278 + $filename = $this->get_filename();
279 + // Normalize slashes
280 + $filename = str_replace( '\\', '/', $filename );
281 + // Remove trailing slashes
282 + $filename = rtrim( $filename, '/' );
283 + // Return the basename
284 + $offset = strrpos( $filename, '/' );
285 + return $offset !== false ? substr( $filename, $offset + 1 ) : $filename;
286 + }
287 +
288 + /**
257 289 * Return the query string. If present, it will be prefixed with '?'. Otherwise, it will be an empty string.
258 290 *
259 291 * @return string
260 292 */
@@ -274,9 +306,9 @@
274 306
275 307 /**
276 308 * Returns the cookies
277 309 *
278 - * @return array<string, string>
310 + * @return array{string, scalar}[]
279 311 */
280 312 public function get_cookies() {
281 313 return flatten_array( $_COOKIE );
282 314 }
@@ -283,9 +315,9 @@
283 315
284 316 /**
285 317 * Returns the GET variables
286 318 *
287 - * @return array<string, mixed|array>
319 + * @return array{string, scalar}[]
288 320 */
289 321 public function get_get_vars() {
290 322 return flatten_array( $_GET );
291 323 }
@@ -290,14 +322,60 @@
290 322 return flatten_array( $_GET );
291 323 }
292 324
293 325 /**
326 + * Returns the POST variables from a JSON body
327 + *
328 + * @return array{string, scalar}[]
329 + */
330 + private function get_json_post_vars() {
331 + $decoded_json = json_decode( $this->get_body(), true ) ?? array();
332 + return flatten_array( $decoded_json, 'json', true );
333 + }
334 +
335 + /**
336 + * Returns the POST variables from a urlencoded body
337 + *
338 + * @return array{string, scalar}[]
339 + */
340 + private function get_urlencoded_post_vars() {
341 + parse_str( $this->get_body(), $params );
342 + return flatten_array( $params );
343 + }
344 +
345 + /**
294 346 * Returns the POST variables
295 347 *
296 - * @return array<string, mixed|array>
348 + * @param string $body_processor Manually specifiy the method to use to process the body. Options are 'URLENCODED' and 'JSON'.
349 + *
350 + * @return array{string, scalar}[]
297 351 */
298 - public function get_post_vars() {
299 - return flatten_array( $_POST );
352 + public function get_post_vars( string $body_processor = '' ) {
353 + $content_type = $this->get_header( 'content-type' );
354 +
355 + // If the body processor is specified by the rules file, trust it.
356 + if ( 'URLENCODED' === $body_processor ) {
357 + return $this->get_urlencoded_post_vars();
358 + }
359 + if ( 'JSON' === $body_processor ) {
360 + return $this->get_json_post_vars();
361 + }
362 +
363 + // Otherwise, use $_POST if it's not empty.
364 + if ( ! empty( $_POST ) ) {
365 + return flatten_array( $_POST );
366 + }
367 +
368 + // Lastly, try to parse the body based on the content type.
369 + if ( strpos( $content_type, 'application/json' ) !== false ) {
370 + return $this->get_json_post_vars();
371 + }
372 + if ( strpos( $content_type, 'application/x-www-form-urlencoded' ) !== false ) {
373 + return $this->get_urlencoded_post_vars();
374 + }
375 +
376 + // Don't try to parse any other content types.
377 + return array();
300 378 }
301 379
302 380 /**
303 381 * Returns the files that were uploaded with this request (i.e. what's in the $_FILES superglobal)