「加入我的最愛」功能受限於瀏覽器安全策略,現代瀏覽器多已封鎖 JS 呼叫 window.external.AddFavorite()。替代方案是動態產生 .url 檔案 (Internet Shortcut) 並以 Content-Disposition: attachment 強制下載,使用者雙擊即可建立桌面捷徑。本文提供 PHP 與 ASP 兩種實作,並說明安全性考量。
.url 檔案本質為 INI 格式文字檔,核心欄位:
[InternetShortcut]
URL=https://example.com/
IconFile=https://example.com/favicon.ico
IconIndex=0
IDList=...
[DEFAULT]
BASEURL=https://example.com/
[{000214A0-0000-0000-C000-000000000046}]
Prop3=19,2
瀏覽器下載後,使用者雙擊即由 Windows Shell 建立捷徑,圖示自動從 IconFile 取得。
<?php
/**
* 產生並下載 .url 桌面捷徑
*
* 參數:
* url - 目標網址 (必填)
* title - 捷徑名稱/下載檔名 (選填,預設為域名)
* icon - 圖示網址 (選填,預設 /favicon.ico)
*/
function send_desktop_shortcut() {
// 1. 參數驗證與清理
$targetUrl = filter_input(INPUT_GET, 'url', FILTER_VALIDATE_URL);
if (!$targetUrl) {
http_response_code(400);
exit('Invalid URL parameter');
}
$title = filter_input(INPUT_GET, 'title', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$title = $title ?: parse_url($targetUrl, PHP_URL_HOST);
$title = preg_replace('/[\\\\/:*?"<>|]/', '_', $title); // 移除非法檔名字元
$iconUrl = filter_input(INPUT_GET, 'icon', FILTER_VALIDATE_URL);
$iconUrl = $iconUrl ?: rtrim($targetUrl, '/') . '/favicon.ico';
// 2. 組裝 .url 內容
$content = <<
<%
' shortcut.asp
' 參數: url, title, icon
Dim targetUrl, title, iconUrl, content, fileName
targetUrl = Request.QueryString("url")
If targetUrl = "" Then
Response.Status = "400 Bad Request"
Response.Write "Missing URL parameter"
Response.End
End If
' 簡易 URL 驗證 (ASP 無內建 FILTER_VALIDATE_URL)
If Not (InStr(targetUrl, "https://") = 1 Or InStr(targetUrl, "https://") = 1) Then
Response.Status = "400 Bad Request"
Response.Write "Invalid URL format"
Response.End
End If
title = Request.QueryString("title")
If title = "" Then
' 簡易取域名
Dim re, matches
Set re = New RegExp
re.Pattern = "https?://([^/]+)"
re.IgnoreCase = True
Set matches = re.Execute(targetUrl)
If matches.Count > 0 Then title = matches(0).SubMatches(0)
End If
' 移除非法檔名字元
title = Replace(title, "\", "_")
title = Replace(title, "/", "_")
title = Replace(title, ":", "_")
title = Replace(title, "*", "_")
title = Replace(title, "?", "_")
title = Replace(title, """", "_")
title = Replace(title, "<", "_")
title = Replace(title, ">", "_")
title = Replace(title, "|", "_")
iconUrl = Request.QueryString("icon")
If iconUrl = "" Then iconUrl = targetUrl & "/favicon.ico"
content = "[InternetShortcut]" & vbCrLf & _
"URL=" & targetUrl & vbCrLf & _
"IconFile=" & iconUrl & vbCrLf & _
"IconIndex=0" & vbCrLf & _
"HotKey=0" & vbCrLf & _
"[{000214A0-0000-0000-C000-000000000046}]" & vbCrLf & _
"Prop3=19,2" & vbCrLf & _
"[DEFAULT]" & vbCrLf & _
"BASEURL=" & targetUrl & vbCrLf
fileName = Server.URLEncode(title) & ".url"
Response.ContentType = "application/octet-stream"
Response.AddHeader "Content-Disposition", "attachment; filename=""" & fileName & """; filename*=UTF-8''" & fileName
Response.AddHeader "Content-Length", Len(content)
Response.CacheControl = "no-store"
Response.AddHeader "Pragma", "no-cache"
Response.Expires = -1
Response.Write content
Response.End
%>
<a href="/shortcut.php?url=https://example.com/pricing&title=方案價格&icon=https://example.com/favicon.ico"
class="btn btn-primary"
download="方案價格.url">
<i class="bi bi-desktop"></i> 放到桌面
</a>
download 屬性建議瀏覽器直接下載而非導航,配合後端 Content-Disposition 雙重保障。
| 風險 | 說明 | 防範措施 |
|---|---|---|
| 惡意網址導向 | 駭客可建立指向釣魚網站的捷徑,偽裝成合法網站圖示 | 僅允許自有域名/白名單域名;驗證 URL 來源 |
| 圖示欺騙 | 使用系統圖示 (我的電腦、回收筒) 偽裝成系統功能 | 限制 IconFile 必須為自有域名或 HTTPS;驗證圖示內容 |
| 大量下載濫用 | 機器人大量請求消耗頻寬 | 加入 CAPTCHA、速率限制、需登入 |
| 檔名注入 | 惡意檔名導致儲存路徑遍歷 | 嚴格過濾檔名字元、使用 URLEncode |
若目標是讓使用者「安裝」網站如原生 App,建議採用 Progressive Web App (PWA):
manifest.json 定義名稱、圖示、啟動頁、顯示模式。; manifest.json 範例
{
"name": "範例商城",
"short_name": "商城",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0066cc",
"icons": [
{"src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png"},
{"src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png"}
]
}