Issues (2010)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

wp-includes/class-oembed.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * API for fetching the HTML to embed remote content based on a provided URL
4
 *
5
 * Used internally by the WP_Embed class, but is designed to be generic.
6
 *
7
 * @link https://codex.wordpress.org/oEmbed oEmbed Codex Article
8
 * @link http://oembed.com/ oEmbed Homepage
9
 *
10
 * @package WordPress
11
 * @subpackage oEmbed
12
 */
13
14
/**
15
 * Core class used to implement oEmbed functionality.
16
 *
17
 * @since 2.9.0
18
 */
19
class WP_oEmbed {
20
21
	/**
22
	 * A list of oEmbed providers.
23
	 *
24
	 * @since 2.9.0
25
	 * @access public
26
	 * @var array
27
	 */
28
	public $providers = array();
29
30
	/**
31
	 * A list of an early oEmbed providers.
32
	 *
33
	 * @since 4.0.0
34
	 * @access public
35
	 * @static
36
	 * @var array
37
	 */
38
	public static $early_providers = array();
39
40
	/**
41
	 * A list of private/protected methods, used for backward compatibility.
42
	 *
43
	 * @since 4.2.0
44
	 * @access private
45
	 * @var array
46
	 */
47
	private $compat_methods = array( '_fetch_with_format', '_parse_json', '_parse_xml', '_parse_body' );
48
49
	/**
50
	 * Constructor.
51
	 *
52
	 * @since 2.9.0
53
	 * @access public
54
	 */
55
	public function __construct() {
56
		$host = urlencode( home_url() );
57
		$providers = array(
58
			'#http://((m|www)\.)?youtube\.com/watch.*#i'          => array( 'http://www.youtube.com/oembed',                             true  ),
59
			'#https://((m|www)\.)?youtube\.com/watch.*#i'         => array( 'http://www.youtube.com/oembed?scheme=https',                true  ),
60
			'#http://((m|www)\.)?youtube\.com/playlist.*#i'       => array( 'http://www.youtube.com/oembed',                             true  ),
61
			'#https://((m|www)\.)?youtube\.com/playlist.*#i'      => array( 'http://www.youtube.com/oembed?scheme=https',                true  ),
62
			'#http://youtu\.be/.*#i'                              => array( 'http://www.youtube.com/oembed',                             true  ),
63
			'#https://youtu\.be/.*#i'                             => array( 'http://www.youtube.com/oembed?scheme=https',                true  ),
64
			'#https?://(.+\.)?vimeo\.com/.*#i'                    => array( 'http://vimeo.com/api/oembed.{format}',                      true  ),
65
			'#https?://(www\.)?dailymotion\.com/.*#i'             => array( 'https://www.dailymotion.com/services/oembed',               true  ),
66
			'#https?://dai.ly/.*#i'                               => array( 'https://www.dailymotion.com/services/oembed',               true  ),
67
			'#https?://(www\.)?flickr\.com/.*#i'                  => array( 'https://www.flickr.com/services/oembed/',                   true  ),
68
			'#https?://flic\.kr/.*#i'                             => array( 'https://www.flickr.com/services/oembed/',                   true  ),
69
			'#https?://(.+\.)?smugmug\.com/.*#i'                  => array( 'http://api.smugmug.com/services/oembed/',                   true  ),
70
			'#https?://(www\.)?hulu\.com/watch/.*#i'              => array( 'http://www.hulu.com/api/oembed.{format}',                   true  ),
71
			'http://i*.photobucket.com/albums/*'                  => array( 'http://api.photobucket.com/oembed',                         false ),
72
			'http://gi*.photobucket.com/groups/*'                 => array( 'http://api.photobucket.com/oembed',                         false ),
73
			'#https?://(www\.)?scribd\.com/doc/.*#i'              => array( 'http://www.scribd.com/services/oembed',                     true  ),
74
			'#https?://wordpress.tv/.*#i'                         => array( 'http://wordpress.tv/oembed/',                               true  ),
75
			'#https?://(.+\.)?polldaddy\.com/.*#i'                => array( 'https://polldaddy.com/oembed/',                             true  ),
76
			'#https?://poll\.fm/.*#i'                             => array( 'https://polldaddy.com/oembed/',                             true  ),
77
			'#https?://(www\.)?funnyordie\.com/videos/.*#i'       => array( 'http://www.funnyordie.com/oembed',                          true  ),
78
			'#https?://(www\.)?twitter\.com/.+?/status(es)?/.*#i' => array( 'https://publish.twitter.com/oembed',                        true  ),
79
			'#https?://(www\.)?twitter\.com/.+?/timelines/.*#i'   => array( 'https://publish.twitter.com/oembed',                        true  ),
80
			'#https?://(www\.)?twitter\.com/i/moments/.*#i'       => array( 'https://publish.twitter.com/oembed',                        true  ),
81
			'#https?://vine.co/v/.*#i'                            => array( 'https://vine.co/oembed.{format}',                           true  ),
82
			'#https?://(www\.)?soundcloud\.com/.*#i'              => array( 'http://soundcloud.com/oembed',                              true  ),
83
			'#https?://(.+?\.)?slideshare\.net/.*#i'              => array( 'https://www.slideshare.net/api/oembed/2',                   true  ),
84
			'#https?://(www\.)?instagr(\.am|am\.com)/p/.*#i'      => array( 'https://api.instagram.com/oembed',                          true  ),
85
			'#https?://(open|play)\.spotify\.com/.*#i'            => array( 'https://embed.spotify.com/oembed/',                         true  ),
86
			'#https?://(.+\.)?imgur\.com/.*#i'                    => array( 'http://api.imgur.com/oembed',                               true  ),
87
			'#https?://(www\.)?meetu(\.ps|p\.com)/.*#i'           => array( 'http://api.meetup.com/oembed',                              true  ),
88
			'#https?://(www\.)?issuu\.com/.+/docs/.+#i'           => array( 'http://issuu.com/oembed_wp',                                true  ),
89
			'#https?://(www\.)?collegehumor\.com/video/.*#i'      => array( 'http://www.collegehumor.com/oembed.{format}',               true  ),
90
			'#https?://(www\.)?mixcloud\.com/.*#i'                => array( 'http://www.mixcloud.com/oembed',                            true  ),
91
			'#https?://(www\.|embed\.)?ted\.com/talks/.*#i'       => array( 'http://www.ted.com/talks/oembed.{format}',                  true  ),
92
			'#https?://(www\.)?(animoto|video214)\.com/play/.*#i' => array( 'https://animoto.com/oembeds/create',                        true  ),
93
			'#https?://(.+)\.tumblr\.com/post/.*#i'               => array( 'https://www.tumblr.com/oembed/1.0',                         true  ),
94
			'#https?://(www\.)?kickstarter\.com/projects/.*#i'    => array( 'https://www.kickstarter.com/services/oembed',               true  ),
95
			'#https?://kck\.st/.*#i'                              => array( 'https://www.kickstarter.com/services/oembed',               true  ),
96
			'#https?://cloudup\.com/.*#i'                         => array( 'https://cloudup.com/oembed',                                true  ),
97
			'#https?://(www\.)?reverbnation\.com/.*#i'            => array( 'https://www.reverbnation.com/oembed',                       true  ),
98
			'#https?://videopress.com/v/.*#'                      => array( 'https://public-api.wordpress.com/oembed/1.0/?for=' . $host, true  ),
99
			'#https?://(www\.)?reddit\.com/r/[^/]+/comments/.*#i' => array( 'https://www.reddit.com/oembed',                             true  ),
100
			'#https?://(www\.)?speakerdeck\.com/.*#i'             => array( 'https://speakerdeck.com/oembed.{format}',                   true  ),
101
		);
102
103 View Code Duplication
		if ( ! empty( self::$early_providers['add'] ) ) {
104
			foreach ( self::$early_providers['add'] as $format => $data ) {
105
				$providers[ $format ] = $data;
106
			}
107
		}
108
109 View Code Duplication
		if ( ! empty( self::$early_providers['remove'] ) ) {
110
			foreach ( self::$early_providers['remove'] as $format ) {
111
				unset( $providers[ $format ] );
112
			}
113
		}
114
115
		self::$early_providers = array();
116
117
		/**
118
		 * Filters the list of whitelisted oEmbed providers.
119
		 *
120
		 * Since WordPress 4.4, oEmbed discovery is enabled for all users and allows embedding of sanitized
121
		 * iframes. The providers in this list are whitelisted, meaning they are trusted and allowed to
122
		 * embed any content, such as iframes, videos, JavaScript, and arbitrary HTML.
123
		 *
124
		 * Supported providers:
125
		 *
126
		 * |   Provider   |        Flavor         | Supports HTTPS |   Since   |
127
		 * | ------------ | --------------------- | :------------: | --------- |
128
		 * | Dailymotion  | dailymotion.com       |      Yes       | 2.9.0     |
129
		 * | Flickr       | flickr.com            |      Yes       | 2.9.0     |
130
		 * | Hulu         | hulu.com              |      Yes       | 2.9.0     |
131
		 * | Photobucket  | photobucket.com       |      No        | 2.9.0     |
132
		 * | Scribd       | scribd.com            |      Yes       | 2.9.0     |
133
		 * | Vimeo        | vimeo.com             |      Yes       | 2.9.0     |
134
		 * | WordPress.tv | wordpress.tv          |      Yes       | 2.9.0     |
135
		 * | YouTube      | youtube.com/watch     |      Yes       | 2.9.0     |
136
		 * | Funny or Die | funnyordie.com        |      Yes       | 3.0.0     |
137
		 * | Polldaddy    | polldaddy.com         |      Yes       | 3.0.0     |
138
		 * | SmugMug      | smugmug.com           |      Yes       | 3.0.0     |
139
		 * | YouTube      | youtu.be              |      Yes       | 3.0.0     |
140
		 * | Twitter      | twitter.com           |      Yes       | 3.4.0     |
141
		 * | Instagram    | instagram.com         |      Yes       | 3.5.0     |
142
		 * | Instagram    | instagr.am            |      Yes       | 3.5.0     |
143
		 * | Slideshare   | slideshare.net        |      Yes       | 3.5.0     |
144
		 * | SoundCloud   | soundcloud.com        |      Yes       | 3.5.0     |
145
		 * | Dailymotion  | dai.ly                |      Yes       | 3.6.0     |
146
		 * | Flickr       | flic.kr               |      Yes       | 3.6.0     |
147
		 * | Spotify      | spotify.com           |      Yes       | 3.6.0     |
148
		 * | Imgur        | imgur.com             |      Yes       | 3.9.0     |
149
		 * | Meetup.com   | meetup.com            |      Yes       | 3.9.0     |
150
		 * | Meetup.com   | meetu.ps              |      Yes       | 3.9.0     |
151
		 * | Animoto      | animoto.com           |      Yes       | 4.0.0     |
152
		 * | Animoto      | video214.com          |      Yes       | 4.0.0     |
153
		 * | CollegeHumor | collegehumor.com      |      Yes       | 4.0.0     |
154
		 * | Issuu        | issuu.com             |      Yes       | 4.0.0     |
155
		 * | Mixcloud     | mixcloud.com          |      Yes       | 4.0.0     |
156
		 * | Polldaddy    | poll.fm               |      Yes       | 4.0.0     |
157
		 * | TED          | ted.com               |      Yes       | 4.0.0     |
158
		 * | YouTube      | youtube.com/playlist  |      Yes       | 4.0.0     |
159
		 * | Vine         | vine.co               |      Yes       | 4.1.0     |
160
		 * | Tumblr       | tumblr.com            |      Yes       | 4.2.0     |
161
		 * | Kickstarter  | kickstarter.com       |      Yes       | 4.2.0     |
162
		 * | Kickstarter  | kck.st                |      Yes       | 4.2.0     |
163
		 * | Cloudup      | cloudup.com           |      Yes       | 4.4.0     |
164
		 * | ReverbNation | reverbnation.com      |      Yes       | 4.4.0     |
165
		 * | VideoPress   | videopress.com        |      Yes       | 4.4.0     |
166
		 * | Reddit       | reddit.com            |      Yes       | 4.4.0     |
167
		 * | Speaker Deck | speakerdeck.com       |      Yes       | 4.4.0     |
168
		 * | Twitter      | twitter.com/timelines |      Yes       | 4.5.0     |
169
		 * | Twitter      | twitter.com/moments   |      Yes       | 4.5.0     |
170
		 *
171
		 * No longer supported providers:
172
		 *
173
		 * |   Provider   |        Flavor        | Supports HTTPS |   Since   |  Removed  |
174
		 * | ------------ | -------------------- | :------------: | --------- | --------- |
175
		 * | Qik          | qik.com              |      Yes       | 2.9.0     | 3.9.0     |
176
		 * | Viddler      | viddler.com          |      Yes       | 2.9.0     | 4.0.0     |
177
		 * | Revision3    | revision3.com        |      No        | 2.9.0     | 4.2.0     |
178
		 * | Blip         | blip.tv              |      No        | 2.9.0     | 4.4.0     |
179
		 * | Rdio         | rdio.com             |      Yes       | 3.6.0     | 4.4.1     |
180
		 * | Rdio         | rd.io                |      Yes       | 3.6.0     | 4.4.1     |
181
		 *
182
		 * @see wp_oembed_add_provider()
183
		 *
184
		 * @since 2.9.0
185
		 *
186
		 * @param array $providers An array of popular oEmbed providers.
187
		 */
188
		$this->providers = apply_filters( 'oembed_providers', $providers );
0 ignored issues
show
Documentation Bug introduced by
It seems like apply_filters('oembed_providers', $providers) of type * is incompatible with the declared type array of property $providers.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
189
190
		// Fix any embeds that contain new lines in the middle of the HTML which breaks wpautop().
191
		add_filter( 'oembed_dataparse', array($this, '_strip_newlines'), 10, 3 );
192
	}
193
194
	/**
195
	 * Exposes private/protected methods for backward compatibility.
196
	 *
197
	 * @since 4.0.0
198
	 * @access public
199
	 *
200
	 * @param callable $name      Method to call.
201
	 * @param array    $arguments Arguments to pass when calling.
202
	 * @return mixed|bool Return value of the callback, false otherwise.
203
	 */
204
	public function __call( $name, $arguments ) {
205
		if ( in_array( $name, $this->compat_methods ) ) {
206
			return call_user_func_array( array( $this, $name ), $arguments );
207
		}
208
		return false;
209
	}
210
211
	/**
212
	 * Takes a URL and returns the corresponding oEmbed provider's URL, if there is one.
213
	 *
214
	 * @since 4.0.0
215
	 * @access public
216
	 *
217
	 * @see WP_oEmbed::discover()
218
	 *
219
	 * @param string        $url  The URL to the content.
220
	 * @param string|array  $args Optional provider arguments.
221
	 * @return false|string False on failure, otherwise the oEmbed provider URL.
222
	 */
223
	public function get_provider( $url, $args = '' ) {
224
		$args = wp_parse_args( $args );
225
226
		$provider = false;
227
228
		if ( !isset($args['discover']) )
229
			$args['discover'] = true;
230
231
		foreach ( $this->providers as $matchmask => $data ) {
232
			list( $providerurl, $regex ) = $data;
233
234
			// Turn the asterisk-type provider URLs into regex
235
			if ( !$regex ) {
236
				$matchmask = '#' . str_replace( '___wildcard___', '(.+)', preg_quote( str_replace( '*', '___wildcard___', $matchmask ), '#' ) ) . '#i';
237
				$matchmask = preg_replace( '|^#http\\\://|', '#https?\://', $matchmask );
238
			}
239
240
			if ( preg_match( $matchmask, $url ) ) {
241
				$provider = str_replace( '{format}', 'json', $providerurl ); // JSON is easier to deal with than XML
242
				break;
243
			}
244
		}
245
246
		if ( !$provider && $args['discover'] )
247
			$provider = $this->discover( $url );
248
249
		return $provider;
250
	}
251
252
	/**
253
	 * Adds an oEmbed provider.
254
	 *
255
	 * The provider is added just-in-time when wp_oembed_add_provider() is called before
256
	 * the {@see 'plugins_loaded'} hook.
257
	 *
258
	 * The just-in-time addition is for the benefit of the {@see 'oembed_providers'} filter.
259
	 *
260
	 * @static
261
	 * @since 4.0.0
262
	 * @access public
263
	 *
264
	 * @see wp_oembed_add_provider()
265
	 *
266
	 * @param string $format   Format of URL that this provider can handle. You can use
267
	 *                         asterisks as wildcards.
268
	 * @param string $provider The URL to the oEmbed provider..
269
	 * @param bool   $regex    Optional. Whether the $format parameter is in a regex format.
270
	 *                         Default false.
271
	 */
272
	public static function _add_provider_early( $format, $provider, $regex = false ) {
273
		if ( empty( self::$early_providers['add'] ) ) {
274
			self::$early_providers['add'] = array();
275
		}
276
277
		self::$early_providers['add'][ $format ] = array( $provider, $regex );
278
	}
279
280
	/**
281
	 * Removes an oEmbed provider.
282
	 *
283
	 * The provider is removed just-in-time when wp_oembed_remove_provider() is called before
284
	 * the {@see 'plugins_loaded'} hook.
285
	 *
286
	 * The just-in-time removal is for the benefit of the {@see 'oembed_providers'} filter.
287
	 *
288
	 * @since 4.0.0
289
	 * @access public
290
	 * @static
291
	 *
292
	 * @see wp_oembed_remove_provider()
293
	 *
294
	 * @param string $format The format of URL that this provider can handle. You can use
295
	 *                       asterisks as wildcards.
296
	 */
297
	public static function _remove_provider_early( $format ) {
298
		if ( empty( self::$early_providers['remove'] ) ) {
299
			self::$early_providers['remove'] = array();
300
		}
301
302
		self::$early_providers['remove'][] = $format;
303
	}
304
305
	/**
306
	 * The do-it-all function that takes a URL and attempts to return the HTML.
307
	 *
308
	 * @see WP_oEmbed::fetch()
309
	 * @see WP_oEmbed::data2html()
310
	 *
311
	 * @since 2.9.0
312
	 * @access public
313
	 *
314
	 * @param string       $url  The URL to the content that should be attempted to be embedded.
315
	 * @param array|string $args Optional. Arguments, usually passed from a shortcode. Default empty.
316
	 * @return false|string False on failure, otherwise the UNSANITIZED (and potentially unsafe) HTML that should be used to embed.
317
	 */
318
	public function get_html( $url, $args = '' ) {
319
		$args = wp_parse_args( $args );
320
321
		/**
322
		 * Filters the oEmbed result before any HTTP requests are made.
323
		 *
324
		 * This allows one to short-circuit the default logic, perhaps by
325
		 * replacing it with a routine that is more optimal for your setup.
326
		 *
327
		 * Passing a non-null value to the filter will effectively short-circuit retrieval,
328
		 * returning the passed value instead.
329
		 *
330
		 * @since 4.5.3
331
		 *
332
		 * @param null|string $result The UNSANITIZED (and potentially unsafe) HTML that should be used to embed. Default null.
333
		 * @param string      $url    The URL to the content that should be attempted to be embedded.
334
		 * @param array       $args   Optional. Arguments, usually passed from a shortcode. Default empty.
335
		 */
336
		$pre = apply_filters( 'pre_oembed_result', null, $url, $args );
337
338
		if ( null !== $pre ) {
339
			return $pre;
340
		}
341
342
		$provider = $this->get_provider( $url, $args );
343
344
		if ( ! $provider || false === $data = $this->fetch( $provider, $url, $args ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $provider of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
345
			return false;
346
		}
347
348
		/**
349
		 * Filters the HTML returned by the oEmbed provider.
350
		 *
351
		 * @since 2.9.0
352
		 *
353
		 * @param string $data The returned oEmbed HTML.
354
		 * @param string $url  URL of the content to be embedded.
355
		 * @param array  $args Optional arguments, usually passed from a shortcode.
356
		 */
357
		return apply_filters( 'oembed_result', $this->data2html( $data, $url ), $url, $args );
358
	}
359
360
	/**
361
	 * Attempts to discover link tags at the given URL for an oEmbed provider.
362
	 *
363
	 * @since 2.9.0
364
	 * @access public
365
	 *
366
	 * @param string $url The URL that should be inspected for discovery `<link>` tags.
367
	 * @return false|string False on failure, otherwise the oEmbed provider URL.
368
	 */
369
	public function discover( $url ) {
370
		$providers = array();
371
		$args = array(
372
			'limit_response_size' => 153600, // 150 KB
373
		);
374
375
		/**
376
		 * Filters oEmbed remote get arguments.
377
		 *
378
		 * @since 4.0.0
379
		 *
380
		 * @see WP_Http::request()
381
		 *
382
		 * @param array  $args oEmbed remote get arguments.
383
		 * @param string $url  URL to be inspected.
384
		 */
385
		$args = apply_filters( 'oembed_remote_get_args', $args, $url );
386
387
		// Fetch URL content
388
		$request = wp_safe_remote_get( $url, $args );
389
		if ( $html = wp_remote_retrieve_body( $request ) ) {
0 ignored issues
show
It seems like $request defined by wp_safe_remote_get($url, $args) on line 388 can also be of type object<WP_Error>; however, wp_remote_retrieve_body() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
390
391
			/**
392
			 * Filters the link types that contain oEmbed provider URLs.
393
			 *
394
			 * @since 2.9.0
395
			 *
396
			 * @param array $format Array of oEmbed link types. Accepts 'application/json+oembed',
397
			 *                      'text/xml+oembed', and 'application/xml+oembed' (incorrect,
398
			 *                      used by at least Vimeo).
399
			 */
400
			$linktypes = apply_filters( 'oembed_linktypes', array(
401
				'application/json+oembed' => 'json',
402
				'text/xml+oembed' => 'xml',
403
				'application/xml+oembed' => 'xml',
404
			) );
405
406
			// Strip <body>
407
			if ( $html_head_end = stripos( $html, '</head>' ) ) {
408
				$html = substr( $html, 0, $html_head_end );
409
			}
410
411
			// Do a quick check
412
			$tagfound = false;
413
			foreach ( $linktypes as $linktype => $format ) {
414
				if ( stripos($html, $linktype) ) {
415
					$tagfound = true;
416
					break;
417
				}
418
			}
419
420
			if ( $tagfound && preg_match_all( '#<link([^<>]+)/?>#iU', $html, $links ) ) {
421
				foreach ( $links[1] as $link ) {
422
					$atts = shortcode_parse_atts( $link );
423
424
					if ( !empty($atts['type']) && !empty($linktypes[$atts['type']]) && !empty($atts['href']) ) {
425
						$providers[$linktypes[$atts['type']]] = htmlspecialchars_decode( $atts['href'] );
426
427
						// Stop here if it's JSON (that's all we need)
428
						if ( 'json' == $linktypes[$atts['type']] )
429
							break;
430
					}
431
				}
432
			}
433
		}
434
435
		// JSON is preferred to XML
436
		if ( !empty($providers['json']) )
437
			return $providers['json'];
438
		elseif ( !empty($providers['xml']) )
439
			return $providers['xml'];
440
		else
441
			return false;
442
	}
443
444
	/**
445
	 * Connects to a oEmbed provider and returns the result.
446
	 *
447
	 * @since 2.9.0
448
	 * @access public
449
	 *
450
	 * @param string       $provider The URL to the oEmbed provider.
451
	 * @param string       $url      The URL to the content that is desired to be embedded.
452
	 * @param array|string $args     Optional. Arguments, usually passed from a shortcode. Default empty.
453
	 * @return false|object False on failure, otherwise the result in the form of an object.
454
	 */
455
	public function fetch( $provider, $url, $args = '' ) {
456
		$args = wp_parse_args( $args, wp_embed_defaults( $url ) );
457
458
		$provider = add_query_arg( 'maxwidth', (int) $args['width'], $provider );
459
		$provider = add_query_arg( 'maxheight', (int) $args['height'], $provider );
460
		$provider = add_query_arg( 'url', urlencode($url), $provider );
461
462
		/**
463
		 * Filters the oEmbed URL to be fetched.
464
		 *
465
		 * @since 2.9.0
466
		 *
467
		 * @param string $provider URL of the oEmbed provider.
468
		 * @param string $url      URL of the content to be embedded.
469
		 * @param array  $args     Optional arguments, usually passed from a shortcode.
470
		 */
471
		$provider = apply_filters( 'oembed_fetch_url', $provider, $url, $args );
472
473
		foreach ( array( 'json', 'xml' ) as $format ) {
474
			$result = $this->_fetch_with_format( $provider, $format );
475
			if ( is_wp_error( $result ) && 'not-implemented' == $result->get_error_code() )
476
				continue;
477
			return ( $result && ! is_wp_error( $result ) ) ? $result : false;
478
		}
479
		return false;
480
	}
481
482
	/**
483
	 * Fetches result from an oEmbed provider for a specific format and complete provider URL
484
	 *
485
	 * @since 3.0.0
486
	 * @access private
487
	 *
488
	 * @param string $provider_url_with_args URL to the provider with full arguments list (url, maxheight, etc.)
489
	 * @param string $format Format to use
490
	 * @return false|object|WP_Error False on failure, otherwise the result in the form of an object.
491
	 */
492
	private function _fetch_with_format( $provider_url_with_args, $format ) {
493
		$provider_url_with_args = add_query_arg( 'format', $format, $provider_url_with_args );
494
495
		/** This filter is documented in wp-includes/class-oembed.php */
496
		$args = apply_filters( 'oembed_remote_get_args', array(), $provider_url_with_args );
497
498
		$response = wp_safe_remote_get( $provider_url_with_args, $args );
499
		if ( 501 == wp_remote_retrieve_response_code( $response ) )
500
			return new WP_Error( 'not-implemented' );
501
		if ( ! $body = wp_remote_retrieve_body( $response ) )
502
			return false;
503
		$parse_method = "_parse_$format";
504
		return $this->$parse_method( $body );
505
	}
506
507
	/**
508
	 * Parses a json response body.
509
	 *
510
	 * @since 3.0.0
511
	 * @access private
512
	 *
513
	 * @param string $response_body
514
	 * @return object|false
515
	 */
516
	private function _parse_json( $response_body ) {
517
		$data = json_decode( trim( $response_body ) );
518
		return ( $data && is_object( $data ) ) ? $data : false;
519
	}
520
521
	/**
522
	 * Parses an XML response body.
523
	 *
524
	 * @since 3.0.0
525
	 * @access private
526
	 *
527
	 * @param string $response_body
528
	 * @return object|false
529
	 */
530
	private function _parse_xml( $response_body ) {
531
		if ( ! function_exists( 'libxml_disable_entity_loader' ) )
532
			return false;
533
534
		$loader = libxml_disable_entity_loader( true );
535
		$errors = libxml_use_internal_errors( true );
536
537
		$return = $this->_parse_xml_body( $response_body );
538
539
		libxml_use_internal_errors( $errors );
540
		libxml_disable_entity_loader( $loader );
541
542
		return $return;
543
	}
544
545
	/**
546
	 * Serves as a helper function for parsing an XML response body.
547
	 *
548
	 * @since 3.6.0
549
	 * @access private
550
	 *
551
	 * @param string $response_body
552
	 * @return object|false
553
	 */
554
	private function _parse_xml_body( $response_body ) {
555
		if ( ! function_exists( 'simplexml_import_dom' ) || ! class_exists( 'DOMDocument', false ) )
556
			return false;
557
558
		$dom = new DOMDocument;
559
		$success = $dom->loadXML( $response_body );
560
		if ( ! $success )
561
			return false;
562
563
		if ( isset( $dom->doctype ) )
564
			return false;
565
566
		foreach ( $dom->childNodes as $child ) {
567
			if ( XML_DOCUMENT_TYPE_NODE === $child->nodeType )
568
				return false;
569
		}
570
571
		$xml = simplexml_import_dom( $dom );
572
		if ( ! $xml )
573
			return false;
574
575
		$return = new stdClass;
576
		foreach ( $xml as $key => $value ) {
577
			$return->$key = (string) $value;
578
		}
579
580
		return $return;
581
	}
582
583
	/**
584
	 * Converts a data object from WP_oEmbed::fetch() and returns the HTML.
585
	 *
586
	 * @since 2.9.0
587
	 * @access public
588
	 *
589
	 * @param object $data A data object result from an oEmbed provider.
590
	 * @param string $url The URL to the content that is desired to be embedded.
591
	 * @return false|string False on error, otherwise the HTML needed to embed.
592
	 */
593
	public function data2html( $data, $url ) {
594
		if ( ! is_object( $data ) || empty( $data->type ) )
595
			return false;
596
597
		$return = false;
598
599
		switch ( $data->type ) {
600
			case 'photo':
601
				if ( empty( $data->url ) || empty( $data->width ) || empty( $data->height ) )
602
					break;
603
				if ( ! is_string( $data->url ) || ! is_numeric( $data->width ) || ! is_numeric( $data->height ) )
604
					break;
605
606
				$title = ! empty( $data->title ) && is_string( $data->title ) ? $data->title : '';
607
				$return = '<a href="' . esc_url( $url ) . '"><img src="' . esc_url( $data->url ) . '" alt="' . esc_attr($title) . '" width="' . esc_attr($data->width) . '" height="' . esc_attr($data->height) . '" /></a>';
608
				break;
609
610
			case 'video':
611
			case 'rich':
612
				if ( ! empty( $data->html ) && is_string( $data->html ) )
613
					$return = $data->html;
614
				break;
615
616
			case 'link':
617
				if ( ! empty( $data->title ) && is_string( $data->title ) )
618
					$return = '<a href="' . esc_url( $url ) . '">' . esc_html( $data->title ) . '</a>';
619
				break;
620
621
			default:
622
				$return = false;
623
		}
624
625
		/**
626
		 * Filters the returned oEmbed HTML.
627
		 *
628
		 * Use this filter to add support for custom data types, or to filter the result.
629
		 *
630
		 * @since 2.9.0
631
		 *
632
		 * @param string $return The returned oEmbed HTML.
633
		 * @param object $data   A data object result from an oEmbed provider.
634
		 * @param string $url    The URL of the content to be embedded.
635
		 */
636
		return apply_filters( 'oembed_dataparse', $return, $data, $url );
637
	}
638
639
	/**
640
	 * Strips any new lines from the HTML.
641
	 *
642
	 * @since 2.9.0 as strip_scribd_newlines()
643
	 * @since 3.0.0
644
	 * @access public
645
	 *
646
	 * @param string $html Existing HTML.
647
	 * @param object $data Data object from WP_oEmbed::data2html()
648
	 * @param string $url The original URL passed to oEmbed.
649
	 * @return string Possibly modified $html
650
	 */
651
	public function _strip_newlines( $html, $data, $url ) {
652
		if ( false === strpos( $html, "\n" ) ) {
653
			return $html;
654
		}
655
656
		$count = 1;
657
		$found = array();
658
		$token = '__PRE__';
659
		$search = array( "\t", "\n", "\r", ' ' );
660
		$replace = array( '__TAB__', '__NL__', '__CR__', '__SPACE__' );
661
		$tokenized = str_replace( $search, $replace, $html );
662
663
		preg_match_all( '#(<pre[^>]*>.+?</pre>)#i', $tokenized, $matches, PREG_SET_ORDER );
664
		foreach ( $matches as $i => $match ) {
0 ignored issues
show
The expression $matches of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
665
			$tag_html = str_replace( $replace, $search, $match[0] );
666
			$tag_token = $token . $i;
667
668
			$found[ $tag_token ] = $tag_html;
669
			$html = str_replace( $tag_html, $tag_token, $html, $count );
670
		}
671
672
		$replaced = str_replace( $replace, $search, $html );
673
		$stripped = str_replace( array( "\r\n", "\n" ), '', $replaced );
674
		$pre = array_values( $found );
675
		$tokens = array_keys( $found );
676
677
		return str_replace( $tokens, $pre, $stripped );
678
	}
679
}
680
681
/**
682
 * Returns the initialized WP_oEmbed object.
683
 *
684
 * @since 2.9.0
685
 * @access private
686
 *
687
 * @staticvar WP_oEmbed $wp_oembed
688
 *
689
 * @return WP_oEmbed object.
690
 */
691
function _wp_oembed_get_object() {
692
	static $wp_oembed = null;
693
694
	if ( is_null( $wp_oembed ) ) {
695
		$wp_oembed = new WP_oEmbed();
696
	}
697
	return $wp_oembed;
698
}
699