php使用Regex驗證網址連結

來源:互聯網
上載者:User

之前折騰了一個短網址程式,過程挺順利的,唯一就是在驗證網址這一步卡了殼,花費了整個過程的一大半時間,最終經過一番搜尋、折騰和測試,才算找到一個完美的解決方案。

在短網址程式中,驗證網址無疑是很重要的。且不說各種安全問題,就是一些「渾水摸魚」的網址佔據大量的短網址都非常讓人頭疼。

提到驗證網址,我相信大多數人第一時間反應出來的都是Regex,的確,這很科學,但何奈自己正則太渣,平時想要匹配一段複雜的 HTML 都要反覆調試半天,更何況我連網址的結構都不能完全梳理清楚,所以還是繞過它,咱能不用就別自己找不自在。

esc_url_raw()

想了想,平時的開發中,驗證網址都是直接調的 WordPress 核心給的函數,所以考慮直接把 WordPress 的 esc_url_raw() 函數搬過來,省時又省力。

事實證明我實在是太天真了。複製的過程中我發現它牽連了太多其它的模組,一個模組勾搭一點,想要完全搬過來實在是太過龐大,到最後我都分不清楚哪是哪了,你到底跟幾個模組有關係啊?照這樣搞下去,還不如直接基於 WordPress 算了。

再想想,其實這個函數的本意是「過濾」網址,儘可能的把不合法的網站變成合法的:沒有協議?咱給它添上協議;不合法字元?直接幹掉它,別髒了眼;瀏覽器或伺服器根本沒法解析?我管你那個?

它只是在把網址變得不出現根本錯誤就沒事,別讓你這一個小小的網址把我整個程式都給搞亂了就行,雖然在 WordPress 的環境中,大多數情況這樣就夠了,但在我們的短網址程式中,這顯然不夠嚴格。

filter_var()

自己的想法還沒能運行一次,就已經胎死腹中了,沒辦法,只好去求助萬能的搜尋引擎大人了。找了半天,看著 Stack Overflow 上的各路大神的一個個長篇大論(其實我現在也好像在長篇大論……),最終決定選擇大多數人推薦的 filter_var() 函數試試,其實我以前也聽說過這個東西,不過一直沒什麼機會實際使用下,也可以趁現在好好研究研究。

filter_var() 是 PHP 內建的一個函數,顧名思義,用來驗證(過濾)變數,印象中應該不止可以驗證 URL,什麼郵箱之類的也都玩得轉。

如果要用它驗證網址,只需要這樣:

if ( filter_var( $url, FILTER_VALIDATE_URL ) !== false )
 echo '可以,這很網址。'; // 驗證成功
else
 echo '這是什麼鬼?'; // 驗證失敗,返回 FALSE。
非常的簡單,不過要注意,失敗時返回 FALSE 並不意味著你可以直接用 if ( !filter_var( ... ) ) 這樣的形式來 草率的判定網址不合法,因為它成功時會返回過濾的內容,而不是 TRUE,如果內容是 '0' 那你怎麼辦?雖然我們驗證網址不可能出現這種情況,但這種習慣還是不要養成的為好,我年輕的時候就曾自作聰明的修改例子代碼,在 strpos() 函數上栽跟頭嘍(其實我現在還經常自作聰明,並且為其付出代價,然後繼續自作聰明)。

OK,經過一番測試,感覺上是沒什麼大問題了,所以把代碼交了出去。過一會,別人小小測試一下,發現類似百度和Google的搜尋結果的網址無法驗證通過,又是一波緊急研究。

