GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#25)
by Leo
02:31
created

Punycode   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 349
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 98.79%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 47
c 1
b 0
f 0
lcom 1
cbo 2
dl 0
loc 349
ccs 163
cts 165
cp 0.9879
rs 8.439

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A calculateThreshold() 0 9 3
A adapt() 0 18 3
A charToCodePoint() 0 13 4
A codePointToChar() 0 12 4
B decode() 0 24 6
B decodePart() 0 42 5
A listCodePoints() 0 21 3
B encode() 0 19 5
C encodePart() 0 66 13

How to fix   Complexity   

Complex Class

Complex classes like Punycode often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Punycode, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace TrueBV;
3
4
use TrueBV\Exception\DomainOutOfBoundsException;
5
use TrueBV\Exception\LabelOutOfBoundsException;
6
7
/**
8
 * Punycode implementation as described in RFC 3492
9
 *
10
 * @link http://tools.ietf.org/html/rfc3492
11
 */
12
class Punycode
13
{
14
15
    /**
16
     * Bootstring parameter values
17
     *
18
     */
19
    const BASE         = 36;
20
    const TMIN         = 1;
21
    const TMAX         = 26;
22
    const SKEW         = 38;
23
    const DAMP         = 700;
24
    const INITIAL_BIAS = 72;
25
    const INITIAL_N    = 128;
26
    const PREFIX       = 'xn--';
27
    const DELIMITER    = '-';
28
29
    /**
30
     * Encode table
31
     *
32
     * @param array
33
     */
34
    protected static $encodeTable = array(
35
        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
36
        'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
37
        'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
38
    );
39
40
    /**
41
     * Decode table
42
     *
43
     * @param array
44
     */
45
    protected static $decodeTable = array(
46
        'a' =>  0, 'b' =>  1, 'c' =>  2, 'd' =>  3, 'e' =>  4, 'f' =>  5,
47
        'g' =>  6, 'h' =>  7, 'i' =>  8, 'j' =>  9, 'k' => 10, 'l' => 11,
48
        'm' => 12, 'n' => 13, 'o' => 14, 'p' => 15, 'q' => 16, 'r' => 17,
49
        's' => 18, 't' => 19, 'u' => 20, 'v' => 21, 'w' => 22, 'x' => 23,
50
        'y' => 24, 'z' => 25, '0' => 26, '1' => 27, '2' => 28, '3' => 29,
51
        '4' => 30, '5' => 31, '6' => 32, '7' => 33, '8' => 34, '9' => 35
52
    );
53
54
    /**
55
     * Character encoding
56
     *
57
     * @param string
58
     */
59
    protected $encoding;
60
61
    /**
62
     * Constructor
63
     *
64
     * @param string $encoding Character encoding
65
     */
66 86
    public function __construct($encoding = 'UTF-8')
67
    {
68 86
        $this->encoding = $encoding;
69 86
    }
70
71
    /**
72
     * Encode a domain to its Punycode version
73
     *
74
     * @param string $input Domain name in Unicode to be encoded
75
     * @return string Punycode representation in ASCII
76
     */
77 43
    public function encode($input)
78
    {
79 43
        $input = mb_strtolower($input, $this->encoding);
80 43
        $parts = explode('.', $input);
81 43
        foreach ($parts as &$part) {
82 43
            $length = strlen($part);
83 43
            if ($length > 63 || $length < 1) {
84 3
                throw new LabelOutOfBoundsException(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length));
85
            }
86 42
            $part = $this->encodePart($part);
87 42
        }
88 40
        $output = implode('.', $parts);
89 40
        $length = strlen($output);
90 40
        if ($length > 255) {
91
            throw new DomainOutOfBoundsException(sprintf('A full domain name is limited to 255 octets (including the separators), %s given.', $length));
92
        }
93
94 40
        return $output;
95
    }
96
97
    /**
98
     * Encode a part of a domain name, such as tld, to its Punycode version
99
     *
100
     * @param string $input Part of a domain name
101
     * @return string Punycode representation of a domain part
102
     */
103 42
    protected function encodePart($input)
