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