Warenwirtschaft: Review-Fix — AP-32-Label als Base64 übertragen (Connector 1.5.0)

Die Brücke hätte live nicht funktioniert: Connector 1.4.0 sendete die
label_url aus dem per 'deny from all' geschützten DHL-Ordner (403), dazu
SSRF-Risiko durch serverseitigen Abruf einer Client-URL und Timeout-
Mismatch (20 s Download vs. 5 s WordPress-Default). Der Connector liest
die PDF jetzt vom Shop-Dateisystem (realpath-Pinning auf wp-uploads) und
sendet sie Base64; storeWpLabel() validiert und speichert ohne Http::get;
tote Shop-Link-Fallbacks aus den Views entfernt; Connector-Timeout 30 s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-20 10:06:39 +00:00
parent ddd6dde593
commit 1b9b6adc44
11 changed files with 173 additions and 83 deletions

View file

@ -3,7 +3,7 @@
/**
* Plugin Name: PARTNERCENTER API Connector
* Description: Connect WooCommerce to PARTNERCENTER API
* Version: 1.4.0
* Version: 1.5.0
* Author: Cross Media Cloud
* Author URI: https://www.cross-media-cloud.de
* License: GNU General Public License v2 or later

View file

@ -45,8 +45,11 @@ trait PARTNERCENTER_API_Methods
$url = $this->build_url($endpoint);
// Return the POST request responds
// (timeout raised from the 5s WordPress default since 1.5.0 -
// order transfers may carry a base64 encoded DHL label)
return wp_safe_remote_post($url, [
'blocking' => true,
'timeout' => 30,
'headers' => $this->get_headers(),
'body' => json_encode($body),
]

View file

@ -140,7 +140,10 @@ trait PARTNERCENTER_API_Order
/**
* Helper to read the DHL label data saved by the "DHL for WooCommerce" plugin.
*
* The plugin stores label URL and tracking number(s) as order meta.
* The plugin stores label path/URL and tracking number(s) as order meta.
* Since 1.5.0 the label FILE is sent base64 encoded (wp_label_pdf): the
* label folder inside wp-uploads is protected by "deny from all", so the
* PARTNERCENTER could never fetch the raw label_url server side.
*
* @return array Empty array when no label exists yet
*
@ -153,15 +156,69 @@ trait PARTNERCENTER_API_Order
$label_meta = $this->order->get_meta('_pr_shipment_dhl_label_tracking');
// Check if a label was created for this order
if (! is_array($label_meta) || empty($label_meta['label_url'])) {
if (! is_array($label_meta) || (empty($label_meta['label_path']) && empty($label_meta['tracking_number']))) {
return [];
}
return [
'wp_label_url' => $label_meta['label_url'],
$label_data = [
// String or array (multi parcel) - the API accepts both
'wp_tracking_number' => isset($label_meta['tracking_number']) ? $label_meta['tracking_number'] : null,
];
// Read the label file from disk and send it along (base64)
$label_pdf = $this->read_label_file(isset($label_meta['label_path']) ? $label_meta['label_path'] : '');
if ($label_pdf !== false) {
$label_data['wp_label_pdf'] = $label_pdf;
}
// Reference only - the API stores it as origin metadata, it is never fetched
if (! empty($label_meta['label_url'])) {
$label_data['wp_label_url'] = $label_meta['label_url'];
}
return $label_data;
}
/**
* Read a DHL label PDF from disk and return it base64 encoded.
*
* Only PDF files inside the wp-uploads directory are accepted - the
* label_path comes from order meta, never from user input, but we
* validate anyway before touching the filesystem.
*
* @param string $label_path Absolute path from the DHL label meta
* @return string|bool Base64 string or false when unreadable/invalid
*
* @since 1.5.0
*/
private function read_label_file($label_path)
{
// No label file saved (tracking-only meta)
if (empty($label_path) || ! is_string($label_path)) {
return false;
}
// Resolve and pin the file to the uploads directory
$real_path = realpath($label_path);
$uploads = wp_get_upload_dir();
$uploads_base = realpath($uploads['basedir']);
if ($real_path === false || $uploads_base === false || strpos($real_path, $uploads_base) !== 0) {
return false;
}
// Only PDF files
if (strtolower(substr($real_path, -4)) !== '.pdf') {
return false;
}
// Read and verify the content really is a PDF
$contents = file_get_contents($real_path);
if ($contents === false || substr($contents, 0, 4) !== '%PDF') {
return false;
}
return base64_encode($contents);
}
/**