Skip to content

Commit a7ba5a8

Browse files
committed
WIP Bundle path validation
1 parent f12933a commit a7ba5a8

1 file changed

Lines changed: 87 additions & 5 deletions

File tree

components/Blueprints/BlueprintParser.php

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ private function createExecutionPlan( array $blueprint ): array {
245245
if ( ! empty( $blueprint['themes'] ) && is_array( $blueprint['themes'] ) ) {
246246
foreach ( $blueprint['themes'] as $themeRef ) {
247247
if ( is_string( $themeRef ) ) {
248-
$plan[] = [
248+
$step = [
249249
'name' => 'installTheme',
250250
'args' => [
251251
'source' => $themeRef,
@@ -255,7 +255,7 @@ private function createExecutionPlan( array $blueprint ): array {
255255
];
256256
} elseif ( is_array( $themeRef ) && isset( $themeRef['source'] ) && is_string( $themeRef['source'] ) ) {
257257
// Pass through the raw definition for extensibility.
258-
$plan[] = [
258+
$step = [
259259
'name' => 'installTheme',
260260
'args' => [
261261
'source' => $themeRef['source'],
@@ -267,14 +267,20 @@ private function createExecutionPlan( array $blueprint ): array {
267267
} else {
268268
throw new InvalidArgumentException( 'Invalid theme reference format in "themes" array.' );
269269
}
270+
271+
$plan[] = $step;
272+
$error = $this->validateDataSource( $step['args']['source'], 'wp-content/themes/*' );
273+
if ( $error ) {
274+
$errors['themes'] = [$error];
275+
}
270276
}
271277
}
272278

273279
// 5. activeTheme (install and activate)
274280
if ( isset( $blueprint['activeTheme'] ) ) {
275281
$themeRef = $blueprint['activeTheme'];
276282
if ( is_string( $themeRef ) ) {
277-
$plan[] = [
283+
$step = [
278284
'name' => 'installTheme',
279285
'args' => [
280286
'source' => $themeRef,
@@ -283,9 +289,9 @@ private function createExecutionPlan( array $blueprint ): array {
283289
]
284290
];
285291
} elseif ( is_array( $themeRef ) && isset( $themeRef['source'] ) && is_string( $themeRef['source'] ) ) {
286-
$plan[] = [
292+
$step = [
287293
'name' => 'installTheme',
288-
'args' => [
294+
'args' => [
289295
'source' => $themeRef['source'],
290296
'active' => true,
291297
'importStarterContent' => $themeRef['importStarterContent'] ?? false,
@@ -295,6 +301,12 @@ private function createExecutionPlan( array $blueprint ): array {
295301
} else {
296302
throw new InvalidArgumentException( 'Invalid theme reference format for "activeTheme".' );
297303
}
304+
305+
$plan[] = $step;
306+
$error = $this->validateDataSource( $step['args']['source'], 'wp-content/themes/*' );
307+
if ( $error ) {
308+
$errors['themes'] = [$error];
309+
}
298310
}
299311

300312
// 6. plugins
@@ -309,6 +321,11 @@ private function createExecutionPlan( array $blueprint ): array {
309321
'name' => 'installPlugin',
310322
'args' => $pluginDef
311323
];
324+
325+
$error = $this->validateDataSource( $pluginDef['source'], 'wp-content/plugins/*' );
326+
if ( $error ) {
327+
$errors['plugins'] = [$error];
328+
}
312329
}
313330
}
314331

@@ -323,6 +340,14 @@ private function createExecutionPlan( array $blueprint ): array {
323340
'name' => 'importMedia',
324341
'args' => [ 'media' => $blueprint['media'] ]
325342
];
343+
344+
foreach ( $blueprint['media'] as $mediaDef ) {
345+
$source = is_array( $mediaDef ) ? $mediaDef['source'] : $mediaDef;
346+
$error = $this->validateDataSource( $source, 'wp-content/uploads/*' );
347+
if ( $error ) {
348+
$errors['media'] = [$error];
349+
}
350+
}
326351
}
327352

328353
// 9. siteLanguage
@@ -364,6 +389,31 @@ private function createExecutionPlan( array $blueprint ): array {
364389
'name' => 'importContent',
365390
'args' => [ 'content' => $blueprint['content'] ]
366391
];
392+
393+
$post_errors = [];
394+
foreach ( $blueprint['content'] as $contentDef ) {
395+
if ( 'posts' === $contentDef['type'] ) {
396+
if ( isset( $contentDef['source'] ) ) {
397+
$sources = $contentDef['source'];
398+
} else {
399+
$sources = $contentDef;
400+
}
401+
402+
if ( ! is_array( $sources ) ) {
403+
$sources = [ $sources ];
404+
}
405+
406+
foreach ( $sources as $source ) {
407+
$error = $this->validateDataSource( $source, 'wp-content/content/posts/*' );
408+
if ( $error ) {
409+
$post_errors[] = $error;
410+
}
411+
}
412+
}
413+
}
414+
if ( count( $post_errors ) > 0 ) {
415+
$errors['content'] = $post_errors;
416+
}
367417
}
368418

369419
// 14. additionalStepsAfterExecution
@@ -410,4 +460,36 @@ private function createExecutionPlan( array $blueprint ): array {
410460

411461
return [ $plan, $errors ];
412462
}
463+
464+
private function validateDataSource( string $source, string $allowed_pattern ): ?string {
465+
if ( strlen( $source ) === 0 ) {
466+
return 'Source must be a non-empty string.';
467+
}
468+
469+
// 1. Absolute URL.
470+
if ( str_contains( $source, '://' ) ) {
471+
return null;
472+
}
473+
474+
// 2. Bundle-relative path.
475+
$byte_1 = $source[0];
476+
$byte_2 = $source[1] ?? null;
477+
if ( str_contains( $source, '/' ) ) {
478+
if ( $byte_1 === '/' ) {
479+
$source = substr( $source, 1 );
480+
} elseif ( $byte_1 === '.' && $byte_2 === '/' ) {
481+
$source = substr( $source, 2 );
482+
}
483+
484+
if ( ! fnmatch( $allowed_pattern, $source ) ) {
485+
return sprintf(
486+
'Invalid path "%s". Expected to match "%s".',
487+
$source,
488+
$allowed_pattern
489+
);
490+
}
491+
}
492+
493+
return null;
494+
}
413495
}

0 commit comments

Comments
 (0)