Skip to content

Commit 924e784

Browse files
Merge pull request #26 from gitlost/issue_22
Cater for symbolic links. Add canonicalize_path and use in check_target_directory.
2 parents fd3a648 + dca2467 commit 924e784

File tree

3 files changed

+150
-37
lines changed

3 files changed

+150
-37
lines changed

features/scaffold-plugin-tests.feature

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,15 +140,60 @@ Feature: Scaffold plugin unit tests
140140

141141
Scenario: Scaffold plugin tests with invalid slug
142142
Given a WP install
143+
Then the {RUN_DIR}/wp-content/plugins/hello.php file should exist
144+
145+
When I try `wp scaffold plugin-tests hello`
146+
Then STDERR should be:
147+
"""
148+
Error: Invalid plugin slug specified. No such target directory '{RUN_DIR}/wp-content/plugins/hello'.
149+
"""
143150

144151
When I try `wp scaffold plugin-tests .`
145-
Then STDERR should contain:
152+
Then STDERR should be:
146153
"""
147-
Error: Invalid plugin slug specified.
154+
Error: Invalid plugin slug specified. The slug cannot be '.' or '..'.
148155
"""
149156

150157
When I try `wp scaffold plugin-tests ../`
151-
Then STDERR should contain:
158+
Then STDERR should be:
159+
"""
160+
Error: Invalid plugin slug specified. The target directory '{RUN_DIR}/wp-content/plugins/../' is not in '{RUN_DIR}/wp-content/plugins'.
161+
"""
162+
163+
Scenario: Scaffold plugin tests with invalid directory
164+
Given a WP install
165+
And I run `wp scaffold plugin hello-world --skip-tests`
166+
167+
When I run `wp plugin path hello-world --dir`
168+
Then save STDOUT as {PLUGIN_DIR}
169+
170+
When I try `wp scaffold plugin-tests hello-world --dir=non-existent-dir`
171+
Then STDERR should be:
172+
"""
173+
Error: Invalid plugin directory specified. No such directory 'non-existent-dir'.
174+
"""
175+
176+
When I run `rm -rf {PLUGIN_DIR} && touch {PLUGIN_DIR}`
177+
Then the return code should be 0
178+
When I try `wp scaffold plugin-tests hello-world`
179+
Then STDERR should be:
180+
"""
181+
Error: Invalid plugin slug specified. No such target directory '{PLUGIN_DIR}'.
182+
"""
183+
184+
Scenario: Scaffold plugin tests with a symbolic link
185+
Given a WP install
186+
And I run `wp scaffold plugin hello-world --skip-tests`
187+
188+
When I run `wp plugin path hello-world --dir`
189+
Then save STDOUT as {PLUGIN_DIR}
190+
191+
When I run `mv {PLUGIN_DIR} {RUN_DIR} && ln -s {RUN_DIR}/hello-world {PLUGIN_DIR}`
192+
Then the return code should be 0
193+
194+
When I run `wp scaffold plugin-tests hello-world`
195+
Then STDOUT should not be empty
196+
And the {PLUGIN_DIR}/tests directory should contain:
152197
"""
153-
Error: Invalid plugin slug specified.
198+
bootstrap.php
154199
"""

features/scaffold-theme-tests.feature

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ Feature: Scaffold theme unit tests
7777
When I try `wp scaffold theme-tests p3child`
7878
Then STDERR should be:
7979
"""
80-
Error: Invalid theme slug specified.
80+
Error: Invalid theme slug specified. The theme 'p3child' does not exist.
8181
"""
8282

8383
Scenario: Scaffold theme tests with Circle as the provider
@@ -101,14 +101,50 @@ Feature: Scaffold theme unit tests
101101
Scenario: Scaffold theme tests with invalid slug
102102

103103
When I try `wp scaffold theme-tests .`
104-
Then STDERR should contain:
104+
Then STDERR should be:
105105
"""
106-
Error: Invalid theme slug specified.
106+
Error: Invalid theme slug specified. The slug cannot be '.' or '..'.
107107
"""
108108

109109
When I try `wp scaffold theme-tests ../`
110-
Then STDERR should contain:
110+
Then STDERR should be:
111+
"""
112+
Error: Invalid theme slug specified. The target directory '{RUN_DIR}/wp-content/themes/../' is not in '{RUN_DIR}/wp-content/themes'.
113+
"""
114+
115+
Scenario: Scaffold theme tests with invalid directory
116+
When I try `wp scaffold theme-tests p2 --dir=non-existent-dir`
117+
Then STDERR should be:
118+
"""
119+
Error: Invalid theme directory specified. No such directory 'non-existent-dir'.
120+
"""
121+
122+
# Temporarily move.
123+
When I run `mv -f {THEME_DIR}/p2 {THEME_DIR}/hide-p2 && touch {THEME_DIR}/p2`
124+
Then the return code should be 0
125+
126+
When I try `wp scaffold theme-tests p2`
127+
Then STDERR should be:
128+
"""
129+
Error: Invalid theme slug specified. No such target directory '{THEME_DIR}/p2'.
111130
"""
112-
Error: Invalid theme slug specified.
131+
132+
# Restore.
133+
When I run `rm -f {THEME_DIR}/p2 && mv -f {THEME_DIR}/hide-p2 {THEME_DIR}/p2`
134+
Then the return code should be 0
135+
136+
Scenario: Scaffold theme tests with a symbolic link
137+
# Temporarily move the whole theme dir and create a symbolic link to it.
138+
When I run `mv -f {THEME_DIR} {RUN_DIR}/alt-themes && ln -s {RUN_DIR}/alt-themes {THEME_DIR}`
139+
Then the return code should be 0
140+
141+
When I run `wp scaffold theme-tests p2`
142+
Then STDOUT should not be empty
143+
And the {THEME_DIR}/p2/tests directory should contain:
144+
"""
145+
bootstrap.php
113146
"""
114147

148+
# Restore.
149+
When I run `unlink {THEME_DIR} && mv -f {RUN_DIR}/alt-themes {THEME_DIR}`
150+
Then the return code should be 0

src/Scaffold_Command.php

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)