diff --git a/common/models/shipping/Dhlexpress.php b/common/models/shipping/Dhlexpress.php
index 2b807f220..301921687 100644
--- a/common/models/shipping/Dhlexpress.php
+++ b/common/models/shipping/Dhlexpress.php
@@ -97,14 +97,20 @@ public function getQuote($address) {
$pieces = ShippingHelper::get_package_piece($dhl_packs);
$weight_unit = 'KG';
$dim_unit = 'CM';
- $fetch_accountrates = ($rate_type == 'ACCOUNT') ? "" . $dhlexpress_account . "" : "";
+ $fetch_accountrates = ($rate_type == 'ACCOUNT') ? "" . $this->escapeXmlValue($dhlexpress_account) . "" : "";
$mailing_date = date('Y-m-d');
$mailing_datetime = date('c');
- $origin_postcode_city = ShippingHelper::get_postcode_city($fromCountryCode, $fromCity, $fromPostcode);
+ $origin_postcode_city = ShippingHelper::get_postcode_city(
+ $fromCountryCode,
+ $this->escapeXmlValue($fromCity),
+ $this->escapeXmlValue($fromPostcode)
+ );
//$total_value = $this->cart->get_total();
- $dutiable_content = ($is_dutiable == "Y") ? "{$selected_currency}{$total_value}" : "";
+ $declared_currency = $this->escapeXmlValue($selected_currency);
+ $declared_value = $this->escapeXmlValue($total_value);
+ $dutiable_content = ($is_dutiable == "Y") ? "{$declared_currency}{$declared_value}" : "";
//$insurance_details = ($this->config->get('shipping_dhlexpress_insurance') == true) ? "". $total_value ."". $this->config->get('config_currency') ."" : "";
//$additional_insurance_details = ($this->config->get('shipping_dhlexpress_insurance') == true) ? "IIXCH" : ""; //
@@ -130,7 +136,11 @@ public function getQuote($address) {
}
}*/
- $destination_postcode_city = ShippingHelper::get_postcode_city($county_code_to, $to_city, $postcode);
+ $destination_postcode_city = ShippingHelper::get_postcode_city(
+ $county_code_to,
+ $this->escapeXmlValue($to_city),
+ $this->escapeXmlValue($postcode)
+ );
$payment_country = $county_code_to;// $this->config->get('shipping_dhlexpress_country_code');
/*if ( !empty($this->config->get('shipping_dhlexpress_pay_con')) && $this->config->get('shipping_dhlexpress_pay_con') == "R" ) {
@@ -149,31 +159,31 @@ public function getQuote($address) {
$xml .= ' ';
$xml .= ' '.$mailing_datetime.'';
$xml .= ' 1234567890123456789012345678901';
- $xml .= ' '.$dhlexpress_key.'';
- $xml .= ' '.$dhlexpress_password.'';
+ $xml .= ' '.$this->escapeXmlValue($dhlexpress_key).'';
+ $xml .= ' '.$this->escapeXmlValue($dhlexpress_password).'';
$xml .= ' ';
$xml .= ' ';
$xml .= ' ';
- $xml .= ' '.$fromCountryCode.'';
+ $xml .= ' '.$this->escapeXmlValue($fromCountryCode).'';
$xml .= ' '.$origin_postcode_city;
$xml .= ' ';
$xml .= ' ';
- $xml .= ' '.$payment_country.'';
+ $xml .= ' '.$this->escapeXmlValue($payment_country).'';
$xml .= ' '.$mailing_date.'';
$xml .= ' PT10H21M';
- $xml .= ' '.$dim_unit.'';
- $xml .= ' '.$weight_unit.'';
+ $xml .= ' '.$this->escapeXmlValue($dim_unit).'';
+ $xml .= ' '.$this->escapeXmlValue($weight_unit).'';
$xml .= ' ';
$xml .= ' '.$pieces;
$xml .= ' ';
$xml .= ' '.$fetch_accountrates;
- $xml .= ' '.$is_dutiable.'';
+ $xml .= ' '.$this->escapeXmlValue($is_dutiable).'';
$xml .= ' AL';
//$xml .= ' '.$additional_insurance_details;
//$xml .= ' '.$insurance_details;
$xml .= ' ';
$xml .= ' ';
- $xml .= ' '.$county_code_to.'';
+ $xml .= ' '.$this->escapeXmlValue($county_code_to).'';
$xml .= ' '.$destination_postcode_city;
$xml .= ' ';
$xml .= ' '.$dutiable_content;
@@ -261,4 +271,9 @@ public function getQuote($address) {
}
return $method_data;
}
-}
\ No newline at end of file
+
+ private function escapeXmlValue($value)
+ {
+ return htmlspecialchars((string) $value, ENT_XML1 | ENT_QUOTES, 'UTF-8');
+ }
+}
diff --git a/tests/check-dhl-rate-xml-escaping.py b/tests/check-dhl-rate-xml-escaping.py
new file mode 100644
index 000000000..9696e1a44
--- /dev/null
+++ b/tests/check-dhl-rate-xml-escaping.py
@@ -0,0 +1,53 @@
+from pathlib import Path
+import re
+
+
+dhl = Path("common/models/shipping/Dhlexpress.php")
+source = dhl.read_text(encoding="utf-8")
+
+required_patterns = [
+ r"private\s+function\s+escapeXmlValue\s*\(\s*\$value\s*\)",
+ r"htmlspecialchars\s*\(\s*\(string\)\s*\$value\s*,\s*ENT_XML1\s*\|\s*ENT_QUOTES\s*,\s*'UTF-8'\s*\)",
+ r"\"\s*\.\s*\$this->escapeXmlValue\s*\(\s*\$dhlexpress_account\s*\)\s*\.\s*\"",
+ r"\$this->escapeXmlValue\s*\(\s*\$fromCity\s*\)",
+ r"\$this->escapeXmlValue\s*\(\s*\$fromPostcode\s*\)",
+ r"\$this->escapeXmlValue\s*\(\s*\$to_city\s*\)",
+ r"\$this->escapeXmlValue\s*\(\s*\$postcode\s*\)",
+ r"\$declared_currency\s*=\s*\$this->escapeXmlValue\s*\(\s*\$selected_currency\s*\)",
+ r"\$declared_value\s*=\s*\$this->escapeXmlValue\s*\(\s*\$total_value\s*\)",
+ r"\{\$declared_currency\}",
+ r"\{\$declared_value\}",
+ r"'\s*\.\s*\$this->escapeXmlValue\s*\(\s*\$dhlexpress_key\s*\)\s*\.\s*'",
+ r"'\s*\.\s*\$this->escapeXmlValue\s*\(\s*\$dhlexpress_password\s*\)\s*\.\s*'",
+ r"'\s*\.\s*\$this->escapeXmlValue\s*\(\s*\$fromCountryCode\s*\)\s*\.\s*'",
+ r"'\s*\.\s*\$this->escapeXmlValue\s*\(\s*\$payment_country\s*\)\s*\.\s*'",
+ r"'\s*\.\s*\$this->escapeXmlValue\s*\(\s*\$dim_unit\s*\)\s*\.\s*'",
+ r"'\s*\.\s*\$this->escapeXmlValue\s*\(\s*\$weight_unit\s*\)\s*\.\s*'",
+ r"'\s*\.\s*\$this->escapeXmlValue\s*\(\s*\$is_dutiable\s*\)\s*\.\s*'",
+ r"'\s*\.\s*\$this->escapeXmlValue\s*\(\s*\$county_code_to\s*\)\s*\.\s*'",
+]
+
+missing = [pattern for pattern in required_patterns if not re.search(pattern, source)]
+if missing:
+ raise SystemExit(
+ "DHL Express XML is missing expected escaping patterns: "
+ + ", ".join(missing)
+ )
+
+unsafe_patterns = [
+ r"\"\s*\.\s*\$dhlexpress_account\s*\.\s*\"",
+ r"ShippingHelper::get_postcode_city\s*\(\s*\$fromCountryCode\s*,\s*\$fromCity\s*,\s*\$fromPostcode\s*\)",
+ r"ShippingHelper::get_postcode_city\s*\(\s*\$county_code_to\s*,\s*\$to_city\s*,\s*\$postcode\s*\)",
+ r"'\s*\.\s*\$dhlexpress_key\s*\.\s*'",
+ r"'\s*\.\s*\$dhlexpress_password\s*\.\s*'",
+ r"'\s*\.\s*\$fromCountryCode\s*\.\s*'",
+ r"'\s*\.\s*\$payment_country\s*\.\s*'",
+ r"'\s*\.\s*\$dim_unit\s*\.\s*'",
+ r"'\s*\.\s*\$weight_unit\s*\.\s*'",
+ r"'\s*\.\s*\$is_dutiable\s*\.\s*'",
+ r"'\s*\.\s*\$county_code_to\s*\.\s*'",
+]
+
+for unsafe_pattern in unsafe_patterns:
+ if re.search(unsafe_pattern, source):
+ raise SystemExit(f"DHL Express XML still writes raw value pattern: {unsafe_pattern}")