CoderCastrov logo
CoderCastrov
Парсер

Запрос информации Whois с помощью PHP

Запрос информации Whois с помощью PHP
просмотров
12 мин чтение
#Парсер

Замечательный мир Whois! Если вы не знаете, что это такое или для чего оно нужно, то, вероятно, эта публикация не для вас. Но если вы знаете, о чем я говорю, то вам будет интересно узнать, что вы можете перестать использовать сторонние сервисы для проверки этой информации, с небольшими усилиями и любовью к программированию вы можете создать свой собственный сервис! (Я пытаюсь добавить волнение к запросу информации, который, хотя и необходим, довольно обычен.)

Давайте, присоединяйтесь ко мне снова, чтобы пройти по желтой кирпичной дорожке... дорожке, полной чудес!

Что такое Whois?

Whois - это общественная служба, которая позволяет проверить, кому принадлежит домен, его статус, даты истечения срока действия, продление и другую интересующую информацию.

Вы можете использовать запрос Whois, чтобы определить, доступен ли домен для покупки, например. (Предупреждение: результаты могут отличаться, Whois обычно довольно надежен, но может давать неожиданные результаты), используйте его осторожно.

Где можно найти серверы Whois для доменных зон верхнего уровня (TLD)?

IANA (организация, отвечающая за регулирование выделения IP-адресов и корневых DNS для разрешения адресов) имеет список по следующей ссылке:

База данных корневой зоны

База данных корневой зоны содержит информацию о делегировании доменных имен верхнего уровня, включая глобальные домены верхнего уровня, такие как .com, и...

www.iana.org

Этот список поможет нам позже извлечь необходимую информацию для нашего скрипта запроса Whois на PHP.

Предупреждение: Сам по себе парсинг не является плохим, но его неправильное использование может испортить всем настроение. Всегда будьте очень ответственными и уважительными к информации, которую вы получаете с помощью этой техники.

Структура нашего решения

Наши каталоги и файлы решения будут следующими:

/whois           # корневой каталог (ваш веб-сервер должен иметь к нему доступ)
  /cache         # В этом каталоге будут храниться файлы json, содержащие адрес сервера Whois для заданного домена верхнего уровня. Эти файлы будут использоваться для сохранения результатов процесса парсинга.
  - index.html   # Этот файл будет нашей точкой входа и графическим интерфейсом для запроса информации Whois.
  - getwhois.php # Этот скрипт будет получать запрос Whois от index.html и возвращать результат.
  - Whois.php    # Наше определение класса со всеми атрибутами и методами, используемыми для запроса информации Whois.

Помните, что у вас должна быть среда разработки веб-приложений с PHP или по крайней мере возможность выполнения php-скриптов через командную консоль. На этот раз мы будем использовать PHP 8.2 на веб-сервере Apache.

Мы напишем наш код на основе трех больших блоков следующим образом:

  • Парсинг
  • Запрос/запрос Whois
  • Интерфейс и представление результатов

1. Парсинг

Давайте проведем парсинг для извлечения сервера Whois для каждого доступного типа домена (TLD) на сайте https://www.iana.org/domains/root/db. Мы будем делать это "по требованию", то есть, мы будем парсить только при отсутствии предыдущего кэш-файла для TLD, таким образом мы уменьшим трафик, будем уважать сайт, откуда получаем информацию, и сократим время ответа при запросе.

Например, мы посетим информацию о TLD ".com", URL будет https://www.iana.org/domains/root/db/com.html, будет показана общая контактная информация и внизу будет указан сервер Whois для этого типа доменов, вот так:

iana.org whois

Адрес рядом с текстом "WHOIS Server" будет интересующим нас данным для нашего парсинга.

Первым шагом будет выполнение запроса к веб-сайту с необходимой информацией и получение HTML-ответа. Мы можем сделать это с помощью нашего дорогого друга cURL вот так:

    /**      * Эта функция загружает HTML-контент с URL-адреса      *      * @param string $url URL-адрес для запроса     *      * @return string|bool HTML, полученный в ответе от веб-сайта      * если произошла ошибка, то вернется false     */
    function curlDownload(string $url): string|bool    {

        $curl = curl_init();

        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 60);
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "GET");
        curl_setopt($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36");
        curl_setopt($curl, CURLOPT_URL, $url);

        $html = curl_exec($curl);
        curl_close($curl);

        return $html;
    }

