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
6 changes: 5 additions & 1 deletion README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Contributors: goran87
Tags: gls, shipping, woocommerce shipping
Requires at least: 4.4
Tested up to: 6.9
Stable tag: 1.4.0
Stable tag: 1.4.1
License: GPLv2
License URI: http://www.gnu.org/licenses/gpl-2.0.html

Expand Down Expand Up @@ -81,6 +81,10 @@ To install and configure this plugin:

== Changelog ==

= 1.4.1 =
* Fix: Fixed Reflected Cross-Site Scripting (XSS) vulnerability in bulk action admin notices via the 'failed_orders' parameter. Added proper input sanitization and output escaping.
* Fix: Added proper exception handling for GLS API errors in bulk label operations to prevent fatal errors. API errors are now displayed as admin notices instead of causing crashes.

= 1.4.0 =
* Secure PDF Label Storage: Labels are now stored in a protected directory with authentication-based download.
* Added Hungary Locker Saturation Filter for parcel locker map (filter-saturation attribute).
Expand Down
4 changes: 2 additions & 2 deletions gls-shipping-for-woocommerce.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/**
* Plugin Name: GLS Shipping for WooCommerce
* Description: Offical GLS Shipping for WooCommerce plugin
* Version: 1.4.0
* Version: 1.4.1
* Author: Inchoo
* License: GPLv2
* License URI: http://www.gnu.org/licenses/gpl-2.0.html
Expand All @@ -24,7 +24,7 @@ final class GLS_Shipping_For_Woo
{
private static $instance;

private $version = '1.4.0';
private $version = '1.4.1';

private function __construct()
{
Expand Down
278 changes: 172 additions & 106 deletions includes/admin/class-gls-shipping-bulk.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,101 +113,116 @@ public function process_bulk_gls_label_generation($redirect, $doaction, $order_i
}
// Bulk print labels, dont generate for each but just print in single PDF
if ('print_gls_labels' === $doaction) {
$prepare_data = new GLS_Shipping_API_Data($order_ids);
$data = $prepare_data->generate_post_fields_multi();

// Send order to GLS API
$is_multi = true;
$api = new GLS_Shipping_API_Service();
$result = $api->send_order($data, $is_multi);
try {
$prepare_data = new GLS_Shipping_API_Data($order_ids);
$data = $prepare_data->generate_post_fields_multi();

// Send order to GLS API
$is_multi = true;
$api = new GLS_Shipping_API_Service();
$result = $api->send_order($data, $is_multi);

$body = $result['body'];
$failed_orders = $result['failed_orders'];

// Check if all orders failed - don't attempt PDF creation if no successful labels
if (count($failed_orders) >= count($order_ids)) {
$redirect = add_query_arg(
array(
'bulk_action' => 'print_gls_labels',
'gls_labels_printed' => 0,
'gls_labels_failed' => count($failed_orders),
'failed_orders' => implode(',', array_column($failed_orders, 'order_id')),
),
$redirect
);
return $redirect;
}

$pdf_filename = $this->bulk_create_print_labels($body);

if ($pdf_filename) {
// Save tracking numbers to order meta
if (!empty($body['PrintLabelsInfoList'])) {
// Group tracking codes by order ID to handle multiple parcels per order
$orders_data = array();

foreach ($body['PrintLabelsInfoList'] as $labelInfo) {
if (isset($labelInfo['ClientReference'])) {
$order_id = str_replace('Order:', '', $labelInfo['ClientReference']);

if (!isset($orders_data[$order_id])) {
$orders_data[$order_id] = array(
'tracking_codes' => array(),
'parcel_ids' => array()
);
}

if (isset($labelInfo['ParcelNumber'])) {
$orders_data[$order_id]['tracking_codes'][] = $labelInfo['ParcelNumber'];
}
if (isset($labelInfo['ParcelId'])) {
$orders_data[$order_id]['parcel_ids'][] = $labelInfo['ParcelId'];
$body = $result['body'];
$failed_orders = $result['failed_orders'];

// Check if all orders failed - don't attempt PDF creation if no successful labels
if (count($failed_orders) >= count($order_ids)) {
$redirect = add_query_arg(
array(
'bulk_action' => 'print_gls_labels',
'gls_labels_printed' => 0,
'gls_labels_failed' => count($failed_orders),
'failed_orders' => implode(',', array_column($failed_orders, 'order_id')),
),
$redirect
);
return $redirect;
}

$pdf_filename = $this->bulk_create_print_labels($body);

if ($pdf_filename) {
// Save tracking numbers to order meta
if (!empty($body['PrintLabelsInfoList'])) {
// Group tracking codes by order ID to handle multiple parcels per order
$orders_data = array();

foreach ($body['PrintLabelsInfoList'] as $labelInfo) {
if (isset($labelInfo['ClientReference'])) {
$order_id = str_replace('Order:', '', $labelInfo['ClientReference']);

if (!isset($orders_data[$order_id])) {
$orders_data[$order_id] = array(
'tracking_codes' => array(),
'parcel_ids' => array()
);
}

if (isset($labelInfo['ParcelNumber'])) {
$orders_data[$order_id]['tracking_codes'][] = $labelInfo['ParcelNumber'];
}
if (isset($labelInfo['ParcelId'])) {
$orders_data[$order_id]['parcel_ids'][] = $labelInfo['ParcelId'];
}
}
}
}

// Now save all tracking codes for each order
$successful_orders = array();
foreach ($orders_data as $order_id => $data) {
$order = wc_get_order($order_id);
if ($order) {
if (!empty($data['tracking_codes'])) {
$order->update_meta_data('_gls_tracking_codes', $data['tracking_codes']);

// Now save all tracking codes for each order
$successful_orders = array();
foreach ($orders_data as $order_id => $data) {
$order = wc_get_order($order_id);
if ($order) {
if (!empty($data['tracking_codes'])) {
$order->update_meta_data('_gls_tracking_codes', $data['tracking_codes']);
}
if (!empty($data['parcel_ids'])) {
$order->update_meta_data('_gls_parcel_ids', $data['parcel_ids']);
}

// Save just the filename, URL with nonce is generated on display
$order->update_meta_data('_gls_print_label', $pdf_filename);
$order->save();

$successful_orders[] = $order_id;
}
if (!empty($data['parcel_ids'])) {
$order->update_meta_data('_gls_parcel_ids', $data['parcel_ids']);
}

// Save just the filename, URL with nonce is generated on display
$order->update_meta_data('_gls_print_label', $pdf_filename);
$order->save();

$successful_orders[] = $order_id;
}

// Fire hook after successful bulk label generation
do_action('gls_bulk_labels_generated', $order_ids, $successful_orders, $failed_orders);
}

// Fire hook after successful bulk label generation
do_action('gls_bulk_labels_generated', $order_ids, $successful_orders, $failed_orders);
}

// Add query args to URL for displaying notices and providing PDF link
$pdf_url = GLS_Shipping_For_Woo::get_label_download_url($pdf_filename);
$redirect = add_query_arg(
array(
'bulk_action' => 'print_gls_labels',
'gls_labels_printed' => count($order_ids) - count($failed_orders),
'gls_labels_failed' => count($failed_orders),
'gls_pdf_url' => urlencode($pdf_url),
'failed_orders' => implode(',', array_column($failed_orders, 'order_id')),
),
$redirect
);
} else {
// Handle error case
// Add query args to URL for displaying notices and providing PDF link
$pdf_url = GLS_Shipping_For_Woo::get_label_download_url($pdf_filename);
$redirect = add_query_arg(
array(
'bulk_action' => 'print_gls_labels',
'gls_labels_printed' => count($order_ids) - count($failed_orders),
'gls_labels_failed' => count($failed_orders),
'gls_pdf_url' => urlencode($pdf_url),
'failed_orders' => implode(',', array_column($failed_orders, 'order_id')),
),
$redirect
);
} else {
// Handle error case
$redirect = add_query_arg(
array(
'bulk_action' => 'print_gls_labels',
'gls_labels_printed_error' => 'true',
),
$redirect
);
}
} catch (Exception $e) {
// Log the error for debugging
error_log('GLS Bulk Print Labels Error: ' . $e->getMessage());

// Handle the exception gracefully - redirect with error message
$redirect = add_query_arg(
array(
'bulk_action' => 'print_gls_labels',
'gls_labels_printed_error' => 'true',
'gls_error_message' => urlencode($e->getMessage()),
),
$redirect
);
Expand Down Expand Up @@ -250,10 +265,25 @@ public function bulk_create_print_labels($body)
// Display admin notice after bulk action
public function gls_bulk_action_admin_notice() {
if (isset($_REQUEST['bulk_action'])) {
if ('generate_gls_labels' == $_REQUEST['bulk_action']) {
$generated = intval($_REQUEST['gls_labels_generated']);
$failed = intval($_REQUEST['gls_labels_failed']);
$failed_orders = isset($_REQUEST['failed_orders']) ? explode(',', $_REQUEST['failed_orders']) : [];
// Sanitize the bulk action parameter
$bulk_action = sanitize_text_field(wp_unslash($_REQUEST['bulk_action']));

if ('generate_gls_labels' === $bulk_action) {
$generated = isset($_REQUEST['gls_labels_generated']) ? intval($_REQUEST['gls_labels_generated']) : 0;
$failed = isset($_REQUEST['gls_labels_failed']) ? intval($_REQUEST['gls_labels_failed']) : 0;

// Sanitize failed_orders - only allow integers (order IDs)
$failed_orders = array();
if (isset($_REQUEST['failed_orders']) && !empty($_REQUEST['failed_orders'])) {
$raw_failed_orders = sanitize_text_field(wp_unslash($_REQUEST['failed_orders']));
$failed_orders_array = explode(',', $raw_failed_orders);
foreach ($failed_orders_array as $order_id) {
$sanitized_id = absint(trim($order_id));
if ($sanitized_id > 0) {
$failed_orders[] = $sanitized_id;
}
}
}

// Prepare success message
$message = sprintf(
Expand All @@ -279,21 +309,38 @@ public function gls_bulk_action_admin_notice() {
),
number_format_i18n($failed)
);
$message .= ' ' . sprintf(
/* translators: %s: comma-separated list of order IDs that failed */
__('Failed order IDs: %s', 'gls-shipping-for-woocommerce'),
implode(', ', $failed_orders)
);
if (!empty($failed_orders)) {
$message .= ' ' . sprintf(
/* translators: %s: comma-separated list of order IDs that failed */
__('Failed order IDs: %s', 'gls-shipping-for-woocommerce'),
esc_html(implode(', ', $failed_orders))
);
}
}

