diff options
author | 2023-08-31 22:31:01 -0700 | |
---|---|---|
committer | 2023-08-31 22:31:01 -0700 | |
commit | d0a58d329f67708fa3427882f04c7268746d937d (patch) | |
tree | f83de8b45833e9976f258ddf9bd31fbfd602f82d | |
parent | b07bb3ce174b500b567bb760e82d4696c88a8b28 (diff) | |
download | bun-dave/es5-class-helper.tar.gz bun-dave/es5-class-helper.tar.zst bun-dave/es5-class-helper.zip |
add es5ClassCompatdave/es5-class-helper
-rw-r--r-- | src/js/internal/shared.ts | 23 | ||||
-rw-r--r-- | src/js/node/fs.js | 1047 | ||||
-rw-r--r-- | src/js/node/http.ts | 6 | ||||
-rw-r--r-- | src/js/node/net.js | 941 | ||||
-rw-r--r-- | src/js/node/tls.js | 310 |
5 files changed, 1128 insertions, 1199 deletions
diff --git a/src/js/internal/shared.ts b/src/js/internal/shared.ts index 98a5b0a71..0b20572ef 100644 --- a/src/js/internal/shared.ts +++ b/src/js/internal/shared.ts @@ -29,8 +29,31 @@ function hideFromStack(...fns) { } } +/** + * This utility ensures that old JS code that uses functions for classes still works. + * Taken from https://github.com/microsoft/vscode/blob/main/src/vs/workbench/api/common/extHostTypes.ts + */ +function es5ClassCompat(target: Function): any { + const interceptFunctions = { + apply: function () { + const args = arguments.length === 1 ? [] : arguments[1]; + return Reflect.construct(target, args, arguments[0].constructor); + }, + call: function () { + if (arguments.length === 0) { + return Reflect.construct(target, []); + } else { + const [thisArg, ...restArgs] = arguments; + return Reflect.construct(target, restArgs, thisArg.constructor); + } + }, + }; + return Object.assign(target, interceptFunctions); +} + export default { NotImplementedError, throwNotImplemented, hideFromStack, + es5ClassCompat, }; diff --git a/src/js/node/fs.js b/src/js/node/fs.js index ab6816904..48960d3ee 100644 --- a/src/js/node/fs.js +++ b/src/js/node/fs.js @@ -5,9 +5,9 @@ const EventEmitter = require("node:events"); const promises = require("node:fs/promises"); const Stream = require("node:stream"); const { isArrayBufferView } = require("node:util/types"); +const { es5ClassCompat } = require("$shared"); const constants = $processBindingConstants.fs; -const { COPYFILE_EXCL } = constants; var fs = Bun.fs(); class FSWatcher extends EventEmitter { @@ -382,338 +382,320 @@ var defaultReadStreamOptions = { autoDestroy: true, }; -var ReadStreamClass; +class ReadStream extends Stream._getNativeReadableStreamPrototype(2, Stream.Readable) { + constructor(pathOrFd, options = defaultReadStreamOptions) { + if (typeof options !== "object" || !options) { + throw new TypeError("Expected options to be an object"); + } -ReadStream = (function (InternalReadStream) { - ReadStreamClass = InternalReadStream; - Object.defineProperty(ReadStreamClass.prototype, Symbol.toStringTag, { - value: "ReadStream", - enumerable: false, - }); - function ReadStream(path, options) { - return new InternalReadStream(path, options); - } - ReadStream.prototype = InternalReadStream.prototype; - return Object.defineProperty(ReadStream, Symbol.hasInstance, { - value(instance) { - return instance instanceof InternalReadStream; - }, - }); -})( - class ReadStream extends Stream._getNativeReadableStreamPrototype(2, Stream.Readable) { - constructor(pathOrFd, options = defaultReadStreamOptions) { - if (typeof options !== "object" || !options) { - throw new TypeError("Expected options to be an object"); - } + var { + flags = defaultReadStreamOptions.flags, + encoding = defaultReadStreamOptions.encoding, + mode = defaultReadStreamOptions.mode, + autoClose = defaultReadStreamOptions.autoClose, + emitClose = defaultReadStreamOptions.emitClose, + start = defaultReadStreamOptions.start, + end = defaultReadStreamOptions.end, + autoDestroy = defaultReadStreamOptions.autoClose, + fs = defaultReadStreamOptions.fs, + highWaterMark = defaultReadStreamOptions.highWaterMark, + fd = defaultReadStreamOptions.fd, + } = options; + + if (pathOrFd?.constructor?.name === "URL") { + pathOrFd = Bun.fileURLToPath(pathOrFd); + } - var { - flags = defaultReadStreamOptions.flags, - encoding = defaultReadStreamOptions.encoding, - mode = defaultReadStreamOptions.mode, - autoClose = defaultReadStreamOptions.autoClose, - emitClose = defaultReadStreamOptions.emitClose, - start = defaultReadStreamOptions.start, - end = defaultReadStreamOptions.end, - autoDestroy = defaultReadStreamOptions.autoClose, - fs = defaultReadStreamOptions.fs, - highWaterMark = defaultReadStreamOptions.highWaterMark, - fd = defaultReadStreamOptions.fd, - } = options; - - if (pathOrFd?.constructor?.name === "URL") { + // This is kinda hacky but we create a temporary object to assign props that we will later pull into the `this` context after we call super + var tempThis = {}; + if (fd != null) { + if (typeof fd !== "number") { + throw new TypeError("Expected options.fd to be a number"); + } + tempThis.fd = tempThis[readStreamPathOrFdSymbol] = fd; + tempThis.autoClose = false; + } else if (typeof pathOrFd === "string") { + if (pathOrFd.startsWith("file://")) { pathOrFd = Bun.fileURLToPath(pathOrFd); } + if (pathOrFd.length === 0) { + throw new TypeError("Expected path to be a non-empty string"); + } + tempThis.path = tempThis.file = tempThis[readStreamPathOrFdSymbol] = pathOrFd; + } else if (typeof pathOrFd === "number") { + pathOrFd |= 0; + if (pathOrFd < 0) { + throw new TypeError("Expected fd to be a positive integer"); + } + tempThis.fd = tempThis[readStreamPathOrFdSymbol] = pathOrFd; - // This is kinda hacky but we create a temporary object to assign props that we will later pull into the `this` context after we call super - var tempThis = {}; - if (fd != null) { - if (typeof fd !== "number") { - throw new TypeError("Expected options.fd to be a number"); - } - tempThis.fd = tempThis[readStreamPathOrFdSymbol] = fd; - tempThis.autoClose = false; - } else if (typeof pathOrFd === "string") { - if (pathOrFd.startsWith("file://")) { - pathOrFd = Bun.fileURLToPath(pathOrFd); - } - if (pathOrFd.length === 0) { - throw new TypeError("Expected path to be a non-empty string"); - } - tempThis.path = tempThis.file = tempThis[readStreamPathOrFdSymbol] = pathOrFd; - } else if (typeof pathOrFd === "number") { - pathOrFd |= 0; - if (pathOrFd < 0) { - throw new TypeError("Expected fd to be a positive integer"); - } - tempThis.fd = tempThis[readStreamPathOrFdSymbol] = pathOrFd; + tempThis.autoClose = false; + } else { + throw new TypeError("Expected a path or file descriptor"); + } - tempThis.autoClose = false; - } else { - throw new TypeError("Expected a path or file descriptor"); - } + // If fd not open for this file, open it + if (tempThis.fd === undefined) { + // NOTE: this fs is local to constructor, from options + tempThis.fd = fs.openSync(pathOrFd, flags, mode); + } + // Get FileRef from fd + var fileRef = Bun.file(tempThis.fd); + + // Get the stream controller + // We need the pointer to the underlying stream controller for the NativeReadable + var stream = fileRef.stream(); + var native = $direct(stream); + if (!native) { + $debug("no native readable stream"); + throw new Error("no native readable stream"); + } + var { stream: ptr } = native; + + super(ptr, { + ...options, + encoding, + autoDestroy, + autoClose, + emitClose, + highWaterMark, + }); - // If fd not open for this file, open it - if (tempThis.fd === undefined) { - // NOTE: this fs is local to constructor, from options - tempThis.fd = fs.openSync(pathOrFd, flags, mode); - } - // Get FileRef from fd - var fileRef = Bun.file(tempThis.fd); - - // Get the stream controller - // We need the pointer to the underlying stream controller for the NativeReadable - var stream = fileRef.stream(); - var native = $direct(stream); - if (!native) { - $debug("no native readable stream"); - throw new Error("no native readable stream"); - } - var { stream: ptr } = native; - - super(ptr, { - ...options, - encoding, - autoDestroy, - autoClose, - emitClose, - highWaterMark, - }); - - // Assign the tempThis props to this - Object.assign(this, tempThis); - this.#fileRef = fileRef; - - this.end = end; - this._read = this.#internalRead; - this.start = start; - this.flags = flags; - this.mode = mode; - this.emitClose = emitClose; - - this[readStreamPathFastPathSymbol] = - start === 0 && - end === Infinity && - autoClose && - fs === defaultReadStreamOptions.fs && - // is it an encoding which we don't need to decode? - (encoding === "buffer" || - encoding === "binary" || - encoding == null || - encoding === "utf-8" || - encoding === "utf8"); - this._readableState.autoClose = autoDestroy = autoClose; - this._readableState.highWaterMark = highWaterMark; - - if (start !== undefined) { - this.pos = start; - } + // Assign the tempThis props to this + Object.assign(this, tempThis); + this.#fileRef = fileRef; + + this.end = end; + this._read = this.#internalRead; + this.start = start; + this.flags = flags; + this.mode = mode; + this.emitClose = emitClose; + + this[readStreamPathFastPathSymbol] = + start === 0 && + end === Infinity && + autoClose && + fs === defaultReadStreamOptions.fs && + // is it an encoding which we don't need to decode? + (encoding === "buffer" || + encoding === "binary" || + encoding == null || + encoding === "utf-8" || + encoding === "utf8"); + this._readableState.autoClose = autoDestroy = autoClose; + this._readableState.highWaterMark = highWaterMark; + + if (start !== undefined) { + this.pos = start; } - #fileRef; - #fs; - file; - path; - fd = null; - flags; - mode; - start; - end; - pos; - bytesRead = 0; - #fileSize = -1; - _read; - - [readStreamSymbol] = true; - [readStreamPathOrFdSymbol]; - [readStreamPathFastPathSymbol]; - - _construct(callback) { - if (super._construct) { - super._construct(callback); - } else { - callback(); - } - this.emit("open", this.fd); - this.emit("ready"); + } + #fileRef; + #fs; + file; + path; + fd = null; + flags; + mode; + start; + end; + pos; + bytesRead = 0; + #fileSize = -1; + _read; + + [readStreamSymbol] = true; + [readStreamPathOrFdSymbol]; + [readStreamPathFastPathSymbol]; + + _construct(callback) { + if (super._construct) { + super._construct(callback); + } else { + callback(); } + this.emit("open", this.fd); + this.emit("ready"); + } - _destroy(err, cb) { - super._destroy(err, cb); - try { - var fd = this.fd; - this[readStreamPathFastPathSymbol] = false; - - if (!fd) { - cb(err); - } else { - this.#fs.close(fd, er => { - cb(er || err); - }); - this.fd = null; - } - } catch (e) { - throw e; + _destroy(err, cb) { + super._destroy(err, cb); + try { + var fd = this.fd; + this[readStreamPathFastPathSymbol] = false; + + if (!fd) { + cb(err); + } else { + this.#fs.close(fd, er => { + cb(er || err); + }); + this.fd = null; } + } catch (e) { + throw e; } + } - close(cb) { - if (typeof cb === "function") Stream.eos(this, cb); - this.destroy(); - } + close(cb) { + if (typeof cb === "function") Stream.eos(this, cb); + this.destroy(); + } - push(chunk) { - // Is it even possible for this to be less than 1? - var bytesRead = chunk?.length ?? 0; - if (bytesRead > 0) { - this.bytesRead += bytesRead; - var currPos = this.pos; - // Handle case of going through bytes before pos if bytesRead is less than pos - // If pos is undefined, we are reading through the whole file - // Otherwise we started from somewhere in the middle of the file - if (currPos !== undefined) { - // At this point we still haven't hit our `start` point - // We should discard this chunk and exit - if (this.bytesRead < currPos) { - return true; - } - // At this point, bytes read is greater than our starting position - // If the current position is still the starting position, that means - // this is the first chunk where we care about the bytes read - // and we need to subtract the bytes read from the start position (n) and slice the last n bytes - if (currPos === this.start) { - var n = this.bytesRead - currPos; - chunk = chunk.slice(-n); - var [_, ...rest] = arguments; - this.pos = this.bytesRead; - if (this.end !== undefined && this.bytesRead > this.end) { - chunk = chunk.slice(0, this.end - this.start + 1); - } - return super.push(chunk, ...rest); - } - var end = this.end; - // This is multi-chunk read case where we go passed the end of the what we want to read in the last chunk - if (end !== undefined && this.bytesRead > end) { - chunk = chunk.slice(0, end - currPos + 1); - var [_, ...rest] = arguments; - this.pos = this.bytesRead; - return super.push(chunk, ...rest); + push(chunk) { + // Is it even possible for this to be less than 1? + var bytesRead = chunk?.length ?? 0; + if (bytesRead > 0) { + this.bytesRead += bytesRead; + var currPos = this.pos; + // Handle case of going through bytes before pos if bytesRead is less than pos + // If pos is undefined, we are reading through the whole file + // Otherwise we started from somewhere in the middle of the file + if (currPos !== undefined) { + // At this point we still haven't hit our `start` point + // We should discard this chunk and exit + if (this.bytesRead < currPos) { + return true; + } + // At this point, bytes read is greater than our starting position + // If the current position is still the starting position, that means + // this is the first chunk where we care about the bytes read + // and we need to subtract the bytes read from the start position (n) and slice the last n bytes + if (currPos === this.start) { + var n = this.bytesRead - currPos; + chunk = chunk.slice(-n); + var [_, ...rest] = arguments; + this.pos = this.bytesRead; + if (this.end !== undefined && this.bytesRead > this.end) { + chunk = chunk.slice(0, this.end - this.start + 1); } + return super.push(chunk, ...rest); + } + var end = this.end; + // This is multi-chunk read case where we go passed the end of the what we want to read in the last chunk + if (end !== undefined && this.bytesRead > end) { + chunk = chunk.slice(0, end - currPos + 1); + var [_, ...rest] = arguments; this.pos = this.bytesRead; + return super.push(chunk, ...rest); } + this.pos = this.bytesRead; } - - return super.push(...arguments); } - // # + return super.push(...arguments); + } - // n should be the the highwatermark passed from Readable.read when calling internal _read (_read is set to this private fn in this class) - #internalRead(n) { - // pos is the current position in the file - // by default, if a start value is provided, pos starts at this.start - var { pos, end, bytesRead, fd, encoding } = this; + // # - n = - pos !== undefined // if there is a pos, then we are reading from that specific position in the file - ? Math.min(end - pos + 1, n) // takes smaller of length of the rest of the file to read minus the cursor position, or the highwatermark - : Math.min(end - bytesRead + 1, n); // takes the smaller of the length of the rest of the file from the bytes that we have marked read, or the highwatermark + // n should be the the highwatermark passed from Readable.read when calling internal _read (_read is set to this private fn in this class) + #internalRead(n) { + // pos is the current position in the file + // by default, if a start value is provided, pos starts at this.start + var { pos, end, bytesRead, fd, encoding } = this; - $debug("n @ fs.ReadStream.#internalRead, after clamp", n); + n = + pos !== undefined // if there is a pos, then we are reading from that specific position in the file + ? Math.min(end - pos + 1, n) // takes smaller of length of the rest of the file to read minus the cursor position, or the highwatermark + : Math.min(end - bytesRead + 1, n); // takes the smaller of the length of the rest of the file from the bytes that we have marked read, or the highwatermark - // If n is 0 or less, then we read all the file, push null to stream, ending it - if (n <= 0) { - this.push(null); - return; - } + $debug("n @ fs.ReadStream.#internalRead, after clamp", n); - // At this point, n is the lesser of the length of the rest of the file to read or the highwatermark - // Which means n is the maximum number of bytes to read - - // Basically if we don't know the file size yet, then check it - // Then if n is bigger than fileSize, set n to be fileSize - // This is a fast path to avoid allocating more than the file size for a small file (is this respected by native stream though) - if (this.#fileSize === -1 && bytesRead === 0 && pos === undefined) { - var stat = fstatSync(fd); - this.#fileSize = stat.size; - if (this.#fileSize > 0 && n > this.#fileSize) { - n = this.#fileSize + 1; - } - $debug("fileSize", this.#fileSize); - } + // If n is 0 or less, then we read all the file, push null to stream, ending it + if (n <= 0) { + this.push(null); + return; + } - // At this point, we know the file size and how much we want to read of the file - this[kIoDone] = false; - var res = super._read(n); - $debug("res -- undefined? why?", res); - if ($isPromise(res)) { - var then = res?.then; - if (then && $isCallable(then)) { - res.then( - () => { - this[kIoDone] = true; - // Tell ._destroy() that it's safe to close the fd now. - if (this.destroyed) { - this.emit(kIoDone); - } - }, - er => { - this[kIoDone] = true; - this.#errorOrDestroy(er); - }, - ); - } - } else { - this[kIoDone] = true; - if (this.destroyed) { - this.emit(kIoDone); - this.#errorOrDestroy(new Error("ERR_STREAM_PREMATURE_CLOSE")); - } + // At this point, n is the lesser of the length of the rest of the file to read or the highwatermark + // Which means n is the maximum number of bytes to read + + // Basically if we don't know the file size yet, then check it + // Then if n is bigger than fileSize, set n to be fileSize + // This is a fast path to avoid allocating more than the file size for a small file (is this respected by native stream though) + if (this.#fileSize === -1 && bytesRead === 0 && pos === undefined) { + var stat = fstatSync(fd); + this.#fileSize = stat.size; + if (this.#fileSize > 0 && n > this.#fileSize) { + n = this.#fileSize + 1; } + $debug("fileSize", this.#fileSize); } - #errorOrDestroy(err, sync = null) { - var { - _readableState: r = { destroyed: false, autoDestroy: false }, - _writableState: w = { destroyed: false, autoDestroy: false }, - } = this; - - if (w?.destroyed || r?.destroyed) { - return this; + // At this point, we know the file size and how much we want to read of the file + this[kIoDone] = false; + var res = super._read(n); + $debug("res -- undefined? why?", res); + if ($isPromise(res)) { + var then = res?.then; + if (then && $isCallable(then)) { + res.then( + () => { + this[kIoDone] = true; + // Tell ._destroy() that it's safe to close the fd now. + if (this.destroyed) { + this.emit(kIoDone); + } + }, + er => { + this[kIoDone] = true; + this.#errorOrDestroy(er); + }, + ); } - if (r?.autoDestroy || w?.autoDestroy) this.destroy(err); - else if (err) { - this.emit("error", err); + } else { + this[kIoDone] = true; + if (this.destroyed) { + this.emit(kIoDone); + this.#errorOrDestroy(new Error("ERR_STREAM_PREMATURE_CLOSE")); } } + } - pause() { - this[readStreamPathFastPathSymbol] = false; - return super.pause(); - } + #errorOrDestroy(err, sync = null) { + var { + _readableState: r = { destroyed: false, autoDestroy: false }, + _writableState: w = { destroyed: false, autoDestroy: false }, + } = this; - resume() { - this[readStreamPathFastPathSymbol] = false; - return super.resume(); + if (w?.destroyed || r?.destroyed) { + return this; } - - unshift(...args) { - this[readStreamPathFastPathSymbol] = false; - return super.unshift(...args); + if (r?.autoDestroy || w?.autoDestroy) this.destroy(err); + else if (err) { + this.emit("error", err); } + } - pipe(dest, pipeOpts) { - if (this[readStreamPathFastPathSymbol] && (pipeOpts?.end ?? true) && this._readableState?.pipes?.length === 0) { - if (writeStreamPathFastPathSymbol in dest && dest[writeStreamPathFastPathSymbol]) { - if (dest[writeStreamPathFastPathCallSymbol](this, pipeOpts)) { - return this; - } + pause() { + this[readStreamPathFastPathSymbol] = false; + return super.pause(); + } + + resume() { + this[readStreamPathFastPathSymbol] = false; + return super.resume(); + } + + unshift(...args) { + this[readStreamPathFastPathSymbol] = false; + return super.unshift(...args); + } + + pipe(dest, pipeOpts) { + if (this[readStreamPathFastPathSymbol] && (pipeOpts?.end ?? true) && this._readableState?.pipes?.length === 0) { + if (writeStreamPathFastPathSymbol in dest && dest[writeStreamPathFastPathSymbol]) { + if (dest[writeStreamPathFastPathCallSymbol](this, pipeOpts)) { + return this; } } - - this[readStreamPathFastPathSymbol] = false; - return super.pipe(dest, pipeOpts); } - }, -); + + this[readStreamPathFastPathSymbol] = false; + return super.pipe(dest, pipeOpts); + } +} +es5ClassCompat(ReadStream); function createReadStream(path, options) { return new ReadStream(path, options); @@ -734,324 +716,287 @@ var defaultWriteStreamOptions = { }, }; -var WriteStreamClass; -WriteStream = (function (InternalWriteStream) { - WriteStreamClass = InternalWriteStream; - Object.defineProperty(WriteStreamClass.prototype, Symbol.toStringTag, { - value: "WritesStream", - enumerable: false, - }); - - function WriteStream(path, options) { - return new InternalWriteStream(path, options); - } - WriteStream.prototype = InternalWriteStream.prototype; - return Object.defineProperty(WriteStream, Symbol.hasInstance, { - value(instance) { - return instance instanceof InternalWriteStream; - }, - }); -})( - class WriteStream extends Stream.NativeWritable { - constructor(path, options = defaultWriteStreamOptions) { - if (!options) { - throw new TypeError("Expected options to be an object"); - } - - var { - fs = defaultWriteStreamOptions.fs, - start = defaultWriteStreamOptions.start, - flags = defaultWriteStreamOptions.flags, - mode = defaultWriteStreamOptions.mode, - autoClose = true, - emitClose = false, - autoDestroy = autoClose, - encoding = defaultWriteStreamOptions.encoding, - fd = defaultWriteStreamOptions.fd, - pos = defaultWriteStreamOptions.pos, - } = options; - - var tempThis = {}; - if (fd != null) { - if (typeof fd !== "number") { - throw new Error("Expected options.fd to be a number"); - } - tempThis.fd = fd; - tempThis[writeStreamPathFastPathSymbol] = false; - } else if (typeof path === "string") { - if (path.length === 0) { - throw new TypeError("Expected a non-empty path"); - } - - if (path.startsWith("file:")) { - path = Bun.fileURLToPath(path); - } +class WriteStream extends Stream.NativeWritable { + constructor(path, options = defaultWriteStreamOptions) { + if (!options) { + throw new TypeError("Expected options to be an object"); + } - tempThis.path = path; - tempThis.fd = null; - tempThis[writeStreamPathFastPathSymbol] = - autoClose && - (start === undefined || start === 0) && - fs.write === defaultWriteStreamOptions.fs.write && - fs.close === defaultWriteStreamOptions.fs.close; + var { + fs = defaultWriteStreamOptions.fs, + start = defaultWriteStreamOptions.start, + flags = defaultWriteStreamOptions.flags, + mode = defaultWriteStreamOptions.mode, + autoClose = true, + emitClose = false, + autoDestroy = autoClose, + encoding = defaultWriteStreamOptions.encoding, + fd = defaultWriteStreamOptions.fd, + pos = defaultWriteStreamOptions.pos, + } = options; + + var tempThis = {}; + if (fd != null) { + if (typeof fd !== "number") { + throw new Error("Expected options.fd to be a number"); } - - if (tempThis.fd == null) { - tempThis.fd = fs.openSync(path, flags, mode); + tempThis.fd = fd; + tempThis[writeStreamPathFastPathSymbol] = false; + } else if (typeof path === "string") { + if (path.length === 0) { + throw new TypeError("Expected a non-empty path"); } - super(tempThis.fd, { - ...options, - decodeStrings: false, - autoDestroy, - emitClose, - fd: tempThis, - }); - Object.assign(this, tempThis); - - if (typeof fs?.write !== "function") { - throw new TypeError("Expected fs.write to be a function"); + if (path.startsWith("file:")) { + path = Bun.fileURLToPath(path); } - if (typeof fs?.close !== "function") { - throw new TypeError("Expected fs.close to be a function"); - } + tempThis.path = path; + tempThis.fd = null; + tempThis[writeStreamPathFastPathSymbol] = + autoClose && + (start === undefined || start === 0) && + fs.write === defaultWriteStreamOptions.fs.write && + fs.close === defaultWriteStreamOptions.fs.close; + } - if (typeof fs?.open !== "function") { - throw new TypeError("Expected fs.open to be a function"); - } + if (tempThis.fd == null) { + tempThis.fd = fs.openSync(path, flags, mode); + } - if (typeof path === "object" && path) { - if (path instanceof URL) { - path = Bun.fileURLToPath(path); - } - } + super(tempThis.fd, { + ...options, + decodeStrings: false, + autoDestroy, + emitClose, + fd: tempThis, + }); + Object.assign(this, tempThis); - if (typeof path !== "string" && typeof fd !== "number") { - throw new TypeError("Expected a path or file descriptor"); - } + if (typeof fs?.write !== "function") { + throw new TypeError("Expected fs.write to be a function"); + } - this.start = start; - this.#fs = fs; - this.flags = flags; - this.mode = mode; + if (typeof fs?.close !== "function") { + throw new TypeError("Expected fs.close to be a function"); + } - if (this.start !== undefined) { - this.pos = this.start; - } + if (typeof fs?.open !== "function") { + throw new TypeError("Expected fs.open to be a function"); + } - if (encoding !== defaultWriteStreamOptions.encoding) { - this.setDefaultEncoding(encoding); - if (encoding !== "buffer" && encoding !== "utf8" && encoding !== "utf-8" && encoding !== "binary") { - this[writeStreamPathFastPathSymbol] = false; - } + if (typeof path === "object" && path) { + if (path instanceof URL) { + path = Bun.fileURLToPath(path); } } - get autoClose() { - return this._writableState.autoDestroy; + if (typeof path !== "string" && typeof fd !== "number") { + throw new TypeError("Expected a path or file descriptor"); } - set autoClose(val) { - this._writableState.autoDestroy = val; - } + this.start = start; + this.#fs = fs; + this.flags = flags; + this.mode = mode; - destroySoon = this.end; // TODO: what is this for? - - // noop, node has deprecated this - open() {} - - path; - fd; - flags; - mode; - #fs; - bytesWritten = 0; - pos; - [writeStreamPathFastPathSymbol]; - [writeStreamSymbol] = true; - start; - - [writeStreamPathFastPathCallSymbol](readStream, pipeOpts) { - if (!this[writeStreamPathFastPathSymbol]) { - return false; - } + if (this.start !== undefined) { + this.pos = this.start; + } - if (this.fd !== null) { + if (encoding !== defaultWriteStreamOptions.encoding) { + this.setDefaultEncoding(encoding); + if (encoding !== "buffer" && encoding !== "utf8" && encoding !== "utf-8" && encoding !== "binary") { this[writeStreamPathFastPathSymbol] = false; - return false; } - - this[kIoDone] = false; - readStream[kIoDone] = false; - return Bun.write(this[writeStreamPathFastPathSymbol], readStream[readStreamPathOrFdSymbol]).then( - bytesWritten => { - readStream[kIoDone] = this[kIoDone] = true; - this.bytesWritten += bytesWritten; - readStream.bytesRead += bytesWritten; - this.end(); - readStream.close(); - }, - err => { - readStream[kIoDone] = this[kIoDone] = true; - this.#errorOrDestroy(err); - readStream.emit("error", err); - }, - ); } + } + + get autoClose() { + return this._writableState.autoDestroy; + } - isBunFastPathEnabled() { - return this[writeStreamPathFastPathSymbol]; + set autoClose(val) { + this._writableState.autoDestroy = val; + } + + destroySoon = this.end; // TODO: what is this for? + + // noop, node has deprecated this + open() {} + + path; + fd; + flags; + mode; + #fs; + bytesWritten = 0; + pos; + [writeStreamPathFastPathSymbol]; + [writeStreamSymbol] = true; + start; + + [writeStreamPathFastPathCallSymbol](readStream, pipeOpts) { + if (!this[writeStreamPathFastPathSymbol]) { + return false; } - disableBunFastPath() { + if (this.fd !== null) { this[writeStreamPathFastPathSymbol] = false; + return false; } - #handleWrite(er, bytes) { - if (er) { - return this.#errorOrDestroy(er); - } + this[kIoDone] = false; + readStream[kIoDone] = false; + return Bun.write(this[writeStreamPathFastPathSymbol], readStream[readStreamPathOrFdSymbol]).then( + bytesWritten => { + readStream[kIoDone] = this[kIoDone] = true; + this.bytesWritten += bytesWritten; + readStream.bytesRead += bytesWritten; + this.end(); + readStream.close(); + }, + err => { + readStream[kIoDone] = this[kIoDone] = true; + this.#errorOrDestroy(err); + readStream.emit("error", err); + }, + ); + } - this.bytesWritten += bytes; - } + isBunFastPathEnabled() { + return this[writeStreamPathFastPathSymbol]; + } - #internalClose(err, cb) { - this[writeStreamPathFastPathSymbol] = false; - var fd = this.fd; - this.#fs.close(fd, er => { - this.fd = null; - cb(err || er); - }); + disableBunFastPath() { + this[writeStreamPathFastPathSymbol] = false; + } + + #handleWrite(er, bytes) { + if (er) { + return this.#errorOrDestroy(er); } - _construct(callback) { - if (typeof this.fd === "number") { - callback(); - return; - } + this.bytesWritten += bytes; + } + #internalClose(err, cb) { + this[writeStreamPathFastPathSymbol] = false; + var fd = this.fd; + this.#fs.close(fd, er => { + this.fd = null; + cb(err || er); + }); + } + + _construct(callback) { + if (typeof this.fd === "number") { callback(); - this.emit("open", this.fd); - this.emit("ready"); + return; } - _destroy(err, cb) { - if (this.fd === null) { - return cb(err); - } + callback(); + this.emit("open", this.fd); + this.emit("ready"); + } - if (this[kIoDone]) { - this.once(kIoDone, () => this.#internalClose(err, cb)); - return; - } + _destroy(err, cb) { + if (this.fd === null) { + return cb(err); + } - this.#internalClose(err, cb); + if (this[kIoDone]) { + this.once(kIoDone, () => this.#internalClose(err, cb)); + return; } - [kIoDone] = false; + this.#internalClose(err, cb); + } - close(cb) { - if (cb) { - if (this.closed) { - process.nextTick(cb); - return; - } - this.on("close", cb); - } + [kIoDone] = false; - // If we are not autoClosing, we should call - // destroy on 'finish'. - if (!this.autoClose) { - this.on("finish", this.destroy); + close(cb) { + if (cb) { + if (this.closed) { + process.nextTick(cb); + return; } + this.on("close", cb); + } - // We use end() instead of destroy() because of - // https://github.com/nodejs/node/issues/2006 - this.end(); + // If we are not autoClosing, we should call + // destroy on 'finish'. + if (!this.autoClose) { + this.on("finish", this.destroy); } - write(chunk, encoding = this._writableState.defaultEncoding, cb) { - this[writeStreamPathFastPathSymbol] = false; - if (typeof chunk === "string") { - chunk = Buffer.from(chunk, encoding); - } + // We use end() instead of destroy() because of + // https://github.com/nodejs/node/issues/2006 + this.end(); + } - // TODO: Replace this when something like lseek is available - var native = this.pos === undefined; - const callback = native - ? (err, bytes) => { - this[kIoDone] = false; - this.#handleWrite(err, bytes); - this.emit(kIoDone); - if (cb) !err ? cb() : cb(err); - } - : () => {}; - this[kIoDone] = true; - if (this._write) { - return this._write(chunk, encoding, callback); - } else { - return super.write(chunk, encoding, callback, native); - } + write(chunk, encoding = this._writableState.defaultEncoding, cb) { + this[writeStreamPathFastPathSymbol] = false; + if (typeof chunk === "string") { + chunk = Buffer.from(chunk, encoding); } - end(chunk, encoding, cb) { - var native = this.pos === undefined; - return super.end(chunk, encoding, cb, native); + // TODO: Replace this when something like lseek is available + var native = this.pos === undefined; + const callback = native + ? (err, bytes) => { + this[kIoDone] = false; + this.#handleWrite(err, bytes); + this.emit(kIoDone); + if (cb) !err ? cb() : cb(err); + } + : () => {}; + this[kIoDone] = true; + if (this._write) { + return this._write(chunk, encoding, callback); + } else { + return super.write(chunk, encoding, callback, native); } + } - _write = undefined; - _writev = undefined; + end(chunk, encoding, cb) { + var native = this.pos === undefined; + return super.end(chunk, encoding, cb, native); + } - get pending() { - return this.fd === null; - } + _write = undefined; + _writev = undefined; - _destroy(err, cb) { - this.close(err, cb); - } + get pending() { + return this.fd === null; + } - #errorOrDestroy(err) { - var { - _readableState: r = { destroyed: false, autoDestroy: false }, - _writableState: w = { destroyed: false, autoDestroy: false }, - } = this; + _destroy(err, cb) { + this.close(err, cb); + } - if (w?.destroyed || r?.destroyed) { - return this; - } - if (r?.autoDestroy || w?.autoDestroy) this.destroy(err); - else if (err) { - this.emit("error", err); - } + #errorOrDestroy(err) { + var { + _readableState: r = { destroyed: false, autoDestroy: false }, + _writableState: w = { destroyed: false, autoDestroy: false }, + } = this; + + if (w?.destroyed || r?.destroyed) { + return this; } - }, -); + if (r?.autoDestroy || w?.autoDestroy) this.destroy(err); + else if (err) { + this.emit("error", err); + } + } +} +es5ClassCompat(WriteStream); function createWriteStream(path, options) { // const WriteStream = getLazyWriteStream(); return new WriteStream(path, options); } -// NOTE: This was too smart and doesn't actually work -// WriteStream = Object.defineProperty( -// function WriteStream(path, options) { -// var _InternalWriteStream = getLazyWriteStream(); -// return new _InternalWriteStream(path, options); -// }, -// Symbol.hasInstance, -// { value: (instance) => instance[writeStreamSymbol] === true }, -// ); - -// ReadStream = Object.defineProperty( -// function ReadStream(path, options) { -// var _InternalReadStream = getLazyReadStream(); -// return new _InternalReadStream(path, options); -// }, -// Symbol.hasInstance, -// { value: (instance) => instance[readStreamSymbol] === true }, -// ); - Object.defineProperties(fs, { createReadStream: { value: createReadStream, @@ -1209,10 +1154,6 @@ export default { writeSync, writev, writevSync, - [Symbol.for("::bunternal::")]: { - ReadStreamClass, - WriteStreamClass, - }, // get WriteStream() { // return getLazyWriteStream(); // }, diff --git a/src/js/node/http.ts b/src/js/node/http.ts index 0d41cf996..f1dc5c4d5 100644 --- a/src/js/node/http.ts +++ b/src/js/node/http.ts @@ -1,4 +1,5 @@ // Hardcoded module "node:http" +const { es5ClassCompat } = require("$shared"); const EventEmitter = require("node:events"); const { isTypedArray } = require("node:util/types"); const { Duplex, Readable, Writable } = require("node:stream"); @@ -555,6 +556,7 @@ class Server extends EventEmitter { } setTimeout(msecs, callback) {} } +es5ClassCompat(Server); function assignHeaders(object, req) { var headers = req.headers.toJSON(); @@ -736,6 +738,7 @@ class IncomingMessage extends Readable { throw new Error("not implemented"); } } +es5ClassCompat(IncomingMessage); function emitErrorNt(msg, err, callback) { callback(err); @@ -952,6 +955,7 @@ class OutgoingMessage extends Writable { return this; } } +es5ClassCompat(OutgoingMessage); let OriginalWriteHeadFn, OriginalImplicitHeadFn; class ServerResponse extends Writable { @@ -1188,6 +1192,7 @@ class ServerResponse extends Writable { return this; } } +es5ClassCompat(ServerResponse); OriginalWriteHeadFn = ServerResponse.prototype.writeHead; OriginalImplicitHeadFn = ServerResponse.prototype._implicitHeader; @@ -1607,6 +1612,7 @@ class ClientRequest extends OutgoingMessage { return this; } } +es5ClassCompat(ClientRequest); function urlToHttpOptions(url) { var { protocol, hostname, hash, search, pathname, href, port, username, password } = url; diff --git a/src/js/node/net.js b/src/js/node/net.js index 0513f44d3..c4efe2f62 100644 --- a/src/js/node/net.js +++ b/src/js/node/net.js @@ -21,6 +21,7 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. const { Duplex } = require("node:stream"); const EventEmitter = require("node:events"); +const { es5ClassCompat } = require("$shared"); // IPv4 Segment const v4Seg = "(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"; @@ -66,394 +67,398 @@ const bunSocketServerOptions = Symbol.for("::bunnetserveroptions::"); const bunSocketInternal = Symbol.for("::bunnetsocketinternal::"); const bunTLSConnectOptions = Symbol.for("::buntlsconnectoptions::"); -var SocketClass; -const Socket = (function (InternalSocket) { - SocketClass = InternalSocket; - Object.defineProperty(SocketClass.prototype, Symbol.toStringTag, { - value: "Socket", - enumerable: false, - }); - - return Object.defineProperty( - function Socket(options) { - return new InternalSocket(options); - }, - Symbol.hasInstance, - { - value(instance) { - return instance instanceof InternalSocket; - }, - }, - ); -})( - class Socket extends Duplex { - static #Handlers = { - close: Socket.#Close, - connectError(socket, error) { - const self = socket.data; - self.emit("error", error); - }, - data({ data: self }, buffer) { - self.bytesRead += buffer.length; - const queue = self.#readQueue; - - if (queue.isEmpty()) { - if (self.push(buffer)) return; - } - queue.push(buffer); - }, - drain: Socket.#Drain, - end: Socket.#Close, - error(socket, error) { - const self = socket.data; - const callback = self.#writeCallback; - if (callback) { - self.#writeCallback = null; - callback(error); - } - self.emit("error", error); - }, - open(socket) { - const self = socket.data; - socket.timeout(self.timeout); - socket.ref(); - self[bunSocketInternal] = socket; - self.connecting = false; - const options = self[bunTLSConnectOptions]; - - if (options) { - const { session } = options; - if (session) { - self.setSession(session); - } - } - - if (!self.#upgraded) { - // this is not actually emitted on nodejs when socket used on the connection - // this is already emmited on non-TLS socket and on TLS socket is emmited secureConnect after handshake - self.emit("connect", self); - } - - Socket.#Drain(socket); - }, - handshake(socket, success, verifyError) { - const { data: self } = socket; - - self._securePending = false; - self.secureConnecting = false; - self._secureEstablished = !!success; - self.emit("secure", self); - - const { checkServerIdentity } = self[bunTLSConnectOptions]; - if (!verifyError && typeof checkServerIdentity === "function" && self.servername) { - const cert = self.getPeerCertificate(true); - verifyError = checkServerIdentity(self.servername, cert); - } - - if (self._requestCert || self._rejectUnauthorized) { - if (verifyError) { - self.authorized = false; - self.authorizationError = verifyError.code || verifyError.message; - if (self._rejectUnauthorized) { - self.destroy(verifyError); - return; - } - } - } else { - self.authorized = true; - } - self.emit("secureConnect", verifyError); - }, - timeout(socket) { - const self = socket.data; - self.emit("timeout", self); - }, - binaryType: "buffer", - }; - - static #Close(socket) { +class Socket extends Duplex { + static #Handlers = { + close: Socket.#Close, + connectError(socket, error) { const self = socket.data; - if (self.#closed) return; - self.#closed = true; - //socket cannot be used after close - self[bunSocketInternal] = null; + self.emit("error", error); + }, + data({ data: self }, buffer) { + self.bytesRead += buffer.length; const queue = self.#readQueue; + if (queue.isEmpty()) { - if (self.push(null)) return; + if (self.push(buffer)) return; } - queue.push(null); - } - - static #Drain(socket) { + queue.push(buffer); + }, + drain: Socket.#Drain, + end: Socket.#Close, + error(socket, error) { const self = socket.data; - const callback = self.#writeCallback; if (callback) { - const chunk = self.#writeChunk; - const written = socket.write(chunk); - - self.bytesWritten += written; - if (written < chunk.length) { - self.#writeChunk = chunk.slice(written); - } else { - self.#writeCallback = null; - self.#writeChunk = null; - callback(null); + self.#writeCallback = null; + callback(error); + } + self.emit("error", error); + }, + open(socket) { + const self = socket.data; + socket.timeout(self.timeout); + socket.ref(); + self[bunSocketInternal] = socket; + self.connecting = false; + const options = self[bunTLSConnectOptions]; + + if (options) { + const { session } = options; + if (session) { + self.setSession(session); } } - } - static [bunSocketServerHandlers] = { - data: Socket.#Handlers.data, - close(socket) { - Socket.#Handlers.close(socket); - this.data[bunSocketServerConnections]--; - }, - end(socket) { - Socket.#Handlers.end(socket); - this.data[bunSocketServerConnections]--; - }, - open(socket) { - const self = this.data; - const options = self[bunSocketServerOptions]; - const { pauseOnConnect, connectionListener, InternalSocketClass, requestCert, rejectUnauthorized } = options; - const _socket = new InternalSocketClass({}); - _socket.isServer = true; - _socket._requestCert = requestCert; - _socket._rejectUnauthorized = rejectUnauthorized; - - _socket.#attach(this.localPort, socket); - if (self.maxConnections && self[bunSocketServerConnections] >= self.maxConnections) { - const data = { - localAddress: _socket.localAddress, - localPort: _socket.localPort, - localFamily: _socket.localFamily, - remoteAddress: _socket.remoteAddress, - remotePort: _socket.remotePort, - remoteFamily: _socket.remoteFamily || "IPv4", - }; - - socket.end(); - - self.emit("drop", data); - return; - } - // the duplex implementation start paused, so we resume when pauseOnConnect is falsy - if (!pauseOnConnect) { - _socket.resume(); - } + if (!self.#upgraded) { + // this is not actually emitted on nodejs when socket used on the connection + // this is already emmited on non-TLS socket and on TLS socket is emmited secureConnect after handshake + self.emit("connect", self); + } - self[bunSocketServerConnections]++; + Socket.#Drain(socket); + }, + handshake(socket, success, verifyError) { + const { data: self } = socket; + + self._securePending = false; + self.secureConnecting = false; + self._secureEstablished = !!success; + self.emit("secure", self); + + const { checkServerIdentity } = self[bunTLSConnectOptions]; + if (!verifyError && typeof checkServerIdentity === "function" && self.servername) { + const cert = self.getPeerCertificate(true); + verifyError = checkServerIdentity(self.servername, cert); + } - if (typeof connectionListener == "function") { - if (InternalSocketClass.name === "TLSSocket") { - // add secureConnection event handler - self.once("secureConnection", () => connectionListener(_socket)); - } else { - connectionListener(_socket); + if (self._requestCert || self._rejectUnauthorized) { + if (verifyError) { + self.authorized = false; + self.authorizationError = verifyError.code || verifyError.message; + if (self._rejectUnauthorized) { + self.destroy(verifyError); + return; } } + } else { + self.authorized = true; + } + self.emit("secureConnect", verifyError); + }, + timeout(socket) { + const self = socket.data; + self.emit("timeout", self); + }, + binaryType: "buffer", + }; + + static #Close(socket) { + const self = socket.data; + if (self.#closed) return; + self.#closed = true; + //socket cannot be used after close + self[bunSocketInternal] = null; + const queue = self.#readQueue; + if (queue.isEmpty()) { + if (self.push(null)) return; + } + queue.push(null); + } - self.emit("connection", _socket); - }, - handshake(socket, success, verifyError) { - const { data: self } = socket; - self.emit("secure", self); - - self._securePending = false; - self.secureConnecting = false; - self._secureEstablished = !!success; - - if (self._requestCert || self._rejectUnauthorized) { - if (verifyError) { - self.authorized = false; - self.authorizationError = verifyError.code || verifyError.message; - if (self._rejectUnauthorized) { - self.destroy(verifyError); - return; - } - } - } else { - self.authorized = true; - } - self.emit("secureConnection", verifyError); - }, - error(socket, error) { - Socket.#Handlers.error(socket, error); - this.data.emit("error", error); - }, - timeout: Socket.#Handlers.timeout, - connectError: Socket.#Handlers.connectError, - drain: Socket.#Handlers.drain, - binaryType: "buffer", - }; + static #Drain(socket) { + const self = socket.data; - bytesRead = 0; - bytesWritten = 0; - #closed = false; - connecting = false; - localAddress = "127.0.0.1"; - #readQueue = $createFIFO(); - remotePort; - [bunSocketInternal] = null; - [bunTLSConnectOptions] = null; - timeout = 0; - #writeCallback; - #writeChunk; - #pendingRead; - - isServer = false; - _handle; - _parent; - _parentWrap; - #socket; - #upgraded; - - constructor(options) { - const { socket, signal, write, read, allowHalfOpen = false, ...opts } = options || {}; - super({ - ...opts, - allowHalfOpen, - readable: true, - writable: true, - }); - this._handle = this; - this._parent = this; - this._parentWrap = this; - this.#pendingRead = undefined; - this.#upgraded = false; - if (socket instanceof Socket) { - this.#socket = socket; + const callback = self.#writeCallback; + if (callback) { + const chunk = self.#writeChunk; + const written = socket.write(chunk); + + self.bytesWritten += written; + if (written < chunk.length) { + self.#writeChunk = chunk.slice(written); + } else { + self.#writeCallback = null; + self.#writeChunk = null; + callback(null); } - signal?.once("abort", () => this.destroy()); - this.once("connect", () => this.emit("ready")); } + } - address() { - return { - address: this.localAddress, - family: this.localFamily, - port: this.localPort, - }; - } + static [bunSocketServerHandlers] = { + data: Socket.#Handlers.data, + close(socket) { + Socket.#Handlers.close(socket); + this.data[bunSocketServerConnections]--; + }, + end(socket) { + Socket.#Handlers.end(socket); + this.data[bunSocketServerConnections]--; + }, + open(socket) { + const self = this.data; + const options = self[bunSocketServerOptions]; + const { pauseOnConnect, connectionListener, InternalSocketClass, requestCert, rejectUnauthorized } = options; + const _socket = new InternalSocketClass({}); + _socket.isServer = true; + _socket._requestCert = requestCert; + _socket._rejectUnauthorized = rejectUnauthorized; + + _socket.#attach(this.localPort, socket); + if (self.maxConnections && self[bunSocketServerConnections] >= self.maxConnections) { + const data = { + localAddress: _socket.localAddress, + localPort: _socket.localPort, + localFamily: _socket.localFamily, + remoteAddress: _socket.remoteAddress, + remotePort: _socket.remotePort, + remoteFamily: _socket.remoteFamily || "IPv4", + }; - get bufferSize() { - return this.writableLength; - } + socket.end(); - #attach(port, socket) { - this.remotePort = port; - socket.data = this; - socket.timeout(this.timeout); - socket.ref(); - this[bunSocketInternal] = socket; - this.connecting = false; - if (!this.#upgraded) { - // this is not actually emitted on nodejs when socket used on the connection - // this is already emmited on non-TLS socket and on TLS socket is emmited secureConnect after handshake - this.emit("connect", this); + self.emit("drop", data); + return; + } + // the duplex implementation start paused, so we resume when pauseOnConnect is falsy + if (!pauseOnConnect) { + _socket.resume(); } - Socket.#Drain(socket); - } - connect(port, host, connectListener) { - var path; - var connection = this.#socket; - var _checkServerIdentity = undefined; - if (typeof port === "string") { - path = port; - port = undefined; + self[bunSocketServerConnections]++; - if (typeof host === "function") { - connectListener = host; - host = undefined; + if (typeof connectionListener == "function") { + if (InternalSocketClass.name === "TLSSocket") { + // add secureConnection event handler + self.once("secureConnection", () => connectionListener(_socket)); + } else { + connectionListener(_socket); } - } else if (typeof host == "function") { - if (typeof port === "string") { - path = port; - port = undefined; + } + + self.emit("connection", _socket); + }, + handshake(socket, success, verifyError) { + const { data: self } = socket; + self.emit("secure", self); + + self._securePending = false; + self.secureConnecting = false; + self._secureEstablished = !!success; + + if (self._requestCert || self._rejectUnauthorized) { + if (verifyError) { + self.authorized = false; + self.authorizationError = verifyError.code || verifyError.message; + if (self._rejectUnauthorized) { + self.destroy(verifyError); + return; + } } + } else { + self.authorized = true; + } + self.emit("secureConnection", verifyError); + }, + error(socket, error) { + Socket.#Handlers.error(socket, error); + this.data.emit("error", error); + }, + timeout: Socket.#Handlers.timeout, + connectError: Socket.#Handlers.connectError, + drain: Socket.#Handlers.drain, + binaryType: "buffer", + }; + + bytesRead = 0; + bytesWritten = 0; + #closed = false; + connecting = false; + localAddress = "127.0.0.1"; + #readQueue = $createFIFO(); + remotePort; + [bunSocketInternal] = null; + [bunTLSConnectOptions] = null; + timeout = 0; + #writeCallback; + #writeChunk; + #pendingRead; + + isServer = false; + _handle; + _parent; + _parentWrap; + #socket; + #upgraded; + + constructor(options) { + const { socket, signal, write, read, allowHalfOpen = false, ...opts } = options || {}; + super({ + ...opts, + allowHalfOpen, + readable: true, + writable: true, + }); + this._handle = this; + this._parent = this; + this._parentWrap = this; + this.#pendingRead = undefined; + this.#upgraded = false; + if (socket instanceof Socket) { + this.#socket = socket; + } + signal?.once("abort", () => this.destroy()); + this.once("connect", () => this.emit("ready")); + } + + address() { + return { + address: this.localAddress, + family: this.localFamily, + port: this.localPort, + }; + } + get bufferSize() { + return this.writableLength; + } + + #attach(port, socket) { + this.remotePort = port; + socket.data = this; + socket.timeout(this.timeout); + socket.ref(); + this[bunSocketInternal] = socket; + this.connecting = false; + if (!this.#upgraded) { + // this is not actually emitted on nodejs when socket used on the connection + // this is already emmited on non-TLS socket and on TLS socket is emmited secureConnect after handshake + this.emit("connect", this); + } + Socket.#Drain(socket); + } + + connect(port, host, connectListener) { + var path; + var connection = this.#socket; + var _checkServerIdentity = undefined; + if (typeof port === "string") { + path = port; + port = undefined; + + if (typeof host === "function") { connectListener = host; host = undefined; } - if (typeof port == "object") { - var { - port, - host, - path, - socket, - // TODOs - localAddress, - localPort, - family, - hints, - lookup, - noDelay, - keepAlive, - keepAliveInitialDelay, - requestCert, - rejectUnauthorized, - pauseOnConnect, - servername, - checkServerIdentity, - session, - } = port; - _checkServerIdentity = checkServerIdentity; - this.servername = servername; - if (socket) { - connection = socket; - } + } else if (typeof host == "function") { + if (typeof port === "string") { + path = port; + port = undefined; } - if (!pauseOnConnect) { - this.resume(); + connectListener = host; + host = undefined; + } + if (typeof port == "object") { + var { + port, + host, + path, + socket, + // TODOs + localAddress, + localPort, + family, + hints, + lookup, + noDelay, + keepAlive, + keepAliveInitialDelay, + requestCert, + rejectUnauthorized, + pauseOnConnect, + servername, + checkServerIdentity, + session, + } = port; + _checkServerIdentity = checkServerIdentity; + this.servername = servername; + if (socket) { + connection = socket; } - this.connecting = true; - this.remotePort = port; + } - const bunTLS = this[bunTlsSymbol]; - var tls = undefined; + if (!pauseOnConnect) { + this.resume(); + } + this.connecting = true; + this.remotePort = port; - if (typeof bunTLS === "function") { - tls = bunTLS.call(this, port, host, true); - // Client always request Cert - this._requestCert = true; - this._rejectUnauthorized = rejectUnauthorized; - - if (tls) { - tls.rejectUnauthorized = rejectUnauthorized; - tls.requestCert = true; - tls.session = session || tls.session; - this.servername = tls.servername; - tls.checkServerIdentity = _checkServerIdentity || tls.checkServerIdentity; - this[bunTLSConnectOptions] = tls; - if (!connection && tls.socket) { - connection = tls.socket; - } - } - if (connection) { - if ( - typeof connection !== "object" || - !(connection instanceof Socket) || - typeof connection[bunTlsSymbol] === "function" - ) { - throw new TypeError("socket must be an instance of net.Socket"); - } - } - this.authorized = false; - this.secureConnecting = true; - this._secureEstablished = false; - this._securePending = true; + const bunTLS = this[bunTlsSymbol]; + var tls = undefined; - if (connectListener) this.on("secureConnect", connectListener); - } else if (connectListener) this.on("connect", connectListener); - // start using existing connection + if (typeof bunTLS === "function") { + tls = bunTLS.call(this, port, host, true); + // Client always request Cert + this._requestCert = true; + this._rejectUnauthorized = rejectUnauthorized; + if (tls) { + tls.rejectUnauthorized = rejectUnauthorized; + tls.requestCert = true; + tls.session = session || tls.session; + this.servername = tls.servername; + tls.checkServerIdentity = _checkServerIdentity || tls.checkServerIdentity; + this[bunTLSConnectOptions] = tls; + if (!connection && tls.socket) { + connection = tls.socket; + } + } if (connection) { - const socket = connection[bunSocketInternal]; + if ( + typeof connection !== "object" || + !(connection instanceof Socket) || + typeof connection[bunTlsSymbol] === "function" + ) { + throw new TypeError("socket must be an instance of net.Socket"); + } + } + this.authorized = false; + this.secureConnecting = true; + this._secureEstablished = false; + this._securePending = true; + + if (connectListener) this.on("secureConnect", connectListener); + } else if (connectListener) this.on("connect", connectListener); + // start using existing connection + + if (connection) { + const socket = connection[bunSocketInternal]; + + if (socket) { + this.connecting = true; + this.#upgraded = true; + const result = socket.upgradeTLS({ + data: this, + tls, + socket: Socket.#Handlers, + }); + if (result) { + const [raw, tls] = result; + // replace socket + connection[bunSocketInternal] = raw; + raw.timeout(raw.timeout); + raw.connecting = false; + this[bunSocketInternal] = tls; + } else { + this[bunSocketInternal] = null; + throw new Error("Invalid socket"); + } + } else { + // wait to be connected + connection.once("connect", () => { + const socket = connection[bunSocketInternal]; + if (!socket) return; - if (socket) { this.connecting = true; this.#upgraded = true; const result = socket.upgradeTLS({ @@ -461,6 +466,7 @@ const Socket = (function (InternalSocket) { tls, socket: Socket.#Handlers, }); + if (result) { const [raw, tls] = result; // replace socket @@ -472,161 +478,136 @@ const Socket = (function (InternalSocket) { this[bunSocketInternal] = null; throw new Error("Invalid socket"); } - } else { - // wait to be connected - connection.once("connect", () => { - const socket = connection[bunSocketInternal]; - if (!socket) return; - - this.connecting = true; - this.#upgraded = true; - const result = socket.upgradeTLS({ - data: this, - tls, - socket: Socket.#Handlers, - }); - - if (result) { - const [raw, tls] = result; - // replace socket - connection[bunSocketInternal] = raw; - raw.timeout(raw.timeout); - raw.connecting = false; - this[bunSocketInternal] = tls; - } else { - this[bunSocketInternal] = null; - throw new Error("Invalid socket"); - } - }); - } - } else if (path) { - // start using unix socket - bunConnect({ - data: this, - unix: path, - socket: Socket.#Handlers, - tls, - }).catch(error => { - this.emit("error", error); - }); - } else { - // default start - bunConnect({ - data: this, - hostname: host || "localhost", - port: port, - socket: Socket.#Handlers, - tls, - }).catch(error => { - this.emit("error", error); }); } - return this; + } else if (path) { + // start using unix socket + bunConnect({ + data: this, + unix: path, + socket: Socket.#Handlers, + tls, + }).catch(error => { + this.emit("error", error); + }); + } else { + // default start + bunConnect({ + data: this, + hostname: host || "localhost", + port: port, + socket: Socket.#Handlers, + tls, + }).catch(error => { + this.emit("error", error); + }); } + return this; + } - _destroy(err, callback) { - this[bunSocketInternal]?.end(); - callback(err); - } + _destroy(err, callback) { + this[bunSocketInternal]?.end(); + callback(err); + } - _final(callback) { - this[bunSocketInternal]?.end(); - callback(); - } + _final(callback) { + this[bunSocketInternal]?.end(); + callback(); + } - get localAddress() { - return "127.0.0.1"; - } + get localAddress() { + return "127.0.0.1"; + } - get localFamily() { - return "IPv4"; - } + get localFamily() { + return "IPv4"; + } - get localPort() { - return this[bunSocketInternal]?.localPort; - } + get localPort() { + return this[bunSocketInternal]?.localPort; + } - get pending() { - return this.connecting; - } + get pending() { + return this.connecting; + } - _read(size) { - const queue = this.#readQueue; - let chunk; - while ((chunk = queue.peek())) { - if (!this.push(chunk)) return; - queue.shift(); - } + _read(size) { + const queue = this.#readQueue; + let chunk; + while ((chunk = queue.peek())) { + if (!this.push(chunk)) return; + queue.shift(); } + } - get readyState() { - if (this.connecting) return "opening"; - if (this.readable) { - return this.writable ? "open" : "readOnly"; - } else { - return this.writable ? "writeOnly" : "closed"; - } + get readyState() { + if (this.connecting) return "opening"; + if (this.readable) { + return this.writable ? "open" : "readOnly"; + } else { + return this.writable ? "writeOnly" : "closed"; } + } - ref() { - this[bunSocketInternal]?.ref(); - } + ref() { + this[bunSocketInternal]?.ref(); + } - get remoteAddress() { - return this[bunSocketInternal]?.remoteAddress; - } + get remoteAddress() { + return this[bunSocketInternal]?.remoteAddress; + } - get remoteFamily() { - return "IPv4"; - } + get remoteFamily() { + return "IPv4"; + } - resetAndDestroy() { - this[bunSocketInternal]?.end(); - } + resetAndDestroy() { + this[bunSocketInternal]?.end(); + } - setKeepAlive(enable = false, initialDelay = 0) { - // TODO - return this; - } + setKeepAlive(enable = false, initialDelay = 0) { + // TODO + return this; + } - setNoDelay(noDelay = true) { - // TODO - return this; - } + setNoDelay(noDelay = true) { + // TODO + return this; + } - setTimeout(timeout, callback) { - this[bunSocketInternal]?.timeout(timeout); - this.timeout = timeout; - if (callback) this.once("timeout", callback); - return this; - } + setTimeout(timeout, callback) { + this[bunSocketInternal]?.timeout(timeout); + this.timeout = timeout; + if (callback) this.once("timeout", callback); + return this; + } - unref() { - this[bunSocketInternal]?.unref(); - } + unref() { + this[bunSocketInternal]?.unref(); + } - _write(chunk, encoding, callback) { - if (typeof chunk == "string" && encoding !== "ascii") chunk = Buffer.from(chunk, encoding); - var written = this[bunSocketInternal]?.write(chunk); - if (written == chunk.length) { - callback(); - } else if (this.#writeCallback) { - callback(new Error("overlapping _write()")); - } else { - if (written > 0) { - if (typeof chunk == "string") { - chunk = chunk.slice(written); - } else { - chunk = chunk.subarray(written); - } + _write(chunk, encoding, callback) { + if (typeof chunk == "string" && encoding !== "ascii") chunk = Buffer.from(chunk, encoding); + var written = this[bunSocketInternal]?.write(chunk); + if (written == chunk.length) { + callback(); + } else if (this.#writeCallback) { + callback(new Error("overlapping _write()")); + } else { + if (written > 0) { + if (typeof chunk == "string") { + chunk = chunk.slice(written); + } else { + chunk = chunk.subarray(written); } - - this.#writeCallback = callback; - this.#writeChunk = chunk; } + + this.#writeCallback = callback; + this.#writeChunk = chunk; } - }, -); + } +} +es5ClassCompat(Socket); function createConnection(port, host, connectListener) { if (typeof port === "object") { @@ -861,6 +842,7 @@ class Server extends EventEmitter { return this; } } +es5ClassCompat(Server); function emitErrorNextTick(self, error) { self.emit("error", error); @@ -890,5 +872,4 @@ export default { isIPv4, isIPv6, Socket, - [Symbol.for("::bunternal::")]: SocketClass, }; diff --git a/src/js/node/tls.js b/src/js/node/tls.js index fc2d9065a..2ed350e02 100644 --- a/src/js/node/tls.js +++ b/src/js/node/tls.js @@ -1,7 +1,8 @@ // Hardcoded module "node:tls" const { isArrayBufferView, isTypedArray } = require("node:util/types"); +const { es5ClassCompat } = require("$shared"); const net = require("node:net"); -const { Server: NetServer, [Symbol.for("::bunternal::")]: InternalTCPSocket } = net; +const { Server: NetServer, Socket: TCPSocket } = net; const bunSocketInternal = Symbol.for("::bunnetsocketinternal::"); const { rootCertificates, canonicalizeIP } = $lazy("internal/tls"); @@ -203,7 +204,7 @@ function checkServerIdentity(hostname, cert) { } } -var InternalSecureContext = class SecureContext { +class SecureContext { context; constructor(options) { @@ -258,11 +259,8 @@ var InternalSecureContext = class SecureContext { } this.context = context; } -}; - -function SecureContext(options) { - return new InternalSecureContext(options); } +es5ClassCompat(SecureContext); function createSecureContext(options) { return new SecureContext(options); @@ -299,188 +297,168 @@ function translatePeerCertificate(c) { const buntls = Symbol.for("::buntls::"); -var SocketClass; -const TLSSocket = (function (InternalTLSSocket) { - SocketClass = InternalTLSSocket; - Object.defineProperty(SocketClass.prototype, Symbol.toStringTag, { - value: "TLSSocket", - enumerable: false, - }); - - return Object.defineProperty( - function Socket(options) { - return new InternalTLSSocket(options); - }, - Symbol.hasInstance, - { - value(instance) { - return instance instanceof InternalTLSSocket; - }, - }, - ); -})( - class TLSSocket extends InternalTCPSocket { - #secureContext; - ALPNProtocols; - #socket; - #checkServerIdentity; - #session; - - constructor(socket, options) { - super(socket instanceof InternalTCPSocket ? options : options || socket); - options = options || socket || {}; - if (typeof options === "object") { - const { ALPNProtocols } = options; - if (ALPNProtocols) { - convertALPNProtocols(ALPNProtocols, this); - } - if (socket instanceof InternalTCPSocket) { - this.#socket = socket; - } +class TLSSocket extends TCPSocket { + #secureContext; + ALPNProtocols; + #socket; + #checkServerIdentity; + #session; + + constructor(socket, options) { + super(socket instanceof TCPSocket ? options : options || socket); + options = options || socket || {}; + if (typeof options === "object") { + const { ALPNProtocols } = options; + if (ALPNProtocols) { + convertALPNProtocols(ALPNProtocols, this); + } + if (socket instanceof TCPSocket) { + this.#socket = socket; } - - this.#secureContext = options.secureContext || createSecureContext(options); - this.authorized = false; - this.secureConnecting = true; - this._secureEstablished = false; - this._securePending = true; - this.#checkServerIdentity = options.checkServerIdentity || checkServerIdentity; - this.#session = options.session || null; } - _secureEstablished = false; - _securePending = true; - _newSessionPending; - _controlReleased; - secureConnecting = false; - _SNICallback; - servername; - authorized = false; - authorizationError; - #renegotiationDisabled = false; - - encrypted = true; - - _start() { - // some frameworks uses this _start internal implementation is suposed to start TLS handshake/connect - this.connect(); - } + this.#secureContext = options.secureContext || createSecureContext(options); + this.authorized = false; + this.secureConnecting = true; + this._secureEstablished = false; + this._securePending = true; + this.#checkServerIdentity = options.checkServerIdentity || checkServerIdentity; + this.#session = options.session || null; + } - getSession() { - return this[bunSocketInternal]?.getSession(); - } + _secureEstablished = false; + _securePending = true; + _newSessionPending; + _controlReleased; + secureConnecting = false; + _SNICallback; + servername; + authorized = false; + authorizationError; + #renegotiationDisabled = false; - getEphemeralKeyInfo() { - return this[bunSocketInternal]?.getEphemeralKeyInfo(); - } + encrypted = true; - getCipher() { - return this[bunSocketInternal]?.getCipher(); - } + _start() { + // some frameworks uses this _start internal implementation is suposed to start TLS handshake/connect + this.connect(); + } - getSharedSigalgs() { - return this[bunSocketInternal]?.getSharedSigalgs(); - } + getSession() { + return this[bunSocketInternal]?.getSession(); + } - getProtocol() { - return this[bunSocketInternal]?.getTLSVersion(); - } + getEphemeralKeyInfo() { + return this[bunSocketInternal]?.getEphemeralKeyInfo(); + } - getFinished() { - return this[bunSocketInternal]?.getTLSFinishedMessage() || undefined; - } + getCipher() { + return this[bunSocketInternal]?.getCipher(); + } - getPeerFinished() { - return this[bunSocketInternal]?.getTLSPeerFinishedMessage() || undefined; - } - isSessionReused() { - return !!this.#session; - } + getSharedSigalgs() { + return this[bunSocketInternal]?.getSharedSigalgs(); + } - renegotiate() { - if (this.#renegotiationDisabled) { - const error = new Error("ERR_TLS_RENEGOTIATION_DISABLED: TLS session renegotiation disabled for this socket"); - error.name = "ERR_TLS_RENEGOTIATION_DISABLED"; - throw error; - } + getProtocol() { + return this[bunSocketInternal]?.getTLSVersion(); + } - throw Error("Not implented in Bun yet"); - } - disableRenegotiation() { - this.#renegotiationDisabled = true; - } - getTLSTicket() { - return this[bunSocketInternal]?.getTLSTicket(); - } - exportKeyingMaterial(length, label, context) { - if (context) { - return this[bunSocketInternal]?.exportKeyingMaterial(length, label, context); - } - return this[bunSocketInternal]?.exportKeyingMaterial(length, label); + getFinished() { + return this[bunSocketInternal]?.getTLSFinishedMessage() || undefined; + } + + getPeerFinished() { + return this[bunSocketInternal]?.getTLSPeerFinishedMessage() || undefined; + } + isSessionReused() { + return !!this.#session; + } + + renegotiate() { + if (this.#renegotiationDisabled) { + const error = new Error("ERR_TLS_RENEGOTIATION_DISABLED: TLS session renegotiation disabled for this socket"); + error.name = "ERR_TLS_RENEGOTIATION_DISABLED"; + throw error; } - setMaxSendFragment(size) { - return this[bunSocketInternal]?.setMaxSendFragment(size) || false; + throw Error("Not implented in Bun yet"); + } + disableRenegotiation() { + this.#renegotiationDisabled = true; + } + getTLSTicket() { + return this[bunSocketInternal]?.getTLSTicket(); + } + exportKeyingMaterial(length, label, context) { + if (context) { + return this[bunSocketInternal]?.exportKeyingMaterial(length, label, context); } + return this[bunSocketInternal]?.exportKeyingMaterial(length, label); + } - // only for debug purposes so we just mock for now - enableTrace() {} + setMaxSendFragment(size) { + return this[bunSocketInternal]?.setMaxSendFragment(size) || false; + } - setServername(name) { - if (this.isServer) { - let error = new Error("ERR_TLS_SNI_FROM_SERVER: Cannot issue SNI from a TLS server-side socket"); - error.name = "ERR_TLS_SNI_FROM_SERVER"; - throw error; - } - // if the socket is detached we can't set the servername but we set this property so when open will auto set to it - this.servername = name; - this[bunSocketInternal]?.setServername(name); - } - setSession(session) { - this.#session = session; - if (typeof session === "string") session = Buffer.from(session, "latin1"); - return this[bunSocketInternal]?.setSession(session); - } - getPeerCertificate(abbreviated) { - const cert = - arguments.length < 1 - ? this[bunSocketInternal]?.getPeerCertificate() - : this[bunSocketInternal]?.getPeerCertificate(abbreviated); - if (cert) { - return translatePeerCertificate(cert); - } - } - getCertificate() { - // need to implement certificate on socket.zig - const cert = this[bunSocketInternal]?.getCertificate(); - if (cert) { - // It's not a peer cert, but the formatting is identical. - return translatePeerCertificate(cert); - } + // only for debug purposes so we just mock for now + enableTrace() {} + + setServername(name) { + if (this.isServer) { + let error = new Error("ERR_TLS_SNI_FROM_SERVER: Cannot issue SNI from a TLS server-side socket"); + error.name = "ERR_TLS_SNI_FROM_SERVER"; + throw error; } - getPeerX509Certificate() { - throw Error("Not implented in Bun yet"); + // if the socket is detached we can't set the servername but we set this property so when open will auto set to it + this.servername = name; + this[bunSocketInternal]?.setServername(name); + } + setSession(session) { + this.#session = session; + if (typeof session === "string") session = Buffer.from(session, "latin1"); + return this[bunSocketInternal]?.setSession(session); + } + getPeerCertificate(abbreviated) { + const cert = + arguments.length < 1 + ? this[bunSocketInternal]?.getPeerCertificate() + : this[bunSocketInternal]?.getPeerCertificate(abbreviated); + if (cert) { + return translatePeerCertificate(cert); } - getX509Certificate() { - throw Error("Not implented in Bun yet"); + } + getCertificate() { + // need to implement certificate on socket.zig + const cert = this[bunSocketInternal]?.getCertificate(); + if (cert) { + // It's not a peer cert, but the formatting is identical. + return translatePeerCertificate(cert); } + } + getPeerX509Certificate() { + throw Error("Not implented in Bun yet"); + } + getX509Certificate() { + throw Error("Not implented in Bun yet"); + } - get alpnProtocol() { - return this[bunSocketInternal]?.alpnProtocol; - } + get alpnProtocol() { + return this[bunSocketInternal]?.alpnProtocol; + } - [buntls](port, host) { - return { - socket: this.#socket, - ALPNProtocols: this.ALPNProtocols, - serverName: this.servername || host || "localhost", - checkServerIdentity: this.#checkServerIdentity, - session: this.#session, - ...this.#secureContext, - }; - } - }, -); + [buntls](port, host) { + return { + socket: this.#socket, + ALPNProtocols: this.ALPNProtocols, + serverName: this.servername || host || "localhost", + checkServerIdentity: this.#checkServerIdentity, + session: this.#session, + ...this.#secureContext, + }; + } +} +es5ClassCompat(TLSSocket); class Server extends NetServer { key; |