Here is our function in question.
File: wp-includes/theme.php
320: /**
321: * Retrieve theme directory URI.
322: *
323: * @since 1.5.0
324: *
325: * @return string Template directory URI.
326: */
327: function get_template_directory_uri() {
328: $template = str_replace( '%2F', '/', rawurlencode( get_template() ) );
329: $theme_root_uri = get_theme_root_uri( $template );
330: $template_dir_uri = "$theme_root_uri/$template";
331:
332: /**
333: * Filters the current theme directory URI.
334: *
335: * @since 1.5.0
336: *
337: * @param string $template_dir_uri The URI of the current theme directory.
338: * @param string $template Directory name of the current theme.
339: * @param string $theme_root_uri The themes root URI.
340: */
341: return apply_filters( 'template_directory_uri', $template_dir_uri, $template, $theme_root_uri );
342: }
343:
You write something like this in your plugin:
add_filter('template_directory_uri',function(){ return 'http://badserver.com/'; });
And you will have something like this on a website:

Bright is the future because I see a WP_Theme
class doesn't implement the filter in the WP_Theme::get_template_directory_uri()
method. We know slowly WordPress is getting better and better and more encapsulated.
File: wp-includes/class-wp-theme.php
898: /**
899: * Returns the URL to the directory of a theme's "template" files.
900: *
901: * In the case of a child theme, this is the URL to the directory of the
902: * parent theme's files.
903: *
904: * @since 3.4.0
905: * @access public
906: *
907: * @return string URL to the template directory.
908: */
909: public function get_template_directory_uri() {
910: if ( $this->parent() )
911: $theme_root_uri = $this->parent()->get_theme_root_uri();
912: else
913: $theme_root_uri = $this->get_theme_root_uri();
914:
915: return $theme_root_uri . '/' . str_replace( '%2F', '/', rawurlencode( $this->template ) );
916: }
More notes
You see someone defines in the theme someting like this:
define( 'TEMPL_URI', get_template_directory_uri() );
They say it is because they would like to make 'TEMPL_URI'
unfilterable. They are wrong. If a plugin set a bad filter this will happen before the theme, so the 'TEMPL_URI'
will be dirty.
However, it is OK to define the constants, I just wanted to say the constant will not protect you.
Another thing I found in Twenty Seventeen
. In there I saw get_theme_file_uri
function ( @since 4.7 )
File: wp-includes/link-template.php
4026: /**
4027: * Retrieves the URL of a file in the theme.
4028: *
4029: * Searches in the stylesheet directory before the template directory so themes
4030: * which inherit from a parent theme can just override one file.
4031: *
4032: * @since 4.7.0
4033: *
4034: * @param string $file Optional. File to search for in the stylesheet directory.
4035: * @return string The URL of the file.
4036: */
4037: function get_theme_file_uri( $file = '' ) {
4038: $file = ltrim( $file, '/' );
4039:
4040: if ( empty( $file ) ) {
4041: $url = get_stylesheet_directory_uri();
4042: } elseif ( file_exists( get_stylesheet_directory() . '/' . $file ) ) {
4043: $url = get_stylesheet_directory_uri() . '/' . $file;
4044: } else {
4045: $url = get_template_directory_uri() . '/' . $file;
4046: }
4047:
4048: /**
4049: * Filters the URL to a file in the theme.
4050: *
4051: * @since 4.7.0
4052: *
4053: * @param string $url The file URL.
4054: * @param string $file The requested file to search for.
4055: */
4056: return apply_filters( 'theme_file_uri', $url, $file );
4057: }
Again this filter theme_file_uri
may impose a security problem like @toscho explained for get_template_directory_uri
.
Conclusion
When the security is in question we need to be very careful. The problem in here is deeper than just escaping the single URL. It considers the whole WordPress plugins security model.
Plugins do not need get_template_directory_uri()
function and the filter in there to do the bad things. They can execute with the same privileges as the WordPress core — they can do anything.
Malicious JavaScript that bad plugins may inject may read your passwords while you type.
WP_Styles
which does all the escaping (seeWP_Styles::_css_href()
).WP_Styles
instance lives in a global variable (WordPress …) and can be replaced easily.