// Display the notice
printf('<div id="message" class="updated notice is-dismissible"><p>' . $message . '</p></div>');
} elseif ('print_gls_labels' == $_REQUEST['bulk_action']) {
// Display the notice with proper escaping
printf(
'<div id="message" class="updated notice is-dismissible"><p>%s</p></div>',
wp_kses_post($message)
);
} elseif ('print_gls_labels' === $bulk_action) {
if (isset($_REQUEST['gls_labels_printed']) && isset($_REQUEST['gls_pdf_url'])) {
$printed = intval($_REQUEST['gls_labels_printed']);
$failed = intval($_REQUEST['gls_labels_failed']);
$pdf_url = urldecode($_REQUEST['gls_pdf_url']);
$failed_orders = isset($_REQUEST['failed_orders']) ? explode(',', $_REQUEST['failed_orders']) : [];
$failed = isset($_REQUEST['gls_labels_failed']) ? intval($_REQUEST['gls_labels_failed']) : 0;
$pdf_url = esc_url_raw(urldecode($_REQUEST['gls_pdf_url']));

// Sanitize failed_orders - only allow integers (order IDs)
$failed_orders = array();
if (isset($_REQUEST['failed_orders']) && !empty($_REQUEST['failed_orders'])) {
$raw_failed_orders = sanitize_text_field(wp_unslash($_REQUEST['failed_orders']));
$failed_orders_array = explode(',', $raw_failed_orders);
foreach ($failed_orders_array as $order_id) {
$sanitized_id = absint(trim($order_id));
if ($sanitized_id > 0) {
$failed_orders[] = $sanitized_id;
}
}
}

// Prepare success message
$message = sprintf(
Expand All @@ -319,10 +366,12 @@ public function gls_bulk_action_admin_notice() {
),
number_format_i18n($failed)
);
$message .= sprintf(
__('Failed order IDs: %s', 'gls-shipping-for-woocommerce'),
implode(', ', $failed_orders)
);
if (!empty($failed_orders)) {
$message .= sprintf(
__('Failed order IDs: %s', 'gls-shipping-for-woocommerce'),
esc_html(implode(', ', $failed_orders))
);
}
}

$message .= sprintf(
Expand All @@ -331,11 +380,28 @@ public function gls_bulk_action_admin_notice() {
esc_url($pdf_url)
);

// Display the notice
printf('<div id="message" class="updated notice is-dismissible"><p>' . $message . '</p></div>');
// Display the notice with proper escaping
printf(
'<div id="message" class="updated notice is-dismissible"><p>%s</p></div>',
wp_kses_post($message)
);
} elseif (isset($_REQUEST['gls_labels_printed_error'])) {
$message = __('An error occurred while generating the GLS labels PDF.', 'gls-shipping-for-woocommerce');
printf('<div id="message" class="error notice is-dismissible"><p>' . $message . '</p></div>');

// Display specific error message if available
if (isset($_REQUEST['gls_error_message']) && !empty($_REQUEST['gls_error_message'])) {
$error_detail = sanitize_text_field(urldecode($_REQUEST['gls_error_message']));
$message .= ' ' . sprintf(
/* translators: %s: error message from GLS API */
__('Error: %s', 'gls-shipping-for-woocommerce'),
$error_detail
);
}

printf(
'<div id="message" class="error notice is-dismissible"><p>%s</p></div>',
esc_html($message)
);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions includes/admin/class-gls-shipping-pickup.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public function pickup_admin_page()
$message = '';
$error = '';

if (isset($_POST['schedule_pickup']) && wp_verify_nonce($_POST['gls_pickup_nonce'], 'gls_pickup_action')) {
if (isset($_POST['schedule_pickup']) && isset($_POST['gls_pickup_nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['gls_pickup_nonce'])), 'gls_pickup_action')) {
$result = $this->process_pickup_form();
if (is_wp_error($result)) {
$error = $result->get_error_message();
Expand All @@ -114,7 +114,7 @@ public function pickup_admin_page()
// Get all addresses (including store fallback as first option)
$all_addresses = GLS_Shipping_Sender_Address_Helper::get_all_addresses_with_store_fallback();

$current_tab = isset($_GET['tab']) ? $_GET['tab'] : 'schedule';
$current_tab = isset($_GET['tab']) ? sanitize_key(wp_unslash($_GET['tab'])) : 'schedule';

?>
<div class="wrap">
Expand Down
Loading