В заголовке User-Agent мы устанавливаем значение, которое имитирует посещение с настольного браузера.

Теперь, когда мы получили содержимое сайта, необходимо извлечь из полученного HTML-кода адрес сервера Whois. Это можно сделать с помощью очень полезного инструмента, инструмента, который вызывает панику или огромную ненависть в глубине тех, кто осмеливается смотреть ему прямо в глаза - регулярных выражений.

Как бы вы ни относились к этому, всегда старайтесь помнить мудрые слова "Macho man" Рэнди Сэвиджа:

You may not like it, but accept it! — “Macho Man” Randy Savage wisdom

Мы собираемся рассмотреть содержимое и создать регулярное выражение, имейте в виду, что мы не будем подробно рассматривать, как оно работает, мы рассмотрим то, что важно, то, что оно работает для нашей текущей цели.

Давайте посмотрим на исходный код страницы, где находится имя сервера Whois. Раздел, который нас интересует, имеет следующую структуру:

    <p>
    
        <b>URL for registration services:</b> <a href="http://www.verisigninc.com">http://www.verisigninc.com</a><br/>
    
    
        <b>WHOIS Server:</b> whois.verisign-grs.com
    
    </p>

Теперь создадим регулярное выражение, чтобы извлечь только текст "whois.verisign-grs.com", оно будет выглядеть примерно так:

$this->regexWhoisServer = '#(?s)(?<=\<b\>WHOIS Server\:\<\/b\>)(.+?)(?=\<\/p\>)#';

Это выражение ищет текст между текстовым шаблоном "<b>WHOIS Server:</b>" и первым совпадением с текстом "</p>", используя PHP, мы можем захватить возвращенное значение и сохранить его в переменной для использования позже в нашем запросе Whois.

Наш примерный код для понимания этой концепции будет выглядеть так:

// Массив, который будет использоваться для хранения результатов регулярного выражения.
$matchesWhois = array();

// Теперь мы используем нашу функцию загрузки HTML, созданную ранее.
$html = curlDownload("https://www.iana.org/domains/root/db/com.html");
// Используем регулярное выражение для извлечения сервера Whois из HTML.
$resWhois = preg_match("#(?s)(?<=\<b\>WHOIS Server\:\<\/b\>)(.+?)(?=\<\/p\>)#", $html, $matchesWhois, PREG_OFFSET_CAPTURE);

// Теперь удаляем пробелы из текста результата, хранящегося в элементе [0][0] массива.
$matchesWhois[0][0] = trim($matchesWhois[0][0]);

// Наконец, присваиваем имя сервера Whois переменной.
$whoisServer = $matchesWhois[0][0];
// Открываем соединение с сервером whois на порту 43 с ограничением времени ожидания в 20 секунд.
$whoisSock = @fsockopen("whois.verisign-grs.com", 43, $errno, $errstr, 20);
// Эта переменная будет использоваться для хранения результата запроса whois.
$whoisQueryResult = "";

// Отправляем доменное имя, завершая его символом новой строки.
fputs($whoisSock, "mytestdomain.com" . "\r\n");

$content = "";

// Читаем ответ сервера.
while (!feof($whoisSock)) {
    $content .= fgets($whoisSock);
}

// Закрываем сокет.
fclose($whoisSock);

// Преобразуем строку в массив (один элемент для каждой строки в строке)
$arrResponseLines = explode("\n", $content);

foreach ($arrResponseLines as $line) {

    $line = trim($line);

    // Игнорируем пустую строку или строку, начинающуюся с "#" или "%"
    if (($line != '') && (!str_starts_with($line, '#')) && (!str_starts_with($line, '%'))) {
        // Добавляем строку к результату запроса whois.
        $whoisQueryResult .= $line . PHP_EOL;
    }
}

