@@ -239,10 +239,6 @@ public function _s( $args, $assoc_args ) {
239239 $ url = "http://underscores.me " ;
240240 $ timeout = 30 ;
241241
242- if ( in_array ( $ theme_slug , array ( '. ' , '.. ' ) ) ) {
243- WP_CLI ::error ( "Invalid theme slug specified. " );
244- }
245-
246242 if ( ! preg_match ( '/^[a-z_]\w+$/i ' , str_replace ( '- ' , '_ ' , $ theme_slug ) ) ) {
247243 WP_CLI ::error ( "Invalid theme slug specified. Theme slugs can only contain letters, numbers, underscores and hyphens, and can only start with a letter or underscore. " );
248244 }
@@ -255,8 +251,8 @@ public function _s( $args, $assoc_args ) {
255251
256252 $ _s_theme_path = "$ theme_path/ $ data [theme_name]" ;
257253
258- if ( ! $ this ->check_target_directory ( "theme " , $ _s_theme_path ) ) {
259- WP_CLI ::error ( "Invalid theme slug specified. " );
254+ if ( $ error_msg = $ this ->check_target_directory ( "theme " , $ _s_theme_path ) ) {
255+ WP_CLI ::error ( "Invalid theme slug specified. { $ error_msg } " );
260256 }
261257
262258 $ force = \WP_CLI \Utils \get_flag_value ( $ assoc_args , 'force ' );
@@ -366,7 +362,7 @@ function child_theme( $args, $assoc_args ) {
366362 $ theme_slug = $ args [0 ];
367363
368364 if ( in_array ( $ theme_slug , array ( '. ' , '.. ' ) ) ) {
369- WP_CLI ::error ( "Invalid theme slug specified. " );
365+ WP_CLI ::error ( "Invalid theme slug specified. The slug cannot be '.' or '..'. " );
370366 }
371367
372368 $ data = wp_parse_args ( $ assoc_args , array (
@@ -382,8 +378,8 @@ function child_theme( $args, $assoc_args ) {
382378
383379 $ theme_dir = WP_CONTENT_DIR . "/themes " . "/ $ theme_slug " ;
384380
385- if ( ! $ this ->check_target_directory ( "theme " , $ theme_dir ) ) {
386- WP_CLI ::error ( "Invalid theme slug specified. " );
381+ if ( $ error_msg = $ this ->check_target_directory ( "theme " , $ theme_dir ) ) {
382+ WP_CLI ::error ( "Invalid theme slug specified. { $ error_msg } " );
387383 }
388384
389385 $ theme_style_path = "$ theme_dir/style.css " ;
@@ -512,7 +508,7 @@ function plugin( $args, $assoc_args ) {
512508 $ plugin_package = str_replace ( ' ' , '_ ' , $ plugin_name );
513509
514510 if ( in_array ( $ plugin_slug , array ( '. ' , '.. ' ) ) ) {
515- WP_CLI ::error ( "Invalid plugin slug specified. " );
511+ WP_CLI ::error ( "Invalid plugin slug specified. The slug cannot be '.' or '..'. " );
516512 }
517513
518514 $ data = wp_parse_args ( $ assoc_args , array (
@@ -537,8 +533,8 @@ function plugin( $args, $assoc_args ) {
537533 $ plugin_dir = WP_PLUGIN_DIR . "/ $ plugin_slug " ;
538534 $ this ->maybe_create_plugins_dir ();
539535
540- if ( ! $ this ->check_target_directory ( "plugin " , $ plugin_dir ) ) {
541- WP_CLI ::error ( "Invalid plugin slug specified. " );
536+ if ( $ error_msg = $ this ->check_target_directory ( "plugin " , $ plugin_dir ) ) {
537+ WP_CLI ::error ( "Invalid plugin slug specified. { $ error_msg } " );
542538 }
543539 }
544540
@@ -684,30 +680,30 @@ private function scaffold_plugin_theme_tests( $args, $assoc_args, $type ) {
684680 if ( ! empty ( $ args [0 ] ) ) {
685681 $ slug = $ args [0 ];
686682 if ( in_array ( $ slug , array ( '. ' , '.. ' ) ) ) {
687- WP_CLI ::error ( "Invalid {$ type } slug specified. " );
683+ WP_CLI ::error ( "Invalid {$ type } slug specified. The slug cannot be '.' or '..'. " );
688684 }
689685 if ( 'theme ' === $ type ) {
690686 $ theme = wp_get_theme ( $ slug );
691687 if ( $ theme ->exists () ) {
692688 $ target_dir = $ theme ->get_stylesheet_directory ();
693689 } else {
694- WP_CLI ::error ( "Invalid {$ type } slug specified. " );
690+ WP_CLI ::error ( "Invalid {$ type } slug specified. The theme ' { $ slug } ' does not exist. " );
695691 }
696692 } else {
697693 $ target_dir = WP_PLUGIN_DIR . "/ $ slug " ;
698694 }
699695 if ( empty ( $ assoc_args ['dir ' ] ) && ! is_dir ( $ target_dir ) ) {
700- WP_CLI ::error ( "Invalid {$ type } slug specified. " );
696+ WP_CLI ::error ( "Invalid {$ type } slug specified. No such target directory ' { $ target_dir } '. " );
701697 }
702- if ( ! $ this ->check_target_directory ( $ type , $ target_dir ) ) {
703- WP_CLI ::error ( "Invalid {$ type } slug specified. " );
698+ if ( $ error_msg = $ this ->check_target_directory ( $ type , $ target_dir ) ) {
699+ WP_CLI ::error ( "Invalid {$ type } slug specified. { $ error_msg } " );
704700 }
705701 }
706702
707703 if ( ! empty ( $ assoc_args ['dir ' ] ) ) {
708704 $ target_dir = $ assoc_args ['dir ' ];
709705 if ( ! is_dir ( $ target_dir ) ) {
710- WP_CLI ::error ( "Invalid {$ type } directory specified. " );
706+ WP_CLI ::error ( "Invalid {$ type } directory specified. No such directory ' { $ target_dir } '. " );
711707 }
712708 if ( empty ( $ slug ) ) {
713709 $ slug = Utils \basename ( $ target_dir );
@@ -788,22 +784,27 @@ private function scaffold_plugin_theme_tests( $args, $assoc_args, $type ) {
788784 );
789785 }
790786
787+ /**
788+ * Checks that the `$target_dir` is a child directory of the WP themes or plugins directory, depending on `$type`.
789+ *
790+ * @param string $type "theme" or "plugin"
791+ * @param string $target_dir The theme/plugin directory to check.
792+ *
793+ * @return null|string Returns null on success, error message on error.
794+ */
791795 private function check_target_directory ( $ type , $ target_dir ) {
792- if ( realpath ( $ target_dir ) ) {
793- $ target_dir = realpath ( $ target_dir );
794- }
795-
796- $ parent_dir = str_replace ( '\\' , '/ ' , dirname ( $ target_dir ) );
796+ $ parent_dir = dirname ( self ::canonicalize_path ( str_replace ( '\\' , '/ ' , $ target_dir ) ) );
797797
798- if ( 'theme ' === $ type && str_replace ( '\\' , '/ ' , WP_CONTENT_DIR . '/themes ' ) = == $ parent_dir ) {
799- return true ;
798+ if ( 'theme ' === $ type && str_replace ( '\\' , '/ ' , WP_CONTENT_DIR . '/themes ' ) ! == $ parent_dir ) {
799+ return sprintf ( ' The target directory \' %1$s \' is not in \' %2$s \' . ' , $ target_dir , WP_CONTENT_DIR . ' /themes ' ) ;
800800 }
801801
802- if ( 'plugin ' === $ type && str_replace ( '\\' , '/ ' , WP_PLUGIN_DIR ) = == $ parent_dir ) {
803- return true ;
802+ if ( 'plugin ' === $ type && str_replace ( '\\' , '/ ' , WP_PLUGIN_DIR ) ! == $ parent_dir ) {
803+ return sprintf ( ' The target directory \' %1$s \' is not in \' %2$s \' . ' , $ target_dir , WP_PLUGIN_DIR ) ;
804804 }
805805
806- return false ;
806+ // Success.
807+ return null ;
807808 }
808809
809810 protected function create_files ( $ files_and_contents , $ force ) {
@@ -1004,4 +1005,35 @@ private static function get_template_path( $template ) {
10041005 return $ template_path ;
10051006 }
10061007
1008+ /*
1009+ * Returns the canonicalized path, with dot and double dot segments resolved.
1010+ *
1011+ * Copied from Symfony\Component\DomCrawler\AbstractUriElement::canonicalizePath().
1012+ * Implements RFC 3986, section 5.2.4.
1013+ *
1014+ * @param string $path The path to make canonical.
1015+ *
1016+ * @return string The canonicalized path.
1017+ */
1018+ private static function canonicalize_path ( $ path ) {
1019+ if ( '' === $ path || '/ ' === $ path ) {
1020+ return $ path ;
1021+ }
1022+
1023+ if ( '. ' === substr ( $ path , -1 ) ) {
1024+ $ path .= '/ ' ;
1025+ }
1026+
1027+ $ output = array ();
1028+
1029+ foreach ( explode ( '/ ' , $ path ) as $ segment ) {
1030+ if ( '.. ' === $ segment ) {
1031+ array_pop ( $ output );
1032+ } elseif ( '. ' !== $ segment ) {
1033+ $ output [] = $ segment ;
1034+ }
1035+ }
1036+
1037+ return implode ( '/ ' , $ output );
1038+ }
10071039}
0 commit comments