PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-beta.2
Jetpack – WP Security, Backup, Speed, & Growth v16.1-beta.2
16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / vendor / wp-php-toolkit / data-liberation / URL / class-nativeurlintextprocessorwrapper.php
jetpack / vendor / wp-php-toolkit / data-liberation / URL Last commit date
PHP 6 days ago class-convertedurl.php 6 days ago class-cssurlprocessor.php 6 days ago class-nativeurlintextprocessorwrapper.php 6 days ago class-urlintextprocessor.php 6 days ago class-urlrewritecache.php 6 days ago class-wpurl.php 6 days ago class-wpwhatwgurl.php 6 days ago class-wpwhatwgurlsearchparams.php 6 days ago functions.php 6 days ago public-suffix-list.php 6 days ago
class-nativeurlintextprocessorwrapper.php
242 lines
1 <?php
2 // phpcs:disable Generic.Classes.DuplicateClassName.Found,Generic.Files.OneObjectStructurePerFile.MultipleFound
3
4 namespace WordPress\DataLiberation\URL;
5
6 use WP_HTML_Text_Replacement;
7
8 require_once __DIR__ . '/class-urlrewritecache.php';
9
10 /**
11 * Public URLInTextProcessor adapter backed by the native candidate scanner.
12 *
13 * Starting point: a reprint-shaped benchmark over 5,000 post_content values
14 * measured wp_rewrite_urls() at about 90s with the PHP URL-in-text processor.
15 * Using the native scanner plus bounded PHP caches reduced that benchmark to
16 * about 3.6s with identical output. That result suggests the repeated
17 * PHP-side URL parsing and rewrite decisions were the largest proven cost.
18 *
19 * This wrapper is therefore a conservative first step. The native extension
20 * finds ASCII URL candidates, while PHP still performs the public API's WHATWG
21 * validation, replacement bookkeeping, and non-ASCII fallback behavior. Moving
22 * more of this wrapper into Rust may reduce PHP/native crossings and could
23 * plausibly improve the optimized path further, but that conclusion is still a
24 * hypothesis until measured. Porting WPURL::parse(), is_child_url_of(), and
25 * WPURL::replace_base_url() semantics into native code may provide larger
26 * gains, but would require separate parity work before it is safe to rely on.
27 */
28 class NativeURLInTextProcessorWrapper {
29 private const CANDIDATE_CACHE_MAX = 4096;
30
31 private $text;
32 private $bytes_already_parsed = 0;
33 private $matched_url;
34 private $parsed_url;
35 private $url_starts_at;
36 private $url_length;
37 private $did_prepend_protocol;
38 private $base_url;
39 private $base_protocol;
40 private $native_processor;
41 private $fallback_processor;
42 private $lexical_updates = array();
43
44 private static $candidate_cache;
45
46 public function __construct( $text, $base_url = null ) {
47 $this->text = $text;
48 $this->base_url = $base_url;
49 $this->base_protocol = $base_url ? parse_url( $base_url, PHP_URL_SCHEME ) : null;
50
51 if ( preg_match( '/[^\x00-\x7F]/', $text ) ) {
52 $this->fallback_processor = new PHPURLInTextProcessor( $text, $base_url );
53 return;
54 }
55
56 $native_class = 'WordPress\\DataLiberation\\URL\\NativeURLInTextProcessor';
57 $this->native_processor = new $native_class( $text, $base_url );
58 }
59
60 public function next_url() {
61 if ( null !== $this->fallback_processor ) {
62 return $this->fallback_processor->next_url();
63 }
64
65 while ( $this->native_processor->next_url() ) {
66 $this->matched_url = $this->native_processor->get_raw_url();
67 $this->parsed_url = null;
68 $this->url_starts_at = null;
69 $this->url_length = null;
70 $this->did_prepend_protocol = false;
71
72 if ( false === $this->matched_url || '' === $this->matched_url || '::' === $this->matched_url ) {
73 continue;
74 }
75
76 $url_starts_at = strpos( $this->text, $this->matched_url, $this->bytes_already_parsed );
77 if ( false === $url_starts_at ) {
78 continue;
79 }
80 $this->bytes_already_parsed = $url_starts_at + strlen( $this->matched_url );
81 $this->url_starts_at = $url_starts_at;
82 $this->url_length = strlen( $this->matched_url );
83
84 $had_protocol = method_exists( $this->native_processor, 'had_protocol' )
85 ? $this->native_processor->had_protocol()
86 : WPURL::has_http_https_protocol( $this->matched_url );
87
88 $cache_key = $this->base_url . "\0" . ( $had_protocol ? '1' : '0' ) . "\0" . $this->matched_url;
89 $cached = self::candidate_cache()->get( $cache_key );
90 if ( null !== $cached ) {
91 if ( false === $cached ) {
92 continue;
93 }
94 $this->parsed_url = $cached['parsed_url'];
95 $this->did_prepend_protocol = $cached['did_prepend_protocol'];
96
97 return true;
98 }
99
100 $parsed_url = $this->parse_and_validate_candidate( $had_protocol );
101 if ( false === $parsed_url ) {
102 self::candidate_cache()->set( $cache_key, false );
103 continue;
104 }
105
106 self::candidate_cache()->set(
107 $cache_key,
108 array(
109 'parsed_url' => $parsed_url,
110 'did_prepend_protocol' => $this->did_prepend_protocol,
111 )
112 );
113
114 $this->parsed_url = $parsed_url;
115
116 return true;
117 }
118
119 return false;
120 }
121
122 public function get_raw_url() {
123 if ( null !== $this->fallback_processor ) {
124 return $this->fallback_processor->get_raw_url();
125 }
126
127 return $this->matched_url ?? false;
128 }
129
130 public function get_parsed_url() {
131 if ( null !== $this->fallback_processor ) {
132 return $this->fallback_processor->get_parsed_url();
133 }
134
135 return $this->parsed_url ?? false;
136 }
137
138 public function set_raw_url( $new_url ) {
139 if ( null !== $this->fallback_processor ) {
140 return $this->fallback_processor->set_raw_url( $new_url );
141 }
142
143 if ( null === $this->matched_url ) {
144 return false;
145 }
146
147 if ( $this->did_prepend_protocol ) {
148 $new_url = substr( $new_url, strpos( $new_url, '://' ) + 3 );
149 }
150
151 $this->matched_url = $new_url;
152 $this->lexical_updates[ $this->url_starts_at ] = new WP_HTML_Text_Replacement(
153 $this->url_starts_at,
154 $this->url_length,
155 $new_url
156 );
157
158 return true;
159 }
160
161 public function get_updated_text() {
162 if ( null !== $this->fallback_processor ) {
163 return $this->fallback_processor->get_updated_text();
164 }
165
166 $this->apply_lexical_updates();
167
168 return $this->text;
169 }
170
171 private function parse_and_validate_candidate( $had_protocol ) {
172 $preprocessed_url = $this->matched_url;
173 if ( $this->base_url && $this->base_protocol && ! $had_protocol ) {
174 $preprocessed_url = WPURL::ensure_protocol( $preprocessed_url, $this->base_protocol );
175 $this->did_prepend_protocol = true;
176 }
177
178 $parsed_url = WPURL::parse( $preprocessed_url, $this->base_url );
179 if ( false === $parsed_url ) {
180 return false;
181 }
182
183 if ( $parsed_url->protocol && ! in_array( $parsed_url->protocol, array( 'http:', 'https:' ), true ) ) {
184 return false;
185 }
186
187 if ( $parsed_url->username || $parsed_url->password ) {
188 return false;
189 }
190
191 if ( ! $had_protocol ) {
192 $last_dot_position = strrpos( $parsed_url->hostname, '.' );
193 if ( false === $last_dot_position ) {
194 return false;
195 }
196
197 $tld = substr( $parsed_url->hostname, $last_dot_position + 1 );
198 if ( ! WPURL::is_known_public_domain( $tld ) ) {
199 return false;
200 }
201 }
202
203 return $parsed_url;
204 }
205
206 private function apply_lexical_updates() {
207 if ( ! count( $this->lexical_updates ) ) {
208 return;
209 }
210
211 ksort( $this->lexical_updates );
212
213 $bytes_already_copied = 0;
214 $output_buffer = '';
215 foreach ( $this->lexical_updates as $diff ) {
216 $shift = strlen( $diff->text ) - $diff->length;
217 if ( $diff->start < $this->bytes_already_parsed ) {
218 $this->bytes_already_parsed += $shift;
219 }
220
221 $output_buffer .= substr( $this->text, $bytes_already_copied, $diff->start - $bytes_already_copied );
222 if ( $diff->start === $this->url_starts_at ) {
223 $this->url_starts_at = strlen( $output_buffer );
224 $this->url_length = strlen( $diff->text );
225 }
226 $output_buffer .= $diff->text;
227 $bytes_already_copied = $diff->start + $diff->length;
228 }
229
230 $this->text = $output_buffer . substr( $this->text, $bytes_already_copied );
231 $this->lexical_updates = array();
232 }
233
234 private static function candidate_cache() {
235 if ( null === self::$candidate_cache ) {
236 self::$candidate_cache = new URLRewriteCache( self::CANDIDATE_CACHE_MAX );
237 }
238
239 return self::$candidate_cache;
240 }
241 }
242