doc: improvements to debugger.markdown copy
[platform/upstream/nodejs.git] / lib / querystring.js
1 // Query String Utilities
2
3 'use strict';
4
5 const QueryString = exports;
6 const Buffer = require('buffer').Buffer;
7
8
9 function charCode(c) {
10   return c.charCodeAt(0);
11 }
12
13
14 // a safe fast alternative to decodeURIComponent
15 QueryString.unescapeBuffer = function(s, decodeSpaces) {
16   var out = new Buffer(s.length);
17   var state = 'CHAR'; // states: CHAR, HEX0, HEX1
18   var n, m, hexchar;
19
20   for (var inIndex = 0, outIndex = 0; inIndex <= s.length; inIndex++) {
21     var c = s.charCodeAt(inIndex);
22     switch (state) {
23       case 'CHAR':
24         switch (c) {
25           case charCode('%'):
26             n = 0;
27             m = 0;
28             state = 'HEX0';
29             break;
30           case charCode('+'):
31             if (decodeSpaces) c = charCode(' ');
32             // falls through
33           default:
34             out[outIndex++] = c;
35             break;
36         }
37         break;
38
39       case 'HEX0':
40         state = 'HEX1';
41         hexchar = c;
42         if (charCode('0') <= c && c <= charCode('9')) {
43           n = c - charCode('0');
44         } else if (charCode('a') <= c && c <= charCode('f')) {
45           n = c - charCode('a') + 10;
46         } else if (charCode('A') <= c && c <= charCode('F')) {
47           n = c - charCode('A') + 10;
48         } else {
49           out[outIndex++] = charCode('%');
50           out[outIndex++] = c;
51           state = 'CHAR';
52           break;
53         }
54         break;
55
56       case 'HEX1':
57         state = 'CHAR';
58         if (charCode('0') <= c && c <= charCode('9')) {
59           m = c - charCode('0');
60         } else if (charCode('a') <= c && c <= charCode('f')) {
61           m = c - charCode('a') + 10;
62         } else if (charCode('A') <= c && c <= charCode('F')) {
63           m = c - charCode('A') + 10;
64         } else {
65           out[outIndex++] = charCode('%');
66           out[outIndex++] = hexchar;
67           out[outIndex++] = c;
68           break;
69         }
70         out[outIndex++] = 16 * n + m;
71         break;
72     }
73   }
74
75   // TODO support returning arbitrary buffers.
76
77   return out.slice(0, outIndex - 1);
78 };
79
80
81 QueryString.unescape = function(s, decodeSpaces) {
82   try {
83     return decodeURIComponent(s);
84   } catch (e) {
85     return QueryString.unescapeBuffer(s, decodeSpaces).toString();
86   }
87 };
88
89
90 var hexTable = new Array(256);
91 for (var i = 0; i < 256; ++i)
92   hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();
93 QueryString.escape = function(str) {
94   // replaces encodeURIComponent
95   // http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.4
96   str = '' + str;
97   var len = str.length;
98   var out = '';
99   var i, c;
100
101   if (len === 0)
102     return str;
103
104   for (i = 0; i < len; ++i) {
105     c = str.charCodeAt(i);
106
107     // These characters do not need escaping (in order):
108     // ! - . _ ~
109     // ' ( ) *
110     // digits
111     // alpha (uppercase)
112     // alpha (lowercase)
113     if (c === 0x21 || c === 0x2D || c === 0x2E || c === 0x5F || c === 0x7E ||
114         (c >= 0x27 && c <= 0x2A) ||
115         (c >= 0x30 && c <= 0x39) ||
116         (c >= 0x41 && c <= 0x5A) ||
117         (c >= 0x61 && c <= 0x7A)) {
118       out += str[i];
119       continue;
120     }
121
122     // Other ASCII characters
123     if (c < 0x80) {
124       out += hexTable[c];
125       continue;
126     }
127
128     // Multi-byte characters ...
129     if (c < 0x800) {
130       out += hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)];
131       continue;
132     }
133     if (c < 0xD800 || c >= 0xE000) {
134       out += hexTable[0xE0 | (c >> 12)] +
135              hexTable[0x80 | ((c >> 6) & 0x3F)] +
136              hexTable[0x80 | (c & 0x3F)];
137       continue;
138     }
139     // Surrogate pair
140     ++i;
141     c = 0x10000 + (((c & 0x3FF) << 10) | (str.charCodeAt(i) & 0x3FF));
142     out += hexTable[0xF0 | (c >> 18)] +
143            hexTable[0x80 | ((c >> 12) & 0x3F)] +
144            hexTable[0x80 | ((c >> 6) & 0x3F)] +
145            hexTable[0x80 | (c & 0x3F)];
146   }
147   return out;
148 };
149
150 var stringifyPrimitive = function(v) {
151   if (typeof v === 'string')
152     return v;
153   if (typeof v === 'number' && isFinite(v))
154     return '' + v;
155   if (typeof v === 'boolean')
156     return v ? 'true' : 'false';
157   return '';
158 };
159
160
161 QueryString.stringify = QueryString.encode = function(obj, sep, eq, options) {
162   sep = sep || '&';
163   eq = eq || '=';
164
165   var encode = QueryString.escape;
166   if (options && typeof options.encodeURIComponent === 'function') {
167     encode = options.encodeURIComponent;
168   }
169
170   if (obj !== null && typeof obj === 'object') {
171     var keys = Object.keys(obj);
172     var len = keys.length;
173     var flast = len - 1;
174     var fields = '';
175     for (var i = 0; i < len; ++i) {
176       var k = keys[i];
177       var v = obj[k];
178       var ks = encode(stringifyPrimitive(k)) + eq;
179
180       if (Array.isArray(v)) {
181         var vlen = v.length;
182         var vlast = vlen - 1;
183         for (var j = 0; j < vlen; ++j) {
184           fields += ks + encode(stringifyPrimitive(v[j]));
185           if (j < vlast)
186             fields += sep;
187         }
188         if (vlen && i < flast)
189           fields += sep;
190       } else {
191         fields += ks + encode(stringifyPrimitive(v));
192         if (i < flast)
193           fields += sep;
194       }
195     }
196     return fields;
197   }
198   return '';
199 };
200
201 // Parse a key=val string.
202 QueryString.parse = QueryString.decode = function(qs, sep, eq, options) {
203   sep = sep || '&';
204   eq = eq || '=';
205   const eqLen = eq.length;
206   var obj = {};
207
208   if (typeof qs !== 'string' || qs.length === 0) {
209     return obj;
210   }
211
212   var regexp = /\+/g;
213   qs = qs.split(sep);
214
215   var maxKeys = 1000;
216   if (options && typeof options.maxKeys === 'number') {
217     maxKeys = options.maxKeys;
218   }
219
220   var len = qs.length;
221   // maxKeys <= 0 means that we should not limit keys count
222   if (maxKeys > 0 && len > maxKeys) {
223     len = maxKeys;
224   }
225
226   var decode = QueryString.unescape;
227   if (options && typeof options.decodeURIComponent === 'function') {
228     decode = options.decodeURIComponent;
229   }
230
231   var keys = [];
232   for (var i = 0; i < len; ++i) {
233     var x = qs[i].replace(regexp, '%20'),
234         idx = x.indexOf(eq),
235         k, v;
236
237     if (idx >= 0) {
238       k = decodeStr(x.substring(0, idx), decode);
239       v = decodeStr(x.substring(idx + eqLen), decode);
240     } else {
241       k = decodeStr(x, decode);
242       v = '';
243     }
244
245     if (keys.indexOf(k) === -1) {
246       obj[k] = v;
247       keys.push(k);
248     } else if (Array.isArray(obj[k])) {
249       obj[k].push(v);
250     } else {
251       obj[k] = [obj[k], v];
252     }
253   }
254
255   return obj;
256 };
257
258
259 function decodeStr(s, decoder) {
260   try {
261     return decoder(s);
262   } catch (e) {
263     return QueryString.unescape(s, true);
264   }
265 }