web-dev-qa-db-fra.com

Obtenir le nom de domaine (pas le sous-domaine) en php

J'ai une URL qui peut être l'un des formats suivants:

http://example.com
https://example.com
http://example.com/foo
http://example.com/foo/bar
www.example.com
example.com
foo.example.com
www.foo.example.com
foo.bar.example.com
http://foo.bar.example.com/foo/bar
example.net/foo/bar

Essentiellement, je dois pouvoir faire correspondre n'importe quelle URL normale. Comment puis-je extraire example.com (ou .net, quel que soit le tld qui se trouve. J'ai besoin de cela pour fonctionner avec n'importe quel TLD.) De tous ceux-ci via une seule expression régulière? 

27
Cyclone

Eh bien, vous pouvez utiliser parse_url pour obtenir l'hôte:

$info = parse_url($url);
$Host = $info['Host'];

Ensuite, vous pouvez faire des choses fantaisistes pour n’obtenir que le TLD et l’hôte.

$Host_names = explode(".", $Host);
$bottom_Host_name = $Host_names[count($Host_names)-2] . "." . $Host_names[count($Host_names)-1];

Pas très élégant, mais devrait fonctionner.


Si vous voulez une explication, la voici:

Tout d’abord, nous saisissons tout entre le schéma (http://, etc.), en utilisant les capacités de parse_url pour ... ainsi .... analyser les URL. :)

Ensuite, nous prenons le nom d'hôte et le séparons dans un tableau en fonction de l'endroit où les périodes se situent. test.world.hello.myname deviendrait:

array("test", "world", "hello", "myname");

Après cela, nous prenons le nombre d'éléments dans le tableau (4).

Ensuite, on lui soustrait 2 pour obtenir l'avant-dernière chaîne (le nom d'hôte ou example dans votre exemple)

Ensuite, on lui soustrait 1 pour obtenir la dernière chaîne (car les clés du tableau commencent à 0), également appelé TLD

Ensuite, nous combinons ces deux parties avec un point et vous avez votre nom d’hôte de base.

38
Tyler Carter

Ma solution dans https://Gist.github.com/pocesar/5366899

et les tests sont ici http://codepad.viper-7.com/GAh1tP

Cela fonctionne avec n'importe quel TLD et avec des modèles de sous-domaines hideux (jusqu'à 3 sous-domaines).

Un test est inclus avec de nombreux noms de domaine.

Ne collera pas la fonction ici à cause de l'indentation bizarre du code dans StackOverflow (aurait pu isoler des blocs de code comme github)

12
pocesar

Il n'est pas possible d'obtenir le nom de domaine sans utiliser une liste de TLD avec laquelle comparer, car ils existent dans de nombreux cas avec complètement la même structure et la même longueur:

  1. www.db.de (sous-domaine) versus bbc.co.uk (domaine)
  2. big.uk.com (SLD) contre www.uk.com (TLD)

La liste des suffixes publics de Mozilla devrait être la meilleure option car elle est utilisée par tous les navigateurs major :
https://publicsuffix.org/list/public_suffix_list.dat

N'hésitez pas à utiliser ma fonction:

