diff --git a/api.wordpress.org/public_html/core/browse-happy/1.0/index.php b/api.wordpress.org/public_html/core/browse-happy/1.0/index.php
index b307bc2e58..0f9c624f7f 100644
--- a/api.wordpress.org/public_html/core/browse-happy/1.0/index.php
+++ b/api.wordpress.org/public_html/core/browse-happy/1.0/index.php
@@ -1,38 +1,60 @@
array(
+ 'regexp' => '/^[a-zA-Z_][a-zA-Z0-9_]*\z/',
+ 'default' => '',
+ ),
+ 'flags' => FILTER_REQUIRE_SCALAR,
+);
+
$jsonp = '';
if ( ! empty( $_GET['jsonp'] ) ) {
- $jsonp = preg_replace( '/[^a-zA-Z0-9_]/', '', $_GET['jsonp'] );
+ $jsonp = filter_var( $_GET['jsonp'], FILTER_VALIDATE_REGEXP, $jsonp_filter_args );
header( 'Content-Type: application/javascript' );
} else if ( ! empty( $_GET['callback'] ) ) {
- $jsonp = preg_replace( '/[^a-zA-Z0-9_]/', '', $_GET['callback'] );
+ $jsonp = filter_var( $_GET['callback'], FILTER_VALIDATE_REGEXP, $jsonp_filter_args );
header( 'Content-Type: application/javascript' );
}
-if ( empty( $_REQUEST['useragent'] ) ) {
+if ( empty( $_REQUEST['useragent'] ) || ! is_string( $_REQUEST['useragent'] ) ) {
return;
}
-$user_agent = $_REQUEST['useragent'];
+$user_agent = filter_var( $_REQUEST['useragent'], FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW );
$data = browsehappy_parse_user_agent( $user_agent );
// Collect a sample: One out of every 25.
-if ( 0 === strpos( $_SERVER['HTTP_USER_AGENT'], 'WordPress/' ) && 1 === rand( 1, 25 ) ) {
+// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Only used for a prefix comparison, never output or stored.
+if ( 0 === strpos( $_SERVER['HTTP_USER_AGENT'] ?? '', 'WordPress/' ) && 1 === rand( 1, 25 ) ) {
require( dirname( dirname( dirname( dirname( __FILE__ ) ) ) ) . '/includes/hyperdb/bb-10-hyper-db.php' );
bh_record_data( $user_agent, $data );
}
if ( $jsonp ) {
header( 'Access-Control-Allow-Origin: *' );
- echo $jsonp.'('.json_encode($data).')';
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- validated callback, JSON-encoded payload.
+ echo $jsonp . '(' . json_encode( $data ) . ')';
} elseif ( defined( 'JSON_RESPONSE' ) ) {
header( 'Access-Control-Allow-Origin: *' );
header( 'Content-Type: application/json' );
echo json_encode( $data );
} else {
header( 'Content-Type: text/plain' );
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- serialized payload served as text/plain.
echo serialize( $data );
}
@@ -46,7 +68,12 @@
function bh_record_data( $ua, $data ) {
global $wpdb;
- list( $wp_ver, $url ) = explode( ';', $_SERVER['HTTP_USER_AGENT'], 2 );
+ // The requesting client's own user agent, which is recorded alongside the reported one.
+ $client_ua = filter_var( $_SERVER['HTTP_USER_AGENT'] ?? '', FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW );
+
+ /* Core sends `WordPress/{version}; {site url}`, but the URL may be absent. */
+ list( $wp_ver, $url ) = array_pad( explode( ';', $client_ua, 2 ), 2, '' );
+
$wp_ver = substr( $wp_ver, 10, 64 );
$url = rtrim( strtolower( trim( $url ) ), '/' );
$pk = md5( $url . '|' . $ua );
diff --git a/api.wordpress.org/public_html/core/browse-happy/1.0/test.php b/api.wordpress.org/public_html/core/browse-happy/1.0/test.php
index 18aeb00ce3..c775c8a3e5 100644
--- a/api.wordpress.org/public_html/core/browse-happy/1.0/test.php
+++ b/api.wordpress.org/public_html/core/browse-happy/1.0/test.php
@@ -1,10 +1,24 @@
";
+/**
+ * Browse Happy user agent parser test page.
+ *
+ * This is a standalone diagnostic page: WordPress is not loaded, so request data is
+ * never slashed and the `esc_*()` escaping helpers are unavailable.
+ *
+ * phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
+ *
+ * @package BrowseHappy
+ */
include dirname( __FILE__ ) . '/parse.php';
+$user_agent = filter_var( $_SERVER['HTTP_USER_AGENT'] ?? '', FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW );
+
+// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- htmlspecialchars() escapes; no esc_html() here.
+echo htmlspecialchars( $user_agent, ENT_QUOTES ) . '
';
-$output = browsehappy_parse_user_agent( $_SERVER['HTTP_USER_AGENT'] );
+$output = browsehappy_parse_user_agent( $user_agent );
-foreach ( $output as $k => $v )
- echo htmlspecialchars( $k . ' = ' . ( is_bool( $v ) ? (int) $v : $v ), ENT_QUOTES ) . "
";
+foreach ( $output as $k => $v ) {
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- htmlspecialchars() escapes; no esc_html() here.
+ echo htmlspecialchars( $k . ' = ' . ( is_bool( $v ) ? (int) $v : $v ), ENT_QUOTES ) . '
';
+}
diff --git a/api.wordpress.org/public_html/core/credits/index.php b/api.wordpress.org/public_html/core/credits/index.php
index d554a0e7e5..30cbb05340 100644
--- a/api.wordpress.org/public_html/core/credits/index.php
+++ b/api.wordpress.org/public_html/core/credits/index.php
@@ -1,4 +1,14 @@
array(
+ 'regexp' => '/^[0-9][.0-9]*/',
+ 'default' => '',
+ ),
+ )
+ )
+ );
} elseif ( 'cli' == php_sapi_name() && isset( $argv[1] ) ) {
$version = preg_replace( '/^([.0-9]+).*/', '$1', $argv[1] );
} else {
$version = WP_CORE_LATEST_RELEASE;
}
+// A WP locale, e.g. `de_DE_formal` or `es_419`.
+$requested_locale = isset( $_GET['locale'] ) ? filter_var(
+ $_GET['locale'],
+ FILTER_VALIDATE_REGEXP,
+ array(
+ 'options' => array(
+ 'regexp' => '/^[A-Za-z0-9_-]+\z/',
+ 'default' => '',
+ ),
+ )
+) : '';
+
if (
! is_string( $version ) ||
version_compare( $version, '3.2', '<' ) ||
- ( isset( $_GET['locale'] ) && ! is_string( $_GET['locale'] ) )
+ ( isset( $_GET['locale'] ) && ! is_string( $requested_locale ) )
) {
header( 'HTTP/1.0 400 Bad Request', true, 400 );
die( 'Bad request.' );
@@ -50,10 +85,13 @@ function like_escape( $text ) {
$locale = false;
// Convert a locale from a WP locale to a GP locale.
-if ( ( isset( $_GET['locale'] ) && 'en_US' != $_GET['locale'] ) || ( 'cli' == php_sapi_name() && isset( $argv[2] ) ) ) {
+if (
+ ( isset( $_GET['locale'] ) && 'en_US' != $requested_locale ) ||
+ ( 'cli' == php_sapi_name() && isset( $argv[2] ) )
+) {
require GLOTPRESS_LOCALES_PATH;
- $gp_locale = GP_Locales::by_field( 'wp_locale', isset( $argv[2] ) ? $argv[2] : $_GET['locale'] );
+ $gp_locale = GP_Locales::by_field( 'wp_locale', isset( $argv[2] ) ? $argv[2] : $requested_locale );
if ( $gp_locale ) {
$locale = $gp_locale;
}
diff --git a/api.wordpress.org/public_html/core/credits/wp-credits.php b/api.wordpress.org/public_html/core/credits/wp-credits.php
index 3638294178..3ba85e0af0 100644
--- a/api.wordpress.org/public_html/core/credits/wp-credits.php
+++ b/api.wordpress.org/public_html/core/credits/wp-credits.php
@@ -447,6 +447,7 @@ final public function execute() {
} elseif ( defined( 'JSON_RESPONSE' ) && JSON_RESPONSE ) {
echo json_encode( $results );
} else {
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- serialized payload served as text/plain.
echo serialize( $results );
}
}
diff --git a/api.wordpress.org/public_html/core/importers/1.0/index.php b/api.wordpress.org/public_html/core/importers/1.0/index.php
index 0258dfa6d9..8bd2df4855 100644
--- a/api.wordpress.org/public_html/core/importers/1.0/index.php
+++ b/api.wordpress.org/public_html/core/importers/1.0/index.php
@@ -1,4 +1,14 @@
=' ) ) {
@@ -21,6 +32,7 @@
}
$response = array( 'importers' => $popular_importers, 'translated' => false );
+// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON or serialized payload, not HTML.
echo defined( 'JSON_RESPONSE' ) ? json_encode( $response ) : serialize( $response );
function __( $string ) { return $string; }
diff --git a/api.wordpress.org/public_html/core/serve-happy/1.0/include.php b/api.wordpress.org/public_html/core/serve-happy/1.0/include.php
index 5bcde61f33..481bd6722f 100644
--- a/api.wordpress.org/public_html/core/serve-happy/1.0/include.php
+++ b/api.wordpress.org/public_html/core/serve-happy/1.0/include.php
@@ -1,4 +1,15 @@
array(
+ 'regexp' => '#^HTTP/[0-9]+(\.[0-9]+)?\z#',
+ 'default' => 'HTTP/1.1',
+ ),
+ )
+ );
$http_code_texts = [
400 => 'Bad Request',
];
@@ -39,15 +67,28 @@ function output_response( $data ) {
header( 'Access-Control-Allow-Origin: *' );
- if ( !empty( $_GET['callback'] ) ) {
+ // A JSONP callback is a JavaScript identifier, optionally namespaced; anything else is discarded.
+ $callback = filter_var(
+ $_GET['callback'] ?? '',
+ FILTER_VALIDATE_REGEXP,
+ array(
+ 'options' => array(
+ 'regexp' => '/^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*\z/',
+ 'default' => '',
+ ),
+ 'flags' => FILTER_REQUIRE_SCALAR,
+ )
+ );
+
+ if ( $callback ) {
call_headers( 'application/javascript' );
- echo '/**/' .
- preg_replace('/[^a-zA-Z0-9_.]/', '', $_GET['callback'] ) .
- '(' . $json_data . ')';
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- validated callback, JSON-encoded payload.
+ echo '/**/' . $callback . '(' . $json_data . ')';
} else {
call_headers( 'application/json' );
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON-encoded payload served as application/json.
echo $json_data;
}
}
\ No newline at end of file
diff --git a/api.wordpress.org/public_html/patterns/1.0/index.php b/api.wordpress.org/public_html/patterns/1.0/index.php
index 1c838833e7..1528c3a0f7 100644
--- a/api.wordpress.org/public_html/patterns/1.0/index.php
+++ b/api.wordpress.org/public_html/patterns/1.0/index.php
@@ -1,4 +1,13 @@