• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayFrom,
5  MathMax,
6  MathMin,
7  ObjectDefineProperty,
8  ObjectSetPrototypeOf,
9  PromiseResolve,
10  RegExpPrototypeTest,
11  StringPrototypeToLowerCase,
12  Symbol,
13  SymbolIterator,
14  SymbolToStringTag,
15  Uint8Array,
16} = primordials;
17
18const {
19  createBlob,
20  FixedSizeBlobCopyJob,
21} = internalBinding('buffer');
22
23const { TextDecoder } = require('internal/encoding');
24
25const {
26  JSTransferable,
27  kClone,
28  kDeserialize,
29} = require('internal/worker/js_transferable');
30
31const {
32  isAnyArrayBuffer,
33  isArrayBufferView,
34} = require('internal/util/types');
35
36const {
37  createDeferredPromise,
38  customInspectSymbol: kInspect,
39  emitExperimentalWarning,
40} = require('internal/util');
41const { inspect } = require('internal/util/inspect');
42
43const {
44  AbortError,
45  codes: {
46    ERR_INVALID_ARG_TYPE,
47    ERR_BUFFER_TOO_LARGE,
48  }
49} = require('internal/errors');
50
51const {
52  validateObject,
53  isUint32,
54} = require('internal/validators');
55
56const kHandle = Symbol('kHandle');
57const kType = Symbol('kType');
58const kLength = Symbol('kLength');
59
60const disallowedTypeCharacters = /[^\u{0020}-\u{007E}]/u;
61
62let Buffer;
63
64function lazyBuffer() {
65  if (Buffer === undefined)
66    Buffer = require('buffer').Buffer;
67  return Buffer;
68}
69
70function isBlob(object) {
71  return object?.[kHandle] !== undefined;
72}
73
74function getSource(source, encoding) {
75  if (isBlob(source))
76    return [source.size, source[kHandle]];
77
78  if (isAnyArrayBuffer(source)) {
79    source = new Uint8Array(source);
80  } else if (!isArrayBufferView(source)) {
81    source = lazyBuffer().from(`${source}`, encoding);
82  }
83
84  // We copy into a new Uint8Array because the underlying
85  // BackingStores are going to be detached and owned by
86  // the Blob. We also don't want to have to worry about
87  // byte offsets.
88  source = new Uint8Array(source);
89  return [source.byteLength, source];
90}
91
92class InternalBlob extends JSTransferable {
93  constructor(handle, length, type = '') {
94    super();
95    this[kHandle] = handle;
96    this[kType] = type;
97    this[kLength] = length;
98  }
99}
100
101class Blob extends JSTransferable {
102  constructor(sources = [], options = {}) {
103    emitExperimentalWarning('buffer.Blob');
104    if (sources === null ||
105        typeof sources[SymbolIterator] !== 'function' ||
106        typeof sources === 'string') {
107      throw new ERR_INVALID_ARG_TYPE('sources', 'Iterable', sources);
108    }
109    validateObject(options, 'options');
110    const { encoding = 'utf8' } = options;
111    let { type = '' } = options;
112
113    let length = 0;
114    const sources_ = ArrayFrom(sources, (source) => {
115      const { 0: len, 1: src } = getSource(source, encoding);
116      length += len;
117      return src;
118    });
119
120    if (!isUint32(length))
121      throw new ERR_BUFFER_TOO_LARGE(0xFFFFFFFF);
122
123    super();
124    this[kHandle] = createBlob(sources_, length);
125    this[kLength] = length;
126
127    type = `${type}`;
128    this[kType] = RegExpPrototypeTest(disallowedTypeCharacters, type) ?
129      '' : StringPrototypeToLowerCase(type);
130  }
131
132  [kInspect](depth, options) {
133    if (depth < 0)
134      return this;
135
136    const opts = {
137      ...options,
138      depth: options.depth == null ? null : options.depth - 1
139    };
140
141    return `Blob ${inspect({
142      size: this.size,
143      type: this.type,
144    }, opts)}`;
145  }
146
147  [kClone]() {
148    const handle = this[kHandle];
149    const type = this[kType];
150    const length = this[kLength];
151    return {
152      data: { handle, type, length },
153      deserializeInfo: 'internal/blob:InternalBlob'
154    };
155  }
156
157  [kDeserialize]({ handle, type, length }) {
158    this[kHandle] = handle;
159    this[kType] = type;
160    this[kLength] = length;
161  }
162
163  get type() { return this[kType]; }
164
165  get size() { return this[kLength]; }
166
167  slice(start = 0, end = this[kLength], contentType = '') {
168    if (start < 0) {
169      start = MathMax(this[kLength] + start, 0);
170    } else {
171      start = MathMin(start, this[kLength]);
172    }
173    start |= 0;
174
175    if (end < 0) {
176      end = MathMax(this[kLength] + end, 0);
177    } else {
178      end = MathMin(end, this[kLength]);
179    }
180    end |= 0;
181
182    contentType = `${contentType}`;
183    if (RegExpPrototypeTest(disallowedTypeCharacters, contentType)) {
184      contentType = '';
185    } else {
186      contentType = StringPrototypeToLowerCase(contentType);
187    }
188
189    const span = MathMax(end - start, 0);
190
191    return new InternalBlob(
192      this[kHandle].slice(start, start + span), span, contentType);
193  }
194
195  async arrayBuffer() {
196    const job = new FixedSizeBlobCopyJob(this[kHandle]);
197
198    const ret = job.run();
199    if (ret !== undefined)
200      return PromiseResolve(ret);
201
202    const {
203      promise,
204      resolve,
205      reject
206    } = createDeferredPromise();
207    job.ondone = (err, ab) => {
208      if (err !== undefined)
209        return reject(new AbortError());
210      resolve(ab);
211    };
212
213    return promise;
214  }
215
216  async text() {
217    const dec = new TextDecoder();
218    return dec.decode(await this.arrayBuffer());
219  }
220}
221
222ObjectDefineProperty(Blob.prototype, SymbolToStringTag, {
223  configurable: true,
224  value: 'Blob',
225});
226
227InternalBlob.prototype.constructor = Blob;
228ObjectSetPrototypeOf(
229  InternalBlob.prototype,
230  Blob.prototype);
231
232module.exports = {
233  Blob,
234  InternalBlob,
235  isBlob,
236};
237