104
    {
105 42
        $codePoints = $this->listCodePoints($input);
106
107 42
        $n = static::INITIAL_N;
108 42
        $bias = static::INITIAL_BIAS;
109 42
        $delta = 0;
110 42
        $h = $b = count($codePoints['basic']);
111
112 42
        $output = '';
113 42
        foreach ($codePoints['basic'] as $code) {
114 18
            $output .= $this->codePointToChar($code);
115 42
        }
116 42
        if ($input === $output) {
117 18
            return $output;
118
        }
119 40
        if ($b > 0) {
120 6
            $output .= static::DELIMITER;
121 6
        }
122
123 40
        $codePoints['nonBasic'] = array_unique($codePoints['nonBasic']);
124 40
        sort($codePoints['nonBasic']);
125
126 40
        $i = 0;
127 40
        $length = mb_strlen($input, $this->encoding);
128 40
        while ($h < $length) {
129 40
            $m = $codePoints['nonBasic'][$i++];
130 40
            $delta = $delta + ($m - $n) * ($h + 1);
131 40
            $n = $m;
132
133 40
            foreach ($codePoints['all'] as $c) {
134 40
                if ($c < $n || $c < static::INITIAL_N) {
135 40
                    $delta++;
136 40
                }
137 40
                if ($c === $n) {
138 40
                    $q = $delta;
139 40
                    for ($k = static::BASE;; $k += static::BASE) {
140 40
                        $t = $this->calculateThreshold($k, $bias);
141 40
                        if ($q < $t) {
142 40
                            break;
143
                        }
144
145 40
                        $code = $t + (($q - $t) % (static::BASE - $t));
146 40
                        $output .= static::$encodeTable[$code];
147
148 40
                        $q = ($q - $t) / (static::BASE - $t);
149 40
                    }
150
151 40
                    $output .= static::$encodeTable[$q];
152 40
                    $bias = $this->adapt($delta, $h + 1, ($h === $b));
153 40
                    $delta = 0;
154 40
                    $h++;
155 40
                }
156 40
            }
157
158 40
            $delta++;
159 40
            $n++;
160 40
        }
161 40
        $out = static::PREFIX . $output;
162 40
        $length = strlen($out);
163 40
        if ($length > 63 || $length < 1) {
164
            throw new LabelOutOfBoundsException(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length));
165
        }
166
167 40
        return $out;
168
    }
169
170
    /**
171
     * Decode a Punycode domain name to its Unicode counterpart
172
     *
173
     * @param string $input Domain name in Punycode
174
     * @return string Unicode domain name
175
     */
176 43
    public function decode($input)
177
    {
178 43
        $input = strtolower($input);
179 43
        $parts = explode('.', $input);
180 43
        foreach ($parts as &$part) {
181 43
            $length = strlen($part);
182 43
            if ($length > 63 || $length < 1) {
183 2
                throw new LabelOutOfBoundsException(sprintf('The length of any one label is limited to between 1 and 63 octets, but %s given.', $length));
184
            }
185 41
            if (strpos($part, static::PREFIX) !== 0) {
186 17
                continue;
187
            }
188
189 41
            $part = substr($part, strlen(static::PREFIX));
190 41
            $part = $this->decodePart($part);
191 41
        }
192 41
        $output = implode('.', $parts);
193 41
        $length = strlen($output);
194 41
        if ($length > 255) {
195 1
            throw new DomainOutOfBoundsException(sprintf('A full domain name is limited to 255 octets (including the separators), %s given.', $length));
196
        }
197
198 40
        return $output;
199
    }
200
201
    /**
202
     * Decode a part of domain name, such as tld
203
     *
204
     * @param string $input Part of a domain name
205
     * @return string Unicode domain part
206
     */
207 41
    protected function decodePart($input)
208
    {
209 41
        $n = static::INITIAL_N;
210 41
        $i = 0;
211 41
        $bias = static::INITIAL_BIAS;
212 41
        $output = '';
213
214 41
        $pos = strrpos($input, static::DELIMITER);
215 41
        if ($pos !== false) {
216 6
            $output = substr($input, 0, $pos++);
217 6
        } else {
218 35
            $pos = 0;
219
        }
220
221 41
        $outputLength = strlen($output);
222 41
        $inputLength = strlen($input);
223 41
        while ($pos < $inputLength) {
224 41
            $oldi = $i;
225 41
            $w = 1;
226
227 41
            for ($k = static::BASE;; $k += static::BASE) {
228 41
                $digit = static::$decodeTable[$input[$pos++]];
229 41
                $i = $i + ($digit * $w);
230 41
                $t = $this->calculateThreshold($k, $bias);
231
232 41
                if ($digit < $t) {
233 41
                    break;
234
                }
235
236 41
                $w = $w * (static::BASE - $t);
237 41
            }
238
239 41
            $bias = $this->adapt($i - $oldi, ++$outputLength, ($oldi === 0));
240 41
            $n = $n + (int) ($i / $outputLength);
241 41
            $i = $i % ($outputLength);
242 41
            $output = mb_substr($output, 0, $i, $this->encoding) . $this->codePointToChar($n) . mb_substr($output, $i, $outputLength - 1, $this->encoding);
243
244 41
            $i++;
245 41
        }
246
247 41
        return $output;
248
    }
