-
-
Notifications
You must be signed in to change notification settings - Fork 241
/
Punycode.js
136 lines (120 loc) · 3.56 KB
/
Punycode.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import BootstringEncoder from './Bootstring'
import Chain from '../Chain'
import Encoder from '../Encoder'
import InvalidInputError from '../Error/InvalidInput'
const meta = {
name: 'punycode',
title: 'Punycode',
category: 'Encoding',
type: 'encoder'
}
/**
* Punycode settings as described in section 5 of RFC 3492.
* @type {object}
*/
const bootstringSettingValues = {
basicRangeStart: 0,
basicRangeEnd: 127,
digitMapping: 'abcdefghijklmnopqrstuvwxyz0123456789',
delimiter: '-',
caseSensitivity: false,
initialBias: 72,
initialN: 128,
tmin: 1,
tmax: 26,
skew: 38,
damp: 700
}
/**
* IDNA prefix
* @type {string}
*/
const prefix = 'xn--'
/**
* Encoder brick for Punycode encoding and decoding following RFC 3492.
*/
export default class PunycodeEncoder extends Encoder {
/**
* Returns brick meta.
* @return {object}
*/
static getMeta () {
return meta
}
/**
* Constructor
*/
constructor () {
super()
// Create internal bootstring encoder instance
this._bootstringEncoder = new BootstringEncoder()
this._bootstringEncoder.setSettingValues(bootstringSettingValues)
}
/**
* Performs encode on given content.
* @protected
* @param {Chain} content
* @return {number[]|string|Uint8Array|Chain|Promise} Encoded content
*/
async performEncode (content) {
// Punycode is case insensitive, lowercase the content before continuing
content = content.toLowerCase()
// Split domain into labels
const labels = content.split('.')
const encodedParts = new Array(labels.length)
// Encode each part separately
for (let i = 0; i < labels.length; i++) {
// Check if the code points are all basic
if (this._nonBasicCodePointIndex(labels[i].getCodePoints()) === -1) {
// Return the part as is
encodedParts[i] = labels[i]
} else {
// Encode part using Bootstring and prepend IDNA prefix
const result = await this._bootstringEncoder.encode(labels[i])
encodedParts[i] = prefix + result.getString()
}
}
// Stick domain labels back together
return Chain.join(encodedParts, '.')
}
/**
* Performs decode on given content.
* @protected
* @param {Chain} content
* @return {number[]|string|Uint8Array|Chain|Promise} Decoded content
*/
async performDecode (content) {
// Check if the given input contains any unexpected non-basic code points
const nonBasicIndex = this._nonBasicCodePointIndex(content.getCodePoints())
if (nonBasicIndex !== -1) {
throw new InvalidInputError(
`Invalid Punycode character at index ${nonBasicIndex}`)
}
// Split domain into labels
const labels = content.split('.')
const decodedParts = new Array(labels.length)
// Decode each part separately
for (let i = 0; i < labels.length; i++) {
// Check for the IDNA prefix
if (labels[i].indexOf(prefix) !== 0) {
// Return the part as is
decodedParts[i] = labels[i]
} else {
// Remove IDNA prefix and decode part using Bootstring
const part = labels[i].substr(prefix.length)
decodedParts[i] = await this._bootstringEncoder.decode(part)
}
}
// Stick domain labels back together
return Chain.join(decodedParts, '.')
}
/**
* Returns the first non-basic code point, if any.
* @param {number[]} codePoints Code points
* @return {number} Index of first non-basic code point or -1, if the code
* points are all basic.
*/
_nonBasicCodePointIndex (codePoints) {
return codePoints.findIndex(codePoint => codePoint >= 0x80)
}
}