<?php
/**
 * The Net_PublicSuffix class
 *
 * allows lookup of a registered domain 
 * PHP version 5.4 and later (may work with earlier versions)
 *
 * @category   net
 * @package    PublicSuffix
 * @author     Daniel Kahn Gillmor <dkg@fifthhorseman.net>
 * @copyright  2012 the author
 * @license    GPL-3+
 * @version    0.1
 */

class Net_PublicSuffix_Match
{
  var $rule;
  var $match;
  function __construct($rule, $match) {
    $this->rule = $rule;
    $this->match = $match;
  }
  function is_exception() {
    return $this->rule[0] == '!';
  }
};

class Net_PublicSuffix
{
  static $singleton = null;
  var $table;

  function __construct($fname='/usr/share/publicsuffix/effective_tld_names.dat') {
    $this->table = array();
    $this->table['*'] = array('.' => '*');
    $this->import_names($fname);
  }

  static function _init_singleton() {
    if (is_null(Net_PublicSuffix::$singleton))
      Net_PublicSuffix::$singleton = new Net_PublicSuffix();
  }

  static function registered_domain($name) {
    Net_PublicSuffix::_init_singleton();
    return Net_PublicSuffix::$singleton->get_registered_domain($name);
  }

  static function prevailing_suffix_rule($name) {
    Net_PublicSuffix::_init_singleton();
    return Net_PublicSuffix::$singleton->get_prevailing_public_suffix_rule($name);
  }


  // FIXME: this is horrifically slow -- takes > 1sec to read the
  // canonical public suffix list (as of 2012-08-13) on a 900Mhz Celeron :(
  function import_names($fname) {
    $handle = @fopen($fname, "r");
    if ($handle) {
      while (($buffer = fgets($handle, 4096)) !== false) {
        $buffer = preg_replace('/[[:space:]].*$/', '', $buffer);
        if (strlen($buffer) == 0 || $buffer[0] == '/') {
          // skip blank or comment lines.
        } else {
          $exception = ($buffer[0] == '!');
          if ($exception)
            $buffer = substr($buffer,1);
          $buffer = Net_PublicSuffix::_canonicalize($buffer);
            
          $labels = array_reverse(explode('.', $buffer));
          $x =& $this->table;
          foreach ($labels as $label) {
            if (!array_key_exists($label, $x)) {
              $x[$label] = array();
            }
            $x =& $x[$label];
          }
          $x['.'] = ($exception ? '!' : '').$buffer; // we store the full rule in this position
        }
      }
      if (!feof($handle)) {
        echo "Error: unexpected fgets() fail\n";
      }
      fclose($handle);
    }
  }
  static function _canonicalize($name) {
    // FIXME: should do IDNA translation via Net_IDNA2 if available
    return mb_strtolower($name);
  }

  static function _get_public_suffix_rules($name, &$tree, $matched) {
    $ret = array();
    if (array_key_exists('.', $tree)) {
      $ret[] = new Net_PublicSuffix_Match($tree['.'], $matched);
    }

    if (count($name) == 0)
      return $ret;
    
    $matched[] = $name[0];
    if (array_key_exists('*', $tree)) {
      $ret = array_merge($ret, Net_PublicSuffix::_get_public_suffix_rules(array_slice($name, 1), $tree['*'], $matched));
    } 
    if (array_key_exists($name[0], $tree)) {
      $ret = array_merge($ret, Net_PublicSuffix::_get_public_suffix_rules(array_slice($name, 1), $tree[$name[0]], $matched));
    }

    return $ret;
  }

  function get_matching_public_suffix_rules($name) {
    $name = Net_PublicSuffix::_canonicalize($name);
    $labels = array_reverse(explode('.', trim($name)));
    // if any label element is zero-length, we should return NULL for the whole string.
    if (count(array_filter($labels, function($x) { return empty($x); })))
      return array();

    return Net_PublicSuffix::_get_public_suffix_rules($labels, $this->table, array());
  }

  function get_prevailing_public_suffix_rule($name) {
    $name = Net_PublicSuffix::_canonicalize($name);
    $rules = $this->get_matching_public_suffix_rules($name);
    $myrule = null;
    $maxlen = 0;
    foreach ($rules as $rule) {
      // what if there are more than one exception rule?
      // at the moment, we just choose the first one.
      if ($rule->is_exception()) {
        $myrule =& $rule;
        break;
      }
      if (count($rule->match) > $maxlen) {
        $maxlen = $rule->match;
        $myrule =& $rule;
      }
    }
    return $myrule;
  }

  function get_registered_domain($name) {
    $name = Net_PublicSuffix::_canonicalize($name);
    $rule = $this->get_prevailing_public_suffix_rule($name);
    if (is_null($rule))
      return null;

    if ($rule->is_exception()) {
      return join('.', array_reverse($rule->match));
    }
    $labels = explode('.', trim($name));
    if (count($labels) <= count($rule->match))
      return null;
    return join('.', array_slice($labels, -1 * count($rule->match) - 1));
  }
}
/*
 * Local Variables:
 * indent-tabs-mode: nil
 * c-basic-offset: 2
 * End:
 */
