Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Dependency_Path_Mapping
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 5
650
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
42
 dependency_src_to_fs_path
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
42
 uri_path_to_fs_path
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
90
 is_internal_uri
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 is_descendant_uri
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace Automattic\Jetpack_Boost\Lib\Minify;
4
5/**
6 * This is a class to map script and style URLs to local filesystem paths.
7 * This is necessary when we are deciding what we can concatenate and when
8 * actually building the concatenation.
9 */
10class Dependency_Path_Mapping {
11    /**
12     * Save entire site URL so we can check whether other URLs are based on it (internal URLs)
13     *
14     * @var string
15     */
16    public $site_url;
17
18    /**
19     * Save URI path and dir for mapping URIs to filesystem paths
20     *
21     * @var string
22     */
23    public $site_uri_path    = null;
24    public $site_dir         = null;
25    public $content_uri_path = null;
26    public $content_dir      = null;
27    public $plugin_uri_path  = null;
28    public $plugin_dir       = null;
29
30    public function __construct(
31        // Expose URLs and DIRs for unit test
32        $site_url = null, // default site URL is determined dynamically
33        $site_dir = null,
34        $content_url = WP_CONTENT_URL,
35        $content_dir = WP_CONTENT_DIR,
36        $plugin_url = WP_PLUGIN_URL,
37        $plugin_dir = WP_PLUGIN_DIR
38    ) {
39        if ( null === $site_dir ) {
40            $site_dir = Config::get_abspath();
41        }
42
43        if ( null === $site_url ) {
44            $site_url = is_multisite() ? get_site_url( get_current_blog_id() ) : get_site_url();
45        }
46        $site_url            = trailingslashit( $site_url );
47        $this->site_url      = $site_url;
48        $this->site_uri_path = wp_parse_url( $site_url, PHP_URL_PATH );
49        $this->site_dir      = trailingslashit( $site_dir );
50
51        // Only resolve content URLs if they are under the site URL
52        if ( $this->is_internal_uri( $content_url ) ) {
53            $this->content_uri_path = wp_parse_url( trailingslashit( $content_url ), PHP_URL_PATH );
54            $this->content_dir      = trailingslashit( $content_dir );
55        }
56
57        // Only resolve plugin URLs if they are under the site URL
58        if ( $this->is_internal_uri( $plugin_url ) ) {
59            $this->plugin_uri_path = wp_parse_url( trailingslashit( $plugin_url ), PHP_URL_PATH );
60            $this->plugin_dir      = trailingslashit( $plugin_dir );
61        }
62    }
63
64    /**
65     * Given the full URL of a script/style dependency, return its local filesystem path.
66     */
67    public function dependency_src_to_fs_path( $src ) {
68        if ( ! $this->is_internal_uri( $src ) ) {
69            // If a URI is not internal, we can have no confidence
70            // we are resolving to the correct file.
71            return false;
72        }
73
74        $src_parts = wp_parse_url( $src );
75        if ( false === $src_parts ) {
76            return false;
77        }
78
79        if ( empty( $src_parts['path'] ) ) {
80            // We can't find anything to resolve
81            return false;
82        }
83        $path = $src_parts['path'];
84
85        if ( empty( $src_parts['host'] ) ) {
86            // With no host, this is a path relative to the WordPress root
87            $fs_path = "{$this->site_dir}{$path}";
88
89            return file_exists( $fs_path ) ? $fs_path : false;
90        }
91
92        return $this->uri_path_to_fs_path( $path );
93    }
94
95    /**
96     * Given a URI path of a script/style resource, return its local filesystem path.
97     */
98    public function uri_path_to_fs_path( $uri_path ) {
99        if ( 1 === preg_match( '#(?:^|/)\.\.?(?:/|$)#', $uri_path ) ) {
100            // Reject relative paths
101            return false;
102        }
103
104        // The plugin URI path may be contained within the content URI path, so we check it before the content URI.
105        // And both the plugin and content URI paths must be contained within the site URI path,
106        // so we check them before checking the site URI.
107        if ( isset( $this->plugin_uri_path ) && static::is_descendant_uri( $this->plugin_uri_path, $uri_path ) ) {
108            $file_path = $this->plugin_dir . substr( $uri_path, strlen( $this->plugin_uri_path ) );
109        } elseif ( isset( $this->content_uri_path ) && static::is_descendant_uri( $this->content_uri_path, $uri_path ) ) {
110            $file_path = $this->content_dir . substr( $uri_path, strlen( $this->content_uri_path ) );
111        } elseif ( static::is_descendant_uri( $this->site_uri_path, $uri_path ) ) {
112            $file_path = $this->site_dir . substr( $uri_path, strlen( $this->site_uri_path ) );
113        }
114
115        if ( isset( $file_path ) && file_exists( $file_path ) ) {
116            return $file_path;
117        } else {
118            return false;
119        }
120    }
121
122    /**
123     * Determine whether a URI is internal, contained by this site.
124     *
125     * This method helps ensure we only resolve to local FS paths.
126     */
127    public function is_internal_uri( $uri ) {
128        if ( jetpack_boost_page_optimize_starts_with( '/', $uri ) && ! jetpack_boost_page_optimize_starts_with( '//', $uri ) ) {
129            // Absolute paths are internal because they are based on the site dir (typically ABSPATH),
130            // and this looks like an absolute path.
131            return true;
132        }
133
134        // To be internal, a URL must have the same scheme, host, and port as the site URL
135        // and start with the same path as the site URL.
136        return static::is_descendant_uri( $this->site_url, $uri );
137    }
138
139    /**
140     * Check whether a path is descended from the given directory path.
141     *
142     * Does not handle relative paths.
143     */
144    public static function is_descendant_uri( $dir_path, $candidate ) {
145        // Ensure a trailing slash to avoid false matches like
146        // "/wp-content/resource" being judged a descendant of "/wp".
147        $dir_path = trailingslashit( $dir_path );
148
149        return jetpack_boost_page_optimize_starts_with( $dir_path, $candidate );
150    }
151}