Merge branch 'v0.4'
[platform/upstream/nodejs.git] / lib / buffer.js
1 // Copyright Joyent, Inc. and other Node contributors.
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a
4 // copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to permit
8 // persons to whom the Software is furnished to do so, subject to the
9 // following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20 // USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 var SlowBuffer = process.binding('buffer').SlowBuffer;
23
24
25 function toHex(n) {
26   if (n < 16) return '0' + n.toString(16);
27   return n.toString(16);
28 }
29
30
31 SlowBuffer.prototype.inspect = function() {
32   var out = [],
33       len = this.length;
34   for (var i = 0; i < len; i++) {
35     out[i] = toHex(this[i]);
36   }
37   return '<SlowBuffer ' + out.join(' ') + '>';
38 };
39
40
41 SlowBuffer.prototype.hexSlice = function(start, end) {
42   var len = this.length;
43
44   if (!start || start < 0) start = 0;
45   if (end < 0 || start + end > len) end = len - start;
46
47   var out = '';
48   for (var i = start; i < end; i ++) {
49     out += toHex(this[i]);
50   }
51   return out;
52 };
53
54
55
56 SlowBuffer.prototype.toString = function(encoding, start, end) {
57   encoding = String(encoding || 'utf8').toLowerCase();
58   start = +start || 0;
59   if (typeof end == 'undefined') end = this.length;
60
61   // Fastpath empty strings
62   if (+end == start) {
63     return '';
64   }
65
66   switch (encoding) {
67     case 'hex':
68       return this.hexSlice(start, end);
69
70     case 'utf8':
71     case 'utf-8':
72       return this.utf8Slice(start, end);
73
74     case 'ascii':
75       return this.asciiSlice(start, end);
76
77     case 'binary':
78       return this.binarySlice(start, end);
79
80     case 'base64':
81       return this.base64Slice(start, end);
82
83     case 'ucs2':
84     case 'ucs-2':
85       return this.ucs2Slice(start, end);
86
87     default:
88       throw new Error('Unknown encoding');
89   }
90 };
91
92
93 SlowBuffer.prototype.hexWrite = function(string, offset) {
94   var len = string.length;
95   offset = +offset || 0;
96
97   // must be an even number of digits
98   if (len % 2) {
99     throw new Error('Invalid hex string');
100   }
101   for (var i = 0; i < len / 2; i ++) {
102     var byte = parseInt(string.substr(i * 2, 2), 16);
103     if (isNaN(byte)) throw new Error('Invalid hex string');
104     this[offset + i] = byte;
105   }
106   return i;
107 }
108
109
110 SlowBuffer.prototype.write = function(string, offset, encoding) {
111   // Support both (string, offset, encoding)
112   // and the legacy (string, encoding, offset)
113   if (!isFinite(offset)) {
114     var swap = encoding;
115     encoding = offset;
116     offset = swap;
117   }
118
119   offset = +offset || 0;
120   encoding = String(encoding || 'utf8').toLowerCase();
121
122   switch (encoding) {
123     case 'hex':
124       return this.hexWrite(string, offset);
125
126     case 'utf8':
127     case 'utf-8':
128       return this.utf8Write(string, offset);
129
130     case 'ascii':
131       return this.asciiWrite(string, offset);
132
133     case 'binary':
134       return this.binaryWrite(string, offset);
135
136     case 'base64':
137       return this.base64Write(string, offset);
138
139     case 'ucs2':
140     case 'ucs-2':
141       return this.ucs2Write(start, end);
142
143     default:
144       throw new Error('Unknown encoding');
145   }
146 };
147
148
149 // slice(start, end)
150 SlowBuffer.prototype.slice = function(start, end) {
151   if (end > this.length) {
152     throw new Error('oob');
153   }
154   if (start > end) {
155     throw new Error('oob');
156   }
157
158   return new Buffer(this, end - start, +start);
159 };
160
161
162 // Buffer
163
164 function Buffer(subject, encoding, offset) {
165   if (!(this instanceof Buffer)) {
166     return new Buffer(subject, encoding, offset);
167   }
168
169   var type;
170
171   // Are we slicing?
172   if (typeof offset === 'number') {
173     this.length = encoding;
174     this.parent = subject;
175     this.offset = offset;
176   } else {
177     // Find the length
178     switch (type = typeof subject) {
179       case 'number':
180         this.length = subject;
181         break;
182
183       case 'string':
184         this.length = Buffer.byteLength(subject, encoding);
185         break;
186
187       case 'object': // Assume object is an array
188         this.length = subject.length;
189         break;
190
191       default:
192         throw new Error('First argument needs to be a number, ' +
193                         'array or string.');
194     }
195
196     if (this.length > Buffer.poolSize) {
197       // Big buffer, just alloc one.
198       this.parent = new SlowBuffer(this.length);
199       this.offset = 0;
200
201     } else {
202       // Small buffer.
203       if (!pool || pool.length - pool.used < this.length) allocPool();
204       this.parent = pool;
205       this.offset = pool.used;
206       pool.used += this.length;
207     }
208
209     // Treat array-ish objects as a byte array.
210     if (isArrayIsh(subject)) {
211       for (var i = 0; i < this.length; i++) {
212         this.parent[i + this.offset] = subject[i];
213       }
214     } else if (type == 'string') {
215       // We are a string
216       this.length = this.write(subject, 0, encoding);
217     }
218   }
219
220   SlowBuffer.makeFastBuffer(this.parent, this, this.offset, this.length);
221 }
222
223 function isArrayIsh(subject) {
224   return Array.isArray(subject) || Buffer.isBuffer(subject) ||
225          subject && typeof subject === 'object' &&
226          typeof subject.length === 'number';
227 }
228
229 exports.SlowBuffer = SlowBuffer;
230 exports.Buffer = Buffer;
231
232 Buffer.poolSize = 8 * 1024;
233 var pool;
234
235 function allocPool() {
236   pool = new SlowBuffer(Buffer.poolSize);
237   pool.used = 0;
238 }
239
240
241 // Static methods
242 Buffer.isBuffer = function isBuffer(b) {
243   return b instanceof Buffer || b instanceof SlowBuffer;
244 };
245
246
247 // Inspect
248 Buffer.prototype.inspect = function inspect() {
249   var out = [],
250       len = this.length;
251   for (var i = 0; i < len; i++) {
252     out[i] = toHex(this.parent[i + this.offset]);
253   }
254   return '<Buffer ' + out.join(' ') + '>';
255 };
256
257
258 Buffer.prototype.get = function get(i) {
259   if (i < 0 || i >= this.length) throw new Error('oob');
260   return this.parent[this.offset + i];
261 };
262
263
264 Buffer.prototype.set = function set(i, v) {
265   if (i < 0 || i >= this.length) throw new Error('oob');
266   return this.parent[this.offset + i] = v;
267 };
268
269
270 // write(string, offset = 0, encoding = 'utf8')
271 Buffer.prototype.write = function(string, offset, encoding) {
272   if (!isFinite(offset)) {
273     var swap = encoding;
274     encoding = offset;
275     offset = swap;
276   }
277
278   offset = +offset || 0;
279   encoding = String(encoding || 'utf8').toLowerCase();
280
281   // Make sure we are not going to overflow
282   var maxLength = this.length - offset;
283
284   var ret;
285   switch (encoding) {
286     case 'hex':
287       ret = this.parent.hexWrite(string, this.offset + offset, maxLength);
288       break;
289
290     case 'utf8':
291     case 'utf-8':
292       ret = this.parent.utf8Write(string, this.offset + offset, maxLength);
293       break;
294
295     case 'ascii':
296       ret = this.parent.asciiWrite(string, this.offset + offset, maxLength);
297       break;
298
299     case 'binary':
300       ret = this.parent.binaryWrite(string, this.offset + offset, maxLength);
301       break;
302
303     case 'base64':
304       // Warning: maxLength not taken into account in base64Write
305       ret = this.parent.base64Write(string, this.offset + offset, maxLength);
306       break;
307
308     case 'ucs2':
309     case 'ucs-2':
310       ret = this.parent.ucs2Write(string, this.offset + offset, maxLength);
311       break;
312
313     default:
314       throw new Error('Unknown encoding');
315   }
316
317   Buffer._charsWritten = SlowBuffer._charsWritten;
318
319   return ret;
320 };
321
322
323 // toString(encoding, start=0, end=buffer.length)
324 Buffer.prototype.toString = function(encoding, start, end) {
325   encoding = String(encoding || 'utf8').toLowerCase();
326
327   if (typeof start == 'undefined' || start < 0) {
328     start = 0;
329   } else if (start > this.length) {
330     start = this.length;
331   }
332
333   if (typeof end == 'undefined' || end > this.length) {
334     end = this.length;
335   } else if (end < 0) {
336     end = 0;
337   }
338
339   start = start + this.offset;
340   end = end + this.offset;
341
342   switch (encoding) {
343     case 'hex':
344       return this.parent.hexSlice(start, end);
345
346     case 'utf8':
347     case 'utf-8':
348       return this.parent.utf8Slice(start, end);
349
350     case 'ascii':
351       return this.parent.asciiSlice(start, end);
352
353     case 'binary':
354       return this.parent.binarySlice(start, end);
355
356     case 'base64':
357       return this.parent.base64Slice(start, end);
358
359     case 'ucs2':
360     case 'ucs-2':
361       return this.parent.ucs2Slice(start, end);
362
363     default:
364       throw new Error('Unknown encoding');
365   }
366 };
367
368
369 // byteLength
370 Buffer.byteLength = SlowBuffer.byteLength;
371
372
373 // copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)
374 Buffer.prototype.copy = function(target, target_start, start, end) {
375   var source = this;
376   start || (start = 0);
377   end || (end = this.length);
378   target_start || (target_start = 0);
379
380   if (end < start) throw new Error('sourceEnd < sourceStart');
381
382   // Copy 0 bytes; we're done
383   if (end === start) return 0;
384   if (target.length == 0 || source.length == 0) return 0;
385
386   if (target_start < 0 || target_start >= target.length) {
387     throw new Error('targetStart out of bounds');
388   }
389
390   if (start < 0 || start >= source.length) {
391     throw new Error('sourceStart out of bounds');
392   }
393
394   if (end < 0 || end > source.length) {
395     throw new Error('sourceEnd out of bounds');
396   }
397
398   // Are we oob?
399   if (end > this.length) {
400     end = this.length;
401   }
402
403   if (target.length - target_start < end - start) {
404     end = target.length - target_start + start;
405   }
406
407   return this.parent.copy(target.parent,
408                           target_start + target.offset,
409                           start + this.offset,
410                           end + this.offset);
411 };
412
413
414 // slice(start, end)
415 Buffer.prototype.slice = function(start, end) {
416   if (end === undefined) end = this.length;
417   if (end > this.length) throw new Error('oob');
418   if (start > end) throw new Error('oob');
419
420   return new Buffer(this.parent, end - start, +start + this.offset);
421 };
422
423
424 // Legacy methods for backwards compatibility.
425
426 Buffer.prototype.utf8Slice = function(start, end) {
427   return this.toString('utf8', start, end);
428 };
429
430 Buffer.prototype.binarySlice = function(start, end) {
431   return this.toString('binary', start, end);
432 };
433
434 Buffer.prototype.asciiSlice = function(start, end) {
435   return this.toString('ascii', start, end);
436 };
437
438 Buffer.prototype.utf8Write = function(string, offset) {
439   return this.write(string, offset, 'utf8');
440 };
441
442 Buffer.prototype.binaryWrite = function(string, offset) {
443   return this.write(string, offset, 'binary');
444 };
445
446 Buffer.prototype.asciiWrite = function(string, offset) {
447   return this.write(string, offset, 'ascii');
448 };
449