<?php
// 僅讀取原始 POST 數據(Content-Type: text/xml 或 application/xml)
$xmlString = file_get_contents('php://input');
if (empty($xmlString)) {
http_response_code(400);
exit('Empty XML body');
}
// 解析 XML
libxml_use_internal_errors(true);
$xml = simplexml_load_string($xmlString);
if ($xml === false) {
$errors = libxml_get_errors();
libxml_clear_errors();
http_response_code(400);
exit('Invalid XML: ' . $errors[0]->message);
}
// 業務處理...
$response = ['status' => 'ok', 'received' => (string)$xml->getName()];
header('Content-Type: application/json');
echo json_encode($response);
?>
<?php
function postXml(string $url, string $xmlData, array $options = []): string|false {
$ch = curl_init($url);
$defaultOptions = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $xmlData,
CURLOPT_HTTPHEADER => [
'Content-Type: application/xml; charset=utf-8',
'Content-Length: ' . strlen($xmlData)
],
CURLOPT_TIMEOUT => 30,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => true, // 生產必開
CURLOPT_SSL_VERIFYHOST => 2,
];
curl_setopt_array($ch, $defaultOptions + $options);
$response = curl_exec($ch);
$errNo = curl_errno($ch);
$errMsg = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($errNo) {
error_log("cURL error ($errNo): $errMsg for $url");
return false;
}
if ($httpCode >= 400) {
error_log("HTTP $httpCode from $url: $response");
}
return $response;
}
// 使用
$xml = '123 99.9 ';
$result = postXml('https://api.example.com/orders', $xml);
?>
<?php
function postXmlSocket(string $host, string $path, string $xmlData, int $port = 443, bool $ssl = true): string|false {
$scheme = $ssl ? 'ssl://' : '';
$fp = fsockopen($scheme . $host, $port, $errNo, $errStr, 10);
if (!$fp) {
error_log("Socket connect failed ($errNo): $errStr");
return false;
}
$request = "POST $path HTTP/1.1\r\n";
$request .= "Host: $host\r\n";
$request .= "Content-Type: application/xml; charset=utf-8\r\n";
$request .= "Content-Length: " . strlen($xmlData) . "\r\n";
$request .= "Connection: close\r\n";
$request .= "\r\n";
$request .= $xmlData;
fwrite($fp, $request);
$response = '';
while (!feof($fp)) {
$response .= fgets($fp, 4096);
}
fclose($fp);
// 簡易分離 Header/Body
list($header, $body) = explode("\r\n\r\n", $response, 2);
return $body;
}
// 使用
$body = postXmlSocket('api.example.com', '/orders', $xml);
?>
| 風險 | 說明 | 防護 |
|---|---|---|
| XXE (XML External Entity) | 惡意 XML 引用外部實體讀取本地文件/發起內網請求 | libxml_disable_entity_loader(true) (PHP 8.0+ 預設關閉);simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOENT | LIBXML_DTDLOAD) 禁用實體展開 |
| Billion Laughs (指數級實體擴張) | 遞歸實體導致記憶體耗盡 | 同上禁用實體;限制輸入大小 post_max_size |
| XXE 偷帶端口掃描 | 外部實體發起 HTTP 請求探測內網 | 網絡層面限制出站連線;PHP 禁用實體 |
除非對接銀行、政府、老舊 ERP 強制要求 XML,**新專案請優先使用 JSON**:
json_decode vs XML 解析器)。json_encode / json_decode 雙向無損轉換。PHP 處理 XML 核心:php://input 接收、simplexml_load_string + 禁用實體 解析、cURL 發送並設 Content-Type: application/xml。安全第一:**生產環境必須禁用 XML 外部實體**,否則面臨嚴重 XXE 攻擊風險。