function tld_list($cache_dir=null) {
    // we use "/tmp" if $cache_dir is not set
    $cache_dir = isset($cache_dir) ? $cache_dir : sys_get_temp_dir();
    $lock_dir = $cache_dir . '/public_suffix_list_lock/';
    $list_dir = $cache_dir . '/public_suffix_list/';
    // refresh list all 30 days
    if (file_exists($list_dir) && @filemtime($list_dir) + 2592000 > time()) {
        return $list_dir;
    }
    // use exclusive lock to avoid race conditions
    if (!file_exists($lock_dir) && @mkdir($lock_dir)) {
        // read from source
        $list = @fopen('https://publicsuffix.org/list/public_suffix_list.dat', 'r');
        if ($list) {
            // the list is older than 30 days so delete everything first
            if (file_exists($list_dir)) {
                foreach (glob($list_dir . '*') as $filename) {
                    unlink($filename);
                }
                rmdir($list_dir);
            }
            // now set list directory with new timestamp
            mkdir($list_dir);
            // read line-by-line to avoid high memory usage
            while ($line = fgets($list)) {
                // skip comments and empty lines
                if ($line[0] == '/' || !$line) {
                    continue;
                }
                // remove wildcard
                if ($line[0] . $line[1] == '*.') {
                    $line = substr($line, 2);
                }
                // remove exclamation mark
                if ($line[0] == '!') {
                    $line = substr($line, 1);
                }
                // reverse TLD and remove linebreak
                $line = implode('.', array_reverse(explode('.', (trim($line)))));
                // we split the TLD list to reduce memory usage
                touch($list_dir . $line);
            }
            fclose($list);
        }
        @rmdir($lock_dir);
    }
    // repair locks (should never happen)
    if (file_exists($lock_dir) && mt_Rand(0, 100) == 0 && @filemtime($lock_dir) + 86400 < time()) {
        @rmdir($lock_dir);
    }
    return $list_dir;
}
function get_domain($url=null) {
    // obtain location of public suffix list
    $tld_dir = tld_list();
    // no url = our own Host
    $url = isset($url) ? $url : $_SERVER['SERVER_NAME'];
    // add missing scheme      ftp://            http:// ftps://   https://
    $url = !isset($url[5]) || ($url[3] != ':' && $url[4] != ':' && $url[5] != ':') ? 'http://' . $url : $url;
    // remove "/path/file.html", "/:80", etc.
    $url = parse_url($url, PHP_URL_Host);
    // replace absolute domain name by relative (http://www.dns-sd.org/TrailingDotsInDomainNames.html)
    $url = trim($url, '.');
    // check if TLD exists
    $url = explode('.', $url);
    $parts = array_reverse($url);
    foreach ($parts as $key => $part) {
        $tld = implode('.', $parts);
        if (file_exists($tld_dir . $tld)) {
            return !$key ? '' : implode('.', array_slice($url, $key - 1));
        }
        // remove last part
        array_pop($parts);
    }
    return '';
}

Qu'est-ce que cela rend spécial:

  • il accepte toutes les entrées telles que les URL, les noms d'hôte ou les domaines avec ou sans schéma
  • la liste est téléchargée ligne par ligne pour éviter une utilisation intensive de la mémoire
  • il crée un nouveau fichier par TLD dans un dossier de cache, de sorte que get_domain() n'a besoin de vérifier que file_exists() s'il existe, il n'a donc pas besoin d'inclure une énorme base de données dans chaque requête comme le fait TLDExtract .
  • la liste sera automatiquement mise à jour tous les 30 jours

Tester:

$urls = array(
    'http://www.example.com',// example.com
    'http://subdomain.example.com',// example.com
    'http://www.example.uk.com',// example.uk.com
    'http://www.example.co.uk',// example.co.uk
    'http://www.example.com.ac',// example.com.ac
    'http://example.com.ac',// example.com.ac
    'http://www.example.accident-prevention.aero',// example.accident-prevention.aero
    'http://www.example.sub.ar',// sub.ar
    'http://www.congresodelalengua3.ar',// congresodelalengua3.ar
    'http://congresodelalengua3.ar',// congresodelalengua3.ar
    'http://www.example.pvt.k12.ma.us',// example.pvt.k12.ma.us
    'http://www.example.lib.wy.us',// example.lib.wy.us
    'com',// empty
    '.com',// empty
    'http://big.uk.com',// big.uk.com
    'uk.com',// empty
    'www.uk.com',// www.uk.com
    '.uk.com',// empty
    'stackoverflow.com',// stackoverflow.com
    '.foobarfoo',// empty
    '',// empty
    false,// empty
    ' ',// empty
    1,// empty
    'a',// empty    
);

Version récente avec explications (allemand):
http://www.programmierer-forum.de/domainnamen-ermitteln-t244185.htm

6
mgutt
$onlyHostName = implode('.', array_slice(explode('.', parse_url($link, PHP_URL_Host)), -2));
5
user2116044

Je pense que la meilleure façon de gérer ce problème est:

