Initial commit
[yaffs-website] / node_modules / iconv-lite / encodings / utf7.js
1 "use strict"
2
3 // UTF-7 codec, according to https://tools.ietf.org/html/rfc2152
4 // See also below a UTF-7-IMAP codec, according to http://tools.ietf.org/html/rfc3501#section-5.1.3
5
6 exports.utf7 = Utf7Codec;
7 exports.unicode11utf7 = 'utf7'; // Alias UNICODE-1-1-UTF-7
8 function Utf7Codec(codecOptions, iconv) {
9     this.iconv = iconv;
10 };
11
12 Utf7Codec.prototype.encoder = Utf7Encoder;
13 Utf7Codec.prototype.decoder = Utf7Decoder;
14 Utf7Codec.prototype.bomAware = true;
15
16
17 // -- Encoding
18
19 var nonDirectChars = /[^A-Za-z0-9'\(\),-\.\/:\? \n\r\t]+/g;
20
21 function Utf7Encoder(options, codec) {
22     this.iconv = codec.iconv;
23 }
24
25 Utf7Encoder.prototype.write = function(str) {
26     // Naive implementation.
27     // Non-direct chars are encoded as "+<base64>-"; single "+" char is encoded as "+-".
28     return new Buffer(str.replace(nonDirectChars, function(chunk) {
29         return "+" + (chunk === '+' ? '' : 
30             this.iconv.encode(chunk, 'utf16-be').toString('base64').replace(/=+$/, '')) 
31             + "-";
32     }.bind(this)));
33 }
34
35 Utf7Encoder.prototype.end = function() {
36 }
37
38
39 // -- Decoding
40
41 function Utf7Decoder(options, codec) {
42     this.iconv = codec.iconv;
43     this.inBase64 = false;
44     this.base64Accum = '';
45 }
46
47 var base64Regex = /[A-Za-z0-9\/+]/;
48 var base64Chars = [];
49 for (var i = 0; i < 256; i++)
50     base64Chars[i] = base64Regex.test(String.fromCharCode(i));
51
52 var plusChar = '+'.charCodeAt(0), 
53     minusChar = '-'.charCodeAt(0),
54     andChar = '&'.charCodeAt(0);
55
56 Utf7Decoder.prototype.write = function(buf) {
57     var res = "", lastI = 0,
58         inBase64 = this.inBase64,
59         base64Accum = this.base64Accum;
60
61     // The decoder is more involved as we must handle chunks in stream.
62
63     for (var i = 0; i < buf.length; i++) {
64         if (!inBase64) { // We're in direct mode.
65             // Write direct chars until '+'
66             if (buf[i] == plusChar) {
67                 res += this.iconv.decode(buf.slice(lastI, i), "ascii"); // Write direct chars.
68                 lastI = i+1;
69                 inBase64 = true;
70             }
71         } else { // We decode base64.
72             if (!base64Chars[buf[i]]) { // Base64 ended.
73                 if (i == lastI && buf[i] == minusChar) {// "+-" -> "+"
74                     res += "+";
75                 } else {
76                     var b64str = base64Accum + buf.slice(lastI, i).toString();
77                     res += this.iconv.decode(new Buffer(b64str, 'base64'), "utf16-be");
78                 }
79
80                 if (buf[i] != minusChar) // Minus is absorbed after base64.
81                     i--;
82
83                 lastI = i+1;
84                 inBase64 = false;
85                 base64Accum = '';
86             }
87         }
88     }
89
90     if (!inBase64) {
91         res += this.iconv.decode(buf.slice(lastI), "ascii"); // Write direct chars.
92     } else {
93         var b64str = base64Accum + buf.slice(lastI).toString();
94
95         var canBeDecoded = b64str.length - (b64str.length % 8); // Minimal chunk: 2 quads -> 2x3 bytes -> 3 chars.
96         base64Accum = b64str.slice(canBeDecoded); // The rest will be decoded in future.
97         b64str = b64str.slice(0, canBeDecoded);
98
99         res += this.iconv.decode(new Buffer(b64str, 'base64'), "utf16-be");
100     }
101
102     this.inBase64 = inBase64;
103     this.base64Accum = base64Accum;
104
105     return res;
106 }
107
108 Utf7Decoder.prototype.end = function() {
109     var res = "";
110     if (this.inBase64 && this.base64Accum.length > 0)
111         res = this.iconv.decode(new Buffer(this.base64Accum, 'base64'), "utf16-be");
112
113     this.inBase64 = false;
114     this.base64Accum = '';
115     return res;
116 }
117
118
119 // UTF-7-IMAP codec.
120 // RFC3501 Sec. 5.1.3 Modified UTF-7 (http://tools.ietf.org/html/rfc3501#section-5.1.3)
121 // Differences:
122 //  * Base64 part is started by "&" instead of "+"
123 //  * Direct characters are 0x20-0x7E, except "&" (0x26)
124 //  * In Base64, "," is used instead of "/"
125 //  * Base64 must not be used to represent direct characters.
126 //  * No implicit shift back from Base64 (should always end with '-')
127 //  * String must end in non-shifted position.
128 //  * "-&" while in base64 is not allowed.
129
130
131 exports.utf7imap = Utf7IMAPCodec;
132 function Utf7IMAPCodec(codecOptions, iconv) {
133     this.iconv = iconv;
134 };
135
136 Utf7IMAPCodec.prototype.encoder = Utf7IMAPEncoder;
137 Utf7IMAPCodec.prototype.decoder = Utf7IMAPDecoder;
138 Utf7IMAPCodec.prototype.bomAware = true;
139
140
141 // -- Encoding
142
143 function Utf7IMAPEncoder(options, codec) {
144     this.iconv = codec.iconv;
145     this.inBase64 = false;
146     this.base64Accum = new Buffer(6);
147     this.base64AccumIdx = 0;
148 }
149
150 Utf7IMAPEncoder.prototype.write = function(str) {
151     var inBase64 = this.inBase64,
152         base64Accum = this.base64Accum,
153         base64AccumIdx = this.base64AccumIdx,
154         buf = new Buffer(str.length*5 + 10), bufIdx = 0;
155
156     for (var i = 0; i < str.length; i++) {
157         var uChar = str.charCodeAt(i);
158         if (0x20 <= uChar && uChar <= 0x7E) { // Direct character or '&'.
159             if (inBase64) {
160                 if (base64AccumIdx > 0) {
161                     bufIdx += buf.write(base64Accum.slice(0, base64AccumIdx).toString('base64').replace(/\//g, ',').replace(/=+$/, ''), bufIdx);
162                     base64AccumIdx = 0;
163                 }
164
165                 buf[bufIdx++] = minusChar; // Write '-', then go to direct mode.
166                 inBase64 = false;
167             }
168
169             if (!inBase64) {
170                 buf[bufIdx++] = uChar; // Write direct character
171
172                 if (uChar === andChar)  // Ampersand -> '&-'
173                     buf[bufIdx++] = minusChar;
174             }
175
176         } else { // Non-direct character
177             if (!inBase64) {
178                 buf[bufIdx++] = andChar; // Write '&', then go to base64 mode.
179                 inBase64 = true;
180             }
181             if (inBase64) {
182                 base64Accum[base64AccumIdx++] = uChar >> 8;
183                 base64Accum[base64AccumIdx++] = uChar & 0xFF;
184
185                 if (base64AccumIdx == base64Accum.length) {
186                     bufIdx += buf.write(base64Accum.toString('base64').replace(/\//g, ','), bufIdx);
187                     base64AccumIdx = 0;
188                 }
189             }
190         }
191     }
192
193     this.inBase64 = inBase64;
194     this.base64AccumIdx = base64AccumIdx;
195
196     return buf.slice(0, bufIdx);
197 }
198
199 Utf7IMAPEncoder.prototype.end = function() {
200     var buf = new Buffer(10), bufIdx = 0;
201     if (this.inBase64) {
202         if (this.base64AccumIdx > 0) {
203             bufIdx += buf.write(this.base64Accum.slice(0, this.base64AccumIdx).toString('base64').replace(/\//g, ',').replace(/=+$/, ''), bufIdx);
204             this.base64AccumIdx = 0;
205         }
206
207         buf[bufIdx++] = minusChar; // Write '-', then go to direct mode.
208         this.inBase64 = false;
209     }
210
211     return buf.slice(0, bufIdx);
212 }
213
214
215 // -- Decoding
216
217 function Utf7IMAPDecoder(options, codec) {
218     this.iconv = codec.iconv;
219     this.inBase64 = false;
220     this.base64Accum = '';
221 }
222
223 var base64IMAPChars = base64Chars.slice();
224 base64IMAPChars[','.charCodeAt(0)] = true;
225
226 Utf7IMAPDecoder.prototype.write = function(buf) {
227     var res = "", lastI = 0,
228         inBase64 = this.inBase64,
229         base64Accum = this.base64Accum;
230
231     // The decoder is more involved as we must handle chunks in stream.
232     // It is forgiving, closer to standard UTF-7 (for example, '-' is optional at the end).
233
234     for (var i = 0; i < buf.length; i++) {
235         if (!inBase64) { // We're in direct mode.
236             // Write direct chars until '&'
237             if (buf[i] == andChar) {
238                 res += this.iconv.decode(buf.slice(lastI, i), "ascii"); // Write direct chars.
239                 lastI = i+1;
240                 inBase64 = true;
241             }
242         } else { // We decode base64.
243             if (!base64IMAPChars[buf[i]]) { // Base64 ended.
244                 if (i == lastI && buf[i] == minusChar) { // "&-" -> "&"
245                     res += "&";
246                 } else {
247                     var b64str = base64Accum + buf.slice(lastI, i).toString().replace(/,/g, '/');
248                     res += this.iconv.decode(new Buffer(b64str, 'base64'), "utf16-be");
249                 }
250
251                 if (buf[i] != minusChar) // Minus may be absorbed after base64.
252                     i--;
253
254                 lastI = i+1;
255                 inBase64 = false;
256                 base64Accum = '';
257             }
258         }
259     }
260
261     if (!inBase64) {
262         res += this.iconv.decode(buf.slice(lastI), "ascii"); // Write direct chars.
263     } else {
264         var b64str = base64Accum + buf.slice(lastI).toString().replace(/,/g, '/');
265
266         var canBeDecoded = b64str.length - (b64str.length % 8); // Minimal chunk: 2 quads -> 2x3 bytes -> 3 chars.
267         base64Accum = b64str.slice(canBeDecoded); // The rest will be decoded in future.
268         b64str = b64str.slice(0, canBeDecoded);
269
270         res += this.iconv.decode(new Buffer(b64str, 'base64'), "utf16-be");
271     }
272
273     this.inBase64 = inBase64;
274     this.base64Accum = base64Accum;
275
276     return res;
277 }
278
279 Utf7IMAPDecoder.prototype.end = function() {
280     var res = "";
281     if (this.inBase64 && this.base64Accum.length > 0)
282         res = this.iconv.decode(new Buffer(this.base64Accum, 'base64'), "utf16-be");
283
284     this.inBase64 = false;
285     this.base64Accum = '';
286     return res;
287 }
288
289