buffer.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. 'use strict';
  5. module.metadata = {
  6. 'stability': 'experimental'
  7. };
  8. /*
  9. * Encodings supported by TextEncoder/Decoder:
  10. * utf-8, utf-16le, utf-16be
  11. * http://encoding.spec.whatwg.org/#interface-textencoder
  12. *
  13. * Node however supports the following encodings:
  14. * ascii, utf-8, utf-16le, usc2, base64, hex
  15. */
  16. const { Cu } = require('chrome');
  17. const { isNumber } = require('sdk/lang/type');
  18. const { TextEncoder, TextDecoder } = Cu.import('resource://gre/modules/commonjs/toolkit/loader.js', {});
  19. exports.TextEncoder = TextEncoder;
  20. exports.TextDecoder = TextDecoder;
  21. /**
  22. * Use WeakMaps to work around Bug 929146, which prevents us from adding
  23. * getters or values to typed arrays
  24. * https://bugzilla.mozilla.org/show_bug.cgi?id=929146
  25. */
  26. const parents = new WeakMap();
  27. const views = new WeakMap();
  28. function Buffer(subject, encoding /*, bufferLength */) {
  29. // Allow invocation without `new` constructor
  30. if (!(this instanceof Buffer))
  31. return new Buffer(subject, encoding, arguments[2]);
  32. var type = typeof(subject);
  33. switch (type) {
  34. case 'number':
  35. // Create typed array of the given size if number.
  36. try {
  37. let buffer = Uint8Array(subject > 0 ? Math.floor(subject) : 0);
  38. return buffer;
  39. } catch (e) {
  40. if (/size and count too large/.test(e.message) ||
  41. /invalid arguments/.test(e.message))
  42. throw new RangeError('Could not instantiate buffer: size of buffer may be too large');
  43. else
  44. throw new Error('Could not instantiate buffer');
  45. }
  46. break;
  47. case 'string':
  48. // If string encode it and use buffer for the returned Uint8Array
  49. // to create a local patched version that acts like node buffer.
  50. encoding = encoding || 'utf8';
  51. return Uint8Array(TextEncoder(encoding).encode(subject).buffer);
  52. case 'object':
  53. // This form of the constructor uses the form of
  54. // Uint8Array(buffer, offset, length);
  55. // So we can instantiate a typed array within the constructor
  56. // to inherit the appropriate properties, where both the
  57. // `subject` and newly instantiated buffer share the same underlying
  58. // data structure.
  59. if (arguments.length === 3)
  60. return Uint8Array(subject, encoding, arguments[2]);
  61. // If array or alike just make a copy with a local patched prototype.
  62. else
  63. return Uint8Array(subject);
  64. default:
  65. throw new TypeError('must start with number, buffer, array or string');
  66. }
  67. }
  68. exports.Buffer = Buffer;
  69. // Tests if `value` is a Buffer.
  70. Buffer.isBuffer = value => value instanceof Buffer
  71. // Returns true if the encoding is a valid encoding argument & false otherwise
  72. Buffer.isEncoding = function (encoding) {
  73. if (!encoding) return false;
  74. try {
  75. TextDecoder(encoding);
  76. } catch(e) {
  77. return false;
  78. }
  79. return true;
  80. }
  81. // Gives the actual byte length of a string. encoding defaults to 'utf8'.
  82. // This is not the same as String.prototype.length since that returns the
  83. // number of characters in a string.
  84. Buffer.byteLength = (value, encoding = 'utf8') =>
  85. TextEncoder(encoding).encode(value).byteLength
  86. // Direct copy of the nodejs's buffer implementation:
  87. // https://github.com/joyent/node/blob/b255f4c10a80343f9ce1cee56d0288361429e214/lib/buffer.js#L146-L177
  88. Buffer.concat = function(list, length) {
  89. if (!Array.isArray(list))
  90. throw new TypeError('Usage: Buffer.concat(list[, length])');
  91. if (typeof length === 'undefined') {
  92. length = 0;
  93. for (var i = 0; i < list.length; i++)
  94. length += list[i].length;
  95. } else {
  96. length = ~~length;
  97. }
  98. if (length < 0)
  99. length = 0;
  100. if (list.length === 0)
  101. return new Buffer(0);
  102. else if (list.length === 1)
  103. return list[0];
  104. if (length < 0)
  105. throw new RangeError('length is not a positive number');
  106. var buffer = new Buffer(length);
  107. var pos = 0;
  108. for (var i = 0; i < list.length; i++) {
  109. var buf = list[i];
  110. buf.copy(buffer, pos);
  111. pos += buf.length;
  112. }
  113. return buffer;
  114. };
  115. // Node buffer is very much like Uint8Array although it has bunch of methods
  116. // that typically can be used in combination with `DataView` while preserving
  117. // access by index. Since in SDK each module has it's own set of bult-ins it
  118. // ok to patch ours to make it nodejs Buffer compatible.
  119. Buffer.prototype = Uint8Array.prototype;
  120. Object.defineProperties(Buffer.prototype, {
  121. parent: {
  122. get: function() { return parents.get(this, undefined); }
  123. },
  124. view: {
  125. get: function () {
  126. let view = views.get(this, undefined);
  127. if (view) return view;
  128. view = DataView(this.buffer);
  129. views.set(this, view);
  130. return view;
  131. }
  132. },
  133. toString: {
  134. value: function(encoding, start, end) {
  135. encoding = !!encoding ? (encoding + '').toLowerCase() : 'utf8';
  136. start = Math.max(0, ~~start);
  137. end = Math.min(this.length, end === void(0) ? this.length : ~~end);
  138. return TextDecoder(encoding).decode(this.subarray(start, end));
  139. }
  140. },
  141. toJSON: {
  142. value: function() {
  143. return { type: 'Buffer', data: Array.slice(this, 0) };
  144. }
  145. },
  146. get: {
  147. value: function(offset) {
  148. return this[offset];
  149. }
  150. },
  151. set: {
  152. value: function(offset, value) { this[offset] = value; }
  153. },
  154. copy: {
  155. value: function(target, offset, start, end) {
  156. let length = this.length;
  157. let targetLength = target.length;
  158. offset = isNumber(offset) ? offset : 0;
  159. start = isNumber(start) ? start : 0;
  160. if (start < 0)
  161. throw new RangeError('sourceStart is outside of valid range');
  162. if (end < 0)
  163. throw new RangeError('sourceEnd is outside of valid range');
  164. // If sourceStart > sourceEnd, or targetStart > targetLength,
  165. // zero bytes copied
  166. if (start > end ||
  167. offset > targetLength
  168. )
  169. return 0;
  170. // If `end` is not defined, or if it is defined
  171. // but would overflow `target`, redefine `end`
  172. // so we can copy as much as we can
  173. if (end - start > targetLength - offset ||
  174. end == null) {
  175. let remainingTarget = targetLength - offset;
  176. let remainingSource = length - start;
  177. if (remainingSource <= remainingTarget)
  178. end = length;
  179. else
  180. end = start + remainingTarget;
  181. }
  182. Uint8Array.set(target, this.subarray(start, end), offset);
  183. return end - start;
  184. }
  185. },
  186. slice: {
  187. value: function(start, end) {
  188. let length = this.length;
  189. start = ~~start;
  190. end = end != null ? end : length;
  191. if (start < 0) {
  192. start += length;
  193. if (start < 0) start = 0;
  194. } else if (start > length)
  195. start = length;
  196. if (end < 0) {
  197. end += length;
  198. if (end < 0) end = 0;
  199. } else if (end > length)
  200. end = length;
  201. if (end < start)
  202. end = start;
  203. // This instantiation uses the Uint8Array(buffer, offset, length) version
  204. // of construction to share the same underling data structure
  205. let buffer = new Buffer(this.buffer, start, end - start);
  206. // If buffer has a value, assign its parent value to the
  207. // buffer it shares its underlying structure with. If a slice of
  208. // a slice, then use the root structure
  209. if (buffer.length > 0)
  210. parents.set(buffer, this.parent || this);
  211. return buffer;
  212. }
  213. },
  214. write: {
  215. value: function(string, offset, length, encoding = 'utf8') {
  216. // write(string, encoding);
  217. if (typeof(offset) === 'string' && Number.isNaN(parseInt(offset))) {
  218. ([offset, length, encoding]) = [0, null, offset];
  219. }
  220. // write(string, offset, encoding);
  221. else if (typeof(length) === 'string')
  222. ([length, encoding]) = [null, length];
  223. if (offset < 0 || offset > this.length)
  224. throw new RangeError('offset is outside of valid range');
  225. offset = ~~offset;
  226. // Clamp length if it would overflow buffer, or if its
  227. // undefined
  228. if (length == null || length + offset > this.length)
  229. length = this.length - offset;
  230. let buffer = TextEncoder(encoding).encode(string);
  231. let result = Math.min(buffer.length, length);
  232. if (buffer.length !== length)
  233. buffer = buffer.subarray(0, length);
  234. Uint8Array.set(this, buffer, offset);
  235. return result;
  236. }
  237. },
  238. fill: {
  239. value: function fill(value, start, end) {
  240. let length = this.length;
  241. value = value || 0;
  242. start = start || 0;
  243. end = end || length;
  244. if (typeof(value) === 'string')
  245. value = value.charCodeAt(0);
  246. if (typeof(value) !== 'number' || isNaN(value))
  247. throw TypeError('value is not a number');
  248. if (end < start)
  249. throw new RangeError('end < start');
  250. // Fill 0 bytes; we're done
  251. if (end === start)
  252. return 0;
  253. if (length == 0)
  254. return 0;
  255. if (start < 0 || start >= length)
  256. throw RangeError('start out of bounds');
  257. if (end < 0 || end > length)
  258. throw RangeError('end out of bounds');
  259. let index = start;
  260. while (index < end) this[index++] = value;
  261. }
  262. }
  263. });
  264. // Define nodejs Buffer's getter and setter functions that just proxy
  265. // to internal DataView's equivalent methods.
  266. // TODO do we need to check architecture to see if it's default big/little endian?
  267. [['readUInt16LE', 'getUint16', true],
  268. ['readUInt16BE', 'getUint16', false],
  269. ['readInt16LE', 'getInt16', true],
  270. ['readInt16BE', 'getInt16', false],
  271. ['readUInt32LE', 'getUint32', true],
  272. ['readUInt32BE', 'getUint32', false],
  273. ['readInt32LE', 'getInt32', true],
  274. ['readInt32BE', 'getInt32', false],
  275. ['readFloatLE', 'getFloat32', true],
  276. ['readFloatBE', 'getFloat32', false],
  277. ['readDoubleLE', 'getFloat64', true],
  278. ['readDoubleBE', 'getFloat64', false],
  279. ['readUInt8', 'getUint8'],
  280. ['readInt8', 'getInt8']].forEach(([alias, name, littleEndian]) => {
  281. Object.defineProperty(Buffer.prototype, alias, {
  282. value: function(offset) this.view[name](offset, littleEndian)
  283. });
  284. });
  285. [['writeUInt16LE', 'setUint16', true],
  286. ['writeUInt16BE', 'setUint16', false],
  287. ['writeInt16LE', 'setInt16', true],
  288. ['writeInt16BE', 'setInt16', false],
  289. ['writeUInt32LE', 'setUint32', true],
  290. ['writeUInt32BE', 'setUint32', false],
  291. ['writeInt32LE', 'setInt32', true],
  292. ['writeInt32BE', 'setInt32', false],
  293. ['writeFloatLE', 'setFloat32', true],
  294. ['writeFloatBE', 'setFloat32', false],
  295. ['writeDoubleLE', 'setFloat64', true],
  296. ['writeDoubleBE', 'setFloat64', false],
  297. ['writeUInt8', 'setUint8'],
  298. ['writeInt8', 'setInt8']].forEach(([alias, name, littleEndian]) => {
  299. Object.defineProperty(Buffer.prototype, alias, {
  300. value: function(value, offset) this.view[name](offset, value, littleEndian)
  301. });
  302. });