結論是 filter_var() 會認定所有帶有中文的網址不合法,雖然可以通過轉義中文部分的方法強行通過驗證,但我之前已經為了避免重複所以取消了所有 URL 的轉義啊,這想想感覺會弄得好混亂。而且過會又發現,即使是在沒有中文的情況下,它對錨點(就是網址中「#」後邊的那一堆)連結的支援還有些迷之問題,所以果斷拋棄之。

Happy End

好,重頭戲來了。

無奈,只好繼續去搜尋解法,偶然間看到了一個網址,進去之後感覺進入了天堂一樣,相見恨晚,裡邊簡直有我現在所急需的一切。

如何正確的使用Regex驗證網址連結

這個頁面的作者搜集了很多不同的Regex,並用它們對一系列網址進行測試,有的網址需要驗證通過,也有恰恰相反的。

最終,只有一位叫 Diego Perini 的選手通過了比賽,完美的達到了所有要求,他的Regex足足有 502 個字元,打眼一瞧非常的嚇人,反正我是完全看不懂……

為了方便使用,我把它的Regex封裝成了一個 PHP 函數,想要使用的話,直接調用就行了:

/**
 * 檢測網址是否合法
 *
 * @link https://www.bgbk.org/regex-url/
 * @link https://gist.github.com/dperini/729294
 *
 * @param string $url 需要檢測的網址。
 * @return bool 是否為一個合法的網址。
 */
function is_url( $url ) {
 if ( !trim( $url ) )
  return false;

 if ( strlen( $url ) < 10 )
  return false;

 $pattern = '_^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]+-?)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,})))(?::\d{2,5})?(?:/[^\s]*)?$_iuS';

 $result = preg_match( $pattern, $url );
 $result = (bool) $result;

 return $result;
}
就像這樣:

if ( is_url( $url ) )
 echo '可以,這很網址。'; // 驗證成功
else
 echo '這是什麼鬼?'; // 驗證失敗
雖然我對Regex理解不多,但它看長度就顯然是巨耗費效能的,所以我先對網址進行了一些簡單的預判斷,希望可以減少伺服器的壓力。

另外,如果你想瞭解它的原理,或者想在 JavaScript 中使用這個Regex的話,可以參考作者在 GitHub 上的一個頁面,裡邊有原版的 JavaScript 版本,並且附有大量注釋,方便你學習和使用!

除此之外,你可以點擊下載一份所有測試的 URLs 列表,可以親自嘗試各種Regex,或者驗證你的方法,如果找到了更好的辦法,一定要在評論中分享給大家哦!

附贈資料

最後附贈一個也是偶然發現的頁面,裡邊用非常清晰的方式詳細的介紹了網址的構成規則,感興趣的同學可以點擊此處去參觀參觀。

如何正確的使用Regex驗證網址連結

在 javaScript 中,用 window.location. 加上展示模型裡提到的屬性名稱,就可以擷取到當前頁面網址的那一部分啦,比如 window.location.protocol,一般就會返回「http:」或「https:」。

當然,上邊的網址只是展示了網址基本的構成元素,如果你想瞭解的更多,比如網址每個部分不能出現的字元啊、需要轉義的文本啊,以及各種約定俗稱的規則和注意事項,都可以在這裡找到,非常完整哦!

URI 是 Web上可用的每種資源 - HTML文檔、映像、視頻片段、程式等 - 由一個通用資源標誌符(Uniform Resource Identifier, 簡稱"URI")進行定位。 對象分組:


^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
12            3  4       


測試代碼如下:

<?php
$search = '~^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?~i';
$url = 'http://www.jb51.net/pub/ietf/uri/#Gonn';
$url = trim($url);
preg_match_all($search, $url ,$rr);
printf("<p>輸出URL資料為:</p><pre>%s</pre>\n",var_export( $rr ,TRUE));
 
/*
各分組如下
      $1 = http:
      $2 = http
      $3 = //www.111cn.net
      $4 = www.111cn.net
      $5 = /pub/ietf/uri/
      $6 = <undefined>
      $7 = <undefined>
      $8 = #Gonn
      $9 = Gonn
*/
?>

上面的Regex可以擷取URL中的任何一部分,下面的代碼則簡單一些:


<?php
// 從 URL 中取得主機名稱
preg_match("/^(http:\/\/)?([^\/]+)/i", "http://www.jb51.net/index.html", $matches);
$host = $matches[2];
// 從主機名稱中取得後面兩段
preg_match("/[^\.\/]+\.[^\.\/]+$/", $host, $matches);
echo "domain name is: {$matches[0]}\n";
?>

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.