// Показываем результат.
echo $whoisQueryResult;

3. Интерфейс и представление результатов

Для запроса и отображения результатов мы создадим класс Whois, который интегрирует ранее показанные концепции, файл для получения запросов и веб-интерфейс для отображения результатов.

Начнем с нашего класса, назовем его Whois.php, и он будет иметь следующую структуру:

<?php

class Whois{
    //Совпадения регулярных выражений
    private array $matchesWhois;
    //Путь к файлам кэша
    private string $_CACHE_PATH;
    //Регулярное выражение, используемое для обнаружения сервера Whois при сканировании URL TLD
    private string $regexWhoisServer;
    //Расширение файлов кэша (.json)
    private string $_FILE_EXT;
    //Флаг, True = использование файла кэша, False = полученный результат
    private bool $usingCache;
    //Доменное имя для запроса информации Whois.
    private string $domain;
    //TLD домена
    private string $tld;
    //Имя файла кэша
    private string $cacheFile;
    //URL, используемый для сканирования сервера Whois, который будет использоваться.
    private string $urlWhoisDB;
    //Массив, который будет содержать ответ и ошибки, возникшие во время запроса whois
    private array $response;
    //Массив, содержащий адрес сервера whois
    private array $whoisInfo;
    //Тег, который будет заменен на извлеченный из доменного имени TLD.
    private string $tldUrlTag;
    //Порт Whois, по умолчанию 43
    private int $_WHOIS_PORT;
    //Тайм-аут запроса Whois в секундах, по умолчанию 20
    private int $_WHOIS_TIMEOUT;
    //User Agent, который будет использоваться для сканирования информации о сервере Whois.
    private string $_CURL_USER_AGENT;


    /**     * Конструктор класса     */
    public function __construct()    {
        $this->matchesWhois = array();
        $this->whoisInfo = array();
        $this->_CACHE_PATH = __DIR__ . "/cache/";
        $this->regexWhoisServer = '#(?s)(?<=\<b\>WHOIS Server\:\<\/b\>)(.+?)(?=\<\/p\>)#';
        $this->_FILE_EXT = ".json";
        $this->usingCache = false;
        $this->domain = "";
        $this->tld = "";
        $this->cacheFile = "";
        $this->tldUrlTag = "[TLD]";
        $this->urlWhoisDB = "https://www.iana.org/domains/root/db/{$this->tldUrlTag}.html";
        $this->response = array();
        $this->_WHOIS_PORT = 43;
        $this->_WHOIS_TIMEOUT = 20;
        $this->_CURL_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36";
    }


    /**     * Проверка домена     *      * @param string $domain доменное имя для проверки, например "google.com"     *      * @return bool     */
    public function isDomain(string $domain): bool    {
        return filter_var($domain, FILTER_VALIDATE_DOMAIN);
    }


    /**     * Извлекает TLD из доменного имени     *      * @param mixed $domain доменное имя     *      * @return string     */
    private function extractTld($domain): string    {
        $arrDomain = explode(".", $domain);

        return end($arrDomain);
    }

    /**     * Устанавливает имя файла кэша для заданного TLD, также проверяет, существует ли файл и загружает его содержимое     *      * @param mixed $tld домен (TLD), например "com", "net", "org".      *      * @return void     */
    private function setCacheFileName($tld): void    {
        $this->cacheFile = $this->_CACHE_PATH . $tld . $this->_FILE_EXT;

        if (file_exists($this->cacheFile)) {

            $tmpCache = file_get_contents($this->cacheFile);
            $this->whoisInfo = json_decode($tmpCache, true);

            $this->usingCache = true;
        }
    }

    /**     * Эта функция может использоваться для проверки наличия ошибок в процессе     *      * @return bool true = есть ошибки, false = нет ошибок     */
    public function hasErrors(): bool    {
        return isset($this->response["errors"]);
    }

    /**     * Возвращает полученный ответ, включая ошибки (если есть).     * @param bool $json Позволяет выбрать формат ответа, false = массив, true = json     *      * @return array|string     */
    public function getResponse(bool $json = false): array|string    {
        return ($json) ? json_encode($this->response) : $this->response;
    }

