| @@ -50,10 +50,18 @@ | ||
| 50 | 50 | * @param string $url URL. |
| 51 | 51 | * @return bool |
| 52 | 52 | */ |
| 53 | 53 | public static function is_css_url( string $url ): bool { |
| 54 | - foreach ( self::css_hosts() as $host ) { | |
| 55 | - if ( false !== strpos( $url, $host . '/css' ) || false !== strpos( $url, $host . '/icon' ) ) { | |
| 54 | + $host = strtolower( (string) wp_parse_url( $url, PHP_URL_HOST ) ); | |
| 55 | + $path = (string) wp_parse_url( $url, PHP_URL_PATH ); | |
| 56 | + | |
| 57 | + if ( '' === $host ) { | |
| 58 | + return false; | |
| 59 | + } | |
| 60 | + | |
| 61 | + foreach ( self::css_hosts() as $candidate ) { | |
| 62 | + if ( self::host_matches( $host, $candidate ) | |
| 63 | + && ( 0 === strpos( $path, '/css' ) || 0 === strpos( $path, '/icon' ) ) ) { | |
| 56 | 64 | return true; |
| 57 | 65 | } |
| 58 | 66 | } |
| 59 | 67 | |
| @@ -60,8 +68,27 @@ | ||
| 60 | 68 | return false; |
| 61 | 69 | } |
| 62 | 70 | |
| 63 | 71 | /** |
| 72 | + * Does $host equal an allowed host or a subdomain of it? Parsed-host match | |
| 73 | + * (not substring), so a crafted URL like https://evil.test/fonts.googleapis.com/css | |
| 74 | + * — whose real host is evil.test — is correctly rejected. | |
| 75 | + * | |
| 76 | + * @param string $host Lowercased host from the URL under test. | |
| 77 | + * @param string $candidate Allowed host. | |
| 78 | + * @return bool | |
| 79 | + */ | |
| 80 | + private static function host_matches( string $host, string $candidate ): bool { | |
| 81 | + $candidate = strtolower( trim( $candidate ) ); | |
| 82 | + | |
| 83 | + if ( '' === $candidate ) { | |
| 84 | + return false; | |
| 85 | + } | |
| 86 | + | |
| 87 | + return $host === $candidate || substr( $host, -strlen( '.' . $candidate ) ) === '.' . $candidate; | |
| 88 | + } | |
| 89 | + | |
| 90 | + /** | |
| 64 | 91 | * Provider id for a URL. |
| 65 | 92 | * |
| 66 | 93 | * @param string $url URL. |
| 67 | 94 | * @return string |
| @@ -128,10 +155,16 @@ | ||
| 128 | 155 | * @param string $url URL. |
| 129 | 156 | * @return bool |
| 130 | 157 | */ |
| 131 | 158 | public static function is_provider_url( string $url ): bool { |
| 132 | - foreach ( self::hint_hosts() as $host ) { | |
| 133 | - if ( false !== strpos( $url, $host ) ) { | |
| 159 | + $host = strtolower( (string) wp_parse_url( $url, PHP_URL_HOST ) ); | |
| 160 | + | |
| 161 | + if ( '' === $host ) { | |
| 162 | + return false; | |
| 163 | + } | |
| 164 | + | |
| 165 | + foreach ( self::hint_hosts() as $candidate ) { | |
| 166 | + if ( self::host_matches( $host, $candidate ) ) { | |
| 134 | 167 | return true; |
| 135 | 168 | } |
| 136 | 169 | } |
| 137 | 170 | |