$second_level_domains_regex = '/\.asn\.au$|\.com\.au$|\.net\.au$|\.id\.au$|\.org\.au$|\.edu\.au$|\.gov\.au$|\.csiro\.au$|\.act\.au$|\.nsw\.au$|\.nt\.au$|\.qld\.au$|\.sa\.au$|\.tas\.au$|\.vic\.au$|\.wa\.au$|\.co\.at$|\.or\.at$|\.priv\.at$|\.ac\.at$|\.avocat\.fr$|\.aeroport\.fr$|\.veterinaire\.fr$|\.co\.hu$|\.film\.hu$|\.lakas\.hu$|\.ingatlan\.hu$|\.sport\.hu$|\.hotel\.hu$|\.ac\.nz$|\.co\.nz$|\.geek\.nz$|\.gen\.nz$|\.kiwi\.nz$|\.maori\.nz$|\.net\.nz$|\.org\.nz$|\.school\.nz$|\.cri\.nz$|\.govt\.nz$|\.health\.nz$|\.iwi\.nz$|\.mil\.nz$|\.parliament\.nz$|\.ac\.za$|\.gov\.za$|\.law\.za$|\.mil\.za$|\.nom\.za$|\.school\.za$|\.net\.za$|\.co\.uk$|\.org\.uk$|\.me\.uk$|\.ltd\.uk$|\.plc\.uk$|\.net\.uk$|\.sch\.uk$|\.ac\.uk$|\.gov\.uk$|\.mod\.uk$|\.mil\.uk$|\.nhs\.uk$|\.police\.uk$/';
$domain = $_SERVER['HTTP_Host'];
$domain = explode('.', $domain);
$domain = array_reverse($domain);
if (preg_match($second_level_domains_regex, $_SERVER['HTTP_Host']) {
    $domain = "$domain[2].$domain[1].$domain[0]";
} else {
    $domain = "$domain[1].$domain[0]";
}
5
mmeyer2k

Je recommande d'utiliser TLDExtract library pour toutes les opérations avec un nom de domaine.

4
happy_marmoset

Il existe deux manières d'extraire un sous-domaine d'un hôte:

  1. La première méthode la plus précise consiste à utiliser une base de données de tld (comme public_suffix_list.dat ) et à faire correspondre le domaine à. C'est un peu lourd dans certains cas. Il existe certaines classes PHP pour l'utiliser comme php-domain-parser et TLDExtract .

  2. La seconde façon n’est pas aussi précise que la première, mais elle est très rapide et peut donner la bonne réponse dans de nombreux cas, j’ai écrit cette fonction pour cela:

    function get_domaininfo($url) {
        // regex can be replaced with parse_url
        preg_match("/^(https|http|ftp):\/\/(.*?)\//", "$url/" , $matches);
        $parts = explode(".", $matches[2]);
        $tld = array_pop($parts);
        $Host = array_pop($parts);
        if ( strlen($tld) == 2 && strlen($Host) <= 3 ) {
            $tld = "$Host.$tld";
            $Host = array_pop($parts);
        }
    
        return array(
            'protocol' => $matches[1],
            'subdomain' => implode(".", $parts),
            'domain' => "$Host.$tld",
            'Host'=>$Host,'tld'=>$tld
        );
    }
    

    Exemple:

    print_r(get_domaininfo('http://mysubdomain.domain.co.uk/index.php'));
    

    Résultats:

    Array
    (
        [protocol] => https
        [subdomain] => mysubdomain
        [domain] => domain.co.uk
        [Host] => domain
        [tld] => co.uk
    )
    
4
Ehsan Chavoshi

Voici une fonction que j'ai écrite pour récupérer le domaine sans sous-domaine (s), que le domaine utilise un ccTLD ou un nouveau style long TLD, etc. . Il peut être beaucoup plus court en utilisant l'opérateur ternaire et l'imbrication, mais je l'ai développé pour la lisibilité.

// Per Wikipedia: "All ASCII ccTLD identifiers are two letters long, 
// and all two-letter top-level domains are ccTLDs."

function topDomainFromURL($url) {
  $url_parts = parse_url($url);
  $domain_parts = explode('.', $url_parts['Host']);
  if (strlen(end($domain_parts)) == 2 ) { 
    // ccTLD here, get last three parts
    $top_domain_parts = array_slice($domain_parts, -3);
  } else {
    $top_domain_parts = array_slice($domain_parts, -2);
  }
  $top_domain = implode('.', $top_domain_parts);
  return $top_domain;
}
3
Greg Z

En voici un qui fonctionne pour tous les domaines, y compris ceux avec des domaines de second niveau comme "co.uk" 

function strip_subdomains($url){

    # credits to gavingmiller for maintaining this list
    $second_level_domains = file_get_contents("https://raw.githubusercontent.com/gavingmiller/second-level-domains/master/SLDs.csv");

    # presume sld first ...
    $possible_sld = implode('.', array_slice(explode('.', $url), -2));

    # and then verify it
    if (strpos($second_level_domains, $possible_sld)){
        return  implode('.', array_slice(explode('.', $url), -3));
    } else {
        return  implode('.', array_slice(explode('.', $url), -2));
    }
}

On dirait qu'il y a une question en double ici: delete-subdomain-from-url-string-if-subdomain-is-found

1
BvS

J'ai eu des problèmes avec la solution fournie par pocesar . Lorsque j'utiliserais par exemple subdomain.domain.nl, il ne renverrait pas domain.nl. Au lieu de cela, il renverrait subdomain.domain.nl Un autre problème était que domain.com.br renverrait com.br

Je ne suis pas sûr mais j'ai corrigé ces problèmes avec le code suivant (j'espère que cela aidera quelqu'un, si c'est le cas, je suis un homme heureux):

