Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 67 additions & 19 deletions inc/footer-fragment.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ function libresign_theme_site_fragment_storage_base_url( $fragment_type ) {
* @return array<string, mixed>|WP_Error
*/
function libresign_theme_site_fragment_fetch_url( $url ) {
$response = wp_remote_get(
add_filter( 'http_request_host_is_external', 'libresign_theme_site_fragment_allow_origin_host', 10, 2 );
add_filter( 'http_allowed_safe_ports', 'libresign_theme_site_fragment_allow_origin_port', 10, 2 );

$response = wp_safe_remote_get(
$url,
array(
'timeout' => 15,
Expand All @@ -74,6 +77,9 @@ function libresign_theme_site_fragment_fetch_url( $url ) {
)
);

remove_filter( 'http_request_host_is_external', 'libresign_theme_site_fragment_allow_origin_host', 10 );
remove_filter( 'http_allowed_safe_ports', 'libresign_theme_site_fragment_allow_origin_port', 10 );

if ( is_wp_error( $response ) ) {
return $response;
}
Expand Down Expand Up @@ -103,6 +109,22 @@ function libresign_theme_site_fragment_fetch_url( $url ) {
);
}

function libresign_theme_site_fragment_allow_origin_host( $is_external, $host ) {
$origin_host = (string) wp_parse_url( libresign_theme_site_origin(), PHP_URL_HOST );

return $is_external || ( '' !== $origin_host && strtolower( $origin_host ) === strtolower( (string) $host ) );
}

function libresign_theme_site_fragment_allow_origin_port( $allowed_ports, $host ) {
$origin = wp_parse_url( libresign_theme_site_origin() );

if ( is_array( $origin ) && isset( $origin['host'], $origin['port'] ) && strtolower( $origin['host'] ) === strtolower( (string) $host ) ) {
$allowed_ports[] = (int) $origin['port'];
}

return $allowed_ports;
}

/**
* Determine whether an HTTP error can be ignored for an optional locale.
*
Expand Down Expand Up @@ -198,7 +220,7 @@ function libresign_theme_site_fragment_collect_artifacts( $site_origin, $fragmen
return $fragment_response;
}

$asset_urls = SiteFragment::asset_urls( (string) $fragment_response['body'] );
$asset_urls = SiteFragment::asset_urls( (string) $fragment_response['body'], $site_origin );
if ( null === $asset_urls ) {
if ( '' !== $locale ) {
continue;
Expand Down Expand Up @@ -266,37 +288,40 @@ function libresign_theme_site_fragment_collect_artifacts( $site_origin, $fragmen
* @return array<string, mixed>|WP_Error
*/
function libresign_theme_site_fragment_persist_artifacts( $collected ) {
$staging_dirs = array();
foreach ( libresign_theme_site_fragment_supported_types() as $fragment_type ) {
$base_dir = libresign_theme_site_fragment_storage_base_dir( $fragment_type );
if ( is_dir( $base_dir ) ) {
libresign_theme_site_fragment_recursive_delete( $base_dir );
$staging_dir = libresign_theme_site_fragment_storage_base_dir( $fragment_type ) . '-staging';
libresign_theme_site_fragment_recursive_delete( $staging_dir );

if ( ! wp_mkdir_p( $staging_dir ) ) {
libresign_theme_site_fragment_discard_staging( $staging_dirs );

return libresign_theme_site_fragment_storage_error( $fragment_type );
}

$staging_dirs[ $fragment_type ] = $staging_dir;
}

foreach ( $collected['artifacts'] as $artifact ) {
$fragment_type = (string) $artifact['fragment_type'];
$base_dir = trailingslashit( libresign_theme_site_fragment_storage_base_dir( $fragment_type ) ) . $artifact['locale_key'];
$base_dir = trailingslashit( $staging_dirs[ $fragment_type ] ) . $artifact['locale_key'];
$base_url = trailingslashit( libresign_theme_site_fragment_storage_base_url( $fragment_type ) ) . $artifact['locale_key'];
$prefix = 'header' === $fragment_type ? 'header' : 'footer';

if ( ! wp_mkdir_p( $base_dir ) ) {
return new WP_Error(
'libresign_theme_site_fragment_storage_failed',
sprintf(
/* translators: %s: fragment type. */
__( 'Unable to create local storage for the %s fragment.', 'libresign' ),
$fragment_type
)
);
$resolved_dir = wp_mkdir_p( $base_dir ) ? realpath( $base_dir ) : false;
if ( false === $resolved_dir || dirname( $resolved_dir ) !== realpath( $staging_dirs[ $fragment_type ] ) ) {
libresign_theme_site_fragment_discard_staging( $staging_dirs );

return libresign_theme_site_fragment_storage_error( $fragment_type );
}

$html_file = $prefix . '.html';
$css_file = $prefix . '.css';
$js_file = $prefix . '.js';

file_put_contents( trailingslashit( $base_dir ) . $html_file, (string) $artifact['html'] );
file_put_contents( trailingslashit( $base_dir ) . $css_file, (string) $artifact['css'] );
file_put_contents( trailingslashit( $base_dir ) . $js_file, (string) $artifact['js'] );
file_put_contents( trailingslashit( $resolved_dir ) . $html_file, (string) $artifact['html'] );
file_put_contents( trailingslashit( $resolved_dir ) . $css_file, (string) $artifact['css'] );
file_put_contents( trailingslashit( $resolved_dir ) . $js_file, (string) $artifact['js'] );

$manifest = array(
'fragment_type' => $fragment_type,
Expand All @@ -316,14 +341,37 @@ function libresign_theme_site_fragment_persist_artifacts( $collected ) {
);

file_put_contents(
trailingslashit( $base_dir ) . 'manifest.json',
trailingslashit( $resolved_dir ) . 'manifest.json',
wp_json_encode( $manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES )
);
}

foreach ( $staging_dirs as $fragment_type => $staging_dir ) {
$base_dir = libresign_theme_site_fragment_storage_base_dir( $fragment_type );
libresign_theme_site_fragment_recursive_delete( $base_dir );
rename( $staging_dir, $base_dir );
}

return $collected['summary'];
}

function libresign_theme_site_fragment_storage_error( $fragment_type ) {
return new WP_Error(
'libresign_theme_site_fragment_storage_failed',
sprintf(
/* translators: %s: fragment type. */
__( 'Unable to create local storage for the %s fragment.', 'libresign' ),
$fragment_type
)
);
}

function libresign_theme_site_fragment_discard_staging( $staging_dirs ) {
foreach ( $staging_dirs as $staging_dir ) {
libresign_theme_site_fragment_recursive_delete( $staging_dir );
}
}

/**
* Synchronize site fragments from the configured origin.
*
Expand Down
14 changes: 6 additions & 8 deletions inc/github-site-webhook.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ function libresign_theme_register_github_site_webhook_route() {
*/
function libresign_theme_github_webhook_secret() {
$values = array(
get_theme_mod( 'libresign_github_webhook_secret' ),
getenv( 'LIBRESIGN_GITHUB_WEBHOOK_SECRET' ),
get_theme_mod( 'libresign_github_webhook_secret' ),
get_theme_mod( 'libresign_footer_webhook_secret' ),
getenv( 'LIBRESIGN_FOOTER_WEBHOOK_SECRET' ),
);
Expand Down Expand Up @@ -531,7 +531,10 @@ function libresign_theme_post_pr_sync_comment( string $site_origin, array $sync_
}

$pr_number = (int) $info['pr_number'];
$repository = isset( $info['repository'] ) ? (string) $info['repository'] : libresign_theme_site_deploy_repository_name();
$repository = libresign_theme_site_deploy_repository_name();
if ( ! SiteDeploy::is_repository_name( $repository ) ) {
return;
}

$token = libresign_theme_github_deploy_token();
if ( '' === $token ) {
Expand All @@ -540,13 +543,8 @@ function libresign_theme_post_pr_sync_comment( string $site_origin, array $sync_

$body = SiteDeploy::sync_comment( $sync_result['synced'] ?? array(), $site_origin );

$parts = explode( '/', $repository, 2 );
if ( count( $parts ) !== 2 || '' === $parts[0] || '' === $parts[1] ) {
return;
}

wp_remote_post(
"https://api.github.com/repos/{$parts[0]}/{$parts[1]}/issues/{$pr_number}/comments",
"https://api.github.com/repos/{$repository}/issues/{$pr_number}/comments",
array(
'headers' => array(
'Authorization' => 'Bearer ' . $token,
Expand Down
4 changes: 4 additions & 0 deletions src/SiteDeploy.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ public static function is_deploy_starting( array $payload, string $repository ):
&& self::workflow_name( $payload ) === 'Deploy';
}

public static function is_repository_name( string $repository ): bool {
return 1 === preg_match( '/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/', $repository );
}

public static function merged_pull_request( mixed $pull_requests ): int {
if ( ! is_array( $pull_requests ) ) {
return 0;
Expand Down
41 changes: 37 additions & 4 deletions src/SiteFragment.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ final class SiteFragment {

public const DEFAULT_LOCALE_KEY = 'default';

private const LANGUAGE_TAG_PATTERN = '/^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/';

public static function origin( string $origin ): string {
return rtrim( trim( $origin ), '/' );
}
Expand All @@ -30,7 +32,9 @@ public static function language_tag( string $locale ): string {
}
}

return implode( '-', $parts );
$tag = implode( '-', $parts );

return 1 === preg_match( self::LANGUAGE_TAG_PATTERN, $tag ) ? $tag : '';
}

public static function storage_key( string $locale ): string {
Expand All @@ -48,20 +52,49 @@ public static function url( string $origin, string $fragment_type, string $local
/**
* @return array{css: string, js: string}|null
*/
public static function asset_urls( string $html ): ?array {
public static function asset_urls( string $html, string $origin ): ?array {
if (
! preg_match( '/\bdata-fragment-css=("|\')([^"\']+)\1/i', $html, $css_matches )
|| ! preg_match( '/\bdata-fragment-js=("|\')([^"\']+)\1/i', $html, $js_matches )
) {
return null;
}

$css_url = self::same_origin_url( $origin, $css_matches[2] );
$js_url = self::same_origin_url( $origin, $js_matches[2] );
if ( '' === $css_url || '' === $js_url ) {
return null;
}

return array(
'css' => $css_matches[2],
'js' => $js_matches[2],
'css' => $css_url,
'js' => $js_url,
);
}

public static function same_origin_url( string $origin, string $url ): string {
$origin = self::origin( $origin );
$url = trim( $url );

if ( str_starts_with( $url, '/' ) && ! str_starts_with( $url, '//' ) ) {
$url = $origin . $url;
}

$origin_parts = parse_url( $origin );
$url_parts = parse_url( $url );
if ( ! is_array( $origin_parts ) || ! is_array( $url_parts ) || empty( $origin_parts['host'] ) ) {
return '';
}

foreach ( array( 'scheme', 'host', 'port' ) as $component ) {
if ( strtolower( (string) ( $origin_parts[ $component ] ?? '' ) ) !== strtolower( (string) ( $url_parts[ $component ] ?? '' ) ) ) {
return '';
}
}

return $url;
}

/**
* @return string[]
*/
Expand Down
Loading
Loading