PluginProbe ʕ •ᴥ•ʔ
LiteSpeed Cache / 7.5.0.1
LiteSpeed Cache v7.5.0.1
trunk 1.0.15 1.9.1.1 2.9.9.2 3.6.4 4.6 5.7.0.1 6.5.4 7.0.0.1 7.0.1 7.1 7.2 7.3 7.3.0.1 7.4 7.5 7.5.0.1 7.6 7.6.1 7.6.2 7.7 7.8 7.8.0.1 7.8.1
litespeed-cache / lib / urirewriter.cls.php
litespeed-cache / lib Last commit date
css_js_min 8 months ago guest.cls.php 8 months ago html-min.cls.php 8 months ago object-cache.php 8 months ago php-compatibility.func.php 8 months ago urirewriter.cls.php 8 months ago
urirewriter.cls.php
343 lines
1 <?php
2 // phpcs:ignoreFile
3 /**
4 * Rewrite file-relative URIs as root-relative in CSS files
5 *
6 * @package Minify
7 * @author Stephen Clay <steve@mrclay.org>
8 */
9
10 namespace LiteSpeed\Lib;
11
12 defined( 'WPINC' ) || exit;
13
14 class UriRewriter {
15
16
17 /**
18 * rewrite() and rewriteRelative() append debugging information here
19 *
20 * @var string
21 */
22 public static $debugText = '';
23
24 /**
25 * In CSS content, rewrite file relative URIs as root relative
26 *
27 * @param string $css
28 *
29 * @param string $currentDir The directory of the current CSS file.
30 *
31 * @param string $docRoot The document root of the web site in which
32 * the CSS file resides (default = $_SERVER['DOCUMENT_ROOT']).
33 *
34 * @param array $symlinks (default = array()) If the CSS file is stored in
35 * a symlink-ed directory, provide an array of link paths to
36 * target paths, where the link paths are within the document root. Because
37 * paths need to be normalized for this to work, use "//" to substitute
38 * the doc root in the link paths (the array keys). E.g.:
39 * <code>
40 * array('//symlink' => '/real/target/path') // unix
41 * array('//static' => 'D:\\staticStorage') // Windows
42 * </code>
43 *
44 * @return string
45 */
46 public static function rewrite( $css, $currentDir, $docRoot = null, $symlinks = array() ) {
47 self::$_docRoot = self::_realpath(
48 $docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT']
49 );
50 self::$_currentDir = self::_realpath( $currentDir );
51 self::$_symlinks = array();
52
53 // normalize symlinks in order to map to link
54 foreach ( $symlinks as $link => $target ) {
55 $link = ( $link === '//' ) ? self::$_docRoot : str_replace( '//', self::$_docRoot . '/', $link );
56 $link = strtr( $link, '/', DIRECTORY_SEPARATOR );
57
58 self::$_symlinks[ $link ] = self::_realpath( $target );
59 }
60
61 self::$debugText .= 'docRoot : ' . self::$_docRoot . "\n"
62 . 'currentDir : ' . self::$_currentDir . "\n";
63 if ( self::$_symlinks ) {
64 self::$debugText .= 'symlinks : ' . var_export( self::$_symlinks, 1 ) . "\n";
65 }
66 self::$debugText .= "\n";
67
68 $css = self::_trimUrls( $css );
69
70 $css = self::_owlifySvgPaths( $css );
71
72 // rewrite
73 $pattern = '/@import\\s+([\'"])(.*?)[\'"]/';
74 $css = preg_replace_callback( $pattern, __CLASS__ . '::_processUriCB', $css );
75
76 $pattern = '/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/';
77 $css = preg_replace_callback( $pattern, __CLASS__ . '::_processUriCB', $css );
78
79 $css = self::_unOwlify( $css );
80
81 return $css;
82 }
83
84 /**
85 * In CSS content, prepend a path to relative URIs
86 *
87 * @param string $css
88 *
89 * @param string $path The path to prepend.
90 *
91 * @return string
92 */
93 public static function prepend( $css, $path ) {
94 self::$_prependPath = $path;
95
96 $css = self::_trimUrls( $css );
97
98 $css = self::_owlifySvgPaths( $css );
99
100 // append
101 $pattern = '/@import\\s+([\'"])(.*?)[\'"]/';
102 $css = preg_replace_callback( $pattern, __CLASS__ . '::_processUriCB', $css );
103
104 $pattern = '/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/';
105 $css = preg_replace_callback( $pattern, __CLASS__ . '::_processUriCB', $css );
106
107 $css = self::_unOwlify( $css );
108
109 self::$_prependPath = null;
110
111 return $css;
112 }
113
114 /**
115 * Get a root relative URI from a file relative URI
116 *
117 * <code>
118 * UriRewriter::rewriteRelative(
119 * '../img/hello.gif'
120 * , '/home/user/www/css' // path of CSS file
121 * , '/home/user/www' // doc root
122 * );
123 * // returns '/img/hello.gif'
124 *
125 * // example where static files are stored in a symlinked directory
126 * UriRewriter::rewriteRelative(
127 * 'hello.gif'
128 * , '/var/staticFiles/theme'
129 * , '/home/user/www'
130 * , array('/home/user/www/static' => '/var/staticFiles')
131 * );
132 * // returns '/static/theme/hello.gif'
133 * </code>
134 *
135 * @param string $uri file relative URI
136 *
137 * @param string $realCurrentDir realpath of the current file's directory.
138 *
139 * @param string $realDocRoot realpath of the site document root.
140 *
141 * @param array $symlinks (default = array()) If the file is stored in
142 * a symlink-ed directory, provide an array of link paths to
143 * real target paths, where the link paths "appear" to be within the document
144 * root. E.g.:
145 * <code>
146 * array('/home/foo/www/not/real/path' => '/real/target/path') // unix
147 * array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path') // Windows
148 * </code>
149 *
150 * @return string
151 */
152 public static function rewriteRelative( $uri, $realCurrentDir, $realDocRoot, $symlinks = array() ) {
153 // prepend path with current dir separator (OS-independent)
154 $path = strtr( $realCurrentDir, '/', DIRECTORY_SEPARATOR );
155 $path .= DIRECTORY_SEPARATOR . strtr( $uri, '/', DIRECTORY_SEPARATOR );
156
157 self::$debugText .= "file-relative URI : {$uri}\n"
158 . "path prepended : {$path}\n";
159
160 // "unresolve" a symlink back to doc root
161 foreach ( $symlinks as $link => $target ) {
162 if ( 0 === strpos( $path, $target ) ) {
163 // replace $target with $link
164 $path = $link . substr( $path, strlen( $target ) );
165
166 self::$debugText .= "symlink unresolved : {$path}\n";
167
168 break;
169 }
170 }
171 // strip doc root
172 $path = substr( $path, strlen( $realDocRoot ) );
173
174 self::$debugText .= "docroot stripped : {$path}\n";
175
176 // fix to root-relative URI
177 $uri = strtr( $path, '/\\', '//' );
178 $uri = self::removeDots( $uri );
179
180 self::$debugText .= "traversals removed : {$uri}\n\n";
181
182 return $uri;
183 }
184
185 /**
186 * Remove instances of "./" and "../" where possible from a root-relative URI
187 *
188 * @param string $uri
189 *
190 * @return string
191 */
192 public static function removeDots( $uri ) {
193 $uri = str_replace( '/./', '/', $uri );
194 // inspired by patch from Oleg Cherniy
195 do {
196 $uri = preg_replace( '@/[^/]+/\\.\\./@', '/', $uri, 1, $changed );
197 } while ( $changed );
198
199 return $uri;
200 }
201
202 /**
203 * Get realpath with any trailing slash removed. If realpath() fails,
204 * just remove the trailing slash.
205 *
206 * @param string $path
207 *
208 * @return mixed path with no trailing slash
209 */
210 protected static function _realpath( $path ) {
211 $realPath = realpath( $path );
212 if ( $realPath !== false ) {
213 $path = $realPath;
214 }
215
216 return rtrim( $path, '/\\' );
217 }
218
219 /**
220 * Directory of this stylesheet
221 *
222 * @var string
223 */
224 private static $_currentDir = '';
225
226 /**
227 * DOC_ROOT
228 *
229 * @var string
230 */
231 private static $_docRoot = '';
232
233 /**
234 * directory replacements to map symlink targets back to their
235 * source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
236 *
237 * @var array
238 */
239 private static $_symlinks = array();
240
241 /**
242 * Path to prepend
243 *
244 * @var string
245 */
246 private static $_prependPath = null;
247
248 /**
249 * @param string $css
250 *
251 * @return string
252 */
253 private static function _trimUrls( $css ) {
254 $pattern = '/
255 url\\( # url(
256 \\s*
257 ([^\\)]+?) # 1 = URI (assuming does not contain ")")
258 \\s*
259 \\) # )
260 /x';
261
262 return preg_replace( $pattern, 'url($1)', $css );
263 }
264
265 /**
266 * @param array $m
267 *
268 * @return string
269 */
270 private static function _processUriCB( $m ) {
271 // $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
272 $isImport = ( $m[0][0] === '@' );
273 // determine URI and the quote character (if any)
274 if ( $isImport ) {
275 $quoteChar = $m[1];
276 $uri = $m[2];
277 } else {
278 // $m[1] is either quoted or not
279 $quoteChar = ( $m[1][0] === "'" || $m[1][0] === '"' ) ? $m[1][0] : '';
280
281 $uri = ( $quoteChar === '' ) ? $m[1] : substr( $m[1], 1, strlen( $m[1] ) - 2 );
282 }
283
284 if ( $uri === '' ) {
285 return $m[0];
286 }
287
288 // if not anchor id, not root/scheme relative, and not starts with scheme
289 if ( ! preg_match( '~^(#|/|[a-z]+\:)~', $uri ) ) {
290 // URI is file-relative: rewrite depending on options
291 if ( self::$_prependPath === null ) {
292 $uri = self::rewriteRelative( $uri, self::$_currentDir, self::$_docRoot, self::$_symlinks );
293 } else {
294 $uri = self::$_prependPath . $uri;
295 if ( $uri[0] === '/' ) {
296 $root = '';
297 $rootRelative = $uri;
298 $uri = $root . self::removeDots( $rootRelative );
299 } elseif ( preg_match( '@^((https?\:)?//([^/]+))/@', $uri, $m ) && ( false !== strpos( $m[3], '.' ) ) ) {
300 $root = $m[1];
301 $rootRelative = substr( $uri, strlen( $root ) );
302 $uri = $root . self::removeDots( $rootRelative );
303 }
304 }
305 }
306
307 if ( $isImport ) {
308 return "@import {$quoteChar}{$uri}{$quoteChar}";
309 } else {
310 return "url({$quoteChar}{$uri}{$quoteChar})";
311 }
312 }
313
314 /**
315 * Mungs some inline SVG URL declarations so they won't be touched
316 *
317 * @link https://github.com/mrclay/minify/issues/517
318 * @see _unOwlify
319 *
320 * @param string $css
321 * @return string
322 */
323 private static function _owlifySvgPaths( $css ) {
324 $pattern = '~\b((?:clip-path|mask|-webkit-mask)\s*\:\s*)url(\(\s*#\w+\s*\))~';
325
326 return preg_replace( $pattern, '$1owl$2', $css );
327 }
328
329 /**
330 * Undo work of _owlify
331 *
332 * @see _owlifySvgPaths
333 *
334 * @param string $css
335 * @return string
336 */
337 private static function _unOwlify( $css ) {
338 $pattern = '~\b((?:clip-path|mask|-webkit-mask)\s*\:\s*)owl~';
339
340 return preg_replace( $pattern, '$1url', $css );
341 }
342 }
343