    /**     * Эта функция загружает и возвращает HTML, полученный по указанному URL     *      * @param string $url URL адрес     *      * @return string|bool строка, содержащая полученный HTML, если есть ошибка, возвращает false.     */
    private function curlDownload(string $url): string|bool    {

        $curl = curl_init();

        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 60);
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "GET");
        curl_setopt($curl, CURLOPT_USERAGENT, $this->_CURL_USER_AGENT);
        curl_setopt($curl, CURLOPT_URL, $url);

        $html = curl_exec($curl);
        curl_close($curl);

        return $html;
    }

    /**     * Точка входа для запроса Whois.     *      * @param string $domain доменное имя, для которого нужно проверить whois     *      * @return void     */
    public function getWhoisServerDetails(string $domain): void    {

        $this->domain = $domain;
        $this->tld = $this->extractTld($domain);
        $this->setCacheFileName($this->tld);

        if (!$this->usingCache) {

            $urlWhoisDB = str_replace($this->tldUrlTag, $this->tld, $this->urlWhoisDB);
            $html = $this->curlDownload($urlWhoisDB);

            $resWhois = preg_match($this->regexWhoisServer, $html, $this->matchesWhois, PREG_OFFSET_CAPTURE);

            if ($resWhois != 1) {

                $this->response["errors"][] = array(
                    "error" => "TLD '{$this->tld}' не найден!",
                    "domain" => $domain
                );

                return;
            }

            $this->matchesWhois[0][0] = trim($this->matchesWhois[0][0]);
            $this->whoisInfo["whois"] = $this->matchesWhois[0][0];

            file_put_contents($this->_CACHE_PATH . $this->tld . $this->_FILE_EXT, json_encode($this->whoisInfo, JSON_UNESCAPED_UNICODE));
        }

        if (!isset($this->whoisInfo["whois"])) {

            $this->response["errors"][] = array(
                "error" => "WhoIs Server для TLD {$this->tld} не найден!.",
                "domain" => $domain
            );

            return;
        }

        $whoisSock = @fsockopen($this->whoisInfo["whois"], $this->_WHOIS_PORT, $errno, $errstr, $this->_WHOIS_TIMEOUT);
        $whoisQueryResult = "";

        if (!$whoisSock) {

            $this->response["errors"][] = array(
                "error" => "{$errstr} ({$errno})",
                "domain" => $domain
            );

            return;
        }

        fputs($whoisSock, $this->domain . "\r\n");

        $content = "";

        while (!feof($whoisSock)) {
            $content .= fgets($whoisSock);
        }

        fclose($whoisSock);

        if ((strpos(strtolower($content), "error") === false) && (strpos(strtolower($content), "not allocated") === false)) {

            $arrResponseLines = explode("\n", $content);

            foreach ($arrResponseLines as $line) {

                $line = trim($line);

                if (($line != '') && (!str_starts_with($line, '#')) && (!str_starts_with($line, '%'))) {
                    $whoisQueryResult .= $line . PHP_EOL;
                }
            }
        }

        $this->response["whoisinfo"] = $whoisQueryResult;
    }
}

В этом классе у нас есть все основные функции для сканирования, извлечения имени сервера whois, обработки доменного имени и окончательного запроса whois. Обратите внимание, что были добавлены проверка ошибок и обработка ошибок, а также функции для автоматизации сканирования типа домена (TLD) и стратегии кэширования, чтобы избежать лишних запросов к iana.org.

Теперь создадим наш файл, который будет получать запросы на запросы, назовем его getwhois.php, и он будет иметь следующее содержимое:

<?php

//Включаем определение класса.
require("Whois.php");

//Декодируем полученные параметры.
$paramsFetch = json_decode(
    file_get_contents("php://input"),
    true
);

//Создаем наш объект whois
$whoisObj = new Whois();
//Запрос информации whois
$whoisObj->getWhoisServerDetails($paramsFetch["domain"]);

//Возвращаем ответ в формате JSON
echo $whoisObj->getResponse(true);
exit;

Это довольно просто, он включает наш класс Whois.php, захватывает параметры, полученные из HTML-формы, которая использует функцию fetch JavaScript для отправки запроса с доменным именем, создает экземпляр нашего класса и выполняет запрос информации whois, затем возвращает результат в формате JSON и завершает выполнение.

Теперь перейдем к файлу index.html, это будет наш графический интерфейс и точка доступа к запросу и просмотру результатов. Я использую Bulma CSS для элементов управления и стилизации HTML, это довольно просто, не навязчиво и позволяет быстро создавать результаты. Файл будет выглядеть следующим образом:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Whois</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
    <script type="module">        window.addEventListener('load', (event) => {            //Обработчик событий для кнопки поиска.            document.querySelector(".search").addEventListener('click', (event) => {                //Показываем, что запрос выполняется                event.currentTarget.classList.add('is-loading');                //отключаем кнопку                event.currentTarget.disabled = true;                //Скрываем секции с результатами.                document.querySelector(".result").parentElement.classList.add("is-hidden");                document.querySelector(".error").parentElement.classList.add("is-hidden");                //Подготавливаем данные                const payload = JSON.stringify({                    "domain": document.querySelector(".domain").value                });                                //Отправляем запрос на getwhois.php                fetch('getwhois.php', {                    method: 'POST',                    headers: {                        'Content-Type': 'application/json',                    },                    body: payload,                })                    .then(response => response.json())                    .then(data => {                        //Обрабатываем ответ.                        if (data.errors != undefined) {                            document.querySelector(".error").parentElement.classList.remove("is-hidden");                            for (const item in data.errors) {                                document.querySelector(".error").innerText = data.errors[item].error + "\n";                            }                        } else {                            document.querySelector(".result").parentElement.classList.remove("is-hidden");                            document.querySelector(".result").innerText = data.whoisinfo;                        }                    })                    .catch((error) => {                        document.querySelector(".error").parentElement.classList.remove("is-hidden");                        document.querySelector(".error").innerText = error;                        console.error('Error:', error);                    }).finally(() => {                        document.querySelector(".search").classList.remove('is-loading');                        document.querySelector(".search").disabled = false;                    });            });        });    </script>
</head>

<body>
    <section class="section">
        <div class="container">
            <div class="columns">
                <div class="column">
                    <div class="columns">
                        <div class="column"></div>
                        <div class="column has-text-centered">
                            <h1 class="title">
                                Поиск WhoIs
                            </h1>
                        </div>
                        <div class="column"></div>
                    </div>
                    <div class="columns">
                        <div class="column"></div>
                        <div class="column has-text-centered">
                            <div class="field is-grouped is-grouped-centered">
                                <div class="control">
                                    <input class="input domain" type="text" placeholder="Домен">
                                </div>
                                <div class="control">
                                    <button class="button is-info search">
                                        Поиск
                                    </button>
                                </div>
                            </div>
                        </div>
                        <div class="column"></div>
                    </div>
                </div>
            </div>
            <div class="columns box is-hidden">
                <div class="column result"></div>
            </div>
            <div class="columns box is-hidden">
                <div class="column notification is-danger error has-text-centered">
                </div>
            </div>
        </div>
    </section>
</body>

</html>

Тестирование

Для выполнения тестов достаточно указать веб-браузеру путь к расположению наших скриптов, в моем случае http://localhost/whois, откроется поле для ввода доменного имени и кнопка "Поиск" для запроса информации Whois. Вы можете попробовать популярный домен, например "google.com", результат будет выглядеть так:

После успешного запроса Whois вы заметите, что в каталоге /cache будет создан файл с расширением TLD, например "com.json", и он будет содержать имя соответствующего сервера Whois. Это позволит нам избежать повторного парсинга.

И вот все, никогда не забывайте, что тот, кто смел, действительно свободен, пейте достаточно воды, занимайтесь физическими упражнениями и получайте достаточно сна.

На Winkhosting.co мы предлагаем гораздо больше, чем просто хостинг. Если вам нужен общий хостинг, домены или серверы, загляните к нам.