function get_domain($domain, $debug = false){
    $original = $domain = strtolower($domain);
    if (filter_var($domain, FILTER_VALIDATE_IP)) {
        return $domain;
    }
    $debug ? print('<strong style="color:green">&raquo;</strong> Parsing: '.$original) : false;
    $arr = array_slice(array_filter(explode('.', $domain, 4), function($value){
        return $value !== 'www';
    }), 0); //rebuild array indexes
    if (count($arr) > 2){
        $count = count($arr);
        $_sub = explode('.', $count === 4 ? $arr[3] : $arr[2]);
        $debug ? print(" (parts count: {$count})") : false;
        if (count($_sub) === 2){ // two level TLD
            $removed = array_shift($arr);
            if ($count === 4){ // got a subdomain acting as a domain
                $removed = array_shift($arr);
            }
            $debug ? print("<br>\n" . '[*] Two level TLD: <strong>' . join('.', $_sub) . '</strong> ') : false;
        }elseif (count($_sub) === 1){ // one level TLD
            $removed = array_shift($arr); //remove the subdomain
            if (strlen($arr[0]) === 2 && $count === 3){ // TLD domain must be 2 letters
                array_unshift($arr, $removed);
            }elseif(strlen($arr[0]) === 3 && $count === 3){
                array_unshift($arr, $removed);
            }else{
                // non country TLD according to IANA
                $tlds = array(
                    'aero',
                    'arpa',
                    'asia',
                    'biz',
                    'cat',
                    'com',
                    'coop',
                    'edu',
                    'gov',
                    'info',
                    'jobs',
                    'mil',
                    'mobi',
                    'museum',
                    'name',
                    'net',
                    'org',
                    'post',
                    'pro',
                    'tel',
                    'travel',
                    'xxx',
                );
                if (count($arr) > 2 && in_array($_sub[0], $tlds) !== false){ //special TLD don't have a country
                    array_shift($arr);
                }
            }
            $debug ? print("<br>\n" .'[*] One level TLD: <strong>'.join('.', $_sub).'</strong> ') : false;
        }else{ // more than 3 levels, something is wrong
            for ($i = count($_sub); $i > 1; $i--){
                $removed = array_shift($arr);
            }
            $debug ? print("<br>\n" . '[*] Three level TLD: <strong>' . join('.', $_sub) . '</strong> ') : false;
        }
    }elseif (count($arr) === 2){
        $arr0 = array_shift($arr);
        if (strpos(join('.', $arr), '.') === false && in_array($arr[0], array('localhost','test','invalid')) === false){ // not a reserved domain
            $debug ? print("<br>\n" .'Seems invalid domain: <strong>'.join('.', $arr).'</strong> re-adding: <strong>'.$arr0.'</strong> ') : false;
            // seems invalid domain, restore it
            array_unshift($arr, $arr0);
        }
    }
    $debug ? print("<br>\n".'<strong style="color:gray">&laquo;</strong> Done parsing: <span style="color:red">' . $original . '</span> as <span style="color:blue">'. join('.', $arr) ."</span><br>\n") : false;
    return join('.', $arr);
}
1
Rens Tillmann

Essayez simplement ceci:

   preg_match('/(www.)?([^.]+\.[^.]+)$/', $yourHost, $matches);

   echo "domain name is: {$matches[0]}\n"; 

cela fonctionne pour la majorité des domaines.