UNPKG

76.6 kBJavaScriptView Raw
1/**
2 * Utility functions for web applications.
3 *
4 * @author Dave Longley
5 *
6 * Copyright (c) 2010-2018 Digital Bazaar, Inc.
7 */
8var forge = require('./forge');
9var baseN = require('./baseN');
10
11/* Utilities API */
12var util = module.exports = forge.util = forge.util || {};
13
14// define setImmediate and nextTick
15(function() {
16 // use native nextTick (unless we're in webpack)
17 // webpack (or better node-libs-browser polyfill) sets process.browser.
18 // this way we can detect webpack properly
19 if(typeof process !== 'undefined' && process.nextTick && !process.browser) {
20 util.nextTick = process.nextTick;
21 if(typeof setImmediate === 'function') {
22 util.setImmediate = setImmediate;
23 } else {
24 // polyfill setImmediate with nextTick, older versions of node
25 // (those w/o setImmediate) won't totally starve IO
26 util.setImmediate = util.nextTick;
27 }
28 return;
29 }
30
31 // polyfill nextTick with native setImmediate
32 if(typeof setImmediate === 'function') {
33 util.setImmediate = function() { return setImmediate.apply(undefined, arguments); };
34 util.nextTick = function(callback) {
35 return setImmediate(callback);
36 };
37 return;
38 }
39
40 /* Note: A polyfill upgrade pattern is used here to allow combining
41 polyfills. For example, MutationObserver is fast, but blocks UI updates,
42 so it needs to allow UI updates periodically, so it falls back on
43 postMessage or setTimeout. */
44
45 // polyfill with setTimeout
46 util.setImmediate = function(callback) {
47 setTimeout(callback, 0);
48 };
49
50 // upgrade polyfill to use postMessage
51 if(typeof window !== 'undefined' &&
52 typeof window.postMessage === 'function') {
53 var msg = 'forge.setImmediate';
54 var callbacks = [];
55 util.setImmediate = function(callback) {
56 callbacks.push(callback);
57 // only send message when one hasn't been sent in
58 // the current turn of the event loop
59 if(callbacks.length === 1) {
60 window.postMessage(msg, '*');
61 }
62 };
63 function handler(event) {
64 if(event.source === window && event.data === msg) {
65 event.stopPropagation();
66 var copy = callbacks.slice();
67 callbacks.length = 0;
68 copy.forEach(function(callback) {
69 callback();
70 });
71 }
72 }
73 window.addEventListener('message', handler, true);
74 }
75
76 // upgrade polyfill to use MutationObserver
77 if(typeof MutationObserver !== 'undefined') {
78 // polyfill with MutationObserver
79 var now = Date.now();
80 var attr = true;
81 var div = document.createElement('div');
82 var callbacks = [];
83 new MutationObserver(function() {
84 var copy = callbacks.slice();
85 callbacks.length = 0;
86 copy.forEach(function(callback) {
87 callback();
88 });
89 }).observe(div, {attributes: true});
90 var oldSetImmediate = util.setImmediate;
91 util.setImmediate = function(callback) {
92 if(Date.now() - now > 15) {
93 now = Date.now();
94 oldSetImmediate(callback);
95 } else {
96 callbacks.push(callback);
97 // only trigger observer when it hasn't been triggered in
98 // the current turn of the event loop
99 if(callbacks.length === 1) {
100 div.setAttribute('a', attr = !attr);
101 }
102 }
103 };
104 }
105
106 util.nextTick = util.setImmediate;
107})();
108
109// check if running under Node.js
110util.isNodejs =
111 typeof process !== 'undefined' && process.versions && process.versions.node;
112
113
114// 'self' will also work in Web Workers (instance of WorkerGlobalScope) while
115// it will point to `window` in the main thread.
116// To remain compatible with older browsers, we fall back to 'window' if 'self'
117// is not available.
118util.globalScope = (function() {
119 if(util.isNodejs) {
120 return global;
121 }
122
123 return typeof self === 'undefined' ? window : self;
124})();
125
126// define isArray
127util.isArray = Array.isArray || function(x) {
128 return Object.prototype.toString.call(x) === '[object Array]';
129};
130
131// define isArrayBuffer
132util.isArrayBuffer = function(x) {
133 return typeof ArrayBuffer !== 'undefined' && x instanceof ArrayBuffer;
134};
135
136// define isArrayBufferView
137util.isArrayBufferView = function(x) {
138 return x && util.isArrayBuffer(x.buffer) && x.byteLength !== undefined;
139};
140
141/**
142 * Ensure a bits param is 8, 16, 24, or 32. Used to validate input for
143 * algorithms where bit manipulation, JavaScript limitations, and/or algorithm
144 * design only allow for byte operations of a limited size.
145 *
146 * @param n number of bits.
147 *
148 * Throw Error if n invalid.
149 */
150function _checkBitsParam(n) {
151 if(!(n === 8 || n === 16 || n === 24 || n === 32)) {
152 throw new Error('Only 8, 16, 24, or 32 bits supported: ' + n);
153 }
154}
155
156// TODO: set ByteBuffer to best available backing
157util.ByteBuffer = ByteStringBuffer;
158
159/** Buffer w/BinaryString backing */
160
161/**
162 * Constructor for a binary string backed byte buffer.
163 *
164 * @param [b] the bytes to wrap (either encoded as string, one byte per
165 * character, or as an ArrayBuffer or Typed Array).
166 */
167function ByteStringBuffer(b) {
168 // TODO: update to match DataBuffer API
169
170 // the data in this buffer
171 this.data = '';
172 // the pointer for reading from this buffer
173 this.read = 0;
174
175 if(typeof b === 'string') {
176 this.data = b;
177 } else if(util.isArrayBuffer(b) || util.isArrayBufferView(b)) {
178 if(typeof Buffer !== 'undefined' && b instanceof Buffer) {
179 this.data = b.toString('binary');
180 } else {
181 // convert native buffer to forge buffer
182 // FIXME: support native buffers internally instead
183 var arr = new Uint8Array(b);
184 try {
185 this.data = String.fromCharCode.apply(null, arr);
186 } catch(e) {
187 for(var i = 0; i < arr.length; ++i) {
188 this.putByte(arr[i]);
189 }
190 }
191 }
192 } else if(b instanceof ByteStringBuffer ||
193 (typeof b === 'object' && typeof b.data === 'string' &&
194 typeof b.read === 'number')) {
195 // copy existing buffer
196 this.data = b.data;
197 this.read = b.read;
198 }
199
200 // used for v8 optimization
201 this._constructedStringLength = 0;
202}
203util.ByteStringBuffer = ByteStringBuffer;
204
205/* Note: This is an optimization for V8-based browsers. When V8 concatenates
206 a string, the strings are only joined logically using a "cons string" or
207 "constructed/concatenated string". These containers keep references to one
208 another and can result in very large memory usage. For example, if a 2MB
209 string is constructed by concatenating 4 bytes together at a time, the
210 memory usage will be ~44MB; so ~22x increase. The strings are only joined
211 together when an operation requiring their joining takes place, such as
212 substr(). This function is called when adding data to this buffer to ensure
213 these types of strings are periodically joined to reduce the memory
214 footprint. */
215var _MAX_CONSTRUCTED_STRING_LENGTH = 4096;
216util.ByteStringBuffer.prototype._optimizeConstructedString = function(x) {
217 this._constructedStringLength += x;
218 if(this._constructedStringLength > _MAX_CONSTRUCTED_STRING_LENGTH) {
219 // this substr() should cause the constructed string to join
220 this.data.substr(0, 1);
221 this._constructedStringLength = 0;
222 }
223};
224
225/**
226 * Gets the number of bytes in this buffer.
227 *
228 * @return the number of bytes in this buffer.
229 */
230util.ByteStringBuffer.prototype.length = function() {
231 return this.data.length - this.read;
232};
233
234/**
235 * Gets whether or not this buffer is empty.
236 *
237 * @return true if this buffer is empty, false if not.
238 */
239util.ByteStringBuffer.prototype.isEmpty = function() {
240 return this.length() <= 0;
241};
242
243/**
244 * Puts a byte in this buffer.
245 *
246 * @param b the byte to put.
247 *
248 * @return this buffer.
249 */
250util.ByteStringBuffer.prototype.putByte = function(b) {
251 return this.putBytes(String.fromCharCode(b));
252};
253
254/**
255 * Puts a byte in this buffer N times.
256 *
257 * @param b the byte to put.
258 * @param n the number of bytes of value b to put.
259 *
260 * @return this buffer.
261 */
262util.ByteStringBuffer.prototype.fillWithByte = function(b, n) {
263 b = String.fromCharCode(b);
264 var d = this.data;
265 while(n > 0) {
266 if(n & 1) {
267 d += b;
268 }
269 n >>>= 1;
270 if(n > 0) {
271 b += b;
272 }
273 }
274 this.data = d;
275 this._optimizeConstructedString(n);
276 return this;
277};
278
279/**
280 * Puts bytes in this buffer.
281 *
282 * @param bytes the bytes (as a binary encoded string) to put.
283 *
284 * @return this buffer.
285 */
286util.ByteStringBuffer.prototype.putBytes = function(bytes) {
287 this.data += bytes;
288 this._optimizeConstructedString(bytes.length);
289 return this;
290};
291
292/**
293 * Puts a UTF-16 encoded string into this buffer.
294 *
295 * @param str the string to put.
296 *
297 * @return this buffer.
298 */
299util.ByteStringBuffer.prototype.putString = function(str) {
300 return this.putBytes(util.encodeUtf8(str));
301};
302
303/**
304 * Puts a 16-bit integer in this buffer in big-endian order.
305 *
306 * @param i the 16-bit integer.
307 *
308 * @return this buffer.
309 */
310util.ByteStringBuffer.prototype.putInt16 = function(i) {
311 return this.putBytes(
312 String.fromCharCode(i >> 8 & 0xFF) +
313 String.fromCharCode(i & 0xFF));
314};
315
316/**
317 * Puts a 24-bit integer in this buffer in big-endian order.
318 *
319 * @param i the 24-bit integer.
320 *
321 * @return this buffer.
322 */
323util.ByteStringBuffer.prototype.putInt24 = function(i) {
324 return this.putBytes(
325 String.fromCharCode(i >> 16 & 0xFF) +
326 String.fromCharCode(i >> 8 & 0xFF) +
327 String.fromCharCode(i & 0xFF));
328};
329
330/**
331 * Puts a 32-bit integer in this buffer in big-endian order.
332 *
333 * @param i the 32-bit integer.
334 *
335 * @return this buffer.
336 */
337util.ByteStringBuffer.prototype.putInt32 = function(i) {
338 return this.putBytes(
339 String.fromCharCode(i >> 24 & 0xFF) +
340 String.fromCharCode(i >> 16 & 0xFF) +
341 String.fromCharCode(i >> 8 & 0xFF) +
342 String.fromCharCode(i & 0xFF));
343};
344
345/**
346 * Puts a 16-bit integer in this buffer in little-endian order.
347 *
348 * @param i the 16-bit integer.
349 *
350 * @return this buffer.
351 */
352util.ByteStringBuffer.prototype.putInt16Le = function(i) {
353 return this.putBytes(
354 String.fromCharCode(i & 0xFF) +
355 String.fromCharCode(i >> 8 & 0xFF));
356};
357
358/**
359 * Puts a 24-bit integer in this buffer in little-endian order.
360 *
361 * @param i the 24-bit integer.
362 *
363 * @return this buffer.
364 */
365util.ByteStringBuffer.prototype.putInt24Le = function(i) {
366 return this.putBytes(
367 String.fromCharCode(i & 0xFF) +
368 String.fromCharCode(i >> 8 & 0xFF) +
369 String.fromCharCode(i >> 16 & 0xFF));
370};
371
372/**
373 * Puts a 32-bit integer in this buffer in little-endian order.
374 *
375 * @param i the 32-bit integer.
376 *
377 * @return this buffer.
378 */
379util.ByteStringBuffer.prototype.putInt32Le = function(i) {
380 return this.putBytes(
381 String.fromCharCode(i & 0xFF) +
382 String.fromCharCode(i >> 8 & 0xFF) +
383 String.fromCharCode(i >> 16 & 0xFF) +
384 String.fromCharCode(i >> 24 & 0xFF));
385};
386
387/**
388 * Puts an n-bit integer in this buffer in big-endian order.
389 *
390 * @param i the n-bit integer.
391 * @param n the number of bits in the integer (8, 16, 24, or 32).
392 *
393 * @return this buffer.
394 */
395util.ByteStringBuffer.prototype.putInt = function(i, n) {
396 _checkBitsParam(n);
397 var bytes = '';
398 do {
399 n -= 8;
400 bytes += String.fromCharCode((i >> n) & 0xFF);
401 } while(n > 0);
402 return this.putBytes(bytes);
403};
404
405/**
406 * Puts a signed n-bit integer in this buffer in big-endian order. Two's
407 * complement representation is used.
408 *
409 * @param i the n-bit integer.
410 * @param n the number of bits in the integer (8, 16, 24, or 32).
411 *
412 * @return this buffer.
413 */
414util.ByteStringBuffer.prototype.putSignedInt = function(i, n) {
415 // putInt checks n
416 if(i < 0) {
417 i += 2 << (n - 1);
418 }
419 return this.putInt(i, n);
420};
421
422/**
423 * Puts the given buffer into this buffer.
424 *
425 * @param buffer the buffer to put into this one.
426 *
427 * @return this buffer.
428 */
429util.ByteStringBuffer.prototype.putBuffer = function(buffer) {
430 return this.putBytes(buffer.getBytes());
431};
432
433/**
434 * Gets a byte from this buffer and advances the read pointer by 1.
435 *
436 * @return the byte.
437 */
438util.ByteStringBuffer.prototype.getByte = function() {
439 return this.data.charCodeAt(this.read++);
440};
441
442/**
443 * Gets a uint16 from this buffer in big-endian order and advances the read
444 * pointer by 2.
445 *
446 * @return the uint16.
447 */
448util.ByteStringBuffer.prototype.getInt16 = function() {
449 var rval = (
450 this.data.charCodeAt(this.read) << 8 ^
451 this.data.charCodeAt(this.read + 1));
452 this.read += 2;
453 return rval;
454};
455
456/**
457 * Gets a uint24 from this buffer in big-endian order and advances the read
458 * pointer by 3.
459 *
460 * @return the uint24.
461 */
462util.ByteStringBuffer.prototype.getInt24 = function() {
463 var rval = (
464 this.data.charCodeAt(this.read) << 16 ^
465 this.data.charCodeAt(this.read + 1) << 8 ^
466 this.data.charCodeAt(this.read + 2));
467 this.read += 3;
468 return rval;
469};
470
471/**
472 * Gets a uint32 from this buffer in big-endian order and advances the read
473 * pointer by 4.
474 *
475 * @return the word.
476 */
477util.ByteStringBuffer.prototype.getInt32 = function() {
478 var rval = (
479 this.data.charCodeAt(this.read) << 24 ^
480 this.data.charCodeAt(this.read + 1) << 16 ^
481 this.data.charCodeAt(this.read + 2) << 8 ^
482 this.data.charCodeAt(this.read + 3));
483 this.read += 4;
484 return rval;
485};
486
487/**
488 * Gets a uint16 from this buffer in little-endian order and advances the read
489 * pointer by 2.
490 *
491 * @return the uint16.
492 */
493util.ByteStringBuffer.prototype.getInt16Le = function() {
494 var rval = (
495 this.data.charCodeAt(this.read) ^
496 this.data.charCodeAt(this.read + 1) << 8);
497 this.read += 2;
498 return rval;
499};
500
501/**
502 * Gets a uint24 from this buffer in little-endian order and advances the read
503 * pointer by 3.
504 *
505 * @return the uint24.
506 */
507util.ByteStringBuffer.prototype.getInt24Le = function() {
508 var rval = (
509 this.data.charCodeAt(this.read) ^
510 this.data.charCodeAt(this.read + 1) << 8 ^
511 this.data.charCodeAt(this.read + 2) << 16);
512 this.read += 3;
513 return rval;
514};
515
516/**
517 * Gets a uint32 from this buffer in little-endian order and advances the read
518 * pointer by 4.
519 *
520 * @return the word.
521 */
522util.ByteStringBuffer.prototype.getInt32Le = function() {
523 var rval = (
524 this.data.charCodeAt(this.read) ^
525 this.data.charCodeAt(this.read + 1) << 8 ^
526 this.data.charCodeAt(this.read + 2) << 16 ^
527 this.data.charCodeAt(this.read + 3) << 24);
528 this.read += 4;
529 return rval;
530};
531
532/**
533 * Gets an n-bit integer from this buffer in big-endian order and advances the
534 * read pointer by ceil(n/8).
535 *
536 * @param n the number of bits in the integer (8, 16, 24, or 32).
537 *
538 * @return the integer.
539 */
540util.ByteStringBuffer.prototype.getInt = function(n) {
541 _checkBitsParam(n);
542 var rval = 0;
543 do {
544 // TODO: Use (rval * 0x100) if adding support for 33 to 53 bits.
545 rval = (rval << 8) + this.data.charCodeAt(this.read++);
546 n -= 8;
547 } while(n > 0);
548 return rval;
549};
550
551/**
552 * Gets a signed n-bit integer from this buffer in big-endian order, using
553 * two's complement, and advances the read pointer by n/8.
554 *
555 * @param n the number of bits in the integer (8, 16, 24, or 32).
556 *
557 * @return the integer.
558 */
559util.ByteStringBuffer.prototype.getSignedInt = function(n) {
560 // getInt checks n
561 var x = this.getInt(n);
562 var max = 2 << (n - 2);
563 if(x >= max) {
564 x -= max << 1;
565 }
566 return x;
567};
568
569/**
570 * Reads bytes out as a binary encoded string and clears them from the
571 * buffer. Note that the resulting string is binary encoded (in node.js this
572 * encoding is referred to as `binary`, it is *not* `utf8`).
573 *
574 * @param count the number of bytes to read, undefined or null for all.
575 *
576 * @return a binary encoded string of bytes.
577 */
578util.ByteStringBuffer.prototype.getBytes = function(count) {
579 var rval;
580 if(count) {
581 // read count bytes
582 count = Math.min(this.length(), count);
583 rval = this.data.slice(this.read, this.read + count);
584 this.read += count;
585 } else if(count === 0) {
586 rval = '';
587 } else {
588 // read all bytes, optimize to only copy when needed
589 rval = (this.read === 0) ? this.data : this.data.slice(this.read);
590 this.clear();
591 }
592 return rval;
593};
594
595/**
596 * Gets a binary encoded string of the bytes from this buffer without
597 * modifying the read pointer.
598 *
599 * @param count the number of bytes to get, omit to get all.
600 *
601 * @return a string full of binary encoded characters.
602 */
603util.ByteStringBuffer.prototype.bytes = function(count) {
604 return (typeof(count) === 'undefined' ?
605 this.data.slice(this.read) :
606 this.data.slice(this.read, this.read + count));
607};
608
609/**
610 * Gets a byte at the given index without modifying the read pointer.
611 *
612 * @param i the byte index.
613 *
614 * @return the byte.
615 */
616util.ByteStringBuffer.prototype.at = function(i) {
617 return this.data.charCodeAt(this.read + i);
618};
619
620/**
621 * Puts a byte at the given index without modifying the read pointer.
622 *
623 * @param i the byte index.
624 * @param b the byte to put.
625 *
626 * @return this buffer.
627 */
628util.ByteStringBuffer.prototype.setAt = function(i, b) {
629 this.data = this.data.substr(0, this.read + i) +
630 String.fromCharCode(b) +
631 this.data.substr(this.read + i + 1);
632 return this;
633};
634
635/**
636 * Gets the last byte without modifying the read pointer.
637 *
638 * @return the last byte.
639 */
640util.ByteStringBuffer.prototype.last = function() {
641 return this.data.charCodeAt(this.data.length - 1);
642};
643
644/**
645 * Creates a copy of this buffer.
646 *
647 * @return the copy.
648 */
649util.ByteStringBuffer.prototype.copy = function() {
650 var c = util.createBuffer(this.data);
651 c.read = this.read;
652 return c;
653};
654
655/**
656 * Compacts this buffer.
657 *
658 * @return this buffer.
659 */
660util.ByteStringBuffer.prototype.compact = function() {
661 if(this.read > 0) {
662 this.data = this.data.slice(this.read);
663 this.read = 0;
664 }
665 return this;
666};
667
668/**
669 * Clears this buffer.
670 *
671 * @return this buffer.
672 */
673util.ByteStringBuffer.prototype.clear = function() {
674 this.data = '';
675 this.read = 0;
676 return this;
677};
678
679/**
680 * Shortens this buffer by triming bytes off of the end of this buffer.
681 *
682 * @param count the number of bytes to trim off.
683 *
684 * @return this buffer.
685 */
686util.ByteStringBuffer.prototype.truncate = function(count) {
687 var len = Math.max(0, this.length() - count);
688 this.data = this.data.substr(this.read, len);
689 this.read = 0;
690 return this;
691};
692
693/**
694 * Converts this buffer to a hexadecimal string.
695 *
696 * @return a hexadecimal string.
697 */
698util.ByteStringBuffer.prototype.toHex = function() {
699 var rval = '';
700 for(var i = this.read; i < this.data.length; ++i) {
701 var b = this.data.charCodeAt(i);
702 if(b < 16) {
703 rval += '0';
704 }
705 rval += b.toString(16);
706 }
707 return rval;
708};
709
710/**
711 * Converts this buffer to a UTF-16 string (standard JavaScript string).
712 *
713 * @return a UTF-16 string.
714 */
715util.ByteStringBuffer.prototype.toString = function() {
716 return util.decodeUtf8(this.bytes());
717};
718
719/** End Buffer w/BinaryString backing */
720
721/** Buffer w/UInt8Array backing */
722
723/**
724 * FIXME: Experimental. Do not use yet.
725 *
726 * Constructor for an ArrayBuffer-backed byte buffer.
727 *
728 * The buffer may be constructed from a string, an ArrayBuffer, DataView, or a
729 * TypedArray.
730 *
731 * If a string is given, its encoding should be provided as an option,
732 * otherwise it will default to 'binary'. A 'binary' string is encoded such
733 * that each character is one byte in length and size.
734 *
735 * If an ArrayBuffer, DataView, or TypedArray is given, it will be used
736 * *directly* without any copying. Note that, if a write to the buffer requires
737 * more space, the buffer will allocate a new backing ArrayBuffer to
738 * accommodate. The starting read and write offsets for the buffer may be
739 * given as options.
740 *
741 * @param [b] the initial bytes for this buffer.
742 * @param options the options to use:
743 * [readOffset] the starting read offset to use (default: 0).
744 * [writeOffset] the starting write offset to use (default: the
745 * length of the first parameter).
746 * [growSize] the minimum amount, in bytes, to grow the buffer by to
747 * accommodate writes (default: 1024).
748 * [encoding] the encoding ('binary', 'utf8', 'utf16', 'hex') for the
749 * first parameter, if it is a string (default: 'binary').
750 */
751function DataBuffer(b, options) {
752 // default options
753 options = options || {};
754
755 // pointers for read from/write to buffer
756 this.read = options.readOffset || 0;
757 this.growSize = options.growSize || 1024;
758
759 var isArrayBuffer = util.isArrayBuffer(b);
760 var isArrayBufferView = util.isArrayBufferView(b);
761 if(isArrayBuffer || isArrayBufferView) {
762 // use ArrayBuffer directly
763 if(isArrayBuffer) {
764 this.data = new DataView(b);
765 } else {
766 // TODO: adjust read/write offset based on the type of view
767 // or specify that this must be done in the options ... that the
768 // offsets are byte-based
769 this.data = new DataView(b.buffer, b.byteOffset, b.byteLength);
770 }
771 this.write = ('writeOffset' in options ?
772 options.writeOffset : this.data.byteLength);
773 return;
774 }
775
776 // initialize to empty array buffer and add any given bytes using putBytes
777 this.data = new DataView(new ArrayBuffer(0));
778 this.write = 0;
779
780 if(b !== null && b !== undefined) {
781 this.putBytes(b);
782 }
783
784 if('writeOffset' in options) {
785 this.write = options.writeOffset;
786 }
787}
788util.DataBuffer = DataBuffer;
789
790/**
791 * Gets the number of bytes in this buffer.
792 *
793 * @return the number of bytes in this buffer.
794 */
795util.DataBuffer.prototype.length = function() {
796 return this.write - this.read;
797};
798
799/**
800 * Gets whether or not this buffer is empty.
801 *
802 * @return true if this buffer is empty, false if not.
803 */
804util.DataBuffer.prototype.isEmpty = function() {
805 return this.length() <= 0;
806};
807
808/**
809 * Ensures this buffer has enough empty space to accommodate the given number
810 * of bytes. An optional parameter may be given that indicates a minimum
811 * amount to grow the buffer if necessary. If the parameter is not given,
812 * the buffer will be grown by some previously-specified default amount
813 * or heuristic.
814 *
815 * @param amount the number of bytes to accommodate.
816 * @param [growSize] the minimum amount, in bytes, to grow the buffer by if
817 * necessary.
818 */
819util.DataBuffer.prototype.accommodate = function(amount, growSize) {
820 if(this.length() >= amount) {
821 return this;
822 }
823 growSize = Math.max(growSize || this.growSize, amount);
824
825 // grow buffer
826 var src = new Uint8Array(
827 this.data.buffer, this.data.byteOffset, this.data.byteLength);
828 var dst = new Uint8Array(this.length() + growSize);
829 dst.set(src);
830 this.data = new DataView(dst.buffer);
831
832 return this;
833};
834
835/**
836 * Puts a byte in this buffer.
837 *
838 * @param b the byte to put.
839 *
840 * @return this buffer.
841 */
842util.DataBuffer.prototype.putByte = function(b) {
843 this.accommodate(1);
844 this.data.setUint8(this.write++, b);
845 return this;
846};
847
848/**
849 * Puts a byte in this buffer N times.
850 *
851 * @param b the byte to put.
852 * @param n the number of bytes of value b to put.
853 *
854 * @return this buffer.
855 */
856util.DataBuffer.prototype.fillWithByte = function(b, n) {
857 this.accommodate(n);
858 for(var i = 0; i < n; ++i) {
859 this.data.setUint8(b);
860 }
861 return this;
862};
863
864/**
865 * Puts bytes in this buffer. The bytes may be given as a string, an
866 * ArrayBuffer, a DataView, or a TypedArray.
867 *
868 * @param bytes the bytes to put.
869 * @param [encoding] the encoding for the first parameter ('binary', 'utf8',
870 * 'utf16', 'hex'), if it is a string (default: 'binary').
871 *
872 * @return this buffer.
873 */
874util.DataBuffer.prototype.putBytes = function(bytes, encoding) {
875 if(util.isArrayBufferView(bytes)) {
876 var src = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
877 var len = src.byteLength - src.byteOffset;
878 this.accommodate(len);
879 var dst = new Uint8Array(this.data.buffer, this.write);
880 dst.set(src);
881 this.write += len;
882 return this;
883 }
884
885 if(util.isArrayBuffer(bytes)) {
886 var src = new Uint8Array(bytes);
887 this.accommodate(src.byteLength);
888 var dst = new Uint8Array(this.data.buffer);
889 dst.set(src, this.write);
890 this.write += src.byteLength;
891 return this;
892 }
893
894 // bytes is a util.DataBuffer or equivalent
895 if(bytes instanceof util.DataBuffer ||
896 (typeof bytes === 'object' &&
897 typeof bytes.read === 'number' && typeof bytes.write === 'number' &&
898 util.isArrayBufferView(bytes.data))) {
899 var src = new Uint8Array(bytes.data.byteLength, bytes.read, bytes.length());
900 this.accommodate(src.byteLength);
901 var dst = new Uint8Array(bytes.data.byteLength, this.write);
902 dst.set(src);
903 this.write += src.byteLength;
904 return this;
905 }
906
907 if(bytes instanceof util.ByteStringBuffer) {
908 // copy binary string and process as the same as a string parameter below
909 bytes = bytes.data;
910 encoding = 'binary';
911 }
912
913 // string conversion
914 encoding = encoding || 'binary';
915 if(typeof bytes === 'string') {
916 var view;
917
918 // decode from string
919 if(encoding === 'hex') {
920 this.accommodate(Math.ceil(bytes.length / 2));
921 view = new Uint8Array(this.data.buffer, this.write);
922 this.write += util.binary.hex.decode(bytes, view, this.write);
923 return this;
924 }
925 if(encoding === 'base64') {
926 this.accommodate(Math.ceil(bytes.length / 4) * 3);
927 view = new Uint8Array(this.data.buffer, this.write);
928 this.write += util.binary.base64.decode(bytes, view, this.write);
929 return this;
930 }
931
932 // encode text as UTF-8 bytes
933 if(encoding === 'utf8') {
934 // encode as UTF-8 then decode string as raw binary
935 bytes = util.encodeUtf8(bytes);
936 encoding = 'binary';
937 }
938
939 // decode string as raw binary
940 if(encoding === 'binary' || encoding === 'raw') {
941 // one byte per character
942 this.accommodate(bytes.length);
943 view = new Uint8Array(this.data.buffer, this.write);
944 this.write += util.binary.raw.decode(view);
945 return this;
946 }
947
948 // encode text as UTF-16 bytes
949 if(encoding === 'utf16') {
950 // two bytes per character
951 this.accommodate(bytes.length * 2);
952 view = new Uint16Array(this.data.buffer, this.write);
953 this.write += util.text.utf16.encode(view);
954 return this;
955 }
956
957 throw new Error('Invalid encoding: ' + encoding);
958 }
959
960 throw Error('Invalid parameter: ' + bytes);
961};
962
963/**
964 * Puts the given buffer into this buffer.
965 *
966 * @param buffer the buffer to put into this one.
967 *
968 * @return this buffer.
969 */
970util.DataBuffer.prototype.putBuffer = function(buffer) {
971 this.putBytes(buffer);
972 buffer.clear();
973 return this;
974};
975
976/**
977 * Puts a string into this buffer.
978 *
979 * @param str the string to put.
980 * @param [encoding] the encoding for the string (default: 'utf16').
981 *
982 * @return this buffer.
983 */
984util.DataBuffer.prototype.putString = function(str) {
985 return this.putBytes(str, 'utf16');
986};
987
988/**
989 * Puts a 16-bit integer in this buffer in big-endian order.
990 *
991 * @param i the 16-bit integer.
992 *
993 * @return this buffer.
994 */
995util.DataBuffer.prototype.putInt16 = function(i) {
996 this.accommodate(2);
997 this.data.setInt16(this.write, i);
998 this.write += 2;
999 return this;
1000};
1001
1002/**
1003 * Puts a 24-bit integer in this buffer in big-endian order.
1004 *
1005 * @param i the 24-bit integer.
1006 *
1007 * @return this buffer.
1008 */
1009util.DataBuffer.prototype.putInt24 = function(i) {
1010 this.accommodate(3);
1011 this.data.setInt16(this.write, i >> 8 & 0xFFFF);
1012 this.data.setInt8(this.write, i >> 16 & 0xFF);
1013 this.write += 3;
1014 return this;
1015};
1016
1017/**
1018 * Puts a 32-bit integer in this buffer in big-endian order.
1019 *
1020 * @param i the 32-bit integer.
1021 *
1022 * @return this buffer.
1023 */
1024util.DataBuffer.prototype.putInt32 = function(i) {
1025 this.accommodate(4);
1026 this.data.setInt32(this.write, i);
1027 this.write += 4;
1028 return this;
1029};
1030
1031/**
1032 * Puts a 16-bit integer in this buffer in little-endian order.
1033 *
1034 * @param i the 16-bit integer.
1035 *
1036 * @return this buffer.
1037 */
1038util.DataBuffer.prototype.putInt16Le = function(i) {
1039 this.accommodate(2);
1040 this.data.setInt16(this.write, i, true);
1041 this.write += 2;
1042 return this;
1043};
1044
1045/**
1046 * Puts a 24-bit integer in this buffer in little-endian order.
1047 *
1048 * @param i the 24-bit integer.
1049 *
1050 * @return this buffer.
1051 */
1052util.DataBuffer.prototype.putInt24Le = function(i) {
1053 this.accommodate(3);
1054 this.data.setInt8(this.write, i >> 16 & 0xFF);
1055 this.data.setInt16(this.write, i >> 8 & 0xFFFF, true);
1056 this.write += 3;
1057 return this;
1058};
1059
1060/**
1061 * Puts a 32-bit integer in this buffer in little-endian order.
1062 *
1063 * @param i the 32-bit integer.
1064 *
1065 * @return this buffer.
1066 */
1067util.DataBuffer.prototype.putInt32Le = function(i) {
1068 this.accommodate(4);
1069 this.data.setInt32(this.write, i, true);
1070 this.write += 4;
1071 return this;
1072};
1073
1074/**
1075 * Puts an n-bit integer in this buffer in big-endian order.
1076 *
1077 * @param i the n-bit integer.
1078 * @param n the number of bits in the integer (8, 16, 24, or 32).
1079 *
1080 * @return this buffer.
1081 */
1082util.DataBuffer.prototype.putInt = function(i, n) {
1083 _checkBitsParam(n);
1084 this.accommodate(n / 8);
1085 do {
1086 n -= 8;
1087 this.data.setInt8(this.write++, (i >> n) & 0xFF);
1088 } while(n > 0);
1089 return this;
1090};
1091
1092/**
1093 * Puts a signed n-bit integer in this buffer in big-endian order. Two's
1094 * complement representation is used.
1095 *
1096 * @param i the n-bit integer.
1097 * @param n the number of bits in the integer.
1098 *
1099 * @return this buffer.
1100 */
1101util.DataBuffer.prototype.putSignedInt = function(i, n) {
1102 _checkBitsParam(n);
1103 this.accommodate(n / 8);
1104 if(i < 0) {
1105 i += 2 << (n - 1);
1106 }
1107 return this.putInt(i, n);
1108};
1109
1110/**
1111 * Gets a byte from this buffer and advances the read pointer by 1.
1112 *
1113 * @return the byte.
1114 */
1115util.DataBuffer.prototype.getByte = function() {
1116 return this.data.getInt8(this.read++);
1117};
1118
1119/**
1120 * Gets a uint16 from this buffer in big-endian order and advances the read
1121 * pointer by 2.
1122 *
1123 * @return the uint16.
1124 */
1125util.DataBuffer.prototype.getInt16 = function() {
1126 var rval = this.data.getInt16(this.read);
1127 this.read += 2;
1128 return rval;
1129};
1130
1131/**
1132 * Gets a uint24 from this buffer in big-endian order and advances the read
1133 * pointer by 3.
1134 *
1135 * @return the uint24.
1136 */
1137util.DataBuffer.prototype.getInt24 = function() {
1138 var rval = (
1139 this.data.getInt16(this.read) << 8 ^
1140 this.data.getInt8(this.read + 2));
1141 this.read += 3;
1142 return rval;
1143};
1144
1145/**
1146 * Gets a uint32 from this buffer in big-endian order and advances the read
1147 * pointer by 4.
1148 *
1149 * @return the word.
1150 */
1151util.DataBuffer.prototype.getInt32 = function() {
1152 var rval = this.data.getInt32(this.read);
1153 this.read += 4;
1154 return rval;
1155};
1156
1157/**
1158 * Gets a uint16 from this buffer in little-endian order and advances the read
1159 * pointer by 2.
1160 *
1161 * @return the uint16.
1162 */
1163util.DataBuffer.prototype.getInt16Le = function() {
1164 var rval = this.data.getInt16(this.read, true);
1165 this.read += 2;
1166 return rval;
1167};
1168
1169/**
1170 * Gets a uint24 from this buffer in little-endian order and advances the read
1171 * pointer by 3.
1172 *
1173 * @return the uint24.
1174 */
1175util.DataBuffer.prototype.getInt24Le = function() {
1176 var rval = (
1177 this.data.getInt8(this.read) ^
1178 this.data.getInt16(this.read + 1, true) << 8);
1179 this.read += 3;
1180 return rval;
1181};
1182
1183/**
1184 * Gets a uint32 from this buffer in little-endian order and advances the read
1185 * pointer by 4.
1186 *
1187 * @return the word.
1188 */
1189util.DataBuffer.prototype.getInt32Le = function() {
1190 var rval = this.data.getInt32(this.read, true);
1191 this.read += 4;
1192 return rval;
1193};
1194
1195/**
1196 * Gets an n-bit integer from this buffer in big-endian order and advances the
1197 * read pointer by n/8.
1198 *
1199 * @param n the number of bits in the integer (8, 16, 24, or 32).
1200 *
1201 * @return the integer.
1202 */
1203util.DataBuffer.prototype.getInt = function(n) {
1204 _checkBitsParam(n);
1205 var rval = 0;
1206 do {
1207 // TODO: Use (rval * 0x100) if adding support for 33 to 53 bits.
1208 rval = (rval << 8) + this.data.getInt8(this.read++);
1209 n -= 8;
1210 } while(n > 0);
1211 return rval;
1212};
1213
1214/**
1215 * Gets a signed n-bit integer from this buffer in big-endian order, using
1216 * two's complement, and advances the read pointer by n/8.
1217 *
1218 * @param n the number of bits in the integer (8, 16, 24, or 32).
1219 *
1220 * @return the integer.
1221 */
1222util.DataBuffer.prototype.getSignedInt = function(n) {
1223 // getInt checks n
1224 var x = this.getInt(n);
1225 var max = 2 << (n - 2);
1226 if(x >= max) {
1227 x -= max << 1;
1228 }
1229 return x;
1230};
1231
1232/**
1233 * Reads bytes out as a binary encoded string and clears them from the
1234 * buffer.
1235 *
1236 * @param count the number of bytes to read, undefined or null for all.
1237 *
1238 * @return a binary encoded string of bytes.
1239 */
1240util.DataBuffer.prototype.getBytes = function(count) {
1241 // TODO: deprecate this method, it is poorly named and
1242 // this.toString('binary') replaces it
1243 // add a toTypedArray()/toArrayBuffer() function
1244 var rval;
1245 if(count) {
1246 // read count bytes
1247 count = Math.min(this.length(), count);
1248 rval = this.data.slice(this.read, this.read + count);
1249 this.read += count;
1250 } else if(count === 0) {
1251 rval = '';
1252 } else {
1253 // read all bytes, optimize to only copy when needed
1254 rval = (this.read === 0) ? this.data : this.data.slice(this.read);
1255 this.clear();
1256 }
1257 return rval;
1258};
1259
1260/**
1261 * Gets a binary encoded string of the bytes from this buffer without
1262 * modifying the read pointer.
1263 *
1264 * @param count the number of bytes to get, omit to get all.
1265 *
1266 * @return a string full of binary encoded characters.
1267 */
1268util.DataBuffer.prototype.bytes = function(count) {
1269 // TODO: deprecate this method, it is poorly named, add "getString()"
1270 return (typeof(count) === 'undefined' ?
1271 this.data.slice(this.read) :
1272 this.data.slice(this.read, this.read + count));
1273};
1274
1275/**
1276 * Gets a byte at the given index without modifying the read pointer.
1277 *
1278 * @param i the byte index.
1279 *
1280 * @return the byte.
1281 */
1282util.DataBuffer.prototype.at = function(i) {
1283 return this.data.getUint8(this.read + i);
1284};
1285
1286/**
1287 * Puts a byte at the given index without modifying the read pointer.
1288 *
1289 * @param i the byte index.
1290 * @param b the byte to put.
1291 *
1292 * @return this buffer.
1293 */
1294util.DataBuffer.prototype.setAt = function(i, b) {
1295 this.data.setUint8(i, b);
1296 return this;
1297};
1298
1299/**
1300 * Gets the last byte without modifying the read pointer.
1301 *
1302 * @return the last byte.
1303 */
1304util.DataBuffer.prototype.last = function() {
1305 return this.data.getUint8(this.write - 1);
1306};
1307
1308/**
1309 * Creates a copy of this buffer.
1310 *
1311 * @return the copy.
1312 */
1313util.DataBuffer.prototype.copy = function() {
1314 return new util.DataBuffer(this);
1315};
1316
1317/**
1318 * Compacts this buffer.
1319 *
1320 * @return this buffer.
1321 */
1322util.DataBuffer.prototype.compact = function() {
1323 if(this.read > 0) {
1324 var src = new Uint8Array(this.data.buffer, this.read);
1325 var dst = new Uint8Array(src.byteLength);
1326 dst.set(src);
1327 this.data = new DataView(dst);
1328 this.write -= this.read;
1329 this.read = 0;
1330 }
1331 return this;
1332};
1333
1334/**
1335 * Clears this buffer.
1336 *
1337 * @return this buffer.
1338 */
1339util.DataBuffer.prototype.clear = function() {
1340 this.data = new DataView(new ArrayBuffer(0));
1341 this.read = this.write = 0;
1342 return this;
1343};
1344
1345/**
1346 * Shortens this buffer by triming bytes off of the end of this buffer.
1347 *
1348 * @param count the number of bytes to trim off.
1349 *
1350 * @return this buffer.
1351 */
1352util.DataBuffer.prototype.truncate = function(count) {
1353 this.write = Math.max(0, this.length() - count);
1354 this.read = Math.min(this.read, this.write);
1355 return this;
1356};
1357
1358/**
1359 * Converts this buffer to a hexadecimal string.
1360 *
1361 * @return a hexadecimal string.
1362 */
1363util.DataBuffer.prototype.toHex = function() {
1364 var rval = '';
1365 for(var i = this.read; i < this.data.byteLength; ++i) {
1366 var b = this.data.getUint8(i);
1367 if(b < 16) {
1368 rval += '0';
1369 }
1370 rval += b.toString(16);
1371 }
1372 return rval;
1373};
1374
1375/**
1376 * Converts this buffer to a string, using the given encoding. If no
1377 * encoding is given, 'utf8' (UTF-8) is used.
1378 *
1379 * @param [encoding] the encoding to use: 'binary', 'utf8', 'utf16', 'hex',
1380 * 'base64' (default: 'utf8').
1381 *
1382 * @return a string representation of the bytes in this buffer.
1383 */
1384util.DataBuffer.prototype.toString = function(encoding) {
1385 var view = new Uint8Array(this.data, this.read, this.length());
1386 encoding = encoding || 'utf8';
1387
1388 // encode to string
1389 if(encoding === 'binary' || encoding === 'raw') {
1390 return util.binary.raw.encode(view);
1391 }
1392 if(encoding === 'hex') {
1393 return util.binary.hex.encode(view);
1394 }
1395 if(encoding === 'base64') {
1396 return util.binary.base64.encode(view);
1397 }
1398
1399 // decode to text
1400 if(encoding === 'utf8') {
1401 return util.text.utf8.decode(view);
1402 }
1403 if(encoding === 'utf16') {
1404 return util.text.utf16.decode(view);
1405 }
1406
1407 throw new Error('Invalid encoding: ' + encoding);
1408};
1409
1410/** End Buffer w/UInt8Array backing */
1411
1412/**
1413 * Creates a buffer that stores bytes. A value may be given to populate the
1414 * buffer with data. This value can either be string of encoded bytes or a
1415 * regular string of characters. When passing a string of binary encoded
1416 * bytes, the encoding `raw` should be given. This is also the default. When
1417 * passing a string of characters, the encoding `utf8` should be given.
1418 *
1419 * @param [input] a string with encoded bytes to store in the buffer.
1420 * @param [encoding] (default: 'raw', other: 'utf8').
1421 */
1422util.createBuffer = function(input, encoding) {
1423 // TODO: deprecate, use new ByteBuffer() instead
1424 encoding = encoding || 'raw';
1425 if(input !== undefined && encoding === 'utf8') {
1426 input = util.encodeUtf8(input);
1427 }
1428 return new util.ByteBuffer(input);
1429};
1430
1431/**
1432 * Fills a string with a particular value. If you want the string to be a byte
1433 * string, pass in String.fromCharCode(theByte).
1434 *
1435 * @param c the character to fill the string with, use String.fromCharCode
1436 * to fill the string with a byte value.
1437 * @param n the number of characters of value c to fill with.
1438 *
1439 * @return the filled string.
1440 */
1441util.fillString = function(c, n) {
1442 var s = '';
1443 while(n > 0) {
1444 if(n & 1) {
1445 s += c;
1446 }
1447 n >>>= 1;
1448 if(n > 0) {
1449 c += c;
1450 }
1451 }
1452 return s;
1453};
1454
1455/**
1456 * Performs a per byte XOR between two byte strings and returns the result as a
1457 * string of bytes.
1458 *
1459 * @param s1 first string of bytes.
1460 * @param s2 second string of bytes.
1461 * @param n the number of bytes to XOR.
1462 *
1463 * @return the XOR'd result.
1464 */
1465util.xorBytes = function(s1, s2, n) {
1466 var s3 = '';
1467 var b = '';
1468 var t = '';
1469 var i = 0;
1470 var c = 0;
1471 for(; n > 0; --n, ++i) {
1472 b = s1.charCodeAt(i) ^ s2.charCodeAt(i);
1473 if(c >= 10) {
1474 s3 += t;
1475 t = '';
1476 c = 0;
1477 }
1478 t += String.fromCharCode(b);
1479 ++c;
1480 }
1481 s3 += t;
1482 return s3;
1483};
1484
1485/**
1486 * Converts a hex string into a 'binary' encoded string of bytes.
1487 *
1488 * @param hex the hexadecimal string to convert.
1489 *
1490 * @return the binary-encoded string of bytes.
1491 */
1492util.hexToBytes = function(hex) {
1493 // TODO: deprecate: "Deprecated. Use util.binary.hex.decode instead."
1494 var rval = '';
1495 var i = 0;
1496 if(hex.length & 1 == 1) {
1497 // odd number of characters, convert first character alone
1498 i = 1;
1499 rval += String.fromCharCode(parseInt(hex[0], 16));
1500 }
1501 // convert 2 characters (1 byte) at a time
1502 for(; i < hex.length; i += 2) {
1503 rval += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
1504 }
1505 return rval;
1506};
1507
1508/**
1509 * Converts a 'binary' encoded string of bytes to hex.
1510 *
1511 * @param bytes the byte string to convert.
1512 *
1513 * @return the string of hexadecimal characters.
1514 */
1515util.bytesToHex = function(bytes) {
1516 // TODO: deprecate: "Deprecated. Use util.binary.hex.encode instead."
1517 return util.createBuffer(bytes).toHex();
1518};
1519
1520/**
1521 * Converts an 32-bit integer to 4-big-endian byte string.
1522 *
1523 * @param i the integer.
1524 *
1525 * @return the byte string.
1526 */
1527util.int32ToBytes = function(i) {
1528 return (
1529 String.fromCharCode(i >> 24 & 0xFF) +
1530 String.fromCharCode(i >> 16 & 0xFF) +
1531 String.fromCharCode(i >> 8 & 0xFF) +
1532 String.fromCharCode(i & 0xFF));
1533};
1534
1535// base64 characters, reverse mapping
1536var _base64 =
1537 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
1538var _base64Idx = [
1539/*43 -43 = 0*/
1540/*'+', 1, 2, 3,'/' */
1541 62, -1, -1, -1, 63,
1542
1543/*'0','1','2','3','4','5','6','7','8','9' */
1544 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
1545
1546/*15, 16, 17,'=', 19, 20, 21 */
1547 -1, -1, -1, 64, -1, -1, -1,
1548
1549/*65 - 43 = 22*/
1550/*'A','B','C','D','E','F','G','H','I','J','K','L','M', */
1551 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
1552
1553/*'N','O','P','Q','R','S','T','U','V','W','X','Y','Z' */
1554 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
1555
1556/*91 - 43 = 48 */
1557/*48, 49, 50, 51, 52, 53 */
1558 -1, -1, -1, -1, -1, -1,
1559
1560/*97 - 43 = 54*/
1561/*'a','b','c','d','e','f','g','h','i','j','k','l','m' */
1562 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
1563
1564/*'n','o','p','q','r','s','t','u','v','w','x','y','z' */
1565 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
1566];
1567
1568// base58 characters (Bitcoin alphabet)
1569var _base58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
1570
1571/**
1572 * Base64 encodes a 'binary' encoded string of bytes.
1573 *
1574 * @param input the binary encoded string of bytes to base64-encode.
1575 * @param maxline the maximum number of encoded characters per line to use,
1576 * defaults to none.
1577 *
1578 * @return the base64-encoded output.
1579 */
1580util.encode64 = function(input, maxline) {
1581 // TODO: deprecate: "Deprecated. Use util.binary.base64.encode instead."
1582 var line = '';
1583 var output = '';
1584 var chr1, chr2, chr3;
1585 var i = 0;
1586 while(i < input.length) {
1587 chr1 = input.charCodeAt(i++);
1588 chr2 = input.charCodeAt(i++);
1589 chr3 = input.charCodeAt(i++);
1590
1591 // encode 4 character group
1592 line += _base64.charAt(chr1 >> 2);
1593 line += _base64.charAt(((chr1 & 3) << 4) | (chr2 >> 4));
1594 if(isNaN(chr2)) {
1595 line += '==';
1596 } else {
1597 line += _base64.charAt(((chr2 & 15) << 2) | (chr3 >> 6));
1598 line += isNaN(chr3) ? '=' : _base64.charAt(chr3 & 63);
1599 }
1600
1601 if(maxline && line.length > maxline) {
1602 output += line.substr(0, maxline) + '\r\n';
1603 line = line.substr(maxline);
1604 }
1605 }
1606 output += line;
1607 return output;
1608};
1609
1610/**
1611 * Base64 decodes a string into a 'binary' encoded string of bytes.
1612 *
1613 * @param input the base64-encoded input.
1614 *
1615 * @return the binary encoded string.
1616 */
1617util.decode64 = function(input) {
1618 // TODO: deprecate: "Deprecated. Use util.binary.base64.decode instead."
1619
1620 // remove all non-base64 characters
1621 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
1622
1623 var output = '';
1624 var enc1, enc2, enc3, enc4;
1625 var i = 0;
1626
1627 while(i < input.length) {
1628 enc1 = _base64Idx[input.charCodeAt(i++) - 43];
1629 enc2 = _base64Idx[input.charCodeAt(i++) - 43];
1630 enc3 = _base64Idx[input.charCodeAt(i++) - 43];
1631 enc4 = _base64Idx[input.charCodeAt(i++) - 43];
1632
1633 output += String.fromCharCode((enc1 << 2) | (enc2 >> 4));
1634 if(enc3 !== 64) {
1635 // decoded at least 2 bytes
1636 output += String.fromCharCode(((enc2 & 15) << 4) | (enc3 >> 2));
1637 if(enc4 !== 64) {
1638 // decoded 3 bytes
1639 output += String.fromCharCode(((enc3 & 3) << 6) | enc4);
1640 }
1641 }
1642 }
1643
1644 return output;
1645};
1646
1647/**
1648 * Encodes the given string of characters (a standard JavaScript
1649 * string) as a binary encoded string where the bytes represent
1650 * a UTF-8 encoded string of characters. Non-ASCII characters will be
1651 * encoded as multiple bytes according to UTF-8.
1652 *
1653 * @param str a standard string of characters to encode.
1654 *
1655 * @return the binary encoded string.
1656 */
1657util.encodeUtf8 = function(str) {
1658 return unescape(encodeURIComponent(str));
1659};
1660
1661/**
1662 * Decodes a binary encoded string that contains bytes that
1663 * represent a UTF-8 encoded string of characters -- into a
1664 * string of characters (a standard JavaScript string).
1665 *
1666 * @param str the binary encoded string to decode.
1667 *
1668 * @return the resulting standard string of characters.
1669 */
1670util.decodeUtf8 = function(str) {
1671 return decodeURIComponent(escape(str));
1672};
1673
1674// binary encoding/decoding tools
1675// FIXME: Experimental. Do not use yet.
1676util.binary = {
1677 raw: {},
1678 hex: {},
1679 base64: {},
1680 base58: {},
1681 baseN : {
1682 encode: baseN.encode,
1683 decode: baseN.decode
1684 }
1685};
1686
1687/**
1688 * Encodes a Uint8Array as a binary-encoded string. This encoding uses
1689 * a value between 0 and 255 for each character.
1690 *
1691 * @param bytes the Uint8Array to encode.
1692 *
1693 * @return the binary-encoded string.
1694 */
1695util.binary.raw.encode = function(bytes) {
1696 return String.fromCharCode.apply(null, bytes);
1697};
1698
1699/**
1700 * Decodes a binary-encoded string to a Uint8Array. This encoding uses
1701 * a value between 0 and 255 for each character.
1702 *
1703 * @param str the binary-encoded string to decode.
1704 * @param [output] an optional Uint8Array to write the output to; if it
1705 * is too small, an exception will be thrown.
1706 * @param [offset] the start offset for writing to the output (default: 0).
1707 *
1708 * @return the Uint8Array or the number of bytes written if output was given.
1709 */
1710util.binary.raw.decode = function(str, output, offset) {
1711 var out = output;
1712 if(!out) {
1713 out = new Uint8Array(str.length);
1714 }
1715 offset = offset || 0;
1716 var j = offset;
1717 for(var i = 0; i < str.length; ++i) {
1718 out[j++] = str.charCodeAt(i);
1719 }
1720 return output ? (j - offset) : out;
1721};
1722
1723/**
1724 * Encodes a 'binary' string, ArrayBuffer, DataView, TypedArray, or
1725 * ByteBuffer as a string of hexadecimal characters.
1726 *
1727 * @param bytes the bytes to convert.
1728 *
1729 * @return the string of hexadecimal characters.
1730 */
1731util.binary.hex.encode = util.bytesToHex;
1732
1733/**
1734 * Decodes a hex-encoded string to a Uint8Array.
1735 *
1736 * @param hex the hexadecimal string to convert.
1737 * @param [output] an optional Uint8Array to write the output to; if it
1738 * is too small, an exception will be thrown.
1739 * @param [offset] the start offset for writing to the output (default: 0).
1740 *
1741 * @return the Uint8Array or the number of bytes written if output was given.
1742 */
1743util.binary.hex.decode = function(hex, output, offset) {
1744 var out = output;
1745 if(!out) {
1746 out = new Uint8Array(Math.ceil(hex.length / 2));
1747 }
1748 offset = offset || 0;
1749 var i = 0, j = offset;
1750 if(hex.length & 1) {
1751 // odd number of characters, convert first character alone
1752 i = 1;
1753 out[j++] = parseInt(hex[0], 16);
1754 }
1755 // convert 2 characters (1 byte) at a time
1756 for(; i < hex.length; i += 2) {
1757 out[j++] = parseInt(hex.substr(i, 2), 16);
1758 }
1759 return output ? (j - offset) : out;
1760};
1761
1762/**
1763 * Base64-encodes a Uint8Array.
1764 *
1765 * @param input the Uint8Array to encode.
1766 * @param maxline the maximum number of encoded characters per line to use,
1767 * defaults to none.
1768 *
1769 * @return the base64-encoded output string.
1770 */
1771util.binary.base64.encode = function(input, maxline) {
1772 var line = '';
1773 var output = '';
1774 var chr1, chr2, chr3;
1775 var i = 0;
1776 while(i < input.byteLength) {
1777 chr1 = input[i++];
1778 chr2 = input[i++];
1779 chr3 = input[i++];
1780
1781 // encode 4 character group
1782 line += _base64.charAt(chr1 >> 2);
1783 line += _base64.charAt(((chr1 & 3) << 4) | (chr2 >> 4));
1784 if(isNaN(chr2)) {
1785 line += '==';
1786 } else {
1787 line += _base64.charAt(((chr2 & 15) << 2) | (chr3 >> 6));
1788 line += isNaN(chr3) ? '=' : _base64.charAt(chr3 & 63);
1789 }
1790
1791 if(maxline && line.length > maxline) {
1792 output += line.substr(0, maxline) + '\r\n';
1793 line = line.substr(maxline);
1794 }
1795 }
1796 output += line;
1797 return output;
1798};
1799
1800/**
1801 * Decodes a base64-encoded string to a Uint8Array.
1802 *
1803 * @param input the base64-encoded input string.
1804 * @param [output] an optional Uint8Array to write the output to; if it
1805 * is too small, an exception will be thrown.
1806 * @param [offset] the start offset for writing to the output (default: 0).
1807 *
1808 * @return the Uint8Array or the number of bytes written if output was given.
1809 */
1810util.binary.base64.decode = function(input, output, offset) {
1811 var out = output;
1812 if(!out) {
1813 out = new Uint8Array(Math.ceil(input.length / 4) * 3);
1814 }
1815
1816 // remove all non-base64 characters
1817 input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
1818
1819 offset = offset || 0;
1820 var enc1, enc2, enc3, enc4;
1821 var i = 0, j = offset;
1822
1823 while(i < input.length) {
1824 enc1 = _base64Idx[input.charCodeAt(i++) - 43];
1825 enc2 = _base64Idx[input.charCodeAt(i++) - 43];
1826 enc3 = _base64Idx[input.charCodeAt(i++) - 43];
1827 enc4 = _base64Idx[input.charCodeAt(i++) - 43];
1828
1829 out[j++] = (enc1 << 2) | (enc2 >> 4);
1830 if(enc3 !== 64) {
1831 // decoded at least 2 bytes
1832 out[j++] = ((enc2 & 15) << 4) | (enc3 >> 2);
1833 if(enc4 !== 64) {
1834 // decoded 3 bytes
1835 out[j++] = ((enc3 & 3) << 6) | enc4;
1836 }
1837 }
1838 }
1839
1840 // make sure result is the exact decoded length
1841 return output ? (j - offset) : out.subarray(0, j);
1842};
1843
1844// add support for base58 encoding/decoding with Bitcoin alphabet
1845util.binary.base58.encode = function(input, maxline) {
1846 return util.binary.baseN.encode(input, _base58, maxline);
1847};
1848util.binary.base58.decode = function(input, maxline) {
1849 return util.binary.baseN.decode(input, _base58, maxline);
1850};
1851
1852// text encoding/decoding tools
1853// FIXME: Experimental. Do not use yet.
1854util.text = {
1855 utf8: {},
1856 utf16: {}
1857};
1858
1859/**
1860 * Encodes the given string as UTF-8 in a Uint8Array.
1861 *
1862 * @param str the string to encode.
1863 * @param [output] an optional Uint8Array to write the output to; if it
1864 * is too small, an exception will be thrown.
1865 * @param [offset] the start offset for writing to the output (default: 0).
1866 *
1867 * @return the Uint8Array or the number of bytes written if output was given.
1868 */
1869util.text.utf8.encode = function(str, output, offset) {
1870 str = util.encodeUtf8(str);
1871 var out = output;
1872 if(!out) {
1873 out = new Uint8Array(str.length);
1874 }
1875 offset = offset || 0;
1876 var j = offset;
1877 for(var i = 0; i < str.length; ++i) {
1878 out[j++] = str.charCodeAt(i);
1879 }
1880 return output ? (j - offset) : out;
1881};
1882
1883/**
1884 * Decodes the UTF-8 contents from a Uint8Array.
1885 *
1886 * @param bytes the Uint8Array to decode.
1887 *
1888 * @return the resulting string.
1889 */
1890util.text.utf8.decode = function(bytes) {
1891 return util.decodeUtf8(String.fromCharCode.apply(null, bytes));
1892};
1893
1894/**
1895 * Encodes the given string as UTF-16 in a Uint8Array.
1896 *
1897 * @param str the string to encode.
1898 * @param [output] an optional Uint8Array to write the output to; if it
1899 * is too small, an exception will be thrown.
1900 * @param [offset] the start offset for writing to the output (default: 0).
1901 *
1902 * @return the Uint8Array or the number of bytes written if output was given.
1903 */
1904util.text.utf16.encode = function(str, output, offset) {
1905 var out = output;
1906 if(!out) {
1907 out = new Uint8Array(str.length * 2);
1908 }
1909 var view = new Uint16Array(out.buffer);
1910 offset = offset || 0;
1911 var j = offset;
1912 var k = offset;
1913 for(var i = 0; i < str.length; ++i) {
1914 view[k++] = str.charCodeAt(i);
1915 j += 2;
1916 }
1917 return output ? (j - offset) : out;
1918};
1919
1920/**
1921 * Decodes the UTF-16 contents from a Uint8Array.
1922 *
1923 * @param bytes the Uint8Array to decode.
1924 *
1925 * @return the resulting string.
1926 */
1927util.text.utf16.decode = function(bytes) {
1928 return String.fromCharCode.apply(null, new Uint16Array(bytes.buffer));
1929};
1930
1931/**
1932 * Deflates the given data using a flash interface.
1933 *
1934 * @param api the flash interface.
1935 * @param bytes the data.
1936 * @param raw true to return only raw deflate data, false to include zlib
1937 * header and trailer.
1938 *
1939 * @return the deflated data as a string.
1940 */
1941util.deflate = function(api, bytes, raw) {
1942 bytes = util.decode64(api.deflate(util.encode64(bytes)).rval);
1943
1944 // strip zlib header and trailer if necessary
1945 if(raw) {
1946 // zlib header is 2 bytes (CMF,FLG) where FLG indicates that
1947 // there is a 4-byte DICT (alder-32) block before the data if
1948 // its 5th bit is set
1949 var start = 2;
1950 var flg = bytes.charCodeAt(1);
1951 if(flg & 0x20) {
1952 start = 6;
1953 }
1954 // zlib trailer is 4 bytes of adler-32
1955 bytes = bytes.substring(start, bytes.length - 4);
1956 }
1957
1958 return bytes;
1959};
1960
1961/**
1962 * Inflates the given data using a flash interface.
1963 *
1964 * @param api the flash interface.
1965 * @param bytes the data.
1966 * @param raw true if the incoming data has no zlib header or trailer and is
1967 * raw DEFLATE data.
1968 *
1969 * @return the inflated data as a string, null on error.
1970 */
1971util.inflate = function(api, bytes, raw) {
1972 // TODO: add zlib header and trailer if necessary/possible
1973 var rval = api.inflate(util.encode64(bytes)).rval;
1974 return (rval === null) ? null : util.decode64(rval);
1975};
1976
1977/**
1978 * Sets a storage object.
1979 *
1980 * @param api the storage interface.
1981 * @param id the storage ID to use.
1982 * @param obj the storage object, null to remove.
1983 */
1984var _setStorageObject = function(api, id, obj) {
1985 if(!api) {
1986 throw new Error('WebStorage not available.');
1987 }
1988
1989 var rval;
1990 if(obj === null) {
1991 rval = api.removeItem(id);
1992 } else {
1993 // json-encode and base64-encode object
1994 obj = util.encode64(JSON.stringify(obj));
1995 rval = api.setItem(id, obj);
1996 }
1997
1998 // handle potential flash error
1999 if(typeof(rval) !== 'undefined' && rval.rval !== true) {
2000 var error = new Error(rval.error.message);
2001 error.id = rval.error.id;
2002 error.name = rval.error.name;
2003 throw error;
2004 }
2005};
2006
2007/**
2008 * Gets a storage object.
2009 *
2010 * @param api the storage interface.
2011 * @param id the storage ID to use.
2012 *
2013 * @return the storage object entry or null if none exists.
2014 */
2015var _getStorageObject = function(api, id) {
2016 if(!api) {
2017 throw new Error('WebStorage not available.');
2018 }
2019
2020 // get the existing entry
2021 var rval = api.getItem(id);
2022
2023 /* Note: We check api.init because we can't do (api == localStorage)
2024 on IE because of "Class doesn't support Automation" exception. Only
2025 the flash api has an init method so this works too, but we need a
2026 better solution in the future. */
2027
2028 // flash returns item wrapped in an object, handle special case
2029 if(api.init) {
2030 if(rval.rval === null) {
2031 if(rval.error) {
2032 var error = new Error(rval.error.message);
2033 error.id = rval.error.id;
2034 error.name = rval.error.name;
2035 throw error;
2036 }
2037 // no error, but also no item
2038 rval = null;
2039 } else {
2040 rval = rval.rval;
2041 }
2042 }
2043
2044 // handle decoding
2045 if(rval !== null) {
2046 // base64-decode and json-decode data
2047 rval = JSON.parse(util.decode64(rval));
2048 }
2049
2050 return rval;
2051};
2052
2053/**
2054 * Stores an item in local storage.
2055 *
2056 * @param api the storage interface.
2057 * @param id the storage ID to use.
2058 * @param key the key for the item.
2059 * @param data the data for the item (any javascript object/primitive).
2060 */
2061var _setItem = function(api, id, key, data) {
2062 // get storage object
2063 var obj = _getStorageObject(api, id);
2064 if(obj === null) {
2065 // create a new storage object
2066 obj = {};
2067 }
2068 // update key
2069 obj[key] = data;
2070
2071 // set storage object
2072 _setStorageObject(api, id, obj);
2073};
2074
2075/**
2076 * Gets an item from local storage.
2077 *
2078 * @param api the storage interface.
2079 * @param id the storage ID to use.
2080 * @param key the key for the item.
2081 *
2082 * @return the item.
2083 */
2084var _getItem = function(api, id, key) {
2085 // get storage object
2086 var rval = _getStorageObject(api, id);
2087 if(rval !== null) {
2088 // return data at key
2089 rval = (key in rval) ? rval[key] : null;
2090 }
2091
2092 return rval;
2093};
2094
2095/**
2096 * Removes an item from local storage.
2097 *
2098 * @param api the storage interface.
2099 * @param id the storage ID to use.
2100 * @param key the key for the item.
2101 */
2102var _removeItem = function(api, id, key) {
2103 // get storage object
2104 var obj = _getStorageObject(api, id);
2105 if(obj !== null && key in obj) {
2106 // remove key
2107 delete obj[key];
2108
2109 // see if entry has no keys remaining
2110 var empty = true;
2111 for(var prop in obj) {
2112 empty = false;
2113 break;
2114 }
2115 if(empty) {
2116 // remove entry entirely if no keys are left
2117 obj = null;
2118 }
2119
2120 // set storage object
2121 _setStorageObject(api, id, obj);
2122 }
2123};
2124
2125/**
2126 * Clears the local disk storage identified by the given ID.
2127 *
2128 * @param api the storage interface.
2129 * @param id the storage ID to use.
2130 */
2131var _clearItems = function(api, id) {
2132 _setStorageObject(api, id, null);
2133};
2134
2135/**
2136 * Calls a storage function.
2137 *
2138 * @param func the function to call.
2139 * @param args the arguments for the function.
2140 * @param location the location argument.
2141 *
2142 * @return the return value from the function.
2143 */
2144var _callStorageFunction = function(func, args, location) {
2145 var rval = null;
2146
2147 // default storage types
2148 if(typeof(location) === 'undefined') {
2149 location = ['web', 'flash'];
2150 }
2151
2152 // apply storage types in order of preference
2153 var type;
2154 var done = false;
2155 var exception = null;
2156 for(var idx in location) {
2157 type = location[idx];
2158 try {
2159 if(type === 'flash' || type === 'both') {
2160 if(args[0] === null) {
2161 throw new Error('Flash local storage not available.');
2162 }
2163 rval = func.apply(this, args);
2164 done = (type === 'flash');
2165 }
2166 if(type === 'web' || type === 'both') {
2167 args[0] = localStorage;
2168 rval = func.apply(this, args);
2169 done = true;
2170 }
2171 } catch(ex) {
2172 exception = ex;
2173 }
2174 if(done) {
2175 break;
2176 }
2177 }
2178
2179 if(!done) {
2180 throw exception;
2181 }
2182
2183 return rval;
2184};
2185
2186/**
2187 * Stores an item on local disk.
2188 *
2189 * The available types of local storage include 'flash', 'web', and 'both'.
2190 *
2191 * The type 'flash' refers to flash local storage (SharedObject). In order
2192 * to use flash local storage, the 'api' parameter must be valid. The type
2193 * 'web' refers to WebStorage, if supported by the browser. The type 'both'
2194 * refers to storing using both 'flash' and 'web', not just one or the
2195 * other.
2196 *
2197 * The location array should list the storage types to use in order of
2198 * preference:
2199 *
2200 * ['flash']: flash only storage
2201 * ['web']: web only storage
2202 * ['both']: try to store in both
2203 * ['flash','web']: store in flash first, but if not available, 'web'
2204 * ['web','flash']: store in web first, but if not available, 'flash'
2205 *
2206 * The location array defaults to: ['web', 'flash']
2207 *
2208 * @param api the flash interface, null to use only WebStorage.
2209 * @param id the storage ID to use.
2210 * @param key the key for the item.
2211 * @param data the data for the item (any javascript object/primitive).
2212 * @param location an array with the preferred types of storage to use.
2213 */
2214util.setItem = function(api, id, key, data, location) {
2215 _callStorageFunction(_setItem, arguments, location);
2216};
2217
2218/**
2219 * Gets an item on local disk.
2220 *
2221 * Set setItem() for details on storage types.
2222 *
2223 * @param api the flash interface, null to use only WebStorage.
2224 * @param id the storage ID to use.
2225 * @param key the key for the item.
2226 * @param location an array with the preferred types of storage to use.
2227 *
2228 * @return the item.
2229 */
2230util.getItem = function(api, id, key, location) {
2231 return _callStorageFunction(_getItem, arguments, location);
2232};
2233
2234/**
2235 * Removes an item on local disk.
2236 *
2237 * Set setItem() for details on storage types.
2238 *
2239 * @param api the flash interface.
2240 * @param id the storage ID to use.
2241 * @param key the key for the item.
2242 * @param location an array with the preferred types of storage to use.
2243 */
2244util.removeItem = function(api, id, key, location) {
2245 _callStorageFunction(_removeItem, arguments, location);
2246};
2247
2248/**
2249 * Clears the local disk storage identified by the given ID.
2250 *
2251 * Set setItem() for details on storage types.
2252 *
2253 * @param api the flash interface if flash is available.
2254 * @param id the storage ID to use.
2255 * @param location an array with the preferred types of storage to use.
2256 */
2257util.clearItems = function(api, id, location) {
2258 _callStorageFunction(_clearItems, arguments, location);
2259};
2260
2261/**
2262 * Parses the scheme, host, and port from an http(s) url.
2263 *
2264 * @param str the url string.
2265 *
2266 * @return the parsed url object or null if the url is invalid.
2267 */
2268util.parseUrl = function(str) {
2269 // FIXME: this regex looks a bit broken
2270 var regex = /^(https?):\/\/([^:&^\/]*):?(\d*)(.*)$/g;
2271 regex.lastIndex = 0;
2272 var m = regex.exec(str);
2273 var url = (m === null) ? null : {
2274 full: str,
2275 scheme: m[1],
2276 host: m[2],
2277 port: m[3],
2278 path: m[4]
2279 };
2280 if(url) {
2281 url.fullHost = url.host;
2282 if(url.port) {
2283 if(url.port !== 80 && url.scheme === 'http') {
2284 url.fullHost += ':' + url.port;
2285 } else if(url.port !== 443 && url.scheme === 'https') {
2286 url.fullHost += ':' + url.port;
2287 }
2288 } else if(url.scheme === 'http') {
2289 url.port = 80;
2290 } else if(url.scheme === 'https') {
2291 url.port = 443;
2292 }
2293 url.full = url.scheme + '://' + url.fullHost;
2294 }
2295 return url;
2296};
2297
2298/* Storage for query variables */
2299var _queryVariables = null;
2300
2301/**
2302 * Returns the window location query variables. Query is parsed on the first
2303 * call and the same object is returned on subsequent calls. The mapping
2304 * is from keys to an array of values. Parameters without values will have
2305 * an object key set but no value added to the value array. Values are
2306 * unescaped.
2307 *
2308 * ...?k1=v1&k2=v2:
2309 * {
2310 * "k1": ["v1"],
2311 * "k2": ["v2"]
2312 * }
2313 *
2314 * ...?k1=v1&k1=v2:
2315 * {
2316 * "k1": ["v1", "v2"]
2317 * }
2318 *
2319 * ...?k1=v1&k2:
2320 * {
2321 * "k1": ["v1"],
2322 * "k2": []
2323 * }
2324 *
2325 * ...?k1=v1&k1:
2326 * {
2327 * "k1": ["v1"]
2328 * }
2329 *
2330 * ...?k1&k1:
2331 * {
2332 * "k1": []
2333 * }
2334 *
2335 * @param query the query string to parse (optional, default to cached
2336 * results from parsing window location search query).
2337 *
2338 * @return object mapping keys to variables.
2339 */
2340util.getQueryVariables = function(query) {
2341 var parse = function(q) {
2342 var rval = {};
2343 var kvpairs = q.split('&');
2344 for(var i = 0; i < kvpairs.length; i++) {
2345 var pos = kvpairs[i].indexOf('=');
2346 var key;
2347 var val;
2348 if(pos > 0) {
2349 key = kvpairs[i].substring(0, pos);
2350 val = kvpairs[i].substring(pos + 1);
2351 } else {
2352 key = kvpairs[i];
2353 val = null;
2354 }
2355 if(!(key in rval)) {
2356 rval[key] = [];
2357 }
2358 // disallow overriding object prototype keys
2359 if(!(key in Object.prototype) && val !== null) {
2360 rval[key].push(unescape(val));
2361 }
2362 }
2363 return rval;
2364 };
2365
2366 var rval;
2367 if(typeof(query) === 'undefined') {
2368 // set cached variables if needed
2369 if(_queryVariables === null) {
2370 if(typeof(window) !== 'undefined' && window.location && window.location.search) {
2371 // parse window search query
2372 _queryVariables = parse(window.location.search.substring(1));
2373 } else {
2374 // no query variables available
2375 _queryVariables = {};
2376 }
2377 }
2378 rval = _queryVariables;
2379 } else {
2380 // parse given query
2381 rval = parse(query);
2382 }
2383 return rval;
2384};
2385
2386/**
2387 * Parses a fragment into a path and query. This method will take a URI
2388 * fragment and break it up as if it were the main URI. For example:
2389 * /bar/baz?a=1&b=2
2390 * results in:
2391 * {
2392 * path: ["bar", "baz"],
2393 * query: {"k1": ["v1"], "k2": ["v2"]}
2394 * }
2395 *
2396 * @return object with a path array and query object.
2397 */
2398util.parseFragment = function(fragment) {
2399 // default to whole fragment
2400 var fp = fragment;
2401 var fq = '';
2402 // split into path and query if possible at the first '?'
2403 var pos = fragment.indexOf('?');
2404 if(pos > 0) {
2405 fp = fragment.substring(0, pos);
2406 fq = fragment.substring(pos + 1);
2407 }
2408 // split path based on '/' and ignore first element if empty
2409 var path = fp.split('/');
2410 if(path.length > 0 && path[0] === '') {
2411 path.shift();
2412 }
2413 // convert query into object
2414 var query = (fq === '') ? {} : util.getQueryVariables(fq);
2415
2416 return {
2417 pathString: fp,
2418 queryString: fq,
2419 path: path,
2420 query: query
2421 };
2422};
2423
2424/**
2425 * Makes a request out of a URI-like request string. This is intended to
2426 * be used where a fragment id (after a URI '#') is parsed as a URI with
2427 * path and query parts. The string should have a path beginning and
2428 * delimited by '/' and optional query parameters following a '?'. The
2429 * query should be a standard URL set of key value pairs delimited by
2430 * '&'. For backwards compatibility the initial '/' on the path is not
2431 * required. The request object has the following API, (fully described
2432 * in the method code):
2433 * {
2434 * path: <the path string part>.
2435 * query: <the query string part>,
2436 * getPath(i): get part or all of the split path array,
2437 * getQuery(k, i): get part or all of a query key array,
2438 * getQueryLast(k, _default): get last element of a query key array.
2439 * }
2440 *
2441 * @return object with request parameters.
2442 */
2443util.makeRequest = function(reqString) {
2444 var frag = util.parseFragment(reqString);
2445 var req = {
2446 // full path string
2447 path: frag.pathString,
2448 // full query string
2449 query: frag.queryString,
2450 /**
2451 * Get path or element in path.
2452 *
2453 * @param i optional path index.
2454 *
2455 * @return path or part of path if i provided.
2456 */
2457 getPath: function(i) {
2458 return (typeof(i) === 'undefined') ? frag.path : frag.path[i];
2459 },
2460 /**
2461 * Get query, values for a key, or value for a key index.
2462 *
2463 * @param k optional query key.
2464 * @param i optional query key index.
2465 *
2466 * @return query, values for a key, or value for a key index.
2467 */
2468 getQuery: function(k, i) {
2469 var rval;
2470 if(typeof(k) === 'undefined') {
2471 rval = frag.query;
2472 } else {
2473 rval = frag.query[k];
2474 if(rval && typeof(i) !== 'undefined') {
2475 rval = rval[i];
2476 }
2477 }
2478 return rval;
2479 },
2480 getQueryLast: function(k, _default) {
2481 var rval;
2482 var vals = req.getQuery(k);
2483 if(vals) {
2484 rval = vals[vals.length - 1];
2485 } else {
2486 rval = _default;
2487 }
2488 return rval;
2489 }
2490 };
2491 return req;
2492};
2493
2494/**
2495 * Makes a URI out of a path, an object with query parameters, and a
2496 * fragment. Uses jQuery.param() internally for query string creation.
2497 * If the path is an array, it will be joined with '/'.
2498 *
2499 * @param path string path or array of strings.
2500 * @param query object with query parameters. (optional)
2501 * @param fragment fragment string. (optional)
2502 *
2503 * @return string object with request parameters.
2504 */
2505util.makeLink = function(path, query, fragment) {
2506 // join path parts if needed
2507 path = jQuery.isArray(path) ? path.join('/') : path;
2508
2509 var qstr = jQuery.param(query || {});
2510 fragment = fragment || '';
2511 return path +
2512 ((qstr.length > 0) ? ('?' + qstr) : '') +
2513 ((fragment.length > 0) ? ('#' + fragment) : '');
2514};
2515
2516/**
2517 * Check if an object is empty.
2518 *
2519 * Taken from:
2520 * http://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object-from-json/679937#679937
2521 *
2522 * @param object the object to check.
2523 */
2524util.isEmpty = function(obj) {
2525 for(var prop in obj) {
2526 if(obj.hasOwnProperty(prop)) {
2527 return false;
2528 }
2529 }
2530 return true;
2531};
2532
2533/**
2534 * Format with simple printf-style interpolation.
2535 *
2536 * %%: literal '%'
2537 * %s,%o: convert next argument into a string.
2538 *
2539 * @param format the string to format.
2540 * @param ... arguments to interpolate into the format string.
2541 */
2542util.format = function(format) {
2543 var re = /%./g;
2544 // current match
2545 var match;
2546 // current part
2547 var part;
2548 // current arg index
2549 var argi = 0;
2550 // collected parts to recombine later
2551 var parts = [];
2552 // last index found
2553 var last = 0;
2554 // loop while matches remain
2555 while((match = re.exec(format))) {
2556 part = format.substring(last, re.lastIndex - 2);
2557 // don't add empty strings (ie, parts between %s%s)
2558 if(part.length > 0) {
2559 parts.push(part);
2560 }
2561 last = re.lastIndex;
2562 // switch on % code
2563 var code = match[0][1];
2564 switch(code) {
2565 case 's':
2566 case 'o':
2567 // check if enough arguments were given
2568 if(argi < arguments.length) {
2569 parts.push(arguments[argi++ + 1]);
2570 } else {
2571 parts.push('<?>');
2572 }
2573 break;
2574 // FIXME: do proper formating for numbers, etc
2575 //case 'f':
2576 //case 'd':
2577 case '%':
2578 parts.push('%');
2579 break;
2580 default:
2581 parts.push('<%' + code + '?>');
2582 }
2583 }
2584 // add trailing part of format string
2585 parts.push(format.substring(last));
2586 return parts.join('');
2587};
2588
2589/**
2590 * Formats a number.
2591 *
2592 * http://snipplr.com/view/5945/javascript-numberformat--ported-from-php/
2593 */
2594util.formatNumber = function(number, decimals, dec_point, thousands_sep) {
2595 // http://kevin.vanzonneveld.net
2596 // + original by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)
2597 // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
2598 // + bugfix by: Michael White (http://crestidg.com)
2599 // + bugfix by: Benjamin Lupton
2600 // + bugfix by: Allan Jensen (http://www.winternet.no)
2601 // + revised by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)
2602 // * example 1: number_format(1234.5678, 2, '.', '');
2603 // * returns 1: 1234.57
2604
2605 var n = number, c = isNaN(decimals = Math.abs(decimals)) ? 2 : decimals;
2606 var d = dec_point === undefined ? ',' : dec_point;
2607 var t = thousands_sep === undefined ?
2608 '.' : thousands_sep, s = n < 0 ? '-' : '';
2609 var i = parseInt((n = Math.abs(+n || 0).toFixed(c)), 10) + '';
2610 var j = (i.length > 3) ? i.length % 3 : 0;
2611 return s + (j ? i.substr(0, j) + t : '') +
2612 i.substr(j).replace(/(\d{3})(?=\d)/g, '$1' + t) +
2613 (c ? d + Math.abs(n - i).toFixed(c).slice(2) : '');
2614};
2615
2616/**
2617 * Formats a byte size.
2618 *
2619 * http://snipplr.com/view/5949/format-humanize-file-byte-size-presentation-in-javascript/
2620 */
2621util.formatSize = function(size) {
2622 if(size >= 1073741824) {
2623 size = util.formatNumber(size / 1073741824, 2, '.', '') + ' GiB';
2624 } else if(size >= 1048576) {
2625 size = util.formatNumber(size / 1048576, 2, '.', '') + ' MiB';
2626 } else if(size >= 1024) {
2627 size = util.formatNumber(size / 1024, 0) + ' KiB';
2628 } else {
2629 size = util.formatNumber(size, 0) + ' bytes';
2630 }
2631 return size;
2632};
2633
2634/**
2635 * Converts an IPv4 or IPv6 string representation into bytes (in network order).
2636 *
2637 * @param ip the IPv4 or IPv6 address to convert.
2638 *
2639 * @return the 4-byte IPv6 or 16-byte IPv6 address or null if the address can't
2640 * be parsed.
2641 */
2642util.bytesFromIP = function(ip) {
2643 if(ip.indexOf('.') !== -1) {
2644 return util.bytesFromIPv4(ip);
2645 }
2646 if(ip.indexOf(':') !== -1) {
2647 return util.bytesFromIPv6(ip);
2648 }
2649 return null;
2650};
2651
2652/**
2653 * Converts an IPv4 string representation into bytes (in network order).
2654 *
2655 * @param ip the IPv4 address to convert.
2656 *
2657 * @return the 4-byte address or null if the address can't be parsed.
2658 */
2659util.bytesFromIPv4 = function(ip) {
2660 ip = ip.split('.');
2661 if(ip.length !== 4) {
2662 return null;
2663 }
2664 var b = util.createBuffer();
2665 for(var i = 0; i < ip.length; ++i) {
2666 var num = parseInt(ip[i], 10);
2667 if(isNaN(num)) {
2668 return null;
2669 }
2670 b.putByte(num);
2671 }
2672 return b.getBytes();
2673};
2674
2675/**
2676 * Converts an IPv6 string representation into bytes (in network order).
2677 *
2678 * @param ip the IPv6 address to convert.
2679 *
2680 * @return the 16-byte address or null if the address can't be parsed.
2681 */
2682util.bytesFromIPv6 = function(ip) {
2683 var blanks = 0;
2684 ip = ip.split(':').filter(function(e) {
2685 if(e.length === 0) ++blanks;
2686 return true;
2687 });
2688 var zeros = (8 - ip.length + blanks) * 2;
2689 var b = util.createBuffer();
2690 for(var i = 0; i < 8; ++i) {
2691 if(!ip[i] || ip[i].length === 0) {
2692 b.fillWithByte(0, zeros);
2693 zeros = 0;
2694 continue;
2695 }
2696 var bytes = util.hexToBytes(ip[i]);
2697 if(bytes.length < 2) {
2698 b.putByte(0);
2699 }
2700 b.putBytes(bytes);
2701 }
2702 return b.getBytes();
2703};
2704
2705/**
2706 * Converts 4-bytes into an IPv4 string representation or 16-bytes into
2707 * an IPv6 string representation. The bytes must be in network order.
2708 *
2709 * @param bytes the bytes to convert.
2710 *
2711 * @return the IPv4 or IPv6 string representation if 4 or 16 bytes,
2712 * respectively, are given, otherwise null.
2713 */
2714util.bytesToIP = function(bytes) {
2715 if(bytes.length === 4) {
2716 return util.bytesToIPv4(bytes);
2717 }
2718 if(bytes.length === 16) {
2719 return util.bytesToIPv6(bytes);
2720 }
2721 return null;
2722};
2723
2724/**
2725 * Converts 4-bytes into an IPv4 string representation. The bytes must be
2726 * in network order.
2727 *
2728 * @param bytes the bytes to convert.
2729 *
2730 * @return the IPv4 string representation or null for an invalid # of bytes.
2731 */
2732util.bytesToIPv4 = function(bytes) {
2733 if(bytes.length !== 4) {
2734 return null;
2735 }
2736 var ip = [];
2737 for(var i = 0; i < bytes.length; ++i) {
2738 ip.push(bytes.charCodeAt(i));
2739 }
2740 return ip.join('.');
2741};
2742
2743/**
2744 * Converts 16-bytes into an IPv16 string representation. The bytes must be
2745 * in network order.
2746 *
2747 * @param bytes the bytes to convert.
2748 *
2749 * @return the IPv16 string representation or null for an invalid # of bytes.
2750 */
2751util.bytesToIPv6 = function(bytes) {
2752 if(bytes.length !== 16) {
2753 return null;
2754 }
2755 var ip = [];
2756 var zeroGroups = [];
2757 var zeroMaxGroup = 0;
2758 for(var i = 0; i < bytes.length; i += 2) {
2759 var hex = util.bytesToHex(bytes[i] + bytes[i + 1]);
2760 // canonicalize zero representation
2761 while(hex[0] === '0' && hex !== '0') {
2762 hex = hex.substr(1);
2763 }
2764 if(hex === '0') {
2765 var last = zeroGroups[zeroGroups.length - 1];
2766 var idx = ip.length;
2767 if(!last || idx !== last.end + 1) {
2768 zeroGroups.push({start: idx, end: idx});
2769 } else {
2770 last.end = idx;
2771 if((last.end - last.start) >
2772 (zeroGroups[zeroMaxGroup].end - zeroGroups[zeroMaxGroup].start)) {
2773 zeroMaxGroup = zeroGroups.length - 1;
2774 }
2775 }
2776 }
2777 ip.push(hex);
2778 }
2779 if(zeroGroups.length > 0) {
2780 var group = zeroGroups[zeroMaxGroup];
2781 // only shorten group of length > 0
2782 if(group.end - group.start > 0) {
2783 ip.splice(group.start, group.end - group.start + 1, '');
2784 if(group.start === 0) {
2785 ip.unshift('');
2786 }
2787 if(group.end === 7) {
2788 ip.push('');
2789 }
2790 }
2791 }
2792 return ip.join(':');
2793};
2794
2795/**
2796 * Estimates the number of processes that can be run concurrently. If
2797 * creating Web Workers, keep in mind that the main JavaScript process needs
2798 * its own core.
2799 *
2800 * @param options the options to use:
2801 * update true to force an update (not use the cached value).
2802 * @param callback(err, max) called once the operation completes.
2803 */
2804util.estimateCores = function(options, callback) {
2805 if(typeof options === 'function') {
2806 callback = options;
2807 options = {};
2808 }
2809 options = options || {};
2810 if('cores' in util && !options.update) {
2811 return callback(null, util.cores);
2812 }
2813 if(typeof navigator !== 'undefined' &&
2814 'hardwareConcurrency' in navigator &&
2815 navigator.hardwareConcurrency > 0) {
2816 util.cores = navigator.hardwareConcurrency;
2817 return callback(null, util.cores);
2818 }
2819 if(typeof Worker === 'undefined') {
2820 // workers not available
2821 util.cores = 1;
2822 return callback(null, util.cores);
2823 }
2824 if(typeof Blob === 'undefined') {
2825 // can't estimate, default to 2
2826 util.cores = 2;
2827 return callback(null, util.cores);
2828 }
2829
2830 // create worker concurrency estimation code as blob
2831 var blobUrl = URL.createObjectURL(new Blob(['(',
2832 function() {
2833 self.addEventListener('message', function(e) {
2834 // run worker for 4 ms
2835 var st = Date.now();
2836 var et = st + 4;
2837 while(Date.now() < et);
2838 self.postMessage({st: st, et: et});
2839 });
2840 }.toString(),
2841 ')()'], {type: 'application/javascript'}));
2842
2843 // take 5 samples using 16 workers
2844 sample([], 5, 16);
2845
2846 function sample(max, samples, numWorkers) {
2847 if(samples === 0) {
2848 // get overlap average
2849 var avg = Math.floor(max.reduce(function(avg, x) {
2850 return avg + x;
2851 }, 0) / max.length);
2852 util.cores = Math.max(1, avg);
2853 URL.revokeObjectURL(blobUrl);
2854 return callback(null, util.cores);
2855 }
2856 map(numWorkers, function(err, results) {
2857 max.push(reduce(numWorkers, results));
2858 sample(max, samples - 1, numWorkers);
2859 });
2860 }
2861
2862 function map(numWorkers, callback) {
2863 var workers = [];
2864 var results = [];
2865 for(var i = 0; i < numWorkers; ++i) {
2866 var worker = new Worker(blobUrl);
2867 worker.addEventListener('message', function(e) {
2868 results.push(e.data);
2869 if(results.length === numWorkers) {
2870 for(var i = 0; i < numWorkers; ++i) {
2871 workers[i].terminate();
2872 }
2873 callback(null, results);
2874 }
2875 });
2876 workers.push(worker);
2877 }
2878 for(var i = 0; i < numWorkers; ++i) {
2879 workers[i].postMessage(i);
2880 }
2881 }
2882
2883 function reduce(numWorkers, results) {
2884 // find overlapping time windows
2885 var overlaps = [];
2886 for(var n = 0; n < numWorkers; ++n) {
2887 var r1 = results[n];
2888 var overlap = overlaps[n] = [];
2889 for(var i = 0; i < numWorkers; ++i) {
2890 if(n === i) {
2891 continue;
2892 }
2893 var r2 = results[i];
2894 if((r1.st > r2.st && r1.st < r2.et) ||
2895 (r2.st > r1.st && r2.st < r1.et)) {
2896 overlap.push(i);
2897 }
2898 }
2899 }
2900 // get maximum overlaps ... don't include overlapping worker itself
2901 // as the main JS process was also being scheduled during the work and
2902 // would have to be subtracted from the estimate anyway
2903 return overlaps.reduce(function(max, overlap) {
2904 return Math.max(max, overlap.length);
2905 }, 0);
2906 }
2907};