@automattic/newspack-blocks 4.28.1-hotfix-blocks-input-validation.1 → 4.28.1-hotfix-blocks-input-validation.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -5
- package/includes/class-modal-checkout.php +58 -11
- package/newspack-blocks.php +2 -2
- package/package.json +1 -1
- package/src/blocks/author-list/view.php +1 -1
- package/src/blocks/author-profile/class-wp-rest-newspack-authors-controller.php +2 -0
- package/src/blocks/iframe/class-wp-rest-newspack-iframe-controller.php +31 -12
- package/src/modal-checkout/templates/thankyou.php +12 -5
- package/vendor/composer/installed.php +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
## @automattic/newspack-blocks [4.28.1-hotfix-blocks-input-validation.
|
|
1
|
+
## @automattic/newspack-blocks [4.28.1-hotfix-blocks-input-validation.2](https://github.com/Automattic/newspack-workspace/compare/newspack-blocks@4.28.1-hotfix-blocks-input-validation.1...newspack-blocks@4.28.1-hotfix-blocks-input-validation.2) (2026-07-31)
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
### Bug Fixes
|
|
5
5
|
|
|
6
|
-
* **blocks:**
|
|
7
|
-
* **blocks:** keep iframe imports inside the uploads directory ([a0118cc](https://github.com/Automattic/newspack-workspace/commit/a0118cc501ca0bfbb851c9e0fdb0f005702b99da))
|
|
8
|
-
* **blocks:** restrict author contact fields to user managers ([c872eb9](https://github.com/Automattic/newspack-workspace/commit/c872eb90589d056a354228adc0a2a65afa6245d3))
|
|
9
|
-
* **blocks:** validate post-checkout redirect destinations ([f418142](https://github.com/Automattic/newspack-workspace/commit/f418142055e14fc6e8fe82726996c7a7028452d5))
|
|
6
|
+
* **blocks:** close after-success dead ends and tighten write checks ([12e6bd8](https://github.com/Automattic/newspack-workspace/commit/12e6bd898d28eaf4fb6a59633f99b297febc308f))
|
|
10
7
|
|
|
11
8
|
# @automattic/newspack-blocks [4.28.0](https://github.com/Automattic/newspack-workspace/compare/newspack-blocks@4.27.1...newspack-blocks@4.28.0) (2026-07-20)
|
|
12
9
|
|
|
@@ -1188,27 +1188,80 @@ final class Modal_Checkout {
|
|
|
1188
1188
|
ob_end_flush();
|
|
1189
1189
|
}
|
|
1190
1190
|
|
|
1191
|
+
/**
|
|
1192
|
+
* Behaviors that have somewhere to go once checkout finishes.
|
|
1193
|
+
*
|
|
1194
|
+
* Anything else leaves the reader in a modal that won't move and won't close, so it is
|
|
1195
|
+
* treated as "close the modal" instead.
|
|
1196
|
+
*
|
|
1197
|
+
* @var string[]
|
|
1198
|
+
*/
|
|
1199
|
+
const AFTER_SUCCESS_BEHAVIORS = [ 'custom', 'referrer' ];
|
|
1200
|
+
|
|
1191
1201
|
/**
|
|
1192
1202
|
* Reduce a post-checkout destination to one this site is willing to send readers to.
|
|
1193
1203
|
*
|
|
1194
1204
|
* The destination reaches the thank-you page through the request, whether it came from
|
|
1195
1205
|
* the block's own settings or from the link the reader followed, and by that point the
|
|
1196
|
-
* two are indistinguishable. Core's redirect validation decides
|
|
1197
|
-
*
|
|
1198
|
-
* host through
|
|
1206
|
+
* two are indistinguishable. Core's redirect validation decides, so the destinations
|
|
1207
|
+
* this site accepts are the ones `allowed_redirect_hosts` reports. That's this site's
|
|
1208
|
+
* own host, plus whatever a publisher adds through that filter, plus anything another
|
|
1209
|
+
* plugin has added (`Newspack\NRH` registers one).
|
|
1199
1210
|
*
|
|
1200
1211
|
* @param string $url The requested destination.
|
|
1201
1212
|
*
|
|
1202
1213
|
* @return string The destination, or an empty string if it is not allowed.
|
|
1203
1214
|
*/
|
|
1204
1215
|
public static function sanitize_after_success_url( $url ) {
|
|
1216
|
+
$url = sanitize_url( (string) $url );
|
|
1217
|
+
|
|
1205
1218
|
if ( empty( $url ) ) {
|
|
1206
1219
|
return '';
|
|
1207
1220
|
}
|
|
1208
1221
|
|
|
1222
|
+
// Core compares hosts case-sensitively; host names aren't. Normalise first so this
|
|
1223
|
+
// site's own host typed in capitals isn't read as somewhere else.
|
|
1224
|
+
$host = wp_parse_url( $url, PHP_URL_HOST );
|
|
1225
|
+
if ( $host && strtolower( $host ) !== $host ) {
|
|
1226
|
+
$url = str_replace( '://' . $host, '://' . strtolower( $host ), $url );
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1209
1229
|
return (string) wp_validate_redirect( $url, '' );
|
|
1210
1230
|
}
|
|
1211
1231
|
|
|
1232
|
+
/**
|
|
1233
|
+
* Drop an after-success behavior the reader can't act on.
|
|
1234
|
+
*
|
|
1235
|
+
* A behavior with nowhere to go renders a modal that neither navigates nor closes, so
|
|
1236
|
+
* the destination, the behavior and its button label all fall away together and the
|
|
1237
|
+
* reader gets the ordinary "close" button instead of one labelled for a page they
|
|
1238
|
+
* won't be taken to.
|
|
1239
|
+
*
|
|
1240
|
+
* @param array $params After-success params.
|
|
1241
|
+
*
|
|
1242
|
+
* @return array The params, less any the reader can't act on.
|
|
1243
|
+
*/
|
|
1244
|
+
public static function filter_after_success_params( $params ) {
|
|
1245
|
+
$behavior = $params['after_success_behavior'] ?? '';
|
|
1246
|
+
|
|
1247
|
+
if ( '' === $behavior ) {
|
|
1248
|
+
return $params;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
$is_unknown = ! in_array( $behavior, self::AFTER_SUCCESS_BEHAVIORS, true );
|
|
1252
|
+
$has_nowhere = 'custom' === $behavior && empty( $params['after_success_url'] );
|
|
1253
|
+
|
|
1254
|
+
if ( $is_unknown || $has_nowhere ) {
|
|
1255
|
+
unset(
|
|
1256
|
+
$params['after_success_behavior'],
|
|
1257
|
+
$params['after_success_url'],
|
|
1258
|
+
$params['after_success_button_label']
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
return $params;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1212
1265
|
/**
|
|
1213
1266
|
* Get after success button params.
|
|
1214
1267
|
*/
|
|
@@ -1225,19 +1278,13 @@ final class Modal_Checkout {
|
|
|
1225
1278
|
$params = array_filter(
|
|
1226
1279
|
[
|
|
1227
1280
|
'after_success_behavior' => isset( $request_params['after_success_behavior'] ) ? sanitize_text_field( wp_unslash( $request_params['after_success_behavior'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
1228
|
-
'after_success_url' => isset( $request_params['after_success_url'] ) ? self::sanitize_after_success_url(
|
|
1281
|
+
'after_success_url' => isset( $request_params['after_success_url'] ) ? self::sanitize_after_success_url( wp_unslash( $request_params['after_success_url'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
1229
1282
|
'after_success_button_label' => isset( $request_params['after_success_button_label'] ) ? sanitize_text_field( wp_unslash( $request_params['after_success_button_label'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
1230
1283
|
'action_type' => isset( $request_params['action_type'] ) ? sanitize_text_field( wp_unslash( $request_params['action_type'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
1231
1284
|
]
|
|
1232
1285
|
);
|
|
1233
1286
|
|
|
1234
|
-
|
|
1235
|
-
// the reader gets a finished checkout rather than an empty destination.
|
|
1236
|
-
if ( 'custom' === ( $params['after_success_behavior'] ?? '' ) && empty( $params['after_success_url'] ) ) {
|
|
1237
|
-
unset( $params['after_success_behavior'] );
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
return $params;
|
|
1287
|
+
return self::filter_after_success_params( $params );
|
|
1241
1288
|
}
|
|
1242
1289
|
|
|
1243
1290
|
/**
|
package/newspack-blocks.php
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Author URI: https://newspack.com/
|
|
8
8
|
* Text Domain: newspack-blocks
|
|
9
9
|
* Domain Path: /languages
|
|
10
|
-
* Version: 4.28.1-hotfix-blocks-input-validation.
|
|
10
|
+
* Version: 4.28.1-hotfix-blocks-input-validation.2
|
|
11
11
|
*
|
|
12
12
|
* @package Newspack_Blocks
|
|
13
13
|
*/
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
define( 'NEWSPACK_BLOCKS__PLUGIN_FILE', __FILE__ );
|
|
16
16
|
define( 'NEWSPACK_BLOCKS__BLOCKS_DIRECTORY', 'dist/' );
|
|
17
17
|
define( 'NEWSPACK_BLOCKS__PLUGIN_DIR', plugin_dir_path( NEWSPACK_BLOCKS__PLUGIN_FILE ) );
|
|
18
|
-
define( 'NEWSPACK_BLOCKS__VERSION', '4.28.1-hotfix-blocks-input-validation.
|
|
18
|
+
define( 'NEWSPACK_BLOCKS__VERSION', '4.28.1-hotfix-blocks-input-validation.2' );
|
|
19
19
|
|
|
20
20
|
require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'includes/class-newspack-blocks.php';
|
|
21
21
|
require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'includes/class-newspack-blocks-api.php';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automattic/newspack-blocks",
|
|
3
|
-
"version": "4.28.1-hotfix-blocks-input-validation.
|
|
3
|
+
"version": "4.28.1-hotfix-blocks-input-validation.2",
|
|
4
4
|
"author": "Automattic",
|
|
5
5
|
"description": "=== Newspack Blocks === Contributors: (this should be a list of wordpress.org userid's) Donate link: https://example.com/ Tags: comments, spam Requires at least: 4.5 Tested up to: 5.1.1 Stable tag: 0.1.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html",
|
|
6
6
|
"repository": {
|
|
@@ -57,7 +57,7 @@ function newspack_blocks_render_block_author_list( $attributes ) {
|
|
|
57
57
|
'author_type' => $author_type,
|
|
58
58
|
'author_roles' => $author_roles,
|
|
59
59
|
'exclude' => $attributes['exclude'], // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
|
|
60
|
-
'fields' =>
|
|
60
|
+
'fields' => WP_REST_Newspack_Author_List_Controller::DEFAULT_FIELDS,
|
|
61
61
|
];
|
|
62
62
|
|
|
63
63
|
if ( $exclude_empty ) {
|
|
@@ -38,6 +38,8 @@ class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
|
|
|
38
38
|
* @return boolean
|
|
39
39
|
*/
|
|
40
40
|
public static function can_read_user_management_fields() {
|
|
41
|
+
// Keep `list_users` first: on multisite, core maps `edit_users` through
|
|
42
|
+
// `manage_network_users`, so a site administrator passes only on `list_users`.
|
|
41
43
|
return current_user_can( 'list_users' ) || current_user_can( 'edit_users' );
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -309,8 +309,10 @@ class WP_REST_Newspack_Iframe_Controller extends WP_REST_Controller {
|
|
|
309
309
|
/**
|
|
310
310
|
* Confirm that a destination resolves inside a directory.
|
|
311
311
|
*
|
|
312
|
-
* A last check on the resolved paths,
|
|
313
|
-
*
|
|
312
|
+
* A last check on the resolved paths, so that a symlink or a gap in the checks above
|
|
313
|
+
* still cannot place a file outside the directory. The destination itself need not
|
|
314
|
+
* exist yet: the deepest part of it that does exist is what gets resolved, which is
|
|
315
|
+
* what lets this run before anything is created.
|
|
314
316
|
*
|
|
315
317
|
* @param string $directory Absolute path to the directory the write must stay inside.
|
|
316
318
|
* @param string $destination Absolute path to the intended destination.
|
|
@@ -318,14 +320,24 @@ class WP_REST_Newspack_Iframe_Controller extends WP_REST_Controller {
|
|
|
318
320
|
* @return boolean
|
|
319
321
|
*/
|
|
320
322
|
private function is_inside_directory( $directory, $destination ) {
|
|
321
|
-
$directory
|
|
322
|
-
$destination = realpath( dirname( $destination ) );
|
|
323
|
+
$directory = realpath( $directory );
|
|
323
324
|
|
|
324
|
-
if ( ! $directory
|
|
325
|
+
if ( ! $directory ) {
|
|
325
326
|
return false;
|
|
326
327
|
}
|
|
327
328
|
|
|
328
|
-
|
|
329
|
+
$existing = dirname( $destination );
|
|
330
|
+
while ( ! file_exists( $existing ) && dirname( $existing ) !== $existing ) {
|
|
331
|
+
$existing = dirname( $existing );
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
$resolved = realpath( $existing );
|
|
335
|
+
|
|
336
|
+
if ( ! $resolved ) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return 0 === strpos( trailingslashit( $resolved ), trailingslashit( $directory ) );
|
|
329
341
|
}
|
|
330
342
|
|
|
331
343
|
/**
|
|
@@ -540,9 +552,13 @@ class WP_REST_Newspack_Iframe_Controller extends WP_REST_Controller {
|
|
|
540
552
|
$iframe_path = trailingslashit( $iframe_upload_dir . $iframe_folder );
|
|
541
553
|
$data = $request->get_body_params();
|
|
542
554
|
|
|
543
|
-
//
|
|
544
|
-
|
|
545
|
-
|
|
555
|
+
// Create this archive's own directory up front, so each entry's destination can be
|
|
556
|
+
// resolved against it before anything is written.
|
|
557
|
+
if ( ! file_exists( $iframe_path ) ) {
|
|
558
|
+
wp_mkdir_p( $iframe_path );
|
|
559
|
+
}
|
|
560
|
+
if ( ! $this->is_inside_directory( $iframe_upload_dir, $iframe_path ) ) {
|
|
561
|
+
return $invalid_file_error;
|
|
546
562
|
}
|
|
547
563
|
|
|
548
564
|
// Extract files from archive.
|
|
@@ -572,12 +588,15 @@ class WP_REST_Newspack_Iframe_Controller extends WP_REST_Controller {
|
|
|
572
588
|
}
|
|
573
589
|
|
|
574
590
|
$destination = $iframe_path . $entry_relative_path;
|
|
591
|
+
|
|
592
|
+
// Checked before anything is created, so a destination outside this archive's
|
|
593
|
+
// own folder never gets a directory made for it.
|
|
594
|
+
if ( ! $this->is_inside_directory( $iframe_path, $destination ) ) {
|
|
595
|
+
return $invalid_file_error;
|
|
596
|
+
}
|
|
575
597
|
if ( ! file_exists( dirname( $destination ) ) ) {
|
|
576
598
|
wp_mkdir_p( dirname( $destination ) );
|
|
577
599
|
}
|
|
578
|
-
if ( ! $this->is_inside_directory( $iframe_upload_dir, $destination ) ) {
|
|
579
|
-
return $invalid_file_error;
|
|
580
|
-
}
|
|
581
600
|
|
|
582
601
|
$put = $wp_filesystem->put_contents( $destination, $contents );
|
|
583
602
|
if ( ! $put ) {
|
|
@@ -51,11 +51,18 @@ $after_success_url = isset( $_GET['after_success_url'] ) ? esc_url( \Newspa
|
|
|
51
51
|
$after_success_label = isset( $_GET['after_success_button_label'] ) ? \sanitize_text_field( \wp_unslash( $_GET['after_success_button_label'] ) ) : \Newspack_Blocks\Modal_Checkout::get_modal_checkout_labels( 'after_success' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
52
52
|
$checkout_data = Checkout_Data::get_checkout_data( $order );
|
|
53
53
|
|
|
54
|
-
// A
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
// A behavior the reader can't act on falls back to closing the modal, so they get a
|
|
55
|
+
// finished checkout rather than a button that goes nowhere.
|
|
56
|
+
$after_success_params = \Newspack_Blocks\Modal_Checkout::filter_after_success_params(
|
|
57
|
+
[
|
|
58
|
+
'after_success_behavior' => $after_success_behavior,
|
|
59
|
+
'after_success_url' => $after_success_url,
|
|
60
|
+
'after_success_button_label' => $after_success_label,
|
|
61
|
+
]
|
|
62
|
+
);
|
|
63
|
+
$after_success_behavior = $after_success_params['after_success_behavior'] ?? '';
|
|
64
|
+
$after_success_url = $after_success_params['after_success_url'] ?? '';
|
|
65
|
+
$after_success_label = $after_success_params['after_success_button_label'] ?? \Newspack_Blocks\Modal_Checkout::get_modal_checkout_labels( 'after_success' );
|
|
59
66
|
?>
|
|
60
67
|
<div class="woocommerce-order">
|
|
61
68
|
<?php if ( $is_success ) : ?>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
'name' => 'automattic/newspack-blocks',
|
|
4
4
|
'pretty_version' => 'dev-main',
|
|
5
5
|
'version' => 'dev-main',
|
|
6
|
-
'reference' => '
|
|
6
|
+
'reference' => '12e6bd898d28eaf4fb6a59633f99b297febc308f',
|
|
7
7
|
'type' => 'wordpress-plugin',
|
|
8
8
|
'install_path' => __DIR__ . '/../../',
|
|
9
9
|
'aliases' => array(),
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
'automattic/newspack-blocks' => array(
|
|
14
14
|
'pretty_version' => 'dev-main',
|
|
15
15
|
'version' => 'dev-main',
|
|
16
|
-
'reference' => '
|
|
16
|
+
'reference' => '12e6bd898d28eaf4fb6a59633f99b297febc308f',
|
|
17
17
|
'type' => 'wordpress-plugin',
|
|
18
18
|
'install_path' => __DIR__ . '/../../',
|
|
19
19
|
'aliases' => array(),
|