1: <?php
2: namespace Geekwright\RegDom;
3:
4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
15: class RegisteredDomain
16: {
17: protected $tree;
18: protected $psl;
19:
20: 21: 22: 23: 24:
25: public function __construct(PublicSuffixList $psl = null)
26: {
27: if (null === $psl) {
28: $psl = new PublicSuffixList();
29: }
30: $this->psl = $psl;
31: }
32:
33: 34: 35: 36: 37: 38: 39: 40:
41: protected function normalizeHost($url)
42: {
43: $host = (false!==strpos($url, '/')) ? parse_url($url, PHP_URL_HOST) : $url;
44: $parts = explode('.', $host);
45: $utf8Host = '';
46: foreach ($parts as $part) {
47: $utf8Host = $utf8Host . (($utf8Host === '') ? '' : '.') . $this->convertPunycode($part);
48: }
49:
50: return mb_strtolower($utf8Host);
51: }
52:
53: 54: 55: 56: 57: 58: 59:
60: protected function convertPunycode($part)
61: {
62: if (strpos($part,'xn--')===0) {
63: if (function_exists('idn_to_utf8')) {
64: $part = idn_to_utf8($part);
65: } else {
66: $part = $this->decodePunycode($part);
67: }
68: }
69: return $part;
70: }
71:
72: 73: 74: 75: 76: 77: 78: 79:
80: protected function decodePunycode($encoded)
81: {
82: $prefix = 'xn--';
83: $safe_char = 0xFFFC;
84: $base = 36;
85: $tmin = 1;
86: $tmax = 26;
87: $skew = 38;
88: $damp = 700;
89:
90: if (strpos($encoded, $prefix) !== 0 || strlen(trim(str_replace($prefix, '', $encoded))) == 0) {
91: return $encoded;
92: }
93:
94: $is_first = true;
95: $bias = 72;
96: $idx = 0;
97: $char = 0x80;
98: $decoded = array();
99: $output = '';
100:
101: $delim_pos = strrpos($encoded, '-');
102: if ($delim_pos > strlen($prefix)) {
103: for ($k = strlen($prefix); $k < $delim_pos; ++$k) {
104: $decoded[] = ord($encoded{$k});
105: }
106: }
107: $deco_len = count($decoded);
108: $enco_len = strlen($encoded);
109:
110: for ($enco_idx = $delim_pos ? ($delim_pos + 1) : 0; $enco_idx < $enco_len; ++$deco_len) {
111: for ($old_idx = $idx, $w = 1, $k = $base; 1; $k += $base) {
112: $cp = ord($encoded{$enco_idx++});
113: $digit = ($cp - 48 < 10) ? $cp - 22 : (($cp - 65 < 26) ? $cp - 65 : (($cp - 97 < 26) ? $cp - 97 : $base));
114: $idx += $digit * $w;
115: $t = ($k <= $bias) ? $tmin : (($k >= $bias + $tmax) ? $tmax : ($k - $bias));
116: if ($digit < $t) {
117: break;
118: }
119: $w = (int)($w * ($base - $t));
120: }
121: $delta = $idx - $old_idx;
122: $delta = intval($is_first ? ($delta / $damp) : ($delta / 2));
123: $delta += intval($delta / ($deco_len + 1));
124: for ($k = 0; $delta > (($base - $tmin) * $tmax) / 2; $k += $base) {
125: $delta = intval($delta / ($base - $tmin));
126: }
127: $bias = intval($k + ($base - $tmin + 1) * $delta / ($delta + $skew));
128: $is_first = false;
129: $char += (int)($idx / ($deco_len + 1));
130: $idx %= ($deco_len + 1);
131: if ($deco_len > 0) {
132: for ($i = $deco_len; $i > $idx; $i--) {
133: $decoded[$i] = $decoded[($i - 1)];
134: }
135: }
136: $decoded[$idx++] = $char;
137: }
138:
139: foreach ($decoded as $k => $v) {
140: if ($v < 128) {
141: $output .= chr($v);
142: }
143: elseif ($v < (1 << 11)) {
144: $output .= chr(192 + ($v >> 6)) . chr(128 + ($v & 63));
145: }
146: elseif ($v < (1 << 16)) {
147: $output .= chr(224 + ($v >> 12)) . chr(128 + (($v >> 6) & 63)) . chr(128 + ($v & 63));
148: }
149: elseif ($v < (1 << 21)) {
150: $output .= chr(240 + ($v >> 18)) . chr(128 + (($v >> 12) & 63)) . chr(128 + (($v >> 6) & 63)) . chr(128 + ($v & 63));
151: }
152: else {
153: $output .= $safe_char;
154: }
155: }
156: return $output;
157: }
158:
159: 160: 161: 162: 163: 164: 165:
166: public function getRegisteredDomain($host)
167: {
168: $this->tree = $this->psl->getTree();
169:
170: $signingDomain = $this->normalizeHost($host);
171: $signingDomainParts = explode('.', $signingDomain);
172:
173: $result = $this->findRegisteredDomain($signingDomainParts, $this->tree);
174:
175: if (empty($result)) {
176:
177: return null;
178: }
179:
180:
181: if (!strpos($result, '.')) {
182: $cnt = count($signingDomainParts);
183: if ($cnt == 1 || $signingDomainParts[$cnt-2] == '') {
184: return null;
185: }
186: return $signingDomainParts[$cnt-2] . '.' . $signingDomainParts[$cnt-1];
187: }
188: return $result;
189: }
190:
191: 192: 193: 194: 195: 196: 197: 198:
199: protected function findRegisteredDomain($remainingSigningDomainParts, &$treeNode)
200: {
201: $sub = array_pop($remainingSigningDomainParts);
202:
203: $result = null;
204: if (isset($treeNode['!'])) {
205: return '';
206: } elseif (is_array($treeNode) && array_key_exists($sub, $treeNode)) {
207: $result = $this->findRegisteredDomain($remainingSigningDomainParts, $treeNode[$sub]);
208: } elseif (is_array($treeNode) && array_key_exists('*', $treeNode)) {
209: $result = $this->findRegisteredDomain($remainingSigningDomainParts, $treeNode['*']);
210: } else {
211: return $sub;
212: }
213:
214: if ($result === '') {
215: return $sub;
216: } elseif (strlen($result)>0) {
217: return $result . '.' . $sub;
218: }
219: return null;
220: }
221: }
222: