解説コードを読んでみよう
このサンプルでわかること
- ドメイン名から IP アドレスやホスト名を調べる PHP の関数
- 外部の API(ipinfo.io)から JSON を受け取って使う方法
- 「ソケット通信」で whois サーバーに直接問い合わせる方法
使っているファイル
- index.php入力画面(URL を ajax で request.php に送る)
- request.phpいろいろな情報を調べて、表の HTML を返す
- whois_lite.phpwhois サーバーに問い合わせる関数
- list.phpドメインの末尾(.com / .jp など)ごとの whois サーバーの一覧
1. 入力画面から ajax で送る
「調べる」ボタンで、入力された URL を request.php に送ります。調べるのに数秒かかることがあるので、先に「調べています…」と表示しておきます。
<script>
$(function () {
$('#lookup').on('click', function () {
var url = $('input[name="url"]').val();
$('#whois').html('<p>調べています…</p>');
$.ajax({
url: 'request.php', // 同じフォルダの request.php に送る
type: 'POST',
data: { url: url },
timeout: 20000,
dataType: 'html'
})
.done(function (data) {
$('#whois').html(data); // 返ってきた表をそのまま表示
})
.fail(function () {
$('#whois').html('<div class="alert alert-danger">通信に失敗しました。</div>');
});
});
});
</script>
2. このサイトからのリクエストか確認する
request.php を、ほかのサイトから勝手に使われないようにします。ブラウザが送ってくる Referer(どのページから来たか)のホスト名が、このサイトのホスト名と同じときだけ処理を続けます。続けて、URL の形式もチェックし、ドメイン名だけを取り出します。
// 1. このサイトのページから送られたものだけ受け付ける(Referer のホストを確認)
$referer = parse_url($_SERVER['HTTP_REFERER'] ?? '', PHP_URL_HOST);
$myhost = parse_url('http://' . ($_SERVER['HTTP_HOST'] ?? ''), PHP_URL_HOST);
if (!$referer || strcasecmp($referer, (string)$myhost) !== 0) {
stop('外部からのアクセスは受け付けていません。');
}
// 2. URL の形式を確認し、ドメイン名を取り出す
$url = trim((string)($_POST['url'] ?? ''));
if (!preg_match('#^https?://#i', $url) || filter_var($url, FILTER_VALIDATE_URL) === false) {
stop('入力された文字がURLの形式ではありません。');
}
$domain = strtolower((string)parse_url($url, PHP_URL_HOST));
Referer は簡単に偽装できるので、完全な対策ではありません。「普通の使い方で、ほかのサイトに埋め込まれるのを防ぐ」程度のものだと考えてください。
3. IP アドレスとホスト名を調べる
gethostbyname() はドメイン名から IP アドレスを、gethostbyaddr() は IP アドレスからホスト名(サーバーの名前)を調べる関数です。インターネットの「住所録」である DNS に問い合わせています。
// 3. ドメインから IP アドレスとホスト名を調べる
$ip = gethostbyname($domain); // 見つからないときはドメイン名がそのまま返る
$found = filter_var($ip, FILTER_VALIDATE_IP) !== false;
$host = $found ? gethostbyaddr($ip) : '';
// サーバーの内側(プライベートIP)には接続しない
$isPublic = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false;
最後の行の $isPublic では、IP アドレスが「プライベートアドレス(社内ネットワークなど)」でないかを調べています。ユーザーが入力した URL にサーバーがアクセスする機能では、サーバー内部をのぞかれないよう、この確認が大切です。
4. ページのタイトルと meta タグを取り出す
file_get_contents() でページの HTML を取得し、正規表現でタイトルと <meta name="..." content="..."> を取り出します。
// 4. ページのタイトルと meta タグを取得する
$site_title = '';
$meta = [];
if ($isPublic) {
$ctx = stream_context_create(['http' => ['timeout' => 5, 'follow_location' => 0]]);
$html = @file_get_contents($url, false, $ctx);
if (is_string($html) && $html !== '') {
$html = mb_convert_encoding($html, 'UTF-8', 'ASCII,JIS,UTF-8,CP51932,SJIS-win');
if (preg_match('@<title>([^<]++)</title>@i', $html, $m)) {
$site_title = trim($m[1]);
}
// <meta name="..." content="..."> を全部取り出す
preg_match_all('@<meta\s+name=["\']([^"\']+)["\']\s+content=["\']([^"\']*)["\']@i', $html, $mm, PREG_SET_ORDER);
foreach ($mm as $one) {
$meta[$one[1]] = $one[2];
}
}
}
5. サーバーの所在地を API で調べる
ipinfo.io という無料の API に IP アドレスを渡すと、そのサーバーがある国・都市・会社名が JSON で返ってきます。json_decode() で PHP のデータに変換して使います。
// 5. IP アドレスからサーバーの所在地を調べる(ipinfo.io の無料API)
$details = null;
if ($isPublic) {
$json = @file_get_contents("https://ipinfo.io/{$ip}/json", false, stream_context_create(['http' => ['timeout' => 5]]));
$details = is_string($json) ? json_decode($json) : null;
}
6. whois サーバーを選んで問い合わせる
Whois(フーイズ)は、ドメインの登録者や有効期限などを調べるしくみです。.com なら Verisign、.jp なら JPRS のように、ドメインの末尾ごとに問い合わせ先のサーバーが違うので、list.php の一覧から選びます。co.jp と jp の両方に一致するときは、長いほう(より詳しいほう)を使います。
// 6. ドメインの末尾に合う whois サーバーを選ぶ(長く一致したものを優先)
$server = '';
$best = 0;
foreach ($list as $tld => $whoisServer) {
if (preg_match('/(^|\.)' . preg_quote($tld, '/') . '$/', $domain) && strlen($tld) > $best) {
$server = $whoisServer;
$best = strlen($tld);
}
}
// 7. whois サーバーに問い合わせ、「項目: 値」の形に分ける
$result = [];
$data = $server !== '' ? whois_lite_query($domain, $server) : '';
foreach (explode("\n", $data) as $line) {
$line = trim($line);
if ($line === '') {
continue;
}
if (str_contains($line, '>>>')) { // ここから下は注意書きなので読み飛ばす
break;
}
$tmp = explode(':', $line, 2);
$result[] = [trim($tmp[0]), trim($tmp[1] ?? '')];
}
whois の問い合わせはとてもシンプルで、サーバーのポート43番に接続し、ドメイン名を1行送るだけです。fsockopen() で接続し、fwrite() で送り、fread() で返事を読みます。
function whois_lite_query(string $query, string $server, int $timeout = 10): string
{
if ($server === '') {
return '';
}
$fp = @fsockopen($server, 43, $errno, $errstr, $timeout);
if (!$fp) {
return '';
}
stream_set_timeout($fp, $timeout);
fwrite($fp, $query . "\r\n");
$data = '';
while (!feof($fp)) {
$chunk = fread($fp, 8192);
if ($chunk === false) {
break;
}
$data .= $chunk;
$info = stream_get_meta_data($fp);
if ($info['timed_out']) {
break;
}
}
fclose($fp);
// JPRS などは ISO-2022-JP(JIS)で返ってくるので UTF-8 に揃える
return mb_convert_encoding($data, 'UTF-8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-win');
}
日本のドメイン(.jp)の whois 結果は、「JIS」という古い文字コードで返ってきます。そのまま表示すると文字化けするので、最後に mb_convert_encoding() で UTF-8 に変換しています。
PHP 8.3 で書き直したポイント
- 旧版は PEAR というライブラリの
Net_Whoisを使っていました。これは「クラス名と同じ名前の関数をコンストラクタにする」という PHP 4 時代の書き方で、PHP 8 では動きません。ソケットで直接問い合わせる短い関数(whois_lite.php)に置き換えました。 - 一致する whois サーバーがないとき、空の変数を
count()に渡してエラーで止まっていたので、最初に$server = '';で初期化するようにしました。 - 出力する値はすべて
h()でエスケープし、ほかのサイトの内容がそのまま HTML として動かないようにしました。