我們與PHP的距離(一): gethostbyname()

前言

自從加入a8c,PHP作為常備語言之一,時至今日仍然像顆出奇蛋一樣止不盡的驚喜。天底下沒有完美的語言,但至今確實沒有任何一個我寫過的程式語言比PHP讓我感到更『魔幻』。本系列專為從PHP之外的語言開啟碼農人生、卻因命運安排整天與PHP為伍的人撰寫,收集一些從其他語言的角度看來不可思議的設計。如果能讓在讀這篇文章的你在實務上踩到而懷疑人生前就釋懷,莫大的榮幸。

祝各位天天PHP,天天開心。

進入正題

首先來看看今天的主角:gethostbyname() 在文件中是怎麼描述的吧:

gethostbyname ( string $hostname ) : string

Returns the IPv4 address of the Internet host specified by hostname.

舉例來說:

php> print_r( gethostbyname( 'softman.blog' ) );
192.0.78.25
php> print_r( gethostbyname( 'localhost' ) );
127.0.0.1

很簡單吧。這基本上就是一個gethostbyname()系統函式的包裝,

但問題出在它的回傳值設計。根據官方文件:

Returns the IPv4 address or a string containing the unmodified hostname on failure.

『當錯誤發生時,會回傳無修改的hostname引數』

……………..

這神設計有兩個問題。第一,和許多PHP函式的錯誤行為相悖;第二,複雜的錯誤處理讓這個函式很難正確使用。如果你認為:

$hostname === gethostbyname( $hostname )

這樣就結了,那就太小看PHP惹 …

問題一:引數若傳入IP,回傳值語意模糊

想想看,什麼時候會要用到gethostbyname()呢?無非是想要得到正確IP的時候吧?那傳入的剛好就IP呢?

php> print_r( gethostbyname( '192.0.78.25' ) );
192.0.78.25

這就顯現了這個回傳值設計的問題:上例中,如果單看回傳的192.0.78.25,到底是解析成功還是不成功?這就算搭配引數一起看都很難說它到底是不是在報錯,因為IP作為主機名很合理;如果用filter_var()去檢驗,PHP也是認同它是合法的主機名的:

php > print_r( filter_var( '192.168.1.1', FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME ) );
192.168.1.1

因此,它應當是『解析成功』,只是『長得跟錯誤一模一樣』。MAGIC!🧙‍♂️

實務中我們也常看到這樣的用法:

function somehow_i_need_to_process_your_ip( $hostname ) {
    $ip = gethostbyname( $hostname );
    if ( $hostname === $ip ) {
        return false;
    }
    // 開始對IP上下其手囉!
}

somehow_i_need_to_process_your_ip()會從第一個if述句中默默回傳false;但事實上傳入的若是合法IP,它是能夠處理的。

問題二:無法處理夾雜null字元的字串

官方文件沒告訴俺們的是,它事實上在錯誤時回傳的不是『未修改的$hostname引數』,而是『$hostname引數到第一個null字元為止的內容』

……………. 蝦餃?

真心不騙,試試就知:

php> $x = 'abc' . chr(0) . 'xxx';
php> print_r( gethostbyname( $x ) );
abc
php> var_dump( $x === gethostbyname( $x ) );
bool(false)

所以,只要有心人刻意搞了個夾著null字元的字串進去,該判斷式就會誤判,導致整個函式的後續處理崩潰。

其實不只函式崩潰,猶記得踩到這個雷的當下,我的心,也跟著崩潰了。

重拾玻璃心

如果真的要非常嚴謹,應該要把傳入gethostbyname()的引數先sanitize過,再用filter_var()等檢驗確實是合法的主機名,最後再檢驗它是不是合法IP。但這樣是有些過分了;每個語言都有其原生設計,我們應該要盡量發揮其本色,而不是硬要套其他語言的特色。這就好像英文不能拿來寫七言絕句,就算我再怎麼愛強式靜態型別,過度sanitization與validation,寫出來的PHP code不但不自然,還會很慢。

因此,建議只要檢驗回傳值是不是合法IP就行了:

$ip = gethostbyname( $hostname );
if ( filter_var( $ip, FILTER_VALIDATE_IP ) ) {
    // ...
}

這樣就可以把以上情況都處理掉,也不至於把邏輯搞到太複雜。

心,好像又堅強了一點點。

對「我們與PHP的距離(一): gethostbyname()」的一則回應

發表留言

Please log in using one of these methods to post your comment:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.