← All changes
|
includes/public/charitable-template-helpers.php
+71
-7
1.8.12.1
→
1.8.13
View file →
| @@ -8,9 +8,9 @@ | ||
| 8 | 8 | * @author David Bisset |
| 9 | 9 | * @copyright Copyright (c) 2023, WP Charitable LLC |
| 10 | 10 | * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License |
| 11 | 11 | * @since 1.0.0 |
| 12 | - * @version 1.8.9.1 | |
| 12 | + * @version 1.8.13 | |
| 13 | 13 | */ |
| 14 | 14 | |
| 15 | 15 | // Exit if accessed directly. |
| 16 | 16 | if ( ! defined( 'ABSPATH' ) ) { |
| @@ -275,13 +275,54 @@ | ||
| 275 | 275 | return apply_filters( 'charitable_is_main_loop', $is_main_loop ); |
| 276 | 276 | } |
| 277 | 277 | |
| 278 | 278 | /** |
| 279 | + * Re-encodes a raw query string so that every key and value is percent-encoded. | |
| 280 | + * | |
| 281 | + * The query string arrives from the request untrusted and unencoded. Rebuilding it | |
| 282 | + * pair by pair guarantees the result contains no character that could break out of | |
| 283 | + * the HTML attribute, JavaScript string or URL context it is later printed into -- | |
| 284 | + * including HTML entities such as `'` that survive esc_url() and are decoded | |
| 285 | + * back into a quote by the browser's HTML parser. | |
| 286 | + * | |
| 287 | + * Query strings are application/x-www-form-urlencoded, so urldecode()/urlencode() | |
| 288 | + * is the correct pair here: it round-trips `+` as a space the way the request did. | |
| 289 | + * | |
| 290 | + * @since 1.8.13 | |
| 291 | + * | |
| 292 | + * @param string $query_string Raw query string, without the leading '?'. | |
| 293 | + * @return string Re-encoded query string, or an empty string if nothing usable remains. | |
| 294 | + */ | |
| 295 | +function charitable_sanitize_query_string( $query_string ) { | |
| 296 | + $pairs = array(); | |
| 297 | + | |
| 298 | + foreach ( explode( '&', (string) $query_string ) as $pair ) { | |
| 299 | + if ( '' === $pair ) { | |
| 300 | + continue; | |
| 301 | + } | |
| 302 | + | |
| 303 | + $parts = explode( '=', $pair, 2 ); | |
| 304 | + $key = urlencode( urldecode( $parts[0] ) ); | |
| 305 | + | |
| 306 | + if ( '' === $key ) { | |
| 307 | + continue; | |
| 308 | + } | |
| 309 | + | |
| 310 | + $pairs[] = isset( $parts[1] ) ? $key . '=' . urlencode( urldecode( $parts[1] ) ) : $key; | |
| 311 | + } | |
| 312 | + | |
| 313 | + return implode( '&', $pairs ); | |
| 314 | +} | |
| 315 | + | |
| 316 | +/** | |
| 279 | 317 | * Returns the current URL. |
| 280 | 318 | * |
| 281 | 319 | * @see https://gist.github.com/leereamsnyder/fac3b9ccb6b99ab14f36 |
| 282 | 320 | * |
| 283 | 321 | * @since 1.0.0 |
| 322 | + * @since 1.8.13 The path and query string are both percent-encoded instead of being | |
| 323 | + * reflected raw, and the trailing slash is applied to the path rather | |
| 324 | + * than to the end of the query string. | |
| 284 | 325 | * |
| 285 | 326 | * @global WP $wp |
| 286 | 327 | * @return string |
| 287 | 328 | */ |
| @@ -287,15 +328,38 @@ | ||
| 287 | 328 | */ |
| 288 | 329 | function charitable_get_current_url() { |
| 289 | 330 | global $wp; |
| 290 | 331 | |
| 291 | - return trailingslashit( | |
| 292 | - add_query_arg( | |
| 293 | - array_key_exists( 'QUERY_STRING', $_SERVER ) ? $_SERVER['QUERY_STRING'] : '', // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized | |
| 294 | - '', | |
| 295 | - home_url( $wp->request ) | |
| 296 | - ) | |
| 332 | + $path = isset( $wp->request ) ? (string) $wp->request : ''; | |
| 333 | + | |
| 334 | + /* | |
| 335 | + * The requested path is reflected here too, and WordPress hands it over exactly as | |
| 336 | + * it arrived: any %XX escapes are already inert, but characters that arrived | |
| 337 | + * literally are not. Encoding '&' closes off every HTML entity form at once | |
| 338 | + * (', ', ', ", ...), which is what a URL escaper cannot do | |
| 339 | + * because those entities are made of characters it considers legal; the quotes and | |
| 340 | + * angle brackets cover the literal cases. Only these five are touched, so existing | |
| 341 | + * %XX escapes and ordinary slugs pass through unchanged and the result is idempotent. | |
| 342 | + */ | |
| 343 | + $path = str_replace( | |
| 344 | + array( '&', "'", '"', '<', '>' ), | |
| 345 | + array( '%26', '%27', '%22', '%3C', '%3E' ), | |
| 346 | + $path | |
| 297 | 347 | ); |
| 348 | + | |
| 349 | + $url = trailingslashit( home_url( $path ) ); | |
| 350 | + | |
| 351 | + if ( empty( $_SERVER['QUERY_STRING'] ) ) { | |
| 352 | + return $url; | |
| 353 | + } | |
| 354 | + | |
| 355 | + $query_string = charitable_sanitize_query_string( wp_unslash( $_SERVER['QUERY_STRING'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized | |
| 356 | + | |
| 357 | + if ( '' === $query_string ) { | |
| 358 | + return $url; | |
| 359 | + } | |
| 360 | + | |
| 361 | + return $url . '?' . $query_string; | |
| 298 | 362 | } |
| 299 | 363 | |
| 300 | 364 | /** |
| 301 | 365 | * Returns the URL to which the user should be redirected after signing on or registering an account. |