根據 URL 查詢字串 (`?url=target`) 動態導向不同目標網址,常用於短網址、廣告追蹤、外部連結中介頁。
<?php
// 允許的目標網域白名單 (安全性關鍵)
$allowedDomains = [
'example.com',
'partner.com',
'api.service.com'
];
// 取得查詢字串
$query = $_SERVER['QUERY_STRING'] ?? '';
// 解析目標 URL 參數
parse_str($query, $params);
$targetUrl = $params['url'] ?? '';
// 驗證 URL 格式
if (!filter_var($targetUrl, FILTER_VALIDATE_URL)) {
http_response_code(400);
die('無效的 URL 參數');
}
// 解析目標主機
$targetHost = parse_url($targetUrl, PHP_URL_HOST) ?? '';
// 白名單檢查 (防開放式重導向攻擊)
if (!in_array($targetHost, $allowedDomains, true)) {
http_response_code(403);
die('不允許的目標網域: ' . htmlspecialchars($targetHost));
}
// 記錄點擊 (資料庫/日誌/分析)
// log_click($targetUrl, $_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_USER_AGENT']);
// 執行重導向
header("Location: $targetUrl", true, 302); // 302 暫時重導向
exit;
?>
<?php
class Redirector {
private $allowedDomains;
private $db;
public function __construct(array $allowedDomains, PDO $db = null) {
$this->allowedDomains = $allowedDomains;
$this->db = $db;
}
public function handle(): void {
// 1. 取得編碼後的目標參數
$encoded = $_GET['u'] ?? $_GET['url'] ?? '';
if (!$encoded) $this->fail(400, '缺少目標參數');
// 2. 解碼 (支援 Base64URL)
$targetUrl = $this->decodeUrl($encoded);
if (!$targetUrl) $this->fail(400, '參數解碼失敗');
// 3. 驗證
if (!$this->isValidUrl($targetUrl)) $this->fail(400, '無效 URL');
if (!$this->isAllowedDomain($targetUrl)) $this->fail(403, '禁止的網域');
// 4. 記錄統計
$this->logClick($targetUrl);
// 5. 重導向
header("Location: $targetUrl", true, 302);
exit;
}
private function decodeUrl(string $encoded): ?string {
// 嘗試 Base64URL 解碼
$decoded = base64_decode(strtr($encoded, '-_', '+/'));
if ($decoded !== false && filter_var($decoded, FILTER_VALIDATE_URL)) {
return $decoded;
}
// 回退原始 URL
return filter_var($encoded, FILTER_VALIDATE_URL) ? $encoded : null;
}
private function isValidUrl(string $url): bool {
return filter_var($url, FILTER_VALIDATE_URL) !== false;
}
private function isAllowedDomain(string $url): bool {
$host = parse_url($url, PHP_URL_HOST) ?? '';
// 支援子網域比對
foreach ($this->allowedDomains as $domain) {
if ($host === $domain || str_ends_with($host, '.' . $domain)) {
return true;
}
}
return false;
}
private function logClick(string $url): void {
if (!$this->db) return;
try {
$stmt = $this->db->prepare(
"INSERT INTO redirect_logs (target_url, ip, user_agent, referer, created_at)
VALUES (?, ?, ?, ?, NOW())"
);
$stmt->execute([
$url,
$_SERVER['REMOTE_ADDR'] ?? 'unknown',
$_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
$_SERVER['HTTP_REFERER'] ?? 'direct'
]);
} catch (Exception $e) {
error_log("Redirect log failed: " . $e->getMessage());
}
}
private function fail(int $code, string $msg): void {
http_response_code($code);
header('Content-Type: text/plain; charset=utf-8');
echo $msg;
exit;
}
}
// 使用
$allowed = ['example.com', 'shop.example.com', 'partner.org'];
$db = new PDO('mysql:host=localhost;dbname=stats', 'user', 'pass');
(new Redirector($allowed, $db))->handle();
?>
<?php
function generateRedirectUrl(string $targetUrl, array $allowedDomains): string {
$host = parse_url($targetUrl, PHP_URL_HOST);
if (!$host || !in_array($host, $allowedDomains)) return '#';
// Base64URL 編碼 (URL 安全)
$encoded = rtrim(strtr(base64_encode($targetUrl), '+/', '-_'), '=');
return "/redirect.php?u=$encoded";
}
// 使用
$link = generateRedirectUrl('https://shop.example.com/product/123', ['shop.example.com']);
// 輸出: /redirect.php?u=aHR0cHM6Ly9zaG9wLmV4YW1wbGUuY29tL3Byb2R1Y3QvMTIz
?>
<a href="<?= $link ?>" target="_blank" rel="noopener">前往商品頁</a>