javascript - 用於真實世界 URL 的正則表達式驗證
我想驗證給定的字串是否為網址。在文字中匹配網址也不錯,但不是必需的。我已經搜尋並嘗試過,但到目前為止還沒找到符合這些需求的東西:
不得接受那些在被視為連結時會構成安全風險的字串。例如,
<a href="javascript:alert(document.cookie)">clickme</a>是一個有效的 HTML 元素,並且確實在至少某些瀏覽器中會運作(例如彈出警示等)。我擔心如果允許任意的協議(見下文),可能會危及安全(例如,如此處所述:What is the best regular expression to check if a string is a valid URL?)。必須在 JavaScript 中正確運行。
如果它在 Java 中也能這樣運作就好了 —— 我正在使用 GWT 開發,所以這會很好,但不是絕對必要的。
必須接受實際使用的 URL,而不僅僅是符合標準的 URL。 具體例子:
一個。我想接受 http://fr.wikipedia.org/wiki/Français。此 URL 不標準,因為它包含非英語字符,但我的參考瀏覽器 IE(7+) 和 Chrome 接受它。
b. 我想接受 http://fr.wikipedia.org/wiki/Fran%c3%a7ais,這是不標準的,因為百分號編碼的十六進位應該是大寫,但 IE 和 Chrome 仍然接受。我想我可以只做不區分大小寫的匹配——你能想到有什麼缺點嗎?
c. 我想接受 http://localhost/localpath/servlet#action?param=value,這是不標準的,因為片段部分(從‘#’到結束)不應包含‘?’和其他字符,但有些應用程式會生成這樣的 URL,瀏覽器也會接受它們。
d. 我想接受任何方案/協議的 URL(不僅僅是 http、https 和 ftp),因為我整合的各種應用程式及其使用者可能需要傳遞這些 URL。我可以禁止 'javascript:' 並允許其他所有內容;如果你認為這會危及安全,請告訴我。
關於這個主題在 Stack Overflow 和其他地方有很多問題,但我沒有找到一個能滿足我所有需求的正則表達式。例子:
GWT 中匹配 URL 的正則表達式 -- 相當不錯且簡單的正則表達式,但不接受非標準 URL。我可以處理方案部分和百分比編碼的大小寫敏感,但不能處理其他問題。
https://stackoverflow.com/a/190405/96929 -- 巨大的正則表達式(我在想我使用的所有瀏覽器和框架能否處理這麼大的正則表達式),看起來非常全面,但它說符合標準,而我完全搞不懂它。
謝謝!:-)
答案#1
必須接受實際使用的 URL,而不僅僅是符合標準的 URL
實際上,URI 規範相當寬鬆,允許一些通常因兼容性原因你會想排除的結構……
我想接受 http://fr.wikipedia.org/wiki/Français,這不是標準的
它不是 URI,但它 確實 是一個相當標準的 IRI。
- 非標準,因為百分比編碼的十六進位應該是大寫
- 非標準,因為片段部分(從 '#' 到結尾)不應包含 '?'
根據 URI 標準,這兩者都是完全可接受的。RFC 3986 建議,但不要求,在創建百分比編碼時使用大寫字母。
我可以禁止 'javascript:' 並允許其他所有內容;如果你認為這會危及安全,請說明。
會的。不幸的是,URI 協議名稱空間中已經出現了多個潛在危險的新增項,未來無疑還會繼續出現。此外,可能還存在利用編碼字符和控制字符來規避黑名單功能的方法。
此外,任意方案匹配意味著你在文本中檢測地址的次要目標,幾乎每次使用冒號時都會產生假陽性。
白名單是唯一可行的前進方式,因此您將不得不逐一手動允許每個新的協議。這需要一些小心;例如,data: 協議看似無害且有用,但可能面臨與 javascript: 相同的 XSS 問題。
你還需要了解每個方案的一些資訊。像 http 和 ftp 這樣的方案有「基於伺服器的命名權威」:它們可以在該主機中包含單獨的主機名稱和資源路徑;此外,你可能需要它們是絕對的 URI。如果你想允許檔案 URI,你必須檢查它是否沒有主機(file:///)。對於其他方案,URI 標準本身可能不要求具體的語法,但可能會有其他限制,例如 mailto: 必須使用有效的電子郵件地址。
巨型正則表達式(我在問自己我使用的所有瀏覽器和框架是否能處理這個大小),看起來非常全面
這在 JavaScript 中行不通,因為它使用了不支援的 \x{code point} 語法。此外,像 JavaScript 這樣的語言,其正則表達式引擎是以 UTF-16 編碼單元而非完整的 Unicode 代碼點運作的,因此無法處理超出基本多文種平面(BMP)的字元範圍。
你必須將長的 \x{A0}...\x{1FFFD} 群組替換成例如 \u00A0-\uFFFD 這樣較簡單的形式,然後另外檢查無效的代理對,以及 0xnnFFFE-F 非字符,如果你在意這些的話(可能不會)。
可以說,你可能已經在一般的輸入掃描層級中剔除了任何壞的代理項和非字元,早在進行 IRI 驗證之前;沒有理由允許它們出現在任何文本輸入中。將這一步作為單獨步驟來做,比試圖把所有東西擠進單一正則表達式更合理。
替換了那個之後,引用的正則表達式中最長的部分就是那段極其冗長的數字檢查,用來驗證數字型 IP 地址。這正是正則表達式完全不擅長的事情。我會強烈考慮不要去處理 IPv6 和未來的數字型地址:即便假設 IPv6 很快會被廣泛採用,但在可預見的未來,沒有人會使用它們。(你甚至想允許鏈接到數字型地址嗎?這取決於你的應用程式在做什麼,但通常不需要。)
你也可以考慮禁止 userinfo@ 主機名的前綴(因為它們傳統上除了用於偽造攻擊外,沒有其他用途),以及百分比編碼的主機名(因為在有 Punycode 的情況下它們沒有用途,而且在某些瀏覽器中無法使用)。
所以對 IRI 驗證沒有單一的答案,但這裡是你可能開始的地方:
(
https?://
(
([0-9]{1-3}(\.[0-9]{1-3}){3})|
([-0-9a-z\u00A0-\uFFFD]{1-63}(\.[-0-9a-z\u00A0-\uFFFD]{1-63})*)
)
(:[0-9]+)?/
(
%[0-9a-f][0-9a-f]|
[-._!$&'()*+,:;=@~0-9a-z\u00A0-\uFFFD/?#]
)*
)|(
ftp:// // same again but with no ?query
... // or port number
)|(
mailto: // specify requirements for
... // other accepted schemes
)
(假設不區分大小寫。這適用於 DNS 約束,這些約束本身並非 URI 規範的一部分,儘管不完全,因為它並未檢查 DNS 標籤中前導/尾隨的 -,或 IPv4 八位元組的數值範圍。驗證電子郵件地址則留給讀者作為練習,正如其本身是一項艱鉅的任務,如果你想嚴格執行則不適合使用正則表達式。)
答案#2
由於你在伺服器端使用 Java,我建議你使用 URI。它會接受你想要的所有“奇怪”的東西,而且只需使用 .getScheme() 就可以檢查它確實是 HTTP 或 HTTPS。
而且與 URL 不同,URI 不會嘗試進行名稱解析!