249
250
    /**
251
     * Calculate the bias threshold to fall between TMIN and TMAX
252
     *
253
     * @param integer $k
254
     * @param integer $bias
255
     * @return integer
256
     */
257 81
    protected function calculateThreshold($k, $bias)
258
    {
259 81
        if ($k <= $bias + static::TMIN) {
260 81
            return static::TMIN;
261 81
        } elseif ($k >= $bias + static::TMAX) {
262 81
            return static::TMAX;
263
        }
264 57
        return $k - $bias;
265
    }
266
267
    /**
268
     * Bias adaptation
269
     *
270
     * @param integer $delta
271
     * @param integer $numPoints
272
     * @param boolean $firstTime
273
     * @return integer
274
     */
275 81
    protected function adapt($delta, $numPoints, $firstTime)
276
    {
277
        $delta = (int) (
278
            ($firstTime)
279 81
                ? $delta / static::DAMP
280 81
                : $delta / 2
281 81
            );
282 81
        $delta += (int) ($delta / $numPoints);
283
284 81
        $k = 0;
285 81
        while ($delta > ((static::BASE - static::TMIN) * static::TMAX) / 2) {
286 28
            $delta = (int) ($delta / (static::BASE - static::TMIN));
287 28
            $k = $k + static::BASE;
288 28
        }
289 81
        $k = $k + (int) (((static::BASE - static::TMIN + 1) * $delta) / ($delta + static::SKEW));
290
291 81
        return $k;
292
    }
293
294
    /**
295
     * List code points for a given input
296
     *
297
     * @param string $input
298
     * @return array Multi-dimension array with basic, non-basic and aggregated code points
299
     */
300 42
    protected function listCodePoints($input)
301
    {
302
        $codePoints = array(
303 42
            'all'      => array(),
304 42
            'basic'    => array(),
305 42
            'nonBasic' => array(),
306 42
        );
307
308 42
        $length = mb_strlen($input, $this->encoding);
309 42
        for ($i = 0; $i < $length; $i++) {
310 42
            $char = mb_substr($input, $i, 1, $this->encoding);
311 42
            $code = $this->charToCodePoint($char);
312 42
            if ($code < 128) {
313 18
                $codePoints['all'][] = $codePoints['basic'][] = $code;
314 18
            } else {
315 40
                $codePoints['all'][] = $codePoints['nonBasic'][] = $code;
316
            }
317 42
        }
318
319 42
        return $codePoints;
320
    }
321
322
    /**
323
     * Convert a single or multi-byte character to its code point
324
     *
325
     * @param string $char
326
     * @return integer
327
     */
328 42
    protected function charToCodePoint($char)
329
    {
330 42
        $code = ord($char[0]);
331 42
        if ($code < 128) {
332 18
            return $code;
333 40
        } elseif ($code < 224) {
334 24
            return (($code - 192) * 64) + (ord($char[1]) - 128);
335 18
        } elseif ($code < 240) {
336 16
            return (($code - 224) * 4096) + ((ord($char[1]) - 128) * 64) + (ord($char[2]) - 128);
337
        } else {
338 2
            return (($code - 240) * 262144) + ((ord($char[1]) - 128) * 4096) + ((ord($char[2]) - 128) * 64) + (ord($char[3]) - 128);
339
        }
340
    }
341
342
    /**
343
     * Convert a code point to its single or multi-byte character
344
     *
345
     * @param integer $code
346
     * @return string
347
     */
348 59
    protected function codePointToChar($code)
349
    {
350 59
        if ($code <= 0x7F) {
351 18
            return chr($code);
352 41
        } elseif ($code <= 0x7FF) {
353 25
            return chr(($code >> 6) + 192) . chr(($code & 63) + 128);
354 18
        } elseif ($code <= 0xFFFF) {
355 16
            return chr(($code >> 12) + 224) . chr((($code >> 6) & 63) + 128) . chr(($code & 63) + 128);
356
        } else {
357 2
            return chr(($code >> 18) + 240) . chr((($code >> 12) & 63) + 128) . chr((($code >> 6) & 63) + 128) . chr(($code & 63) + 128);
358
        }
359
    }
360
}
361