"\'\)\]]+~i', static function ( $match ) use ( $names ) { return self::is_sensitive( $match[0], $names ) ? self::REPLACEMENT : $match[0]; }, $body ); // preg_replace_callback returns null on a backtrack-limit failure. Storing the original // would defeat the point, so an unredactable body is not stored at all. if ( null === $redacted ) { return self::REPLACEMENT; } if ( strlen( $redacted ) > self::MAX_BYTES ) { $redacted = ( function_exists( 'mb_strcut' ) ? mb_strcut( $redacted, 0, self::MAX_BYTES, 'UTF-8' ) : substr( $redacted, 0, self::MAX_BYTES ) ); // mb_strcut only keeps a character whole when the string it is given is valid UTF-8 to // begin with; on anything else the cut can end on a broken sequence, and // $wpdb->insert() refuses a row holding one. Drop a partial tail, but only when the cut // really did leave the string invalid, so a body that ends on a legitimate multi-byte // character keeps it. A body that was never valid UTF-8 stays invalid here and is // scrubbed by EmailLogStore::insert(), which is where knowing the column belongs. if ( 1 !== preg_match( '/^./us', $redacted ) ) { $redacted = (string) preg_replace( '/[\x80-\xFF]{1,3}$/', '', $redacted ); } // mb_strcut may return slightly under the limit to keep a character whole. $redacted = str_pad( $redacted, self::MAX_BYTES, ' ' ) . self::CUT_MARK; } return $redacted; } /** * Whether a URL is a sign-in, reset or activation link. * * @param string $url URL as it appeared in the body. * @param string[] $names Sensitive argument names. * @return bool */ private static function is_sensitive( $url, array $names ) { $url = html_entity_decode( $url, ENT_QUOTES ); if ( false !== stripos( $url, 'wp-activate.php' ) ) { return true; } $query = (string) wp_parse_url( $url, PHP_URL_QUERY ); $fragment = (string) wp_parse_url( $url, PHP_URL_FRAGMENT ); // A headless or single-page front end keeps its route in the fragment, so the secret rides // there too: https://app.example/#/reset?token=… has no query of its own at all. if ( false !== strpos( $fragment, '?' ) ) { $query .= ( '' === $query ? '' : '&' ) . substr( $fragment, strpos( $fragment, '?' ) + 1 ); } if ( '' === $query ) { return false; } parse_str( $query, $args ); $present = array_map( 'strtolower', array_keys( $args ) ); if ( false !== stripos( $url, 'wp-login.php' ) && isset( $args['action'] ) && in_array( $args['action'], array( 'rp', 'resetpass' ), true ) ) { return true; } return array() !== array_intersect( $present, array_map( 'strtolower